@huyooo/ai-chat-frontend-react 0.2.12 → 0.2.14
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 +99 -84
- package/dist/KaTeX_AMS-Regular-CYEKBG2K.woff +0 -0
- package/dist/KaTeX_AMS-Regular-JKX5W2C4.ttf +0 -0
- package/dist/KaTeX_AMS-Regular-U6PRYMIZ.woff2 +0 -0
- package/dist/KaTeX_Caligraphic-Bold-5QL5CMTE.woff2 +0 -0
- package/dist/KaTeX_Caligraphic-Bold-WZ3QSGD3.woff +0 -0
- package/dist/KaTeX_Caligraphic-Bold-ZTS3R3HK.ttf +0 -0
- package/dist/KaTeX_Caligraphic-Regular-3LKEU76G.woff +0 -0
- package/dist/KaTeX_Caligraphic-Regular-A7XRTZ5Q.ttf +0 -0
- package/dist/KaTeX_Caligraphic-Regular-KX5MEWCF.woff2 +0 -0
- package/dist/KaTeX_Fraktur-Bold-2QVFK6NQ.woff2 +0 -0
- package/dist/KaTeX_Fraktur-Bold-T4SWXBMT.woff +0 -0
- package/dist/KaTeX_Fraktur-Bold-WGHVTYOR.ttf +0 -0
- package/dist/KaTeX_Fraktur-Regular-2PEIFJSJ.woff2 +0 -0
- package/dist/KaTeX_Fraktur-Regular-5U4OPH2X.ttf +0 -0
- package/dist/KaTeX_Fraktur-Regular-PQMHCIK6.woff +0 -0
- package/dist/KaTeX_Main-Bold-2GA4IZIN.woff +0 -0
- package/dist/KaTeX_Main-Bold-W5FBVCZM.ttf +0 -0
- package/dist/KaTeX_Main-Bold-YP5VVQRP.woff2 +0 -0
- package/dist/KaTeX_Main-BoldItalic-4P4C7HJH.woff +0 -0
- package/dist/KaTeX_Main-BoldItalic-N4V3DX7S.woff2 +0 -0
- package/dist/KaTeX_Main-BoldItalic-ODMLBJJQ.ttf +0 -0
- package/dist/KaTeX_Main-Italic-I43T2HSR.ttf +0 -0
- package/dist/KaTeX_Main-Italic-RELBIK7M.woff2 +0 -0
- package/dist/KaTeX_Main-Italic-SASNQFN2.woff +0 -0
- package/dist/KaTeX_Main-Regular-ARRPAO67.woff2 +0 -0
- package/dist/KaTeX_Main-Regular-P5I74A2A.woff +0 -0
- package/dist/KaTeX_Main-Regular-W74P5G27.ttf +0 -0
- package/dist/KaTeX_Math-BoldItalic-6EBV3DK5.woff +0 -0
- package/dist/KaTeX_Math-BoldItalic-K4WTGH3J.woff2 +0 -0
- package/dist/KaTeX_Math-BoldItalic-VB447A4D.ttf +0 -0
- package/dist/KaTeX_Math-Italic-6KGCHLFN.woff2 +0 -0
- package/dist/KaTeX_Math-Italic-KKK3USB2.woff +0 -0
- package/dist/KaTeX_Math-Italic-SON4MRCA.ttf +0 -0
- package/dist/KaTeX_SansSerif-Bold-RRNVJFFW.woff2 +0 -0
- package/dist/KaTeX_SansSerif-Bold-STQ6RXC7.ttf +0 -0
- package/dist/KaTeX_SansSerif-Bold-X5M5EMOD.woff +0 -0
- package/dist/KaTeX_SansSerif-Italic-HMPFTM52.woff2 +0 -0
- package/dist/KaTeX_SansSerif-Italic-PSN4QKYX.woff +0 -0
- package/dist/KaTeX_SansSerif-Italic-WTBAZBGY.ttf +0 -0
- package/dist/KaTeX_SansSerif-Regular-2TL3USAE.ttf +0 -0
- package/dist/KaTeX_SansSerif-Regular-OQCII6EP.woff +0 -0
- package/dist/KaTeX_SansSerif-Regular-XIQ62X4E.woff2 +0 -0
- package/dist/KaTeX_Script-Regular-72OLXYNA.ttf +0 -0
- package/dist/KaTeX_Script-Regular-A5IFOEBS.woff +0 -0
- package/dist/KaTeX_Script-Regular-APUWIHLP.woff2 +0 -0
- package/dist/KaTeX_Size1-Regular-4HRHTS65.woff +0 -0
- package/dist/KaTeX_Size1-Regular-5LRUTBFT.woff2 +0 -0
- package/dist/KaTeX_Size1-Regular-7K6AASVL.ttf +0 -0
- package/dist/KaTeX_Size2-Regular-222HN3GT.ttf +0 -0
- package/dist/KaTeX_Size2-Regular-K5ZHAIS6.woff +0 -0
- package/dist/KaTeX_Size2-Regular-LELKET5D.woff2 +0 -0
- package/dist/KaTeX_Size3-Regular-TLFPAHDE.woff +0 -0
- package/dist/KaTeX_Size3-Regular-UFCO6WCA.ttf +0 -0
- package/dist/KaTeX_Size3-Regular-WQRQ47UD.woff2 +0 -0
- package/dist/KaTeX_Size4-Regular-7PGNVPQK.ttf +0 -0
- package/dist/KaTeX_Size4-Regular-CDMV7U5C.woff2 +0 -0
- package/dist/KaTeX_Size4-Regular-PKMWZHNC.woff +0 -0
- package/dist/KaTeX_Typewriter-Regular-3F5K6SQ6.ttf +0 -0
- package/dist/KaTeX_Typewriter-Regular-MJMFSK64.woff +0 -0
- package/dist/KaTeX_Typewriter-Regular-VBYJ4NRC.woff2 +0 -0
- package/dist/index.css +2156 -603
- package/dist/index.css.map +1 -1
- package/dist/index.d.ts +126 -92
- package/dist/index.js +1605 -976
- package/dist/index.js.map +1 -1
- package/dist/style.css +130 -0
- package/package.json +3 -3
- package/src/components/ChatPanel.tsx +82 -19
- package/src/components/common/SettingsPanel.css +81 -0
- package/src/components/common/SettingsPanel.tsx +96 -1
- package/src/components/input/ChatInput.css +0 -1
- package/src/components/input/ChatInput.tsx +48 -26
- package/src/components/input/DropdownSelector.css +66 -0
- package/src/components/input/DropdownSelector.tsx +157 -19
- package/src/components/message/MessageBubble.css +5 -2
- package/src/components/message/MessageBubble.tsx +44 -35
- package/src/components/message/PartsRenderer.css +8 -0
- package/src/components/message/PartsRenderer.tsx +137 -83
- package/src/components/message/parts/CollapsibleCard.css +4 -2
- package/src/components/message/parts/CollapsibleCard.tsx +4 -1
- package/src/components/message/parts/ImagePart.css +0 -1
- package/src/components/message/parts/TextPart.css +574 -5
- package/src/components/message/parts/TextPart.tsx +201 -8
- package/src/components/message/parts/ToolCallPart.css +139 -115
- package/src/components/message/parts/ToolCallPart.tsx +138 -134
- package/src/components/message/parts/ToolResultPart.css +0 -1
- package/src/components/message/parts/index.ts +3 -1
- package/src/components/message/parts/visual-predicate.ts +43 -0
- package/src/components/message/parts/visual-render.ts +19 -0
- package/src/components/message/parts/visual.ts +12 -0
- package/src/context/RenderersContext.tsx +19 -25
- package/src/hooks/useChat.ts +567 -79
- package/src/hooks/useImageUpload.ts +104 -12
- package/src/hooks/useVoiceInput.ts +17 -0
- package/src/index.ts +19 -16
- package/src/styles.css +130 -0
- package/src/types/index.ts +52 -68
- package/src/components/message/ContentRenderer.tsx +0 -63
- package/src/components/message/ToolResultRenderer.tsx +0 -21
- package/src/components/message/blocks/CodeBlock.tsx +0 -60
- package/src/components/message/blocks/TextBlock.tsx +0 -15
- package/src/components/message/blocks/blocks.css +0 -141
- package/src/components/message/blocks/index.ts +0 -6
- package/src/components/message/parts/ToolResultPart.tsx +0 -96
- package/src/components/message/tool-results/DefaultToolResult.tsx +0 -26
- package/src/components/message/tool-results/SearchResults.tsx +0 -69
- package/src/components/message/tool-results/WeatherCard.tsx +0 -63
- package/src/components/message/tool-results/index.ts +0 -7
- package/src/components/message/tool-results/tool-results.css +0 -181
package/dist/style.css
CHANGED
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
* 仅包含 CSS 变量和 ChatPanel 基础布局
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
/* 导入 Markdown / Mermaid 等共享渲染样式(与 vue 版本保持一致) */
|
|
7
|
+
@import "@huyooo/ai-chat-shared/styles";
|
|
8
|
+
|
|
6
9
|
/**
|
|
7
10
|
* 主题协议(跨项目统一):
|
|
8
11
|
* - 使用 document.documentElement 的 data-theme = light | dark
|
|
@@ -25,6 +28,7 @@
|
|
|
25
28
|
--chat-muted-hover: #e6e5e0;
|
|
26
29
|
--chat-border: #26251e1a;
|
|
27
30
|
--chat-text: #26251eeb;
|
|
31
|
+
--chat-text-strong: #26251e;
|
|
28
32
|
--chat-text-muted: #26251e99;
|
|
29
33
|
/* 主色:light/dark 保持一致(蓝) */
|
|
30
34
|
--chat-primary: #54a9ff;
|
|
@@ -41,6 +45,36 @@
|
|
|
41
45
|
--chat-fab-bg: #ffffff;
|
|
42
46
|
--chat-fab-bg-hover: var(--chat-input-bg);
|
|
43
47
|
--chat-fab-shadow: 0 10px 24px rgba(0, 0, 0, 0.12);
|
|
48
|
+
|
|
49
|
+
/* 代码高亮 - GitHub Light 风格 */
|
|
50
|
+
--chat-hljs-keyword: #cf222e;
|
|
51
|
+
--chat-hljs-built-in: #953800;
|
|
52
|
+
--chat-hljs-type: #953800;
|
|
53
|
+
--chat-hljs-function: #8250df;
|
|
54
|
+
--chat-hljs-string: #0a3069;
|
|
55
|
+
--chat-hljs-number: #0550ae;
|
|
56
|
+
--chat-hljs-literal: #0550ae;
|
|
57
|
+
--chat-hljs-comment: #6e7781;
|
|
58
|
+
--chat-hljs-variable: #953800;
|
|
59
|
+
--chat-hljs-attr: #0550ae;
|
|
60
|
+
--chat-hljs-property: #116329;
|
|
61
|
+
--chat-hljs-operator: #cf222e;
|
|
62
|
+
--chat-hljs-punctuation: #24292f;
|
|
63
|
+
--chat-hljs-params: #24292f;
|
|
64
|
+
--chat-hljs-regexp: #116329;
|
|
65
|
+
--chat-hljs-selector: #116329;
|
|
66
|
+
--chat-hljs-tag: #116329;
|
|
67
|
+
--chat-hljs-name: #116329;
|
|
68
|
+
--chat-hljs-deletion: #82071e;
|
|
69
|
+
--chat-hljs-deletion-bg: rgba(255, 129, 130, 0.15);
|
|
70
|
+
--chat-hljs-addition: #116329;
|
|
71
|
+
--chat-hljs-addition-bg: rgba(46, 160, 67, 0.15);
|
|
72
|
+
--chat-hljs-meta: #6e7781;
|
|
73
|
+
--chat-hljs-link: #0a3069;
|
|
74
|
+
--chat-hljs-symbol: #0550ae;
|
|
75
|
+
--chat-hljs-subst: #24292f;
|
|
76
|
+
--chat-hljs-section: #0550ae;
|
|
77
|
+
--chat-hljs-bullet: #953800;
|
|
44
78
|
}
|
|
45
79
|
|
|
46
80
|
/* 默认跟随系统暗色 */
|
|
@@ -56,6 +90,7 @@
|
|
|
56
90
|
--chat-muted-hover: #3c3c3c;
|
|
57
91
|
--chat-border: #333;
|
|
58
92
|
--chat-text: #ccc;
|
|
93
|
+
--chat-text-strong: #fff;
|
|
59
94
|
--chat-text-muted: #888;
|
|
60
95
|
/* 主色:light/dark 保持一致(蓝) */
|
|
61
96
|
--chat-primary: #54a9ff;
|
|
@@ -71,6 +106,36 @@
|
|
|
71
106
|
--chat-fab-bg: var(--chat-muted);
|
|
72
107
|
--chat-fab-bg-hover: var(--chat-muted-hover);
|
|
73
108
|
--chat-fab-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
|
|
109
|
+
|
|
110
|
+
/* 代码高亮 - GitHub Dark 风格 */
|
|
111
|
+
--chat-hljs-keyword: #ff7b72;
|
|
112
|
+
--chat-hljs-built-in: #ffa657;
|
|
113
|
+
--chat-hljs-type: #ffa657;
|
|
114
|
+
--chat-hljs-function: #d2a8ff;
|
|
115
|
+
--chat-hljs-string: #a5d6ff;
|
|
116
|
+
--chat-hljs-number: #79c0ff;
|
|
117
|
+
--chat-hljs-literal: #79c0ff;
|
|
118
|
+
--chat-hljs-comment: #8b949e;
|
|
119
|
+
--chat-hljs-variable: #ffa657;
|
|
120
|
+
--chat-hljs-attr: #79c0ff;
|
|
121
|
+
--chat-hljs-property: #7ee787;
|
|
122
|
+
--chat-hljs-operator: #ff7b72;
|
|
123
|
+
--chat-hljs-punctuation: #e6edf3;
|
|
124
|
+
--chat-hljs-params: #e6edf3;
|
|
125
|
+
--chat-hljs-regexp: #7ee787;
|
|
126
|
+
--chat-hljs-selector: #7ee787;
|
|
127
|
+
--chat-hljs-tag: #7ee787;
|
|
128
|
+
--chat-hljs-name: #7ee787;
|
|
129
|
+
--chat-hljs-deletion: #ffa198;
|
|
130
|
+
--chat-hljs-deletion-bg: rgba(248, 81, 73, 0.15);
|
|
131
|
+
--chat-hljs-addition: #7ee787;
|
|
132
|
+
--chat-hljs-addition-bg: rgba(46, 160, 67, 0.15);
|
|
133
|
+
--chat-hljs-meta: #8b949e;
|
|
134
|
+
--chat-hljs-link: #a5d6ff;
|
|
135
|
+
--chat-hljs-symbol: #79c0ff;
|
|
136
|
+
--chat-hljs-subst: #e6edf3;
|
|
137
|
+
--chat-hljs-section: #79c0ff;
|
|
138
|
+
--chat-hljs-bullet: #ffa657;
|
|
74
139
|
}
|
|
75
140
|
}
|
|
76
141
|
|
|
@@ -86,6 +151,7 @@
|
|
|
86
151
|
--chat-muted-hover: #e6e5e0;
|
|
87
152
|
--chat-border: #26251e1a;
|
|
88
153
|
--chat-text: #26251eeb;
|
|
154
|
+
--chat-text-strong: #26251e;
|
|
89
155
|
--chat-text-muted: #26251e99;
|
|
90
156
|
--chat-primary: #54a9ff;
|
|
91
157
|
--chat-primary-hover: #2f90ff;
|
|
@@ -99,6 +165,36 @@
|
|
|
99
165
|
--chat-fab-bg: #ffffff;
|
|
100
166
|
--chat-fab-bg-hover: var(--chat-input-bg);
|
|
101
167
|
--chat-fab-shadow: 0 10px 24px rgba(0, 0, 0, 0.12);
|
|
168
|
+
|
|
169
|
+
/* 代码高亮 - GitHub Light 风格 */
|
|
170
|
+
--chat-hljs-keyword: #cf222e;
|
|
171
|
+
--chat-hljs-built-in: #953800;
|
|
172
|
+
--chat-hljs-type: #953800;
|
|
173
|
+
--chat-hljs-function: #8250df;
|
|
174
|
+
--chat-hljs-string: #0a3069;
|
|
175
|
+
--chat-hljs-number: #0550ae;
|
|
176
|
+
--chat-hljs-literal: #0550ae;
|
|
177
|
+
--chat-hljs-comment: #6e7781;
|
|
178
|
+
--chat-hljs-variable: #953800;
|
|
179
|
+
--chat-hljs-attr: #0550ae;
|
|
180
|
+
--chat-hljs-property: #116329;
|
|
181
|
+
--chat-hljs-operator: #cf222e;
|
|
182
|
+
--chat-hljs-punctuation: #24292f;
|
|
183
|
+
--chat-hljs-params: #24292f;
|
|
184
|
+
--chat-hljs-regexp: #116329;
|
|
185
|
+
--chat-hljs-selector: #116329;
|
|
186
|
+
--chat-hljs-tag: #116329;
|
|
187
|
+
--chat-hljs-name: #116329;
|
|
188
|
+
--chat-hljs-deletion: #82071e;
|
|
189
|
+
--chat-hljs-deletion-bg: rgba(255, 129, 130, 0.15);
|
|
190
|
+
--chat-hljs-addition: #116329;
|
|
191
|
+
--chat-hljs-addition-bg: rgba(46, 160, 67, 0.15);
|
|
192
|
+
--chat-hljs-meta: #6e7781;
|
|
193
|
+
--chat-hljs-link: #0a3069;
|
|
194
|
+
--chat-hljs-symbol: #0550ae;
|
|
195
|
+
--chat-hljs-subst: #24292f;
|
|
196
|
+
--chat-hljs-section: #0550ae;
|
|
197
|
+
--chat-hljs-bullet: #953800;
|
|
102
198
|
}
|
|
103
199
|
|
|
104
200
|
:root[data-theme="dark"] {
|
|
@@ -112,6 +208,7 @@
|
|
|
112
208
|
--chat-muted-hover: #3c3c3c;
|
|
113
209
|
--chat-border: #333;
|
|
114
210
|
--chat-text: #ccc;
|
|
211
|
+
--chat-text-strong: #fff;
|
|
115
212
|
--chat-text-muted: #888;
|
|
116
213
|
/* 主色:light/dark 保持一致(蓝) */
|
|
117
214
|
--chat-primary: #54a9ff;
|
|
@@ -126,6 +223,36 @@
|
|
|
126
223
|
--chat-fab-bg: var(--chat-muted);
|
|
127
224
|
--chat-fab-bg-hover: var(--chat-muted-hover);
|
|
128
225
|
--chat-fab-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
|
|
226
|
+
|
|
227
|
+
/* 代码高亮 - GitHub Dark 风格 */
|
|
228
|
+
--chat-hljs-keyword: #ff7b72;
|
|
229
|
+
--chat-hljs-built-in: #ffa657;
|
|
230
|
+
--chat-hljs-type: #ffa657;
|
|
231
|
+
--chat-hljs-function: #d2a8ff;
|
|
232
|
+
--chat-hljs-string: #a5d6ff;
|
|
233
|
+
--chat-hljs-number: #79c0ff;
|
|
234
|
+
--chat-hljs-literal: #79c0ff;
|
|
235
|
+
--chat-hljs-comment: #8b949e;
|
|
236
|
+
--chat-hljs-variable: #ffa657;
|
|
237
|
+
--chat-hljs-attr: #79c0ff;
|
|
238
|
+
--chat-hljs-property: #7ee787;
|
|
239
|
+
--chat-hljs-operator: #ff7b72;
|
|
240
|
+
--chat-hljs-punctuation: #e6edf3;
|
|
241
|
+
--chat-hljs-params: #e6edf3;
|
|
242
|
+
--chat-hljs-regexp: #7ee787;
|
|
243
|
+
--chat-hljs-selector: #7ee787;
|
|
244
|
+
--chat-hljs-tag: #7ee787;
|
|
245
|
+
--chat-hljs-name: #7ee787;
|
|
246
|
+
--chat-hljs-deletion: #ffa198;
|
|
247
|
+
--chat-hljs-deletion-bg: rgba(248, 81, 73, 0.15);
|
|
248
|
+
--chat-hljs-addition: #7ee787;
|
|
249
|
+
--chat-hljs-addition-bg: rgba(46, 160, 67, 0.15);
|
|
250
|
+
--chat-hljs-meta: #8b949e;
|
|
251
|
+
--chat-hljs-link: #a5d6ff;
|
|
252
|
+
--chat-hljs-symbol: #79c0ff;
|
|
253
|
+
--chat-hljs-subst: #e6edf3;
|
|
254
|
+
--chat-hljs-section: #79c0ff;
|
|
255
|
+
--chat-hljs-bullet: #ffa657;
|
|
129
256
|
}
|
|
130
257
|
|
|
131
258
|
/* 统一滚动条样式 - 任何需要自定义滚动条的元素添加此类 */
|
|
@@ -170,6 +297,9 @@
|
|
|
170
297
|
overflow-y: auto;
|
|
171
298
|
padding: 12px;
|
|
172
299
|
scroll-behavior: smooth;
|
|
300
|
+
display: flex;
|
|
301
|
+
flex-direction: column;
|
|
302
|
+
gap: 8px;
|
|
173
303
|
}
|
|
174
304
|
|
|
175
305
|
/* 滚动到底部按钮 */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@huyooo/ai-chat-frontend-react",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.14",
|
|
4
4
|
"description": "AI Chat Frontend - React components with adapter pattern",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -32,8 +32,8 @@
|
|
|
32
32
|
"react-dom": ">=18.0.0"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@huyooo/ai-chat-bridge-electron": "^0.2.
|
|
36
|
-
"@huyooo/ai-chat-shared": "^0.2.
|
|
35
|
+
"@huyooo/ai-chat-bridge-electron": "^0.2.14",
|
|
36
|
+
"@huyooo/ai-chat-shared": "^0.2.14",
|
|
37
37
|
"@iconify/react": "^5.0.2"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
@@ -6,16 +6,16 @@
|
|
|
6
6
|
import { useEffect, useRef, useCallback, useMemo, useState, forwardRef, useImperativeHandle, type ComponentType } from 'react'
|
|
7
7
|
import { Icon } from '@iconify/react'
|
|
8
8
|
import { useChat, type ToolCompleteEvent } from '../hooks/useChat'
|
|
9
|
+
import type { ImageData } from '../adapter'
|
|
9
10
|
import type { ChatAdapter, ModelOption, ChatMode } from '@huyooo/ai-chat-bridge-electron/renderer'
|
|
10
11
|
import type { AutoRunConfig } from '@huyooo/ai-chat-bridge-electron/renderer'
|
|
11
|
-
import type { ToolRendererProps } from '@huyooo/ai-chat-shared'
|
|
12
12
|
import { ChatHeader } from './header/ChatHeader'
|
|
13
13
|
import { WelcomeMessage } from './message/WelcomeMessage'
|
|
14
14
|
import type { WelcomeConfig } from './message/welcome-types'
|
|
15
15
|
import { MessageBubble } from './message/MessageBubble'
|
|
16
16
|
import { ChatInput, type ChatInputHandle } from './input/ChatInput'
|
|
17
17
|
import { ChatInputProvider } from '../context/ChatInputContext'
|
|
18
|
-
import {
|
|
18
|
+
import { PartRenderersProvider, type PartRenderers } from '../context/RenderersContext'
|
|
19
19
|
import { ConfirmDialog } from './common/ConfirmDialog'
|
|
20
20
|
import { Toast } from './common/Toast'
|
|
21
21
|
// ToolApprovalDialog 已移除,工具批准现在内嵌在 ToolCallPart 中
|
|
@@ -54,8 +54,8 @@ interface ChatPanelProps {
|
|
|
54
54
|
className?: string
|
|
55
55
|
/** 欢迎页配置 */
|
|
56
56
|
welcomeConfig?: Partial<WelcomeConfig>
|
|
57
|
-
/**
|
|
58
|
-
|
|
57
|
+
/** 自定义 Part 渲染器 - 根据 part.type 选择渲染组件(如 weather, stock 等) */
|
|
58
|
+
partRenderers?: PartRenderers
|
|
59
59
|
/**
|
|
60
60
|
* 执行步骤折叠模式
|
|
61
61
|
* - 'open': 始终展开
|
|
@@ -75,7 +75,7 @@ export const ChatPanel = forwardRef<ChatPanelHandle, ChatPanelProps>(({
|
|
|
75
75
|
onToolComplete,
|
|
76
76
|
className = '',
|
|
77
77
|
welcomeConfig,
|
|
78
|
-
|
|
78
|
+
partRenderers = {},
|
|
79
79
|
stepsExpandedType = 'auto',
|
|
80
80
|
}, ref) => {
|
|
81
81
|
const messagesRef = useRef<HTMLDivElement>(null)
|
|
@@ -83,7 +83,12 @@ export const ChatPanel = forwardRef<ChatPanelHandle, ChatPanelProps>(({
|
|
|
83
83
|
|
|
84
84
|
// 是否应该自动滚动(用户在底部附近时才自动滚动)
|
|
85
85
|
const [shouldAutoScroll, setShouldAutoScroll] = useState(true)
|
|
86
|
-
|
|
86
|
+
// 距离底部多少像素内算"在底部"
|
|
87
|
+
const SCROLL_THRESHOLD = 25
|
|
88
|
+
// 上次滚动位置(用于检测滚动方向)
|
|
89
|
+
const lastScrollTopRef = useRef(0)
|
|
90
|
+
// 是否正在程序化滚动(用于区分用户手动滚动)
|
|
91
|
+
const isProgrammaticScrollRef = useRef(false)
|
|
87
92
|
|
|
88
93
|
// 设置面板状态
|
|
89
94
|
const [settingsPanelVisible, setSettingsPanelVisible] = useState(false)
|
|
@@ -159,6 +164,7 @@ export const ChatPanel = forwardRef<ChatPanelHandle, ChatPanelProps>(({
|
|
|
159
164
|
cancelRequest,
|
|
160
165
|
copyMessage,
|
|
161
166
|
regenerateMessage,
|
|
167
|
+
resendFromIndex,
|
|
162
168
|
setMode,
|
|
163
169
|
setModel,
|
|
164
170
|
setWebSearch,
|
|
@@ -166,6 +172,10 @@ export const ChatPanel = forwardRef<ChatPanelHandle, ChatPanelProps>(({
|
|
|
166
172
|
setWorkingDirectory,
|
|
167
173
|
autoRunConfig,
|
|
168
174
|
saveAutoRunConfig,
|
|
175
|
+
// 工具管理
|
|
176
|
+
allTools,
|
|
177
|
+
enabledTools,
|
|
178
|
+
saveEnabledTools,
|
|
169
179
|
} = useChat({
|
|
170
180
|
adapter,
|
|
171
181
|
defaultModel,
|
|
@@ -173,6 +183,10 @@ export const ChatPanel = forwardRef<ChatPanelHandle, ChatPanelProps>(({
|
|
|
173
183
|
onToolComplete,
|
|
174
184
|
})
|
|
175
185
|
|
|
186
|
+
const handleCancelToolCall = useCallback((_toolCallId: string) => {
|
|
187
|
+
cancelRequest()
|
|
188
|
+
}, [cancelRequest])
|
|
189
|
+
|
|
176
190
|
// 暴露给外部的方法
|
|
177
191
|
useImperativeHandle(ref, () => ({
|
|
178
192
|
setInputText: (text: string) => {
|
|
@@ -213,16 +227,44 @@ export const ChatPanel = forwardRef<ChatPanelHandle, ChatPanelProps>(({
|
|
|
213
227
|
return scrollHeight - scrollTop - clientHeight < SCROLL_THRESHOLD
|
|
214
228
|
}, [])
|
|
215
229
|
|
|
216
|
-
//
|
|
230
|
+
// 处理滚动事件(优化:检测滚动方向,区分程序化滚动)
|
|
217
231
|
const handleScroll = useCallback(() => {
|
|
218
|
-
|
|
232
|
+
if (!messagesRef.current) return
|
|
233
|
+
|
|
234
|
+
// 忽略程序化滚动触发的事件
|
|
235
|
+
if (isProgrammaticScrollRef.current) return
|
|
236
|
+
|
|
237
|
+
const { scrollTop } = messagesRef.current
|
|
238
|
+
const isScrollingUp = scrollTop < lastScrollTopRef.current
|
|
239
|
+
lastScrollTopRef.current = scrollTop
|
|
240
|
+
|
|
241
|
+
// 用户向上滚动时,立即停止自动滚动
|
|
242
|
+
if (isScrollingUp) {
|
|
243
|
+
setShouldAutoScroll(false)
|
|
244
|
+
return
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// 用户向下滚动到底部附近时,恢复自动滚动
|
|
248
|
+
if (isNearBottom()) {
|
|
249
|
+
setShouldAutoScroll(true)
|
|
250
|
+
}
|
|
219
251
|
}, [isNearBottom])
|
|
220
252
|
|
|
221
253
|
// 滚动到底部
|
|
222
254
|
const scrollToBottom = useCallback((force = false) => {
|
|
223
255
|
if (messagesRef.current && (force || shouldAutoScroll)) {
|
|
256
|
+
// 标记为程序化滚动,避免触发 handleScroll 逻辑
|
|
257
|
+
isProgrammaticScrollRef.current = true
|
|
224
258
|
messagesRef.current.scrollTop = messagesRef.current.scrollHeight
|
|
225
|
-
|
|
259
|
+
lastScrollTopRef.current = messagesRef.current.scrollTop
|
|
260
|
+
// 强制滚动时才恢复自动滚动
|
|
261
|
+
if (force) {
|
|
262
|
+
setShouldAutoScroll(true)
|
|
263
|
+
}
|
|
264
|
+
// 延迟重置标志,确保 scroll 事件已处理
|
|
265
|
+
requestAnimationFrame(() => {
|
|
266
|
+
isProgrammaticScrollRef.current = false
|
|
267
|
+
})
|
|
226
268
|
}
|
|
227
269
|
}, [shouldAutoScroll])
|
|
228
270
|
|
|
@@ -242,8 +284,10 @@ export const ChatPanel = forwardRef<ChatPanelHandle, ChatPanelProps>(({
|
|
|
242
284
|
}, [isLoading, scrollToBottom])
|
|
243
285
|
|
|
244
286
|
// 发送消息
|
|
245
|
-
const handleSend = useCallback((text: string) => {
|
|
246
|
-
|
|
287
|
+
const handleSend = useCallback((text: string, images?: ImageData[]) => {
|
|
288
|
+
// 将 ImageData[] 转换为 string[] (data URL)
|
|
289
|
+
const imageUrls = images?.map(img => `data:${img.mimeType};base64,${img.base64}`)
|
|
290
|
+
sendMessage(text, imageUrls)
|
|
247
291
|
}, [sendMessage])
|
|
248
292
|
|
|
249
293
|
// @ 上下文
|
|
@@ -262,14 +306,20 @@ export const ChatPanel = forwardRef<ChatPanelHandle, ChatPanelProps>(({
|
|
|
262
306
|
|
|
263
307
|
// 重新发送(编辑后)
|
|
264
308
|
const handleResend = useCallback(
|
|
265
|
-
(
|
|
266
|
-
//
|
|
267
|
-
|
|
268
|
-
sendMessage(text)
|
|
309
|
+
(index: number, text: string) => {
|
|
310
|
+
// 与 Vue 版一致:使用分叉重发(以当前索引为锚点,删除后续并继续生成)
|
|
311
|
+
resendFromIndex(index, text)
|
|
269
312
|
},
|
|
270
|
-
[
|
|
313
|
+
[resendFromIndex]
|
|
271
314
|
)
|
|
272
315
|
|
|
316
|
+
// 创建新会话(重置输入框状态)
|
|
317
|
+
const handleNewSession = useCallback(async () => {
|
|
318
|
+
await createNewSession()
|
|
319
|
+
// 重置输入框状态
|
|
320
|
+
inputRef.current?.clear()
|
|
321
|
+
}, [createNewSession])
|
|
322
|
+
|
|
273
323
|
// 关闭
|
|
274
324
|
const handleClose = useCallback(() => {
|
|
275
325
|
onClose?.()
|
|
@@ -290,6 +340,15 @@ export const ChatPanel = forwardRef<ChatPanelHandle, ChatPanelProps>(({
|
|
|
290
340
|
}
|
|
291
341
|
}, [saveAutoRunConfig, showToast])
|
|
292
342
|
|
|
343
|
+
const handleUpdateEnabledTools = useCallback(async (tools: string[] | undefined) => {
|
|
344
|
+
try {
|
|
345
|
+
await saveEnabledTools(tools)
|
|
346
|
+
} catch (error) {
|
|
347
|
+
console.error('保存工具开关失败:', error)
|
|
348
|
+
showToast('保存工具开关失败', 'error')
|
|
349
|
+
}
|
|
350
|
+
}, [saveEnabledTools, showToast])
|
|
351
|
+
|
|
293
352
|
// 模式变更现在在 ToolCallPart 中处理,不再需要此函数
|
|
294
353
|
|
|
295
354
|
// 清空所有对话
|
|
@@ -369,7 +428,7 @@ export const ChatPanel = forwardRef<ChatPanelHandle, ChatPanelProps>(({
|
|
|
369
428
|
|
|
370
429
|
return (
|
|
371
430
|
<ChatInputProvider value={inputContextValue}>
|
|
372
|
-
<
|
|
431
|
+
<PartRenderersProvider partRenderers={partRenderers}>
|
|
373
432
|
<div className={`chat-panel ${className}`.trim()}>
|
|
374
433
|
{/* 确认弹窗 */}
|
|
375
434
|
<ConfirmDialog
|
|
@@ -399,6 +458,9 @@ export const ChatPanel = forwardRef<ChatPanelHandle, ChatPanelProps>(({
|
|
|
399
458
|
<SettingsPanel
|
|
400
459
|
visible={settingsPanelVisible}
|
|
401
460
|
config={autoRunConfig}
|
|
461
|
+
allTools={allTools}
|
|
462
|
+
enabledTools={enabledTools}
|
|
463
|
+
onUpdateEnabledTools={handleUpdateEnabledTools}
|
|
402
464
|
onChange={handleSaveSettings}
|
|
403
465
|
onClose={() => setSettingsPanelVisible(false)}
|
|
404
466
|
/>
|
|
@@ -409,7 +471,7 @@ export const ChatPanel = forwardRef<ChatPanelHandle, ChatPanelProps>(({
|
|
|
409
471
|
sessions={sessions}
|
|
410
472
|
currentSessionId={currentSessionId}
|
|
411
473
|
showClose={!!onClose}
|
|
412
|
-
onNewSession={
|
|
474
|
+
onNewSession={handleNewSession}
|
|
413
475
|
onSwitchSession={switchSession}
|
|
414
476
|
onDeleteSession={deleteSession}
|
|
415
477
|
onHideSession={hideSession}
|
|
@@ -442,6 +504,7 @@ export const ChatPanel = forwardRef<ChatPanelHandle, ChatPanelProps>(({
|
|
|
442
504
|
timestamp={msg.timestamp}
|
|
443
505
|
stepsExpandedType={stepsExpandedType}
|
|
444
506
|
adapter={adapter}
|
|
507
|
+
onCancelToolCall={handleCancelToolCall}
|
|
445
508
|
autoRunConfig={autoRunConfig}
|
|
446
509
|
onSaveConfig={saveAutoRunConfig}
|
|
447
510
|
onCopy={() => copyMessage(msg.id)}
|
|
@@ -481,7 +544,7 @@ export const ChatPanel = forwardRef<ChatPanelHandle, ChatPanelProps>(({
|
|
|
481
544
|
onAtContext={handleAtContext}
|
|
482
545
|
/>
|
|
483
546
|
</div>
|
|
484
|
-
</
|
|
547
|
+
</PartRenderersProvider>
|
|
485
548
|
</ChatInputProvider>
|
|
486
549
|
)
|
|
487
550
|
})
|
|
@@ -254,3 +254,84 @@
|
|
|
254
254
|
.toggle-switch input:focus + .toggle-slider {
|
|
255
255
|
box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.2);
|
|
256
256
|
}
|
|
257
|
+
|
|
258
|
+
/* 工具管理 */
|
|
259
|
+
.setting-section-header {
|
|
260
|
+
display: flex;
|
|
261
|
+
align-items: flex-start;
|
|
262
|
+
justify-content: space-between;
|
|
263
|
+
gap: 16px;
|
|
264
|
+
padding: 16px 0 8px;
|
|
265
|
+
border-bottom: 1px solid var(--chat-border, #333);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
.disable-all-btn {
|
|
269
|
+
flex-shrink: 0;
|
|
270
|
+
height: 32px;
|
|
271
|
+
padding: 0 12px;
|
|
272
|
+
border-radius: 8px;
|
|
273
|
+
border: 1px solid var(--chat-border, #333);
|
|
274
|
+
background: rgba(255, 255, 255, 0.06);
|
|
275
|
+
color: var(--chat-text, #fff);
|
|
276
|
+
font-size: 13px;
|
|
277
|
+
cursor: pointer;
|
|
278
|
+
transition: all 0.15s ease;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
.disable-all-btn:hover {
|
|
282
|
+
background: rgba(255, 255, 255, 0.1);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
.disable-all-btn:disabled {
|
|
286
|
+
opacity: 0.5;
|
|
287
|
+
cursor: not-allowed;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
.tools-list {
|
|
291
|
+
display: flex;
|
|
292
|
+
flex-direction: column;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
.tool-item {
|
|
296
|
+
display: flex;
|
|
297
|
+
align-items: center;
|
|
298
|
+
justify-content: space-between;
|
|
299
|
+
gap: 16px;
|
|
300
|
+
padding: 14px 0;
|
|
301
|
+
border-bottom: 1px solid var(--chat-border, #333);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
.tool-item:last-child {
|
|
305
|
+
border-bottom: none;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
.tool-info {
|
|
309
|
+
flex: 1;
|
|
310
|
+
min-width: 0;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
.tool-name {
|
|
314
|
+
font-size: 14px;
|
|
315
|
+
font-weight: 500;
|
|
316
|
+
color: var(--chat-text, #fff);
|
|
317
|
+
margin-bottom: 4px;
|
|
318
|
+
word-break: break-all;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
.tool-description {
|
|
322
|
+
font-size: 13px;
|
|
323
|
+
color: var(--chat-text-muted, #888);
|
|
324
|
+
line-height: 1.5;
|
|
325
|
+
word-break: break-word;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
.no-tools {
|
|
329
|
+
padding: 16px 0;
|
|
330
|
+
color: var(--chat-text, #fff);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
.no-tools-hint {
|
|
334
|
+
font-size: 12px;
|
|
335
|
+
color: var(--chat-text-muted, #888);
|
|
336
|
+
margin-top: 8px;
|
|
337
|
+
}
|
|
@@ -14,6 +14,12 @@ import './SettingsPanel.css'
|
|
|
14
14
|
interface SettingsPanelProps {
|
|
15
15
|
visible: boolean
|
|
16
16
|
config: AutoRunConfig
|
|
17
|
+
/** 所有可用工具列表(用于工具管理) */
|
|
18
|
+
allTools?: Array<{ name: string; description: string }>
|
|
19
|
+
/** 启用的工具名称列表(undefined 表示全部启用) */
|
|
20
|
+
enabledTools?: string[] | undefined
|
|
21
|
+
/** 更新工具开关(持久化由上层处理) */
|
|
22
|
+
onUpdateEnabledTools?: (tools: string[] | undefined) => void
|
|
17
23
|
onClose: () => void
|
|
18
24
|
onChange: (config: AutoRunConfig) => void
|
|
19
25
|
}
|
|
@@ -23,7 +29,7 @@ const sections = [
|
|
|
23
29
|
{ id: 'indexing', label: '索引与文档', icon: 'lucide:database' },
|
|
24
30
|
] as const
|
|
25
31
|
|
|
26
|
-
export function SettingsPanel({ visible, config, onClose, onChange }: SettingsPanelProps) {
|
|
32
|
+
export function SettingsPanel({ visible, config, allTools, enabledTools, onUpdateEnabledTools, onClose, onChange }: SettingsPanelProps) {
|
|
27
33
|
const [currentSection, setCurrentSection] = useState<string>('agent')
|
|
28
34
|
|
|
29
35
|
const currentSectionLabel = useMemo(
|
|
@@ -54,6 +60,54 @@ export function SettingsPanel({ visible, config, onClose, onChange }: SettingsPa
|
|
|
54
60
|
}
|
|
55
61
|
}, [onClose])
|
|
56
62
|
|
|
63
|
+
const allToolNames = useMemo(() => (allTools ?? []).map((t) => t.name), [allTools])
|
|
64
|
+
|
|
65
|
+
/** 判断工具是否启用 */
|
|
66
|
+
const isToolEnabled = useCallback((toolName: string): boolean => {
|
|
67
|
+
// undefined 表示全部启用
|
|
68
|
+
if (enabledTools === undefined) return true
|
|
69
|
+
return enabledTools.includes(toolName)
|
|
70
|
+
}, [enabledTools])
|
|
71
|
+
|
|
72
|
+
/** 处理工具开关切换 */
|
|
73
|
+
const handleToolToggle = useCallback((toolName: string, checked: boolean) => {
|
|
74
|
+
if (!onUpdateEnabledTools) return
|
|
75
|
+
|
|
76
|
+
// undefined = 全部启用
|
|
77
|
+
if (enabledTools === undefined) {
|
|
78
|
+
if (checked) return
|
|
79
|
+
onUpdateEnabledTools(allToolNames.filter((n) => n !== toolName))
|
|
80
|
+
return
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const set = new Set(enabledTools)
|
|
84
|
+
if (checked) set.add(toolName)
|
|
85
|
+
else set.delete(toolName)
|
|
86
|
+
|
|
87
|
+
// 如果全部启用,回到 undefined(更符合默认语义)
|
|
88
|
+
if (allToolNames.length > 0 && set.size === allToolNames.length) {
|
|
89
|
+
onUpdateEnabledTools(undefined)
|
|
90
|
+
return
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
onUpdateEnabledTools(Array.from(set))
|
|
94
|
+
}, [enabledTools, allToolNames, onUpdateEnabledTools])
|
|
95
|
+
|
|
96
|
+
/** 一键禁用所有工具 */
|
|
97
|
+
const handleDisableAllTools = useCallback(() => {
|
|
98
|
+
onUpdateEnabledTools?.([])
|
|
99
|
+
}, [onUpdateEnabledTools])
|
|
100
|
+
|
|
101
|
+
const isAllToolsDisabled = useMemo(
|
|
102
|
+
() => enabledTools !== undefined && enabledTools.length === 0,
|
|
103
|
+
[enabledTools]
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
const handleEnableAllTools = useCallback(() => {
|
|
107
|
+
// 恢复默认:undefined 表示全部启用
|
|
108
|
+
onUpdateEnabledTools?.(undefined)
|
|
109
|
+
}, [onUpdateEnabledTools])
|
|
110
|
+
|
|
57
111
|
if (!visible) return null
|
|
58
112
|
|
|
59
113
|
return (
|
|
@@ -105,6 +159,47 @@ export function SettingsPanel({ visible, config, onClose, onChange }: SettingsPa
|
|
|
105
159
|
/>
|
|
106
160
|
</div>
|
|
107
161
|
</div>
|
|
162
|
+
|
|
163
|
+
{/* 工具管理 */}
|
|
164
|
+
<div className="setting-section">
|
|
165
|
+
<div className="setting-section-header">
|
|
166
|
+
<div className="setting-info">
|
|
167
|
+
<div className="setting-label">工具管理</div>
|
|
168
|
+
<div className="setting-description">控制哪些工具可以被 AI 使用</div>
|
|
169
|
+
</div>
|
|
170
|
+
<button
|
|
171
|
+
className="disable-all-btn"
|
|
172
|
+
onClick={isAllToolsDisabled ? handleEnableAllTools : handleDisableAllTools}
|
|
173
|
+
disabled={!allTools?.length}
|
|
174
|
+
>
|
|
175
|
+
{isAllToolsDisabled ? '启用所有工具' : '禁用所有工具'}
|
|
176
|
+
</button>
|
|
177
|
+
</div>
|
|
178
|
+
|
|
179
|
+
<div className="tools-list">
|
|
180
|
+
{allTools && allTools.length > 0 ? (
|
|
181
|
+
allTools.map((tool) => (
|
|
182
|
+
<div key={tool.name} className="tool-item">
|
|
183
|
+
<div className="tool-info">
|
|
184
|
+
<div className="tool-name">{tool.name}</div>
|
|
185
|
+
<div className="tool-description">{tool.description}</div>
|
|
186
|
+
</div>
|
|
187
|
+
<ToggleSwitch
|
|
188
|
+
checked={isToolEnabled(tool.name)}
|
|
189
|
+
onChange={(checked) => handleToolToggle(tool.name, checked)}
|
|
190
|
+
/>
|
|
191
|
+
</div>
|
|
192
|
+
))
|
|
193
|
+
) : (
|
|
194
|
+
<div className="no-tools">
|
|
195
|
+
<div>暂无可用工具</div>
|
|
196
|
+
<div className="no-tools-hint">
|
|
197
|
+
工具需要在创建 Electron Bridge 时通过 <code>tools</code> 参数注入
|
|
198
|
+
</div>
|
|
199
|
+
</div>
|
|
200
|
+
)}
|
|
201
|
+
</div>
|
|
202
|
+
</div>
|
|
108
203
|
</>
|
|
109
204
|
)}
|
|
110
205
|
|