@vegintech/langchain-react-agent 0.0.7 → 0.0.9

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 CHANGED
@@ -137,7 +137,7 @@ const searchTool: BackendTool = {
137
137
  render: (props) => (
138
138
  <div className="tool-search">
139
139
  {props.status === "pending" && "等待执行..."}
140
- {props.status === "running" && `搜索: ${props.arguments.query}`}
140
+ {props.status === "running" && `搜索: ${props.args.query}`}
141
141
  {props.status === "success" && `找到 ${props.result?.count || 0} 条结果`}
142
142
  </div>
143
143
  ),
package/dist/index.d.mts CHANGED
@@ -1,9 +1,18 @@
1
1
  import * as react from "react";
2
- import { ReactNode } from "react";
2
+ import { CSSProperties, 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
5
 
6
6
  //#region src/types.d.ts
7
+ /** ToolCard 各部件的样式配置 */
8
+ interface ToolCardStyles {
9
+ /** icon 容器的样式 */
10
+ icon?: CSSProperties;
11
+ /** prefix 文本的样式 */
12
+ prefix?: CSSProperties;
13
+ /** content 文本的样式 */
14
+ content?: CSSProperties;
15
+ }
7
16
  /** ToolCard 组件 Props */
8
17
  interface ToolCardProps {
9
18
  /**
@@ -18,6 +27,14 @@ interface ToolCardProps {
18
27
  * 左侧图标
19
28
  */
20
29
  icon: ReactNode;
30
+ /**
31
+ * 根节点样式
32
+ */
33
+ style?: CSSProperties;
34
+ /**
35
+ * 各部件的样式配置,可分别设置 icon、prefix、content 的样式
36
+ */
37
+ styles?: ToolCardStyles;
21
38
  }
22
39
  /** Interrupt 事件数据结构 */
23
40
  interface InterruptEvent {
@@ -133,7 +150,7 @@ interface ChatMessage {
133
150
  interface ToolCallInput {
134
151
  id: string;
135
152
  name: string;
136
- arguments: Record<string, unknown>;
153
+ args: Record<string, unknown>;
137
154
  /** 后端工具执行结果(来自 ToolMessage) */
138
155
  result?: unknown;
139
156
  }
@@ -143,7 +160,7 @@ type ToolExecutionStatus = "pending" | "running" | "success" | "error";
143
160
  interface ToolExecutionRecord {
144
161
  callId: string;
145
162
  name: string;
146
- arguments: Record<string, unknown>;
163
+ args: Record<string, unknown>;
147
164
  status: ToolExecutionStatus;
148
165
  result?: unknown;
149
166
  error?: string;
@@ -151,7 +168,7 @@ interface ToolExecutionRecord {
151
168
  /** 渲染函数的 props */
152
169
  interface ToolRenderProps<TArgs = Record<string, unknown>> {
153
170
  name: string;
154
- arguments: TArgs;
171
+ args: TArgs;
155
172
  result?: unknown;
156
173
  status: ToolExecutionStatus;
157
174
  error?: string;
@@ -247,4 +264,4 @@ declare const AgentChat: react.ForwardRefExoticComponent<AgentChatProps & react.
247
264
  */
248
265
  declare const ToolCard: React.FC<ToolCardProps>;
249
266
  //#endregion
250
- 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 };
267
+ 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 };
package/dist/index.mjs CHANGED
@@ -65,7 +65,7 @@ const ToolCallRenderer = ({ tool, record }) => {
65
65
  className: "tool-call-wrapper",
66
66
  children: tool.render({
67
67
  name: record.name,
68
- arguments: record.arguments,
68
+ args: record.args,
69
69
  result: record.result,
70
70
  status: record.status,
71
71
  error: record.error
@@ -262,16 +262,20 @@ const renderToolCalls = (toolCalls, tools, toolExecutions) => {
262
262
  return toolCalls.map((call) => {
263
263
  const tool = findTool(tools, call.name);
264
264
  let record;
265
- if (tool && isFrontendTool(tool)) record = toolExecutions.get(call.id) || {
266
- callId: call.id,
267
- name: call.name,
268
- arguments: call.arguments,
269
- status: "pending"
270
- };
271
- else record = {
265
+ if (tool && isFrontendTool(tool)) {
266
+ const execution = toolExecutions.get(call.id);
267
+ record = {
268
+ callId: call.id,
269
+ name: call.name,
270
+ args: call.args,
271
+ status: execution?.status || "pending",
272
+ result: execution?.result,
273
+ error: execution?.error
274
+ };
275
+ } else record = {
272
276
  callId: call.id,
273
277
  name: call.name,
274
- arguments: call.arguments,
278
+ args: call.args,
275
279
  status: call.result !== void 0 ? "success" : "pending",
276
280
  result: call.result
277
281
  };
@@ -426,12 +430,17 @@ function useInterrupt({ interrupt, config, onSubmit }) {
426
430
  * 1. 管理工具执行状态
427
431
  * 2. 自动执行前端工具(避免重复执行)
428
432
  * 3. 通知外部执行状态变化
433
+ * 4. 支持批量提交工具结果
429
434
  */
430
- function useToolExecution({ tools, toolCalls, isLoading = false, onExecutionChange, onToolResult, completedToolResults }) {
435
+ function useToolExecution({ tools, toolCalls, isLoading = false, onExecutionChange, onToolResultsBatch, completedToolResults }) {
431
436
  const executedCallsRef = useRef(/* @__PURE__ */ new Set());
432
437
  const executingCallsRef = useRef(/* @__PURE__ */ new Set());
433
438
  const pendingNotifiedRef = useRef(/* @__PURE__ */ new Set());
434
439
  const initializedRef = useRef(false);
440
+ const batchCallIdsRef = useRef(/* @__PURE__ */ new Set());
441
+ const batchResultsRef = useRef(/* @__PURE__ */ new Map());
442
+ const isProcessingRef = useRef(false);
443
+ const batchSubmittedRef = useRef(false);
435
444
  useEffect(() => {
436
445
  if (initializedRef.current || !completedToolResults || completedToolResults.size === 0) return;
437
446
  initializedRef.current = true;
@@ -441,13 +450,26 @@ function useToolExecution({ tools, toolCalls, isLoading = false, onExecutionChan
441
450
  if (call) onExecutionChange?.({
442
451
  callId,
443
452
  name: call.name,
444
- arguments: call.arguments,
453
+ args: call.args,
445
454
  status: "success",
446
455
  result
447
456
  });
448
457
  });
449
458
  }, [completedToolResults]);
450
459
  /**
460
+ * 检查批次是否完成并提交结果
461
+ */
462
+ const checkAndSubmitBatch = useCallback(() => {
463
+ if (batchSubmittedRef.current) return;
464
+ if (Array.from(batchCallIdsRef.current).every((callId) => executedCallsRef.current.has(callId)) && batchCallIdsRef.current.size > 0) {
465
+ batchSubmittedRef.current = true;
466
+ const results = Array.from(batchResultsRef.current.values());
467
+ if (results.length > 0) onToolResultsBatch?.(results);
468
+ batchCallIdsRef.current.clear();
469
+ batchResultsRef.current.clear();
470
+ }
471
+ }, [onToolResultsBatch]);
472
+ /**
451
473
  * 执行单个前端工具
452
474
  */
453
475
  const executeFrontendTool = useCallback(async (tool, call) => {
@@ -456,38 +478,50 @@ function useToolExecution({ tools, toolCalls, isLoading = false, onExecutionChan
456
478
  onExecutionChange?.({
457
479
  callId,
458
480
  name: call.name,
459
- arguments: call.arguments,
481
+ args: call.args,
460
482
  status: "running"
461
483
  });
462
484
  try {
463
- const result = await tool.execute(call.arguments);
485
+ const result = await tool.execute(call.args);
464
486
  onExecutionChange?.({
465
487
  callId,
466
488
  name: call.name,
467
- arguments: call.arguments,
489
+ args: call.args,
468
490
  status: "success",
469
491
  result
470
492
  });
471
- onToolResult?.(callId, call.name, result);
493
+ batchResultsRef.current.set(callId, {
494
+ callId,
495
+ name: call.name,
496
+ result
497
+ });
472
498
  } catch (error) {
473
499
  const errorMessage = error instanceof Error ? error.message : String(error);
474
500
  onExecutionChange?.({
475
501
  callId,
476
502
  name: call.name,
477
- arguments: call.arguments,
503
+ args: call.args,
478
504
  status: "error",
479
505
  error: errorMessage
480
506
  });
507
+ batchResultsRef.current.set(callId, {
508
+ callId,
509
+ name: call.name,
510
+ result: { error: errorMessage }
511
+ });
481
512
  } finally {
482
513
  executingCallsRef.current.delete(callId);
483
514
  executedCallsRef.current.add(callId);
515
+ checkAndSubmitBatch();
484
516
  }
485
- }, [onExecutionChange, onToolResult]);
517
+ }, [onExecutionChange, checkAndSubmitBatch]);
486
518
  /**
487
519
  * 处理工具调用
488
520
  */
489
521
  const processToolCalls = useCallback(async () => {
490
522
  if (!tools) return;
523
+ if (isProcessingRef.current) return;
524
+ const frontendCalls = [];
491
525
  for (const call of toolCalls) {
492
526
  const callId = call.id;
493
527
  if (executedCallsRef.current.has(callId) || executingCallsRef.current.has(callId)) continue;
@@ -498,7 +532,7 @@ function useToolExecution({ tools, toolCalls, isLoading = false, onExecutionChan
498
532
  onExecutionChange?.({
499
533
  callId,
500
534
  name: call.name,
501
- arguments: call.arguments,
535
+ args: call.args,
502
536
  status: "pending"
503
537
  });
504
538
  }
@@ -508,19 +542,33 @@ function useToolExecution({ tools, toolCalls, isLoading = false, onExecutionChan
508
542
  onExecutionChange?.({
509
543
  callId,
510
544
  name: call.name,
511
- arguments: call.arguments,
545
+ args: call.args,
512
546
  status: "pending"
513
547
  });
514
548
  continue;
515
549
  }
516
- if (isFrontendTool(tool)) await executeFrontendTool(tool, call);
550
+ if (isFrontendTool(tool)) frontendCalls.push({
551
+ tool,
552
+ call
553
+ });
517
554
  else onExecutionChange?.({
518
555
  callId,
519
556
  name: call.name,
520
- arguments: call.arguments,
557
+ args: call.args,
521
558
  status: "pending"
522
559
  });
523
560
  }
561
+ if (frontendCalls.length > 0) {
562
+ batchSubmittedRef.current = false;
563
+ isProcessingRef.current = true;
564
+ batchCallIdsRef.current = new Set(frontendCalls.map(({ call }) => call.id));
565
+ batchResultsRef.current = /* @__PURE__ */ new Map();
566
+ try {
567
+ await Promise.all(frontendCalls.map(({ tool, call }) => executeFrontendTool(tool, call)));
568
+ } finally {
569
+ isProcessingRef.current = false;
570
+ }
571
+ }
524
572
  }, [
525
573
  tools,
526
574
  toolCalls,
@@ -543,7 +591,7 @@ function extractToolCalls(message) {
543
591
  if ("tool_calls" in message && Array.isArray(message.tool_calls)) return message.tool_calls.map((tc) => ({
544
592
  id: tc.id || crypto.randomUUID(),
545
593
  name: tc.name || tc.function?.name || "",
546
- arguments: typeof tc.function?.arguments === "string" ? JSON.parse(tc.function.arguments) : tc.args || {}
594
+ args: typeof tc.function?.args === "string" ? JSON.parse(tc.function.args) : tc.args || {}
547
595
  }));
548
596
  }
549
597
  /**
@@ -632,10 +680,33 @@ const styles = `
632
680
  justify-content: center;
633
681
  }
634
682
 
683
+
684
+
635
685
  .agent-message-list .ant-think-status-text {
636
686
  font-size: 13px;
637
687
  }
638
688
 
689
+ .agent-message-list table {
690
+ border-collapse: collapse;
691
+ width: 100%;
692
+ }
693
+
694
+ .agent-message-list th,
695
+ .agent-message-list td {
696
+ border: 1px solid rgba(128, 128, 128, 0.3);
697
+ padding: 8px 12px;
698
+ text-align: left;
699
+ }
700
+
701
+ .agent-message-list th {
702
+ background-color: rgba(128, 128, 128, 0.12);
703
+ font-weight: 500;
704
+ }
705
+
706
+ .agent-message-list tr:nth-child(even) {
707
+ background-color: rgba(128, 128, 128, 0.06);
708
+ }
709
+
639
710
  .agent-message-empty {
640
711
  color: #8b8d91;
641
712
  font-size: 14px;
@@ -774,7 +845,9 @@ const AgentChat = forwardRef(({ apiUrl, assistantId, headers, threadId: external
774
845
  context: contexts
775
846
  }
776
847
  }, {
848
+ streamSubgraphs: true,
777
849
  streamResumable: true,
850
+ config: { recursion_limit: 1e3 },
778
851
  optimisticValues(prev) {
779
852
  const newMessages = [...prev.messages ?? [], ...submitMessages];
780
853
  return {
@@ -789,13 +862,18 @@ const AgentChat = forwardRef(({ apiUrl, assistantId, headers, threadId: external
789
862
  contexts,
790
863
  agentState
791
864
  ]);
792
- const handleToolResult = useCallback((callId, name, result) => {
793
- submitToStream([{
794
- type: "tool",
795
- content: typeof result === "string" ? result : JSON.stringify(result),
796
- tool_call_id: callId,
797
- name
798
- }]);
865
+ const handleToolResultsBatch = useCallback((results) => {
866
+ const toolMessages = results.map(({ callId, name, result }) => {
867
+ return {
868
+ type: "tool",
869
+ content: typeof result === "string" ? result : JSON.stringify(result),
870
+ tool_call_id: callId,
871
+ name
872
+ };
873
+ });
874
+ setTimeout(() => {
875
+ submitToStream(toolMessages);
876
+ }, 100);
799
877
  }, [submitToStream]);
800
878
  const handleSend = useCallback(async (params) => {
801
879
  let messages = [];
@@ -818,7 +896,7 @@ const AgentChat = forwardRef(({ apiUrl, assistantId, headers, threadId: external
818
896
  toolCalls: allToolCalls,
819
897
  isLoading: stream.isLoading,
820
898
  onExecutionChange: handleExecutionChange,
821
- onToolResult: handleToolResult,
899
+ onToolResultsBatch: handleToolResultsBatch,
822
900
  completedToolResults: toolResults
823
901
  });
824
902
  return /* @__PURE__ */ jsxs("div", {
@@ -859,32 +937,39 @@ AgentChat.displayName = "AgentChat";
859
937
  * ToolCard 组件 - 用于展示工具调用结果
860
938
  * 样式:圆角矩形框,左侧 icon,label 分两行显示(prefix 小字体,content 默认字体)
861
939
  */
862
- const ToolCard = ({ prefix, content, icon }) => {
940
+ const ToolCard = ({ prefix, content, icon, style, styles }) => {
863
941
  return /* @__PURE__ */ jsxs("div", {
864
942
  style: {
865
943
  display: "inline-flex",
866
944
  alignItems: "center",
867
945
  gap: 8,
868
946
  borderRadius: 8,
869
- fontSize: 13
947
+ fontSize: 13,
948
+ ...style
870
949
  },
871
950
  children: [/* @__PURE__ */ jsx("span", {
872
951
  style: {
873
952
  display: "flex",
874
- alignItems: "center"
953
+ alignItems: "center",
954
+ ...styles?.icon
875
955
  },
876
956
  children: icon
877
957
  }), /* @__PURE__ */ jsxs("span", {
878
958
  style: {
879
959
  display: "flex",
880
960
  alignItems: "center",
881
- gap: 4,
882
- color: "rgba(0, 0, 0, 0.45)"
961
+ gap: 4
883
962
  },
884
963
  children: [/* @__PURE__ */ jsx("span", {
885
- style: { fontSize: 12 },
964
+ style: {
965
+ fontSize: 12,
966
+ ...styles?.prefix
967
+ },
886
968
  children: prefix
887
- }), /* @__PURE__ */ jsx("span", { children: content })]
969
+ }), /* @__PURE__ */ jsx("span", {
970
+ style: { ...styles?.content },
971
+ children: content
972
+ })]
888
973
  })]
889
974
  });
890
975
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vegintech/langchain-react-agent",
3
- "version": "0.0.7",
3
+ "version": "0.0.9",
4
4
  "description": "LangChain Agent UI component library for React",
5
5
  "license": "MIT",
6
6
  "files": [