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