@vegintech/langchain-react-agent 0.0.3 → 0.0.5
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 +1 -1
- package/dist/index.d.mts +67 -24
- package/dist/index.mjs +396 -303
- package/package.json +1 -1
package/README.md
CHANGED
package/dist/index.d.mts
CHANGED
|
@@ -2,9 +2,57 @@ import * as react from "react";
|
|
|
2
2
|
import { ReactNode } from "react";
|
|
3
3
|
import { Components } from "streamdown";
|
|
4
4
|
import { BaseNode, InsertPosition, NodeRender, SenderProps, SkillType, SlotConfigType } from "@ant-design/x/es/sender/interface.ts";
|
|
5
|
-
import { BaseMessage } from "@langchain/core/messages";
|
|
6
5
|
|
|
7
6
|
//#region src/types.d.ts
|
|
7
|
+
/** ToolCard 组件 Props */
|
|
8
|
+
interface ToolCardProps {
|
|
9
|
+
/**
|
|
10
|
+
* 前缀文本(中文,使用小字体)
|
|
11
|
+
*/
|
|
12
|
+
prefix: string;
|
|
13
|
+
/**
|
|
14
|
+
* 正文内容(英文/文件名,保持默认大小)
|
|
15
|
+
*/
|
|
16
|
+
content?: string;
|
|
17
|
+
/**
|
|
18
|
+
* 左侧图标
|
|
19
|
+
*/
|
|
20
|
+
icon: ReactNode;
|
|
21
|
+
}
|
|
22
|
+
/** Interrupt 事件数据结构 */
|
|
23
|
+
interface InterruptEvent {
|
|
24
|
+
/** Interrupt 的值,可以是任意类型(由 LangChain 后端决定) */
|
|
25
|
+
value: unknown;
|
|
26
|
+
/** Interrupt 的唯一标识 */
|
|
27
|
+
id: string;
|
|
28
|
+
}
|
|
29
|
+
/** Interrupt 渲染函数的参数 */
|
|
30
|
+
interface InterruptRenderProps {
|
|
31
|
+
/** Interrupt 事件数据 */
|
|
32
|
+
event: InterruptEvent;
|
|
33
|
+
/** 解决 Interrupt 的回调函数,传入响应值 */
|
|
34
|
+
resolve: (response: unknown) => void;
|
|
35
|
+
}
|
|
36
|
+
/** Interrupt 处理配置 */
|
|
37
|
+
interface InterruptConfig {
|
|
38
|
+
/** 自定义 Interrupt 渲染函数 */
|
|
39
|
+
render: (props: InterruptRenderProps) => ReactNode;
|
|
40
|
+
}
|
|
41
|
+
/** InterruptManager Hook 的 Props */
|
|
42
|
+
interface InterruptManagerProps {
|
|
43
|
+
/** 当前 interrupt 事件 */
|
|
44
|
+
interrupt: InterruptEvent | null;
|
|
45
|
+
/** Interrupt 处理配置 */
|
|
46
|
+
config?: InterruptConfig;
|
|
47
|
+
/** 提交回调 */
|
|
48
|
+
onSubmit: (values: {
|
|
49
|
+
[x: string]: unknown;
|
|
50
|
+
} | null | undefined, options?: {
|
|
51
|
+
command?: {
|
|
52
|
+
resume: unknown;
|
|
53
|
+
};
|
|
54
|
+
}) => Promise<void> | void;
|
|
55
|
+
}
|
|
8
56
|
/** Context Item,用于传递上下文信息 */
|
|
9
57
|
interface ContextItem {
|
|
10
58
|
/** 描述信息 */
|
|
@@ -132,6 +180,15 @@ interface BackendTool<TArgs = Record<string, unknown>> extends ToolBase<TArgs> {
|
|
|
132
180
|
}
|
|
133
181
|
/** 工具定义联合类型 */
|
|
134
182
|
type ToolDefinition<TArgs = Record<string, unknown>> = FrontendTool<TArgs> | BackendTool<TArgs>;
|
|
183
|
+
/** 空状态配置 */
|
|
184
|
+
interface EmptyStateConfig {
|
|
185
|
+
/** 空状态标题 */
|
|
186
|
+
title?: string;
|
|
187
|
+
/** 空状态描述 */
|
|
188
|
+
description?: string;
|
|
189
|
+
/** 自定义渲染 */
|
|
190
|
+
render?: () => ReactNode;
|
|
191
|
+
}
|
|
135
192
|
/** 消息渲染配置 */
|
|
136
193
|
interface MessageConfig {
|
|
137
194
|
/** Markdown 自定义组件 */
|
|
@@ -142,6 +199,8 @@ interface MessageConfig {
|
|
|
142
199
|
literalTagContent?: string[];
|
|
143
200
|
/** Loading 动画颜色,支持 CSS 颜色值 */
|
|
144
201
|
loadingColor?: string;
|
|
202
|
+
/** 空状态配置 */
|
|
203
|
+
emptyState?: EmptyStateConfig;
|
|
145
204
|
}
|
|
146
205
|
/** 输入框配置 */
|
|
147
206
|
interface InputConfig extends SenderCustomizationProps {
|
|
@@ -162,6 +221,7 @@ interface AgentChatProps {
|
|
|
162
221
|
messageConfig?: MessageConfig;
|
|
163
222
|
inputConfig?: InputConfig;
|
|
164
223
|
onError?: (error: Error) => void;
|
|
224
|
+
interruptConfig?: InterruptConfig;
|
|
165
225
|
}
|
|
166
226
|
/** AgentChat 组件对外暴露的方法 */
|
|
167
227
|
interface AgentChatRef {
|
|
@@ -175,31 +235,14 @@ interface AgentChatRef {
|
|
|
175
235
|
focusInput: () => void;
|
|
176
236
|
}
|
|
177
237
|
//#endregion
|
|
178
|
-
//#region src/AgentChat.d.ts
|
|
238
|
+
//#region src/components/AgentChat.d.ts
|
|
179
239
|
declare const AgentChat: react.ForwardRefExoticComponent<AgentChatProps & react.RefAttributes<AgentChatRef>>;
|
|
180
240
|
//#endregion
|
|
181
|
-
//#region src/
|
|
182
|
-
/**
|
|
183
|
-
* 从 BaseMessage 中提取 tool_calls
|
|
184
|
-
*/
|
|
185
|
-
declare function extractToolCalls(message: BaseMessage): ToolCallInput[] | undefined;
|
|
186
|
-
/**
|
|
187
|
-
* 从 BaseMessage 中提取文本内容
|
|
188
|
-
*/
|
|
189
|
-
declare function extractContent(message: BaseMessage): string;
|
|
241
|
+
//#region src/components/ToolCard.d.ts
|
|
190
242
|
/**
|
|
191
|
-
*
|
|
243
|
+
* ToolCard 组件 - 用于展示工具调用结果
|
|
244
|
+
* 样式:圆角矩形框,左侧 icon,label 分两行显示(prefix 小字体,content 默认字体)
|
|
192
245
|
*/
|
|
193
|
-
declare
|
|
194
|
-
/**
|
|
195
|
-
* 预处理消息列表:建立 tool_call_id -> result 映射,并过滤 ToolMessage
|
|
196
|
-
*/
|
|
197
|
-
declare function processMessages(rawMessages: BaseMessage[]): {
|
|
198
|
-
messages: ChatMessage[];
|
|
199
|
-
toolResults: Map<string, unknown>;
|
|
200
|
-
};
|
|
201
|
-
//#endregion
|
|
202
|
-
//#region src/injectStyles.d.ts
|
|
203
|
-
declare function injectStyles(): void;
|
|
246
|
+
declare const ToolCard: React.FC<ToolCardProps>;
|
|
204
247
|
//#endregion
|
|
205
|
-
export { AgentChat, type AgentChatInputRef, type AgentChatProps, type AgentChatRef, type BackendTool, type ChatMessage, type ContextItem, type FrontendTool, type InputConfig, type MessageConfig, type MessageType, type SenderCustomizationProps, type SenderSlotConfig, type SenderSubmitParams, type ToolCallInput, type ToolDefinition, type ToolExecutionRecord, type ToolExecutionStatus, type ToolParameterSchema, type ToolRenderProps
|
|
248
|
+
export { AgentChat, type AgentChatInputRef, type AgentChatProps, type AgentChatRef, type BackendTool, type ChatMessage, type ContextItem, type EmptyStateConfig, type FrontendTool, type InputConfig, type InterruptConfig, type InterruptEvent, type InterruptManagerProps, type InterruptRenderProps, type MessageConfig, type MessageType, type SenderCustomizationProps, type SenderSlotConfig, type SenderSubmitParams, type ToolCallInput, ToolCard, type ToolCardProps, type ToolDefinition, type ToolExecutionRecord, type ToolExecutionStatus, type ToolParameterSchema, type ToolRenderProps };
|
package/dist/index.mjs
CHANGED
|
@@ -1,117 +1,9 @@
|
|
|
1
1
|
import { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from "react";
|
|
2
2
|
import { useStream } from "@langchain/react";
|
|
3
|
-
import { Bubble, Sender, Think } from "@ant-design/x";
|
|
3
|
+
import { Actions, Bubble, Sender, Think } from "@ant-design/x";
|
|
4
4
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
5
5
|
import { Streamdown } from "streamdown";
|
|
6
|
-
//#region src/
|
|
7
|
-
const styles = `
|
|
8
|
-
/* Agent Chat Container */
|
|
9
|
-
.agent-chat-container {
|
|
10
|
-
display: flex;
|
|
11
|
-
flex-direction: column;
|
|
12
|
-
height: 100%;
|
|
13
|
-
background-color: transparent;
|
|
14
|
-
overflow: hidden;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/* Message List */
|
|
18
|
-
.agent-message-list {
|
|
19
|
-
flex: 1;
|
|
20
|
-
overflow-y: auto;
|
|
21
|
-
display: flex;
|
|
22
|
-
flex-direction: column;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
.agent-message-list .empty {
|
|
26
|
-
display: flex;
|
|
27
|
-
align-items: center;
|
|
28
|
-
justify-content: center;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
.agent-message-list .ant-think-status-text {
|
|
32
|
-
font-size: 13px;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
.agent-message-empty {
|
|
36
|
-
color: #8b8d91;
|
|
37
|
-
font-size: 14px;
|
|
38
|
-
text-align: center;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
@keyframes agent-loading-bounce {
|
|
42
|
-
0%,
|
|
43
|
-
80%,
|
|
44
|
-
100% {
|
|
45
|
-
transform: scale(0);
|
|
46
|
-
}
|
|
47
|
-
40% {
|
|
48
|
-
transform: scale(1);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/* Chat Input */
|
|
53
|
-
.agent-chat-input-container {
|
|
54
|
-
padding: 12px 4px;
|
|
55
|
-
background-color: transparent;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/* Tool Calls Container */
|
|
59
|
-
.tool-calls-container {
|
|
60
|
-
display: flex;
|
|
61
|
-
flex-direction: column;
|
|
62
|
-
gap: 8px;
|
|
63
|
-
margin-top: 8px;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/* Tool Call Default Style */
|
|
67
|
-
.tool-call-default {
|
|
68
|
-
display: flex;
|
|
69
|
-
flex-direction: column;
|
|
70
|
-
gap: 4px;
|
|
71
|
-
padding: 6px 0;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
.tool-call-header {
|
|
75
|
-
display: flex;
|
|
76
|
-
align-items: center;
|
|
77
|
-
gap: 8px;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
.tool-call-name {
|
|
81
|
-
font-weight: 500;
|
|
82
|
-
font-size: 13px;
|
|
83
|
-
color: #fff;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
.tool-call-status {
|
|
87
|
-
font-size: 12px;
|
|
88
|
-
color: #8b8d91;
|
|
89
|
-
margin-left: auto;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
.tool-call-error {
|
|
93
|
-
display: flex;
|
|
94
|
-
align-items: center;
|
|
95
|
-
gap: 4px;
|
|
96
|
-
font-size: 12px;
|
|
97
|
-
color: #ff4d4f;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
.tool-call-wrapper {
|
|
101
|
-
width: 100%;
|
|
102
|
-
}
|
|
103
|
-
`;
|
|
104
|
-
let injected = false;
|
|
105
|
-
function injectStyles() {
|
|
106
|
-
if (injected || typeof document === "undefined") return;
|
|
107
|
-
const styleElement = document.createElement("style");
|
|
108
|
-
styleElement.textContent = styles;
|
|
109
|
-
document.head.appendChild(styleElement);
|
|
110
|
-
injected = true;
|
|
111
|
-
}
|
|
112
|
-
//#endregion
|
|
113
|
-
//#region src/ChatInput.tsx
|
|
114
|
-
injectStyles();
|
|
6
|
+
//#region src/components/ChatInput.tsx
|
|
115
7
|
const slotConfig = [];
|
|
116
8
|
const ChatInput = forwardRef(({ onSend, onStop, isLoading = false, disabled = false, placeholder = "输入消息...", className = "", footer, skill: externalSkill, header, prefix }, ref) => {
|
|
117
9
|
const senderRef = useRef(null);
|
|
@@ -159,7 +51,7 @@ const ChatInput = forwardRef(({ onSend, onStop, isLoading = false, disabled = fa
|
|
|
159
51
|
});
|
|
160
52
|
ChatInput.displayName = "ChatInput";
|
|
161
53
|
//#endregion
|
|
162
|
-
//#region src/ToolCallRenderer.tsx
|
|
54
|
+
//#region src/components/ToolCallRenderer.tsx
|
|
163
55
|
/**
|
|
164
56
|
* 工具调用渲染组件
|
|
165
57
|
*
|
|
@@ -209,125 +101,7 @@ const DefaultToolCallRenderer = ({ record }) => {
|
|
|
209
101
|
});
|
|
210
102
|
};
|
|
211
103
|
//#endregion
|
|
212
|
-
//#region src/
|
|
213
|
-
/**
|
|
214
|
-
* 工具管理器组件
|
|
215
|
-
*
|
|
216
|
-
* 职责:
|
|
217
|
-
* 1. 管理工具执行状态
|
|
218
|
-
* 2. 自动执行前端工具(避免重复执行)
|
|
219
|
-
* 3. 通知外部执行状态变化
|
|
220
|
-
*
|
|
221
|
-
* 这是一个无 UI 组件,只负责逻辑处理
|
|
222
|
-
*/
|
|
223
|
-
const ToolManager = ({ tools, toolCalls, isLoading = false, onExecutionChange, onToolResult, completedToolResults }) => {
|
|
224
|
-
const executedCallsRef = useRef(/* @__PURE__ */ new Set());
|
|
225
|
-
const executingCallsRef = useRef(/* @__PURE__ */ new Set());
|
|
226
|
-
const pendingNotifiedRef = useRef(/* @__PURE__ */ new Set());
|
|
227
|
-
const initializedRef = useRef(false);
|
|
228
|
-
useEffect(() => {
|
|
229
|
-
if (initializedRef.current || !completedToolResults || completedToolResults.size === 0) return;
|
|
230
|
-
initializedRef.current = true;
|
|
231
|
-
completedToolResults.forEach((result, callId) => {
|
|
232
|
-
executedCallsRef.current.add(callId);
|
|
233
|
-
const call = toolCalls.find((c) => c.id === callId);
|
|
234
|
-
if (call) onExecutionChange?.({
|
|
235
|
-
callId,
|
|
236
|
-
name: call.name,
|
|
237
|
-
arguments: call.arguments,
|
|
238
|
-
status: "success",
|
|
239
|
-
result
|
|
240
|
-
});
|
|
241
|
-
});
|
|
242
|
-
}, [completedToolResults]);
|
|
243
|
-
/**
|
|
244
|
-
* 执行单个前端工具
|
|
245
|
-
*/
|
|
246
|
-
const executeFrontendTool = useCallback(async (tool, call) => {
|
|
247
|
-
const callId = call.id;
|
|
248
|
-
executingCallsRef.current.add(callId);
|
|
249
|
-
onExecutionChange?.({
|
|
250
|
-
callId,
|
|
251
|
-
name: call.name,
|
|
252
|
-
arguments: call.arguments,
|
|
253
|
-
status: "running"
|
|
254
|
-
});
|
|
255
|
-
try {
|
|
256
|
-
const result = await tool.execute(call.arguments);
|
|
257
|
-
onExecutionChange?.({
|
|
258
|
-
callId,
|
|
259
|
-
name: call.name,
|
|
260
|
-
arguments: call.arguments,
|
|
261
|
-
status: "success",
|
|
262
|
-
result
|
|
263
|
-
});
|
|
264
|
-
onToolResult?.(callId, call.name, result);
|
|
265
|
-
} catch (error) {
|
|
266
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
267
|
-
onExecutionChange?.({
|
|
268
|
-
callId,
|
|
269
|
-
name: call.name,
|
|
270
|
-
arguments: call.arguments,
|
|
271
|
-
status: "error",
|
|
272
|
-
error: errorMessage
|
|
273
|
-
});
|
|
274
|
-
} finally {
|
|
275
|
-
executingCallsRef.current.delete(callId);
|
|
276
|
-
executedCallsRef.current.add(callId);
|
|
277
|
-
}
|
|
278
|
-
}, [onExecutionChange, onToolResult]);
|
|
279
|
-
/**
|
|
280
|
-
* 处理工具调用
|
|
281
|
-
*/
|
|
282
|
-
const processToolCalls = useCallback(async () => {
|
|
283
|
-
if (!tools) return;
|
|
284
|
-
for (const call of toolCalls) {
|
|
285
|
-
const callId = call.id;
|
|
286
|
-
if (executedCallsRef.current.has(callId) || executingCallsRef.current.has(callId)) continue;
|
|
287
|
-
const tool = tools.find((t) => t.name === call.name);
|
|
288
|
-
if (isLoading) {
|
|
289
|
-
if (!pendingNotifiedRef.current.has(callId)) {
|
|
290
|
-
pendingNotifiedRef.current.add(callId);
|
|
291
|
-
onExecutionChange?.({
|
|
292
|
-
callId,
|
|
293
|
-
name: call.name,
|
|
294
|
-
arguments: call.arguments,
|
|
295
|
-
status: "pending"
|
|
296
|
-
});
|
|
297
|
-
}
|
|
298
|
-
continue;
|
|
299
|
-
}
|
|
300
|
-
if (!tool) {
|
|
301
|
-
onExecutionChange?.({
|
|
302
|
-
callId,
|
|
303
|
-
name: call.name,
|
|
304
|
-
arguments: call.arguments,
|
|
305
|
-
status: "pending"
|
|
306
|
-
});
|
|
307
|
-
continue;
|
|
308
|
-
}
|
|
309
|
-
if (tool.type === "frontend") await executeFrontendTool(tool, call);
|
|
310
|
-
else onExecutionChange?.({
|
|
311
|
-
callId,
|
|
312
|
-
name: call.name,
|
|
313
|
-
arguments: call.arguments,
|
|
314
|
-
status: "pending"
|
|
315
|
-
});
|
|
316
|
-
}
|
|
317
|
-
}, [
|
|
318
|
-
tools,
|
|
319
|
-
toolCalls,
|
|
320
|
-
isLoading,
|
|
321
|
-
executeFrontendTool,
|
|
322
|
-
onExecutionChange
|
|
323
|
-
]);
|
|
324
|
-
useEffect(() => {
|
|
325
|
-
processToolCalls();
|
|
326
|
-
}, [useMemo(() => {
|
|
327
|
-
return toolCalls.filter((call) => !executedCallsRef.current.has(call.id) && !executingCallsRef.current.has(call.id)).map((call) => call.id).sort().join(",");
|
|
328
|
-
}, [toolCalls]), isLoading]);
|
|
329
|
-
return null;
|
|
330
|
-
};
|
|
104
|
+
//#region src/utils/toolUtils.ts
|
|
331
105
|
/**
|
|
332
106
|
* 辅助函数:根据工具名查找工具定义
|
|
333
107
|
*/
|
|
@@ -341,7 +115,7 @@ function isFrontendTool(tool) {
|
|
|
341
115
|
return tool.type === "frontend";
|
|
342
116
|
}
|
|
343
117
|
//#endregion
|
|
344
|
-
//#region src/ReasoningContent.tsx
|
|
118
|
+
//#region src/components/ReasoningContent.tsx
|
|
345
119
|
const REASONING_CONTENT_MAX_HEIGHT = 58;
|
|
346
120
|
const ReasoningContent = ({ content }) => {
|
|
347
121
|
const [isExpanded, setIsExpanded] = useState(false);
|
|
@@ -374,22 +148,23 @@ const ReasoningContent = ({ content }) => {
|
|
|
374
148
|
justifyContent: "flex-end"
|
|
375
149
|
},
|
|
376
150
|
children: content.trim()
|
|
377
|
-
}), isOverflowing && !isExpanded && /* @__PURE__ */ jsx(
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
151
|
+
}), isOverflowing && !isExpanded && /* @__PURE__ */ jsx(Actions, {
|
|
152
|
+
items: [{
|
|
153
|
+
key: "expand",
|
|
154
|
+
icon: /* @__PURE__ */ jsx("span", {
|
|
155
|
+
style: {
|
|
156
|
+
fontSize: "12px",
|
|
157
|
+
marginLeft: "-4px",
|
|
158
|
+
opacity: .8
|
|
159
|
+
},
|
|
160
|
+
children: "查看全部"
|
|
161
|
+
})
|
|
162
|
+
}],
|
|
163
|
+
onClick: () => setIsExpanded(true)
|
|
389
164
|
})] });
|
|
390
165
|
};
|
|
391
166
|
//#endregion
|
|
392
|
-
//#region src/WaveLoading.tsx
|
|
167
|
+
//#region src/components/WaveLoading.tsx
|
|
393
168
|
/**
|
|
394
169
|
* 波浪动画 Loading 组件
|
|
395
170
|
* 三个小圆点波浪状动画效果
|
|
@@ -428,8 +203,7 @@ const WaveLoading = ({ color = "#1890ff" }) => {
|
|
|
428
203
|
});
|
|
429
204
|
};
|
|
430
205
|
//#endregion
|
|
431
|
-
//#region src/MessageList.tsx
|
|
432
|
-
injectStyles();
|
|
206
|
+
//#region src/components/MessageList.tsx
|
|
433
207
|
const CustomParagraph = (props) => {
|
|
434
208
|
const { node, ...rest } = props;
|
|
435
209
|
return /* @__PURE__ */ jsx("span", { ...rest });
|
|
@@ -530,7 +304,7 @@ const renderToolCalls = (toolCalls, tools, toolExecutions) => {
|
|
|
530
304
|
tool,
|
|
531
305
|
record
|
|
532
306
|
}, call.id);
|
|
533
|
-
});
|
|
307
|
+
}).filter(Boolean);
|
|
534
308
|
};
|
|
535
309
|
const roleConfig = {
|
|
536
310
|
user: {
|
|
@@ -546,7 +320,7 @@ const roleConfig = {
|
|
|
546
320
|
}
|
|
547
321
|
}
|
|
548
322
|
};
|
|
549
|
-
const MessageList = ({ messages, isLoading = false, className = "", tools, toolExecutions, components, securityConfig, loadingColor }) => {
|
|
323
|
+
const MessageList = ({ messages, isLoading = false, className = "", tools, toolExecutions, components, securityConfig, loadingColor, interruptRender, emptyState }) => {
|
|
550
324
|
const reasoningCacheRef = useRef(/* @__PURE__ */ new Map());
|
|
551
325
|
const processedMessages = useMemo(() => {
|
|
552
326
|
const cache = reasoningCacheRef.current;
|
|
@@ -576,13 +350,19 @@ const MessageList = ({ messages, isLoading = false, className = "", tools, toolE
|
|
|
576
350
|
}, [processedMessages]);
|
|
577
351
|
const items = groupedItems.map((group, groupIndex) => {
|
|
578
352
|
const isLastGroup = groupIndex === groupedItems.length - 1;
|
|
353
|
+
const isLastAiGroup = isLastGroup && group.type === "ai";
|
|
579
354
|
if (group.type === "user" || group.messages.length === 1) {
|
|
580
355
|
const message = group.messages[0];
|
|
581
|
-
|
|
356
|
+
const bubbleItem = toBubbleItem(message, isLastGroup && group.messages.length === 1, isLoading, tools, toolExecutions, components, securityConfig);
|
|
357
|
+
if (isLastAiGroup && interruptRender) return {
|
|
358
|
+
...bubbleItem,
|
|
359
|
+
content: /* @__PURE__ */ jsxs(Fragment, { children: [bubbleItem.content, interruptRender()] })
|
|
360
|
+
};
|
|
361
|
+
return bubbleItem;
|
|
582
362
|
}
|
|
583
|
-
const mergedContent = /* @__PURE__ */
|
|
363
|
+
const mergedContent = /* @__PURE__ */ jsxs(Fragment, { children: [group.messages.map((message, msgIndex) => {
|
|
584
364
|
return renderMessageContent(message, isLastGroup && msgIndex === group.messages.length - 1, isLoading, tools, toolExecutions, components, securityConfig);
|
|
585
|
-
}) });
|
|
365
|
+
}), isLastAiGroup && interruptRender?.()] });
|
|
586
366
|
return {
|
|
587
367
|
key: group.messages.map((m) => m.id).join("-"),
|
|
588
368
|
role: "ai",
|
|
@@ -597,13 +377,19 @@ const MessageList = ({ messages, isLoading = false, className = "", tools, toolE
|
|
|
597
377
|
placement: "start",
|
|
598
378
|
style: { paddingBlock: 0 }
|
|
599
379
|
}] : items;
|
|
600
|
-
if (allItems.length === 0)
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
380
|
+
if (allItems.length === 0) {
|
|
381
|
+
if (emptyState?.render) return /* @__PURE__ */ jsx("div", {
|
|
382
|
+
className: `agent-message-list empty ${className}`,
|
|
383
|
+
children: emptyState.render()
|
|
384
|
+
});
|
|
385
|
+
return /* @__PURE__ */ jsx("div", {
|
|
386
|
+
className: `agent-message-list empty ${className}`,
|
|
387
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
388
|
+
className: "agent-message-empty",
|
|
389
|
+
children: [emptyState?.title && /* @__PURE__ */ jsx("h4", { children: emptyState.title }), emptyState?.description ? /* @__PURE__ */ jsx("p", { children: emptyState.description }) : /* @__PURE__ */ jsx("p", { children: "开始对话吧" })]
|
|
390
|
+
})
|
|
391
|
+
});
|
|
392
|
+
}
|
|
607
393
|
return /* @__PURE__ */ jsx(Bubble.List, {
|
|
608
394
|
className: `agent-message-list ${className}`,
|
|
609
395
|
items: allItems,
|
|
@@ -612,7 +398,169 @@ const MessageList = ({ messages, isLoading = false, className = "", tools, toolE
|
|
|
612
398
|
});
|
|
613
399
|
};
|
|
614
400
|
//#endregion
|
|
615
|
-
//#region src/
|
|
401
|
+
//#region src/hooks/useInterrupt.tsx
|
|
402
|
+
/**
|
|
403
|
+
* InterruptManager - 管理 Interrupt 状态的 Hook
|
|
404
|
+
*
|
|
405
|
+
* 封装 Interrupt 状态管理和处理逻辑,避免在 AgentChat 组件中臃肿
|
|
406
|
+
*/
|
|
407
|
+
function useInterrupt({ interrupt, config, onSubmit }) {
|
|
408
|
+
const [activeInterrupt, setActiveInterrupt] = useState(null);
|
|
409
|
+
const processedInterruptIdRef = useRef(null);
|
|
410
|
+
useEffect(() => {
|
|
411
|
+
if (interrupt) {
|
|
412
|
+
const interruptEvent = interrupt;
|
|
413
|
+
if (interruptEvent.id !== processedInterruptIdRef.current) {
|
|
414
|
+
processedInterruptIdRef.current = interruptEvent.id;
|
|
415
|
+
setActiveInterrupt(interruptEvent);
|
|
416
|
+
}
|
|
417
|
+
} else {
|
|
418
|
+
processedInterruptIdRef.current = null;
|
|
419
|
+
setActiveInterrupt(null);
|
|
420
|
+
}
|
|
421
|
+
}, [interrupt?.id]);
|
|
422
|
+
const handleResolveInterrupt = useCallback((response) => {
|
|
423
|
+
onSubmit(null, { command: { resume: response } });
|
|
424
|
+
setActiveInterrupt(null);
|
|
425
|
+
}, [onSubmit]);
|
|
426
|
+
return {
|
|
427
|
+
activeInterrupt,
|
|
428
|
+
handleResolveInterrupt,
|
|
429
|
+
renderInterrupt: useCallback(() => {
|
|
430
|
+
if (!activeInterrupt || !config?.render) return null;
|
|
431
|
+
return /* @__PURE__ */ jsx("div", {
|
|
432
|
+
className: "agent-chat-interrupt",
|
|
433
|
+
children: config.render({
|
|
434
|
+
event: activeInterrupt,
|
|
435
|
+
resolve: handleResolveInterrupt
|
|
436
|
+
})
|
|
437
|
+
});
|
|
438
|
+
}, [
|
|
439
|
+
activeInterrupt,
|
|
440
|
+
config,
|
|
441
|
+
handleResolveInterrupt
|
|
442
|
+
])
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
//#endregion
|
|
446
|
+
//#region src/hooks/useToolExecution.ts
|
|
447
|
+
/**
|
|
448
|
+
* useToolExecution - 管理前端工具执行的 Hook
|
|
449
|
+
*
|
|
450
|
+
* 职责:
|
|
451
|
+
* 1. 管理工具执行状态
|
|
452
|
+
* 2. 自动执行前端工具(避免重复执行)
|
|
453
|
+
* 3. 通知外部执行状态变化
|
|
454
|
+
*/
|
|
455
|
+
function useToolExecution({ tools, toolCalls, isLoading = false, onExecutionChange, onToolResult, completedToolResults }) {
|
|
456
|
+
const executedCallsRef = useRef(/* @__PURE__ */ new Set());
|
|
457
|
+
const executingCallsRef = useRef(/* @__PURE__ */ new Set());
|
|
458
|
+
const pendingNotifiedRef = useRef(/* @__PURE__ */ new Set());
|
|
459
|
+
const initializedRef = useRef(false);
|
|
460
|
+
useEffect(() => {
|
|
461
|
+
if (initializedRef.current || !completedToolResults || completedToolResults.size === 0) return;
|
|
462
|
+
initializedRef.current = true;
|
|
463
|
+
completedToolResults.forEach((result, callId) => {
|
|
464
|
+
executedCallsRef.current.add(callId);
|
|
465
|
+
const call = toolCalls.find((c) => c.id === callId);
|
|
466
|
+
if (call) onExecutionChange?.({
|
|
467
|
+
callId,
|
|
468
|
+
name: call.name,
|
|
469
|
+
arguments: call.arguments,
|
|
470
|
+
status: "success",
|
|
471
|
+
result
|
|
472
|
+
});
|
|
473
|
+
});
|
|
474
|
+
}, [completedToolResults]);
|
|
475
|
+
/**
|
|
476
|
+
* 执行单个前端工具
|
|
477
|
+
*/
|
|
478
|
+
const executeFrontendTool = useCallback(async (tool, call) => {
|
|
479
|
+
const callId = call.id;
|
|
480
|
+
executingCallsRef.current.add(callId);
|
|
481
|
+
onExecutionChange?.({
|
|
482
|
+
callId,
|
|
483
|
+
name: call.name,
|
|
484
|
+
arguments: call.arguments,
|
|
485
|
+
status: "running"
|
|
486
|
+
});
|
|
487
|
+
try {
|
|
488
|
+
const result = await tool.execute(call.arguments);
|
|
489
|
+
onExecutionChange?.({
|
|
490
|
+
callId,
|
|
491
|
+
name: call.name,
|
|
492
|
+
arguments: call.arguments,
|
|
493
|
+
status: "success",
|
|
494
|
+
result
|
|
495
|
+
});
|
|
496
|
+
onToolResult?.(callId, call.name, result);
|
|
497
|
+
} catch (error) {
|
|
498
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
499
|
+
onExecutionChange?.({
|
|
500
|
+
callId,
|
|
501
|
+
name: call.name,
|
|
502
|
+
arguments: call.arguments,
|
|
503
|
+
status: "error",
|
|
504
|
+
error: errorMessage
|
|
505
|
+
});
|
|
506
|
+
} finally {
|
|
507
|
+
executingCallsRef.current.delete(callId);
|
|
508
|
+
executedCallsRef.current.add(callId);
|
|
509
|
+
}
|
|
510
|
+
}, [onExecutionChange, onToolResult]);
|
|
511
|
+
/**
|
|
512
|
+
* 处理工具调用
|
|
513
|
+
*/
|
|
514
|
+
const processToolCalls = useCallback(async () => {
|
|
515
|
+
if (!tools) return;
|
|
516
|
+
for (const call of toolCalls) {
|
|
517
|
+
const callId = call.id;
|
|
518
|
+
if (executedCallsRef.current.has(callId) || executingCallsRef.current.has(callId)) continue;
|
|
519
|
+
const tool = findTool(tools, call.name);
|
|
520
|
+
if (isLoading) {
|
|
521
|
+
if (!pendingNotifiedRef.current.has(callId)) {
|
|
522
|
+
pendingNotifiedRef.current.add(callId);
|
|
523
|
+
onExecutionChange?.({
|
|
524
|
+
callId,
|
|
525
|
+
name: call.name,
|
|
526
|
+
arguments: call.arguments,
|
|
527
|
+
status: "pending"
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
continue;
|
|
531
|
+
}
|
|
532
|
+
if (!tool) {
|
|
533
|
+
onExecutionChange?.({
|
|
534
|
+
callId,
|
|
535
|
+
name: call.name,
|
|
536
|
+
arguments: call.arguments,
|
|
537
|
+
status: "pending"
|
|
538
|
+
});
|
|
539
|
+
continue;
|
|
540
|
+
}
|
|
541
|
+
if (isFrontendTool(tool)) await executeFrontendTool(tool, call);
|
|
542
|
+
else onExecutionChange?.({
|
|
543
|
+
callId,
|
|
544
|
+
name: call.name,
|
|
545
|
+
arguments: call.arguments,
|
|
546
|
+
status: "pending"
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
}, [
|
|
550
|
+
tools,
|
|
551
|
+
toolCalls,
|
|
552
|
+
isLoading,
|
|
553
|
+
executeFrontendTool,
|
|
554
|
+
onExecutionChange
|
|
555
|
+
]);
|
|
556
|
+
useEffect(() => {
|
|
557
|
+
processToolCalls();
|
|
558
|
+
}, [useMemo(() => {
|
|
559
|
+
return toolCalls.filter((call) => !executedCallsRef.current.has(call.id) && !executingCallsRef.current.has(call.id)).map((call) => call.id).sort().join(",");
|
|
560
|
+
}, [toolCalls]), isLoading]);
|
|
561
|
+
}
|
|
562
|
+
//#endregion
|
|
563
|
+
//#region src/utils/messageUtils.ts
|
|
616
564
|
/**
|
|
617
565
|
* 从 BaseMessage 中提取 tool_calls
|
|
618
566
|
*/
|
|
@@ -684,9 +632,115 @@ function processMessages(rawMessages) {
|
|
|
684
632
|
};
|
|
685
633
|
}
|
|
686
634
|
//#endregion
|
|
687
|
-
//#region src/
|
|
635
|
+
//#region src/utils/injectStyles.ts
|
|
636
|
+
const styles = `
|
|
637
|
+
/* Agent Chat Container */
|
|
638
|
+
.agent-chat-container {
|
|
639
|
+
display: flex;
|
|
640
|
+
flex-direction: column;
|
|
641
|
+
height: 100%;
|
|
642
|
+
background-color: transparent;
|
|
643
|
+
overflow: hidden;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
/* Message List */
|
|
647
|
+
.agent-message-list {
|
|
648
|
+
flex: 1;
|
|
649
|
+
overflow-y: auto;
|
|
650
|
+
display: flex;
|
|
651
|
+
flex-direction: column;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
.agent-message-list .empty {
|
|
655
|
+
display: flex;
|
|
656
|
+
align-items: center;
|
|
657
|
+
justify-content: center;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
.agent-message-list .ant-think-status-text {
|
|
661
|
+
font-size: 13px;
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
.agent-message-empty {
|
|
665
|
+
color: #8b8d91;
|
|
666
|
+
font-size: 14px;
|
|
667
|
+
text-align: center;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
@keyframes agent-loading-bounce {
|
|
671
|
+
0%,
|
|
672
|
+
80%,
|
|
673
|
+
100% {
|
|
674
|
+
transform: scale(0);
|
|
675
|
+
}
|
|
676
|
+
40% {
|
|
677
|
+
transform: scale(1);
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
/* Chat Input */
|
|
682
|
+
.agent-chat-input-container {
|
|
683
|
+
padding: 12px 4px;
|
|
684
|
+
background-color: transparent;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
/* Tool Calls Container */
|
|
688
|
+
.tool-calls-container {
|
|
689
|
+
display: flex;
|
|
690
|
+
flex-direction: column;
|
|
691
|
+
gap: 8px;
|
|
692
|
+
margin-top: 8px;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
/* Tool Call Default Style */
|
|
696
|
+
.tool-call-default {
|
|
697
|
+
display: flex;
|
|
698
|
+
flex-direction: column;
|
|
699
|
+
gap: 4px;
|
|
700
|
+
padding: 6px 0;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
.tool-call-header {
|
|
704
|
+
display: flex;
|
|
705
|
+
align-items: center;
|
|
706
|
+
gap: 8px;
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
.tool-call-name {
|
|
710
|
+
font-weight: 500;
|
|
711
|
+
font-size: 13px;
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
.tool-call-status {
|
|
715
|
+
font-size: 12px;
|
|
716
|
+
color: #8b8d91;
|
|
717
|
+
margin-left: auto;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
.tool-call-error {
|
|
721
|
+
display: flex;
|
|
722
|
+
align-items: center;
|
|
723
|
+
gap: 4px;
|
|
724
|
+
font-size: 12px;
|
|
725
|
+
color: #ff4d4f;
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
.tool-call-wrapper {
|
|
729
|
+
width: 100%;
|
|
730
|
+
}
|
|
731
|
+
`;
|
|
732
|
+
let injected = false;
|
|
733
|
+
function injectStyles() {
|
|
734
|
+
if (injected || typeof document === "undefined") return;
|
|
735
|
+
const styleElement = document.createElement("style");
|
|
736
|
+
styleElement.textContent = styles;
|
|
737
|
+
document.head.appendChild(styleElement);
|
|
738
|
+
injected = true;
|
|
739
|
+
}
|
|
740
|
+
//#endregion
|
|
741
|
+
//#region src/components/AgentChat.tsx
|
|
688
742
|
injectStyles();
|
|
689
|
-
const AgentChat = forwardRef(({ apiUrl, assistantId, headers, threadId: externalThreadId, onThreadIdChange, className = "", tools, contexts, messageConfig, inputConfig, onError }, ref) => {
|
|
743
|
+
const AgentChat = forwardRef(({ apiUrl, assistantId, headers, threadId: externalThreadId, onThreadIdChange, className = "", tools, contexts, messageConfig, inputConfig, onError, interruptConfig }, ref) => {
|
|
690
744
|
const [internalThreadId, setInternalThreadId] = useState(externalThreadId);
|
|
691
745
|
useEffect(() => {
|
|
692
746
|
setInternalThreadId(externalThreadId);
|
|
@@ -714,6 +768,11 @@ const AgentChat = forwardRef(({ apiUrl, assistantId, headers, threadId: external
|
|
|
714
768
|
onError?.(err);
|
|
715
769
|
}
|
|
716
770
|
});
|
|
771
|
+
const { renderInterrupt: interruptRender } = useInterrupt({
|
|
772
|
+
interrupt: stream.interrupt,
|
|
773
|
+
config: interruptConfig,
|
|
774
|
+
onSubmit: stream.submit
|
|
775
|
+
});
|
|
717
776
|
const [toolExecutions, setToolExecutions] = useState(/* @__PURE__ */ new Map());
|
|
718
777
|
const { messages, toolResults } = useMemo(() => {
|
|
719
778
|
return processMessages(stream.messages);
|
|
@@ -728,8 +787,8 @@ const AgentChat = forwardRef(({ apiUrl, assistantId, headers, threadId: external
|
|
|
728
787
|
return next;
|
|
729
788
|
});
|
|
730
789
|
}, []);
|
|
731
|
-
const submitToStream = useCallback((submitMessages, extraState = {}) => {
|
|
732
|
-
stream.submit({
|
|
790
|
+
const submitToStream = useCallback(async (submitMessages, extraState = {}) => {
|
|
791
|
+
await stream.submit({
|
|
733
792
|
messages: submitMessages,
|
|
734
793
|
...extraState,
|
|
735
794
|
agentkit: {
|
|
@@ -774,51 +833,85 @@ const AgentChat = forwardRef(({ apiUrl, assistantId, headers, threadId: external
|
|
|
774
833
|
}];
|
|
775
834
|
}
|
|
776
835
|
if (messages.length === 0) return;
|
|
777
|
-
submitToStream(messages, extraState);
|
|
836
|
+
await submitToStream(messages, extraState);
|
|
778
837
|
}, [onPreSend, submitToStream]);
|
|
779
|
-
const handleStop = useCallback(() => {
|
|
780
|
-
stream.stop();
|
|
838
|
+
const handleStop = useCallback(async () => {
|
|
839
|
+
await stream.stop();
|
|
781
840
|
}, [stream]);
|
|
841
|
+
useToolExecution({
|
|
842
|
+
tools,
|
|
843
|
+
toolCalls: allToolCalls,
|
|
844
|
+
isLoading: stream.isLoading,
|
|
845
|
+
onExecutionChange: handleExecutionChange,
|
|
846
|
+
onToolResult: handleToolResult,
|
|
847
|
+
completedToolResults: toolResults
|
|
848
|
+
});
|
|
782
849
|
return /* @__PURE__ */ jsxs("div", {
|
|
783
850
|
className: `agent-chat-container ${className}`,
|
|
784
|
-
children: [
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
className: "agent-chat-input",
|
|
812
|
-
footer,
|
|
813
|
-
skill,
|
|
814
|
-
slotConfig,
|
|
815
|
-
header,
|
|
816
|
-
prefix,
|
|
817
|
-
placeholder
|
|
818
|
-
})
|
|
819
|
-
]
|
|
851
|
+
children: [/* @__PURE__ */ jsx(MessageList, {
|
|
852
|
+
messages,
|
|
853
|
+
isLoading: stream.isLoading,
|
|
854
|
+
className: "agent-chat-messages",
|
|
855
|
+
tools,
|
|
856
|
+
toolExecutions,
|
|
857
|
+
components: messageConfig?.components,
|
|
858
|
+
securityConfig: {
|
|
859
|
+
allowedTags: messageConfig?.allowedTags,
|
|
860
|
+
literalTagContent: messageConfig?.literalTagContent
|
|
861
|
+
},
|
|
862
|
+
loadingColor: messageConfig?.loadingColor,
|
|
863
|
+
interruptRender,
|
|
864
|
+
emptyState: messageConfig?.emptyState
|
|
865
|
+
}), /* @__PURE__ */ jsx(ChatInput, {
|
|
866
|
+
ref: chatInputRef,
|
|
867
|
+
onSend: handleSend,
|
|
868
|
+
onStop: handleStop,
|
|
869
|
+
isLoading: stream.isLoading,
|
|
870
|
+
className: "agent-chat-input",
|
|
871
|
+
footer,
|
|
872
|
+
skill,
|
|
873
|
+
slotConfig,
|
|
874
|
+
header,
|
|
875
|
+
prefix,
|
|
876
|
+
placeholder
|
|
877
|
+
})]
|
|
820
878
|
});
|
|
821
879
|
});
|
|
822
880
|
AgentChat.displayName = "AgentChat";
|
|
823
881
|
//#endregion
|
|
824
|
-
|
|
882
|
+
//#region src/components/ToolCard.tsx
|
|
883
|
+
/**
|
|
884
|
+
* ToolCard 组件 - 用于展示工具调用结果
|
|
885
|
+
* 样式:圆角矩形框,左侧 icon,label 分两行显示(prefix 小字体,content 默认字体)
|
|
886
|
+
*/
|
|
887
|
+
const ToolCard = ({ prefix, content, icon }) => {
|
|
888
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
889
|
+
style: {
|
|
890
|
+
display: "inline-flex",
|
|
891
|
+
alignItems: "center",
|
|
892
|
+
gap: 8,
|
|
893
|
+
borderRadius: 8,
|
|
894
|
+
fontSize: 13
|
|
895
|
+
},
|
|
896
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
897
|
+
style: {
|
|
898
|
+
display: "flex",
|
|
899
|
+
alignItems: "center"
|
|
900
|
+
},
|
|
901
|
+
children: icon
|
|
902
|
+
}), /* @__PURE__ */ jsxs("span", {
|
|
903
|
+
style: {
|
|
904
|
+
display: "flex",
|
|
905
|
+
alignItems: "center",
|
|
906
|
+
gap: 4,
|
|
907
|
+
color: "rgba(0, 0, 0, 0.45)"
|
|
908
|
+
},
|
|
909
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
910
|
+
style: { fontSize: 12 },
|
|
911
|
+
children: prefix
|
|
912
|
+
}), /* @__PURE__ */ jsx("span", { children: content })]
|
|
913
|
+
})]
|
|
914
|
+
});
|
|
915
|
+
};
|
|
916
|
+
//#endregion
|
|
917
|
+
export { AgentChat, ToolCard };
|