@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 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: Array<{
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
- content: string;
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
- 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 ToolCardStyles, type ToolDefinition, type ToolExecutionRecord, type ToolExecutionStatus, type ToolParameterSchema, type ToolRenderProps };
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 shouldBlink = isLastMessage && isLoading && !message.content && message.toolCalls?.length == 0;
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
- message.content && /* @__PURE__ */ jsx("div", {
285
+ !isContentEmpty && /* @__PURE__ */ jsx("div", {
229
286
  style: { marginTop: message.reasoningContent ? "8px" : "3px" },
230
- children: /* @__PURE__ */ jsx(Streamdown, {
231
- components: {
232
- p: CustomParagraph,
233
- ...components
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 allItems = processedMessages[processedMessages.length - 1]?.type === "human" && isLoading ? [...items, {
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) => typeof c === "object" && "text" in c ? c.text : "").join("");
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 content = extractContent(message);
1197
+ const textContent = extractTextFromContent(extractContent(message));
1106
1198
  try {
1107
- toolResults.set(toolCallId, JSON.parse(content));
1199
+ toolResults.set(toolCallId, JSON.parse(textContent));
1108
1200
  } catch {
1109
- toolResults.set(toolCallId, content);
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).map((m) => ({ ...m }));
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.12",
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
+ }