@vegintech/langchain-react-agent 0.0.12 → 0.0.13
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/dist/index.d.mts +80 -7
- package/dist/index.mjs +120 -28
- package/package.json +9 -8
package/dist/index.d.mts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as react from "react";
|
|
2
|
-
import { CSSProperties, ReactNode } from "react";
|
|
2
|
+
import React$1, { CSSProperties, ReactNode } from "react";
|
|
3
3
|
import { Components } from "streamdown";
|
|
4
|
+
import { BaseMessage } from "@langchain/core/messages";
|
|
4
5
|
import { BaseNode, InsertPosition, NodeRender, SenderProps, SkillType, SlotConfigType } from "@ant-design/x/es/sender/interface.ts";
|
|
5
6
|
|
|
6
7
|
//#region src/types.d.ts
|
|
@@ -88,10 +89,7 @@ interface SenderSubmitParams {
|
|
|
88
89
|
/** onPreSend 钩子返回值:只返回消息数组 */
|
|
89
90
|
interface PreSendResult {
|
|
90
91
|
/** 要发送的消息数组 */
|
|
91
|
-
messages:
|
|
92
|
-
type: string;
|
|
93
|
-
content: string;
|
|
94
|
-
}>;
|
|
92
|
+
messages: BaseMessage[];
|
|
95
93
|
}
|
|
96
94
|
/** Sender 组件的可定制参数 */
|
|
97
95
|
interface SenderCustomizationProps {
|
|
@@ -130,11 +128,27 @@ interface ToolParameterSchema {
|
|
|
130
128
|
}
|
|
131
129
|
/** 消息类型,用于区分用户和助手消息 */
|
|
132
130
|
type MessageType = "human" | "ai" | "system" | "tool" | "function";
|
|
131
|
+
/** 消息内容块类型 */
|
|
132
|
+
type MessageContentBlock = {
|
|
133
|
+
type: "text";
|
|
134
|
+
text: string;
|
|
135
|
+
} | {
|
|
136
|
+
type: "image_url";
|
|
137
|
+
image_url: {
|
|
138
|
+
url: string;
|
|
139
|
+
};
|
|
140
|
+
} | {
|
|
141
|
+
type: string;
|
|
142
|
+
[key: string]: unknown;
|
|
143
|
+
};
|
|
144
|
+
/** 消息内容类型:字符串或内容块数组 */
|
|
145
|
+
type MessageContent = string | MessageContentBlock[];
|
|
133
146
|
/** 扩展的消息类型,确保包含 id 和必要的字段 */
|
|
134
147
|
interface ChatMessage {
|
|
135
148
|
id: string;
|
|
136
149
|
type: MessageType;
|
|
137
|
-
|
|
150
|
+
/** 消息内容,保留原始结构(字符串或内容块数组) */
|
|
151
|
+
content: MessageContent;
|
|
138
152
|
name?: string;
|
|
139
153
|
additional_kwargs?: Record<string, unknown>;
|
|
140
154
|
/** 思考内容(来自 additional_kwargs.reasoning_content) */
|
|
@@ -255,6 +269,36 @@ interface AgentChatRef {
|
|
|
255
269
|
/** 聚焦输入框 */
|
|
256
270
|
focusInput: () => void;
|
|
257
271
|
}
|
|
272
|
+
/** Streamdown 安全过滤配置 */
|
|
273
|
+
interface StreamdownSecurityConfig {
|
|
274
|
+
/** 允许的标签及其属性 */
|
|
275
|
+
allowedTags?: Record<string, string[]>;
|
|
276
|
+
/** 作为字面量内容处理的标签(不解析内部 Markdown) */
|
|
277
|
+
literalTagContent?: string[];
|
|
278
|
+
}
|
|
279
|
+
interface MessageListProps {
|
|
280
|
+
messages: ChatMessage[];
|
|
281
|
+
isLoading?: boolean;
|
|
282
|
+
className?: string;
|
|
283
|
+
/** 工具定义列表 */
|
|
284
|
+
tools?: ToolDefinition<any>[];
|
|
285
|
+
/** 工具执行记录 */
|
|
286
|
+
toolExecutions: Map<string, ToolExecutionRecord>;
|
|
287
|
+
/** Markdown 自定义组件 */
|
|
288
|
+
components?: Components;
|
|
289
|
+
/** Streamdown 安全过滤配置 */
|
|
290
|
+
securityConfig?: StreamdownSecurityConfig;
|
|
291
|
+
/** Loading 动画颜色,支持 CSS 颜色值 */
|
|
292
|
+
loadingColor?: string;
|
|
293
|
+
/** Interrupt 渲染函数,用于在消息列表中渲染中断 UI */
|
|
294
|
+
interruptRender?: () => ReactNode;
|
|
295
|
+
/** 空状态配置 */
|
|
296
|
+
emptyState?: {
|
|
297
|
+
/** 空状态标题 */title?: string; /** 空状态描述 */
|
|
298
|
+
description?: string; /** 自定义渲染 */
|
|
299
|
+
render?: () => ReactNode;
|
|
300
|
+
};
|
|
301
|
+
}
|
|
258
302
|
//#endregion
|
|
259
303
|
//#region src/components/AgentChat.d.ts
|
|
260
304
|
declare const AgentChat: react.ForwardRefExoticComponent<AgentChatProps & react.RefAttributes<AgentChatRef>>;
|
|
@@ -266,4 +310,33 @@ declare const AgentChat: react.ForwardRefExoticComponent<AgentChatProps & react.
|
|
|
266
310
|
*/
|
|
267
311
|
declare const ToolCard: React.FC<ToolCardProps>;
|
|
268
312
|
//#endregion
|
|
269
|
-
|
|
313
|
+
//#region src/components/MessageContentRenderer.d.ts
|
|
314
|
+
interface MessageContentRendererProps {
|
|
315
|
+
content: MessageContent;
|
|
316
|
+
components?: Components;
|
|
317
|
+
securityConfig?: MessageListProps["securityConfig"];
|
|
318
|
+
}
|
|
319
|
+
/** 消息内容渲染组件 - 支持字符串和多模态内容块 */
|
|
320
|
+
declare const MessageContentRenderer: React$1.FC<MessageContentRendererProps>;
|
|
321
|
+
//#endregion
|
|
322
|
+
//#region src/utils/messageUtils.d.ts
|
|
323
|
+
/**
|
|
324
|
+
* 创建人类消息(HumanMessage)
|
|
325
|
+
* @param content 消息内容,可以是字符串或多模态内容块数组
|
|
326
|
+
* @returns HumanMessage 实例
|
|
327
|
+
* @example
|
|
328
|
+
* // 纯文本消息
|
|
329
|
+
* createHumanMessage("你好")
|
|
330
|
+
*
|
|
331
|
+
* // 多模态消息
|
|
332
|
+
* createHumanMessage([
|
|
333
|
+
* { type: "text", text: "看看这张图片" },
|
|
334
|
+
* { type: "image_url", image_url: { url: "https://example.com/image.png" } }
|
|
335
|
+
* ])
|
|
336
|
+
*/
|
|
337
|
+
declare function createHumanMessage(content: string | {
|
|
338
|
+
type: string;
|
|
339
|
+
[key: string]: unknown;
|
|
340
|
+
}[]): BaseMessage;
|
|
341
|
+
//#endregion
|
|
342
|
+
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 MessageContent, type MessageContentBlock, MessageContentRenderer, type MessageContentRendererProps, type MessageType, type SenderCustomizationProps, type SenderSlotConfig, type SenderSubmitParams, type ToolCallInput, ToolCard, type ToolCardProps, type ToolCardStyles, type ToolDefinition, type ToolExecutionRecord, type ToolExecutionStatus, type ToolParameterSchema, type ToolRenderProps, createHumanMessage };
|
package/dist/index.mjs
CHANGED
|
@@ -3,6 +3,7 @@ import { useStream } from "@langchain/react";
|
|
|
3
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
|
+
import { HumanMessage } from "@langchain/core/messages";
|
|
6
7
|
//#region src/components/ChatInput.tsx
|
|
7
8
|
const slotConfig = [];
|
|
8
9
|
const ChatInput = forwardRef(({ onSend, onStop, isLoading = false, disabled = false, placeholder = "输入消息...", className = "", footer, skill: externalSkill, header, prefix }, ref) => {
|
|
@@ -164,6 +165,65 @@ const ReasoningContent = ({ content }) => {
|
|
|
164
165
|
})] });
|
|
165
166
|
};
|
|
166
167
|
//#endregion
|
|
168
|
+
//#region src/components/MessageContentRenderer.tsx
|
|
169
|
+
const CustomParagraph = (props) => {
|
|
170
|
+
const { node: _node, ...rest } = props;
|
|
171
|
+
return /* @__PURE__ */ jsx("span", { ...rest });
|
|
172
|
+
};
|
|
173
|
+
/** 渲染单个内容块 */
|
|
174
|
+
const ContentBlock = ({ block, index, components, securityConfig }) => {
|
|
175
|
+
if (block.type === "text" && "text" in block) return /* @__PURE__ */ jsx(Streamdown, {
|
|
176
|
+
components: {
|
|
177
|
+
p: CustomParagraph,
|
|
178
|
+
...components
|
|
179
|
+
},
|
|
180
|
+
allowedTags: securityConfig?.allowedTags,
|
|
181
|
+
literalTagContent: securityConfig?.literalTagContent,
|
|
182
|
+
controls: { table: {
|
|
183
|
+
copy: false,
|
|
184
|
+
download: false,
|
|
185
|
+
fullscreen: false
|
|
186
|
+
} },
|
|
187
|
+
children: String(block.text)
|
|
188
|
+
}, index);
|
|
189
|
+
if (block.type === "image_url" && "image_url" in block) {
|
|
190
|
+
const imageUrl = block.image_url.url;
|
|
191
|
+
return /* @__PURE__ */ jsx("img", {
|
|
192
|
+
src: imageUrl,
|
|
193
|
+
alt: "Message content",
|
|
194
|
+
style: {
|
|
195
|
+
maxWidth: "100%",
|
|
196
|
+
borderRadius: "8px",
|
|
197
|
+
marginTop: "8px"
|
|
198
|
+
}
|
|
199
|
+
}, index);
|
|
200
|
+
}
|
|
201
|
+
return null;
|
|
202
|
+
};
|
|
203
|
+
/** 消息内容渲染组件 - 支持字符串和多模态内容块 */
|
|
204
|
+
const MessageContentRenderer = ({ content, components, securityConfig }) => {
|
|
205
|
+
if (typeof content === "string") return /* @__PURE__ */ jsx(Streamdown, {
|
|
206
|
+
components: {
|
|
207
|
+
p: CustomParagraph,
|
|
208
|
+
...components
|
|
209
|
+
},
|
|
210
|
+
allowedTags: securityConfig?.allowedTags,
|
|
211
|
+
literalTagContent: securityConfig?.literalTagContent,
|
|
212
|
+
controls: { table: {
|
|
213
|
+
copy: false,
|
|
214
|
+
download: false,
|
|
215
|
+
fullscreen: false
|
|
216
|
+
} },
|
|
217
|
+
children: content
|
|
218
|
+
});
|
|
219
|
+
return /* @__PURE__ */ jsx(Fragment, { children: content.map((block, index) => /* @__PURE__ */ jsx(ContentBlock, {
|
|
220
|
+
block,
|
|
221
|
+
index,
|
|
222
|
+
components,
|
|
223
|
+
securityConfig
|
|
224
|
+
}, index)) });
|
|
225
|
+
};
|
|
226
|
+
//#endregion
|
|
167
227
|
//#region src/components/WaveLoading.tsx
|
|
168
228
|
/**
|
|
169
229
|
* 波浪动画 Loading 组件
|
|
@@ -204,13 +264,10 @@ const WaveLoading = ({ color = "#1890ff" }) => {
|
|
|
204
264
|
};
|
|
205
265
|
//#endregion
|
|
206
266
|
//#region src/components/MessageList.tsx
|
|
207
|
-
const CustomParagraph = (props) => {
|
|
208
|
-
const { node, ...rest } = props;
|
|
209
|
-
return /* @__PURE__ */ jsx("span", { ...rest });
|
|
210
|
-
};
|
|
211
267
|
const renderMessageContent = (message, isLastMessage, isLoading, tools, toolExecutions, components, securityConfig) => {
|
|
212
268
|
const hasToolCalls = message.toolCalls && message.toolCalls.length > 0;
|
|
213
|
-
const
|
|
269
|
+
const isContentEmpty = !message.content || (typeof message.content === "string" ? message.content === "" : message.content.length === 0);
|
|
270
|
+
const shouldBlink = isLastMessage && isLoading && isContentEmpty && message.toolCalls?.length == 0;
|
|
214
271
|
return /* @__PURE__ */ jsxs("div", {
|
|
215
272
|
className: "message-item",
|
|
216
273
|
children: [
|
|
@@ -225,21 +282,12 @@ const renderMessageContent = (message, isLastMessage, isLoading, tools, toolExec
|
|
|
225
282
|
blink: shouldBlink,
|
|
226
283
|
children: /* @__PURE__ */ jsx(ReasoningContent, { content: message.reasoningContent })
|
|
227
284
|
}),
|
|
228
|
-
|
|
285
|
+
!isContentEmpty && /* @__PURE__ */ jsx("div", {
|
|
229
286
|
style: { marginTop: message.reasoningContent ? "8px" : "3px" },
|
|
230
|
-
children: /* @__PURE__ */ jsx(
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
},
|
|
235
|
-
allowedTags: securityConfig?.allowedTags,
|
|
236
|
-
literalTagContent: securityConfig?.literalTagContent,
|
|
237
|
-
controls: { table: {
|
|
238
|
-
copy: false,
|
|
239
|
-
download: false,
|
|
240
|
-
fullscreen: false
|
|
241
|
-
} },
|
|
242
|
-
children: message.content
|
|
287
|
+
children: /* @__PURE__ */ jsx(MessageContentRenderer, {
|
|
288
|
+
content: message.content,
|
|
289
|
+
components,
|
|
290
|
+
securityConfig
|
|
243
291
|
})
|
|
244
292
|
}),
|
|
245
293
|
hasToolCalls && /* @__PURE__ */ jsx("div", {
|
|
@@ -349,7 +397,16 @@ const MessageList = ({ messages, isLoading = false, className = "", tools, toolE
|
|
|
349
397
|
placement: "start"
|
|
350
398
|
};
|
|
351
399
|
});
|
|
352
|
-
const
|
|
400
|
+
const hasVisibleContent = (msg) => {
|
|
401
|
+
if (!msg) return false;
|
|
402
|
+
const hasContent = msg.content && (typeof msg.content === "string" ? msg.content !== "" : msg.content.length > 0);
|
|
403
|
+
const hasReasoning = !!msg.reasoningContent;
|
|
404
|
+
const hasToolCalls = msg.toolCalls && msg.toolCalls.length > 0;
|
|
405
|
+
return hasContent || hasReasoning || hasToolCalls;
|
|
406
|
+
};
|
|
407
|
+
const lastMessage = processedMessages[processedMessages.length - 1];
|
|
408
|
+
const isLastMessageFromUser = lastMessage?.type === "human";
|
|
409
|
+
const allItems = isLoading && (isLastMessageFromUser || !hasVisibleContent(lastMessage)) ? [...items, {
|
|
353
410
|
key: "loading",
|
|
354
411
|
role: "ai",
|
|
355
412
|
content: /* @__PURE__ */ jsx(WaveLoading, { color: loadingColor }),
|
|
@@ -509,7 +566,8 @@ function DebugPanel({ messages, streamState, visible = true }) {
|
|
|
509
566
|
};
|
|
510
567
|
const formatContent = (content) => {
|
|
511
568
|
if (!content) return "";
|
|
512
|
-
return content;
|
|
569
|
+
if (Array.isArray(content)) return content.map((item) => typeof item === "string" ? item : JSON.stringify(item)).join("");
|
|
570
|
+
return String(content);
|
|
513
571
|
};
|
|
514
572
|
const renderMessageContent = (message) => {
|
|
515
573
|
const hasToolCalls = message.tool_calls && Array.isArray(message.tool_calls) && message.tool_calls.length > 0;
|
|
@@ -1051,6 +1109,23 @@ function useToolExecution({ tools, toolCalls, isLoading = false, onExecutionChan
|
|
|
1051
1109
|
//#endregion
|
|
1052
1110
|
//#region src/utils/messageUtils.ts
|
|
1053
1111
|
/**
|
|
1112
|
+
* 创建人类消息(HumanMessage)
|
|
1113
|
+
* @param content 消息内容,可以是字符串或多模态内容块数组
|
|
1114
|
+
* @returns HumanMessage 实例
|
|
1115
|
+
* @example
|
|
1116
|
+
* // 纯文本消息
|
|
1117
|
+
* createHumanMessage("你好")
|
|
1118
|
+
*
|
|
1119
|
+
* // 多模态消息
|
|
1120
|
+
* createHumanMessage([
|
|
1121
|
+
* { type: "text", text: "看看这张图片" },
|
|
1122
|
+
* { type: "image_url", image_url: { url: "https://example.com/image.png" } }
|
|
1123
|
+
* ])
|
|
1124
|
+
*/
|
|
1125
|
+
function createHumanMessage(content) {
|
|
1126
|
+
return new HumanMessage({ content });
|
|
1127
|
+
}
|
|
1128
|
+
/**
|
|
1054
1129
|
* 从 BaseMessage 中提取 tool_calls
|
|
1055
1130
|
*/
|
|
1056
1131
|
function extractToolCalls(message) {
|
|
@@ -1061,14 +1136,31 @@ function extractToolCalls(message) {
|
|
|
1061
1136
|
}));
|
|
1062
1137
|
}
|
|
1063
1138
|
/**
|
|
1064
|
-
* 从 BaseMessage
|
|
1139
|
+
* 从 BaseMessage 中提取原始内容
|
|
1140
|
+
* 保留原始结构:字符串或内容块数组
|
|
1065
1141
|
*/
|
|
1066
1142
|
function extractContent(message) {
|
|
1067
1143
|
if (typeof message.content === "string") return message.content;
|
|
1068
|
-
if (Array.isArray(message.content)) return message.content.map((c) =>
|
|
1144
|
+
if (Array.isArray(message.content)) return message.content.map((c) => {
|
|
1145
|
+
if (typeof c === "object" && c !== null) return c;
|
|
1146
|
+
return {
|
|
1147
|
+
type: "text",
|
|
1148
|
+
text: String(c)
|
|
1149
|
+
};
|
|
1150
|
+
});
|
|
1069
1151
|
return "";
|
|
1070
1152
|
}
|
|
1071
1153
|
/**
|
|
1154
|
+
* 从 MessageContent 中提取纯文本内容(用于渲染)
|
|
1155
|
+
*/
|
|
1156
|
+
function extractTextFromContent(content) {
|
|
1157
|
+
if (typeof content === "string") return content;
|
|
1158
|
+
return content.map((block) => {
|
|
1159
|
+
if (block.type === "text") return block.text;
|
|
1160
|
+
return "";
|
|
1161
|
+
}).join("");
|
|
1162
|
+
}
|
|
1163
|
+
/**
|
|
1072
1164
|
* 将 BaseMessage 转换为 ChatMessage
|
|
1073
1165
|
*/
|
|
1074
1166
|
function toChatMessage(message, toolResults) {
|
|
@@ -1102,11 +1194,11 @@ function processMessages(rawMessages) {
|
|
|
1102
1194
|
for (const message of rawMessages) if (message.type === "tool") {
|
|
1103
1195
|
const toolCallId = message.tool_call_id || message.additional_kwargs?.tool_call_id;
|
|
1104
1196
|
if (toolCallId) {
|
|
1105
|
-
const
|
|
1197
|
+
const textContent = extractTextFromContent(extractContent(message));
|
|
1106
1198
|
try {
|
|
1107
|
-
toolResults.set(toolCallId, JSON.parse(
|
|
1199
|
+
toolResults.set(toolCallId, JSON.parse(textContent));
|
|
1108
1200
|
} catch {
|
|
1109
|
-
toolResults.set(toolCallId,
|
|
1201
|
+
toolResults.set(toolCallId, textContent);
|
|
1110
1202
|
}
|
|
1111
1203
|
}
|
|
1112
1204
|
}
|
|
@@ -1343,7 +1435,7 @@ const AgentChat = forwardRef(({ apiUrl, assistantId, headers, threadId: external
|
|
|
1343
1435
|
}, [submitToStream]);
|
|
1344
1436
|
const handleSend = useCallback(async (params) => {
|
|
1345
1437
|
let messages = [];
|
|
1346
|
-
if (onPreSend && params.message.trim()) messages = (await onPreSend(params)).messages.filter((m) => m != null)
|
|
1438
|
+
if (onPreSend && params.message.trim()) messages = (await onPreSend(params)).messages.filter((m) => m != null);
|
|
1347
1439
|
else {
|
|
1348
1440
|
if (!params.message.trim()) return;
|
|
1349
1441
|
messages = [{
|
|
@@ -1448,4 +1540,4 @@ const ToolCard = ({ prefix, content, icon, style, styles }) => {
|
|
|
1448
1540
|
});
|
|
1449
1541
|
};
|
|
1450
1542
|
//#endregion
|
|
1451
|
-
export { AgentChat, ToolCard };
|
|
1543
|
+
export { AgentChat, MessageContentRenderer, ToolCard, createHumanMessage };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vegintech/langchain-react-agent",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.13",
|
|
4
4
|
"description": "LangChain Agent UI component library for React",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"files": [
|
|
@@ -15,6 +15,13 @@
|
|
|
15
15
|
"publishConfig": {
|
|
16
16
|
"access": "public"
|
|
17
17
|
},
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "vp pack",
|
|
20
|
+
"dev": "vp pack --watch",
|
|
21
|
+
"test": "vp test",
|
|
22
|
+
"check": "vp check",
|
|
23
|
+
"prepublishOnly": "vp run build"
|
|
24
|
+
},
|
|
18
25
|
"dependencies": {
|
|
19
26
|
"@ant-design/x": "^2.4.0",
|
|
20
27
|
"@langchain/core": "^1.1.36",
|
|
@@ -36,11 +43,5 @@
|
|
|
36
43
|
"peerDependencies": {
|
|
37
44
|
"react": ">=18.0.0",
|
|
38
45
|
"react-dom": ">=18.0.0"
|
|
39
|
-
},
|
|
40
|
-
"scripts": {
|
|
41
|
-
"build": "vp pack",
|
|
42
|
-
"dev": "vp pack --watch",
|
|
43
|
-
"test": "vp test",
|
|
44
|
-
"check": "vp check"
|
|
45
46
|
}
|
|
46
|
-
}
|
|
47
|
+
}
|