@vegintech/langchain-react-agent 0.0.8 → 0.0.10

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
@@ -150,7 +150,7 @@ interface ChatMessage {
150
150
  interface ToolCallInput {
151
151
  id: string;
152
152
  name: string;
153
- arguments: Record<string, unknown>;
153
+ args: Record<string, unknown>;
154
154
  /** 后端工具执行结果(来自 ToolMessage) */
155
155
  result?: unknown;
156
156
  }
@@ -160,7 +160,7 @@ type ToolExecutionStatus = "pending" | "running" | "success" | "error";
160
160
  interface ToolExecutionRecord {
161
161
  callId: string;
162
162
  name: string;
163
- arguments: Record<string, unknown>;
163
+ args: Record<string, unknown>;
164
164
  status: ToolExecutionStatus;
165
165
  result?: unknown;
166
166
  error?: string;
@@ -168,7 +168,7 @@ interface ToolExecutionRecord {
168
168
  /** 渲染函数的 props */
169
169
  interface ToolRenderProps<TArgs = Record<string, unknown>> {
170
170
  name: string;
171
- arguments: TArgs;
171
+ args: TArgs;
172
172
  result?: unknown;
173
173
  status: ToolExecutionStatus;
174
174
  error?: string;
@@ -241,6 +241,8 @@ interface AgentChatProps {
241
241
  agentState?: Record<string, unknown>;
242
242
  /** Agent 状态变化回调 */
243
243
  onAgentStateChange?: (state: Record<string, unknown>) => void;
244
+ /** 是否显示调试面板,默认 false。仅在开发环境生效 */
245
+ showDebug?: boolean;
244
246
  }
245
247
  /** AgentChat 组件对外暴露的方法 */
246
248
  interface AgentChatRef {
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
@@ -226,7 +226,7 @@ const renderMessageContent = (message, isLastMessage, isLoading, tools, toolExec
226
226
  children: /* @__PURE__ */ jsx(ReasoningContent, { content: message.reasoningContent })
227
227
  }),
228
228
  message.content && /* @__PURE__ */ jsx("div", {
229
- style: { marginTop: message.reasoningContent ? "8px" : 0 },
229
+ style: { marginTop: message.reasoningContent ? "8px" : "3px" },
230
230
  children: /* @__PURE__ */ jsx(Streamdown, {
231
231
  components: {
232
232
  p: CustomParagraph,
@@ -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
  };
@@ -373,6 +377,443 @@ const MessageList = ({ messages, isLoading = false, className = "", tools, toolE
373
377
  });
374
378
  };
375
379
  //#endregion
380
+ //#region src/components/DebugPanel.tsx
381
+ const isDevelopment = () => {
382
+ if (typeof process !== "undefined" && process.env) return process.env.NODE_ENV === "development";
383
+ const viteEnv = import.meta.env;
384
+ if (typeof import.meta !== "undefined" && viteEnv) return viteEnv.DEV === true || viteEnv.MODE === "development";
385
+ return false;
386
+ };
387
+ function DebugPanel({ messages, streamState, visible = true }) {
388
+ if (!isDevelopment() || !visible) return null;
389
+ const [isOpen, setIsOpen] = useState(false);
390
+ const [activeTab, setActiveTab] = useState("messages");
391
+ const [expandedRows, setExpandedRows] = useState(/* @__PURE__ */ new Set());
392
+ const [buttonPos, setButtonPos] = useState({
393
+ x: 0,
394
+ y: 0
395
+ });
396
+ const [isDraggingButton, setIsDraggingButton] = useState(false);
397
+ const buttonDragRef = useRef(null);
398
+ const [dialogPos, setDialogPos] = useState({
399
+ x: -400,
400
+ y: -300
401
+ });
402
+ const [isDraggingDialog, setIsDraggingDialog] = useState(false);
403
+ const dialogDragRef = useRef(null);
404
+ const handleButtonMouseDown = useCallback((e) => {
405
+ e.preventDefault();
406
+ buttonDragRef.current = {
407
+ startX: e.clientX,
408
+ startY: e.clientY,
409
+ initialX: buttonPos.x,
410
+ initialY: buttonPos.y
411
+ };
412
+ setIsDraggingButton(true);
413
+ }, [buttonPos]);
414
+ const handleDialogMouseDown = useCallback((e) => {
415
+ if (!e.target.closest(".debug-panel-header")) return;
416
+ e.preventDefault();
417
+ dialogDragRef.current = {
418
+ startX: e.clientX,
419
+ startY: e.clientY,
420
+ initialX: dialogPos.x,
421
+ initialY: dialogPos.y
422
+ };
423
+ setIsDraggingDialog(true);
424
+ }, [dialogPos]);
425
+ useEffect(() => {
426
+ const handleMouseMove = (e) => {
427
+ if (isDraggingButton && buttonDragRef.current) {
428
+ const dx = e.clientX - buttonDragRef.current.startX;
429
+ const dy = e.clientY - buttonDragRef.current.startY;
430
+ setButtonPos({
431
+ x: buttonDragRef.current.initialX + dx,
432
+ y: buttonDragRef.current.initialY + dy
433
+ });
434
+ }
435
+ if (isDraggingDialog && dialogDragRef.current) {
436
+ const dx = e.clientX - dialogDragRef.current.startX;
437
+ const dy = e.clientY - dialogDragRef.current.startY;
438
+ setDialogPos({
439
+ x: dialogDragRef.current.initialX + dx,
440
+ y: dialogDragRef.current.initialY + dy
441
+ });
442
+ }
443
+ };
444
+ const handleMouseUp = () => {
445
+ setIsDraggingButton(false);
446
+ setIsDraggingDialog(false);
447
+ buttonDragRef.current = null;
448
+ dialogDragRef.current = null;
449
+ };
450
+ if (isDraggingButton || isDraggingDialog) {
451
+ document.addEventListener("mousemove", handleMouseMove);
452
+ document.addEventListener("mouseup", handleMouseUp);
453
+ }
454
+ return () => {
455
+ document.removeEventListener("mousemove", handleMouseMove);
456
+ document.removeEventListener("mouseup", handleMouseUp);
457
+ };
458
+ }, [isDraggingButton, isDraggingDialog]);
459
+ const formatJson = (data) => {
460
+ try {
461
+ return JSON.stringify(data, null, 2);
462
+ } catch {
463
+ return String(data);
464
+ }
465
+ };
466
+ const toggleRow = (index) => {
467
+ setExpandedRows((prev) => {
468
+ const next = new Set(prev);
469
+ if (next.has(index)) next.delete(index);
470
+ else next.add(index);
471
+ return next;
472
+ });
473
+ };
474
+ const getTypeTagStyle = (type) => {
475
+ const color = {
476
+ human: {
477
+ bg: "#e6f4ff",
478
+ color: "#0958d9"
479
+ },
480
+ ai: {
481
+ bg: "#f6ffed",
482
+ color: "#389e0d"
483
+ },
484
+ tool: {
485
+ bg: "#fff7e6",
486
+ color: "#d46b08"
487
+ },
488
+ system: {
489
+ bg: "#f9f0ff",
490
+ color: "#722ed1"
491
+ },
492
+ function: {
493
+ bg: "#fff2f0",
494
+ color: "#cf1322"
495
+ }
496
+ }[type || ""] || {
497
+ bg: "#f5f5f5",
498
+ color: "#666"
499
+ };
500
+ return {
501
+ display: "inline-block",
502
+ padding: "2px 8px",
503
+ borderRadius: "4px",
504
+ fontSize: "12px",
505
+ fontWeight: 500,
506
+ background: color.bg,
507
+ color: color.color
508
+ };
509
+ };
510
+ const formatContent = (content) => {
511
+ if (!content) return "-";
512
+ return content;
513
+ };
514
+ const buttonStyle = {
515
+ position: "fixed",
516
+ right: `${20 - buttonPos.x}px`,
517
+ bottom: `${20 - buttonPos.y}px`,
518
+ width: "48px",
519
+ height: "48px",
520
+ borderRadius: "50%",
521
+ background: "#1677ff",
522
+ color: "#fff",
523
+ border: "none",
524
+ cursor: isDraggingButton ? "grabbing" : "grab",
525
+ boxShadow: "0 4px 12px rgba(0, 0, 0, 0.15)",
526
+ display: "flex",
527
+ alignItems: "center",
528
+ justifyContent: "center",
529
+ fontSize: "20px",
530
+ zIndex: 9998,
531
+ transition: isDraggingButton ? "none" : "box-shadow 0.2s",
532
+ userSelect: "none"
533
+ };
534
+ const dialogStyle = {
535
+ position: "fixed",
536
+ right: `${20 - dialogPos.x}px`,
537
+ bottom: `${80 - dialogPos.y}px`,
538
+ width: "800px",
539
+ maxWidth: "90vw",
540
+ height: "600px",
541
+ maxHeight: "85vh",
542
+ background: "#fff",
543
+ borderRadius: "12px",
544
+ boxShadow: "0 8px 32px rgba(0, 0, 0, 0.2)",
545
+ zIndex: 9999,
546
+ display: "flex",
547
+ flexDirection: "column",
548
+ overflow: "hidden",
549
+ cursor: isDraggingDialog ? "grabbing" : "default",
550
+ userSelect: "none"
551
+ };
552
+ const headerStyle = {
553
+ padding: "12px 16px",
554
+ background: "#f5f5f5",
555
+ borderBottom: "1px solid #e8e8e8",
556
+ display: "flex",
557
+ alignItems: "center",
558
+ justifyContent: "space-between",
559
+ cursor: isDraggingDialog ? "grabbing" : "grab"
560
+ };
561
+ const tabContainerStyle = {
562
+ display: "flex",
563
+ gap: "8px"
564
+ };
565
+ const getTabStyle = (isActive) => ({
566
+ padding: "6px 16px",
567
+ borderRadius: "6px",
568
+ border: "none",
569
+ background: isActive ? "#1677ff" : "transparent",
570
+ color: isActive ? "#fff" : "#666",
571
+ cursor: "pointer",
572
+ fontSize: "14px",
573
+ transition: "all 0.2s"
574
+ });
575
+ const closeButtonStyle = {
576
+ width: "28px",
577
+ height: "28px",
578
+ borderRadius: "50%",
579
+ border: "none",
580
+ background: "#ff4d4f",
581
+ color: "#fff",
582
+ cursor: "pointer",
583
+ display: "flex",
584
+ alignItems: "center",
585
+ justifyContent: "center",
586
+ fontSize: "16px",
587
+ transition: "background 0.2s"
588
+ };
589
+ const contentStyle = {
590
+ flex: 1,
591
+ overflow: "auto",
592
+ padding: "16px",
593
+ background: "#fafafa"
594
+ };
595
+ const preStyle = {
596
+ margin: 0,
597
+ padding: "12px",
598
+ background: "#f8f9fa",
599
+ color: "#333",
600
+ borderRadius: "8px",
601
+ fontSize: "12px",
602
+ fontFamily: "\"Fira Code\", \"Monaco\", \"Consolas\", monospace",
603
+ lineHeight: 1.5,
604
+ overflow: "auto",
605
+ whiteSpace: "pre-wrap",
606
+ wordBreak: "break-word",
607
+ border: "1px solid #e8e8e8"
608
+ };
609
+ const statsStyle = {
610
+ padding: "8px 12px",
611
+ background: "#e6f4ff",
612
+ borderRadius: "6px",
613
+ marginBottom: "12px",
614
+ fontSize: "13px",
615
+ color: "#0958d9"
616
+ };
617
+ const tableStyle = {
618
+ width: "100%",
619
+ borderCollapse: "collapse",
620
+ fontSize: "13px",
621
+ background: "#fff",
622
+ borderRadius: "8px",
623
+ overflow: "hidden",
624
+ boxShadow: "0 1px 2px rgba(0, 0, 0, 0.05)"
625
+ };
626
+ const tableHeaderStyle = {
627
+ background: "#f5f5f5",
628
+ fontWeight: 600,
629
+ color: "#333",
630
+ textAlign: "left",
631
+ padding: "10px 12px",
632
+ borderBottom: "1px solid #e8e8e8",
633
+ whiteSpace: "nowrap"
634
+ };
635
+ const tableCellStyle = {
636
+ padding: "10px 12px",
637
+ borderBottom: "1px solid #f0f0f0",
638
+ verticalAlign: "top"
639
+ };
640
+ const expandButtonStyle = {
641
+ width: "20px",
642
+ height: "20px",
643
+ border: "none",
644
+ background: "transparent",
645
+ cursor: "pointer",
646
+ display: "flex",
647
+ alignItems: "center",
648
+ justifyContent: "center",
649
+ fontSize: "12px",
650
+ color: "#666",
651
+ borderRadius: "4px",
652
+ transition: "background 0.2s"
653
+ };
654
+ const expandedRowStyle = { background: "#fafafa" };
655
+ const jsonContainerStyle = {
656
+ padding: "12px",
657
+ background: "#fff",
658
+ borderRadius: "6px",
659
+ margin: "8px 0",
660
+ border: "1px solid #e8e8e8"
661
+ };
662
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("button", {
663
+ style: buttonStyle,
664
+ onMouseDown: handleButtonMouseDown,
665
+ onClick: () => !isDraggingButton && setIsOpen(true),
666
+ title: "打开调试面板",
667
+ children: "🐛"
668
+ }), isOpen && /* @__PURE__ */ jsxs("div", {
669
+ style: dialogStyle,
670
+ onMouseDown: handleDialogMouseDown,
671
+ children: [/* @__PURE__ */ jsxs("div", {
672
+ className: "debug-panel-header",
673
+ style: headerStyle,
674
+ children: [/* @__PURE__ */ jsxs("div", {
675
+ style: tabContainerStyle,
676
+ children: [/* @__PURE__ */ jsxs("button", {
677
+ style: getTabStyle(activeTab === "messages"),
678
+ onClick: () => setActiveTab("messages"),
679
+ children: [
680
+ "Messages (",
681
+ messages.length,
682
+ ")"
683
+ ]
684
+ }), /* @__PURE__ */ jsx("button", {
685
+ style: getTabStyle(activeTab === "state"),
686
+ onClick: () => setActiveTab("state"),
687
+ children: "State"
688
+ })]
689
+ }), /* @__PURE__ */ jsx("button", {
690
+ style: closeButtonStyle,
691
+ onClick: () => setIsOpen(false),
692
+ title: "关闭",
693
+ children: "×"
694
+ })]
695
+ }), /* @__PURE__ */ jsx("div", {
696
+ style: contentStyle,
697
+ children: activeTab === "messages" ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("div", {
698
+ style: statsStyle,
699
+ children: [
700
+ "共 ",
701
+ messages.length,
702
+ " 条消息"
703
+ ]
704
+ }), /* @__PURE__ */ jsxs("table", {
705
+ style: tableStyle,
706
+ children: [/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
707
+ /* @__PURE__ */ jsx("th", { style: {
708
+ ...tableHeaderStyle,
709
+ width: "30px"
710
+ } }),
711
+ /* @__PURE__ */ jsx("th", {
712
+ style: {
713
+ ...tableHeaderStyle,
714
+ width: "60px"
715
+ },
716
+ children: "类型"
717
+ }),
718
+ /* @__PURE__ */ jsx("th", {
719
+ style: tableHeaderStyle,
720
+ children: "内容"
721
+ }),
722
+ /* @__PURE__ */ jsx("th", {
723
+ style: {
724
+ ...tableHeaderStyle,
725
+ width: "100px"
726
+ },
727
+ children: "ID"
728
+ })
729
+ ] }) }), /* @__PURE__ */ jsx("tbody", { children: messages.map((msg, index) => {
730
+ const message = msg;
731
+ const isExpanded = expandedRows.has(index);
732
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("tr", {
733
+ style: {
734
+ cursor: "pointer",
735
+ transition: "background 0.2s"
736
+ },
737
+ onClick: () => toggleRow(index),
738
+ onMouseEnter: (e) => {
739
+ e.currentTarget.style.background = "#f5f5f5";
740
+ },
741
+ onMouseLeave: (e) => {
742
+ e.currentTarget.style.background = "transparent";
743
+ },
744
+ children: [
745
+ /* @__PURE__ */ jsx("td", {
746
+ style: tableCellStyle,
747
+ children: /* @__PURE__ */ jsx("button", {
748
+ style: expandButtonStyle,
749
+ onClick: (e) => {
750
+ e.stopPropagation();
751
+ toggleRow(index);
752
+ },
753
+ children: isExpanded ? "▼" : "▶"
754
+ })
755
+ }),
756
+ /* @__PURE__ */ jsx("td", {
757
+ style: tableCellStyle,
758
+ children: /* @__PURE__ */ jsx("span", {
759
+ style: getTypeTagStyle(message.type),
760
+ children: message.type || "unknown"
761
+ })
762
+ }),
763
+ /* @__PURE__ */ jsx("td", {
764
+ style: tableCellStyle,
765
+ children: /* @__PURE__ */ jsx("div", {
766
+ style: {
767
+ color: "#333",
768
+ lineHeight: 1.5,
769
+ wordBreak: "break-all"
770
+ },
771
+ children: formatContent(message.content)
772
+ })
773
+ }),
774
+ /* @__PURE__ */ jsx("td", {
775
+ style: {
776
+ ...tableCellStyle,
777
+ color: "#999",
778
+ fontSize: "12px"
779
+ },
780
+ children: message.id?.slice(-8) || "-"
781
+ })
782
+ ]
783
+ }, index), isExpanded && /* @__PURE__ */ jsx("tr", {
784
+ style: expandedRowStyle,
785
+ children: /* @__PURE__ */ jsx("td", {
786
+ colSpan: 4,
787
+ style: {
788
+ padding: 0,
789
+ borderBottom: "1px solid #e8e8e8"
790
+ },
791
+ children: /* @__PURE__ */ jsx("div", {
792
+ style: jsonContainerStyle,
793
+ children: /* @__PURE__ */ jsx("pre", {
794
+ style: {
795
+ ...preStyle,
796
+ margin: 0,
797
+ maxHeight: "300px",
798
+ overflow: "auto"
799
+ },
800
+ children: formatJson(message)
801
+ })
802
+ })
803
+ })
804
+ })] });
805
+ }) })]
806
+ })] }) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("div", {
807
+ style: statsStyle,
808
+ children: ["State 键数量: ", Object.keys(streamState).length]
809
+ }), /* @__PURE__ */ jsx("pre", {
810
+ style: preStyle,
811
+ children: formatJson(streamState)
812
+ })] })
813
+ })]
814
+ })] });
815
+ }
816
+ //#endregion
376
817
  //#region src/hooks/useInterrupt.tsx
377
818
  /**
378
819
  * InterruptManager - 管理 Interrupt 状态的 Hook
@@ -426,12 +867,17 @@ function useInterrupt({ interrupt, config, onSubmit }) {
426
867
  * 1. 管理工具执行状态
427
868
  * 2. 自动执行前端工具(避免重复执行)
428
869
  * 3. 通知外部执行状态变化
870
+ * 4. 支持批量提交工具结果
429
871
  */
430
- function useToolExecution({ tools, toolCalls, isLoading = false, onExecutionChange, onToolResult, completedToolResults }) {
872
+ function useToolExecution({ tools, toolCalls, isLoading = false, onExecutionChange, onToolResultsBatch, completedToolResults }) {
431
873
  const executedCallsRef = useRef(/* @__PURE__ */ new Set());
432
874
  const executingCallsRef = useRef(/* @__PURE__ */ new Set());
433
875
  const pendingNotifiedRef = useRef(/* @__PURE__ */ new Set());
434
876
  const initializedRef = useRef(false);
877
+ const batchCallIdsRef = useRef(/* @__PURE__ */ new Set());
878
+ const batchResultsRef = useRef(/* @__PURE__ */ new Map());
879
+ const isProcessingRef = useRef(false);
880
+ const batchSubmittedRef = useRef(false);
435
881
  useEffect(() => {
436
882
  if (initializedRef.current || !completedToolResults || completedToolResults.size === 0) return;
437
883
  initializedRef.current = true;
@@ -441,13 +887,26 @@ function useToolExecution({ tools, toolCalls, isLoading = false, onExecutionChan
441
887
  if (call) onExecutionChange?.({
442
888
  callId,
443
889
  name: call.name,
444
- arguments: call.arguments,
890
+ args: call.args,
445
891
  status: "success",
446
892
  result
447
893
  });
448
894
  });
449
895
  }, [completedToolResults]);
450
896
  /**
897
+ * 检查批次是否完成并提交结果
898
+ */
899
+ const checkAndSubmitBatch = useCallback(() => {
900
+ if (batchSubmittedRef.current) return;
901
+ if (Array.from(batchCallIdsRef.current).every((callId) => executedCallsRef.current.has(callId)) && batchCallIdsRef.current.size > 0) {
902
+ batchSubmittedRef.current = true;
903
+ const results = Array.from(batchResultsRef.current.values());
904
+ if (results.length > 0) onToolResultsBatch?.(results);
905
+ batchCallIdsRef.current.clear();
906
+ batchResultsRef.current.clear();
907
+ }
908
+ }, [onToolResultsBatch]);
909
+ /**
451
910
  * 执行单个前端工具
452
911
  */
453
912
  const executeFrontendTool = useCallback(async (tool, call) => {
@@ -456,38 +915,50 @@ function useToolExecution({ tools, toolCalls, isLoading = false, onExecutionChan
456
915
  onExecutionChange?.({
457
916
  callId,
458
917
  name: call.name,
459
- arguments: call.arguments,
918
+ args: call.args,
460
919
  status: "running"
461
920
  });
462
921
  try {
463
- const result = await tool.execute(call.arguments);
922
+ const result = await tool.execute(call.args);
464
923
  onExecutionChange?.({
465
924
  callId,
466
925
  name: call.name,
467
- arguments: call.arguments,
926
+ args: call.args,
468
927
  status: "success",
469
928
  result
470
929
  });
471
- onToolResult?.(callId, call.name, result);
930
+ batchResultsRef.current.set(callId, {
931
+ callId,
932
+ name: call.name,
933
+ result
934
+ });
472
935
  } catch (error) {
473
936
  const errorMessage = error instanceof Error ? error.message : String(error);
474
937
  onExecutionChange?.({
475
938
  callId,
476
939
  name: call.name,
477
- arguments: call.arguments,
940
+ args: call.args,
478
941
  status: "error",
479
942
  error: errorMessage
480
943
  });
944
+ batchResultsRef.current.set(callId, {
945
+ callId,
946
+ name: call.name,
947
+ result: { error: errorMessage }
948
+ });
481
949
  } finally {
482
950
  executingCallsRef.current.delete(callId);
483
951
  executedCallsRef.current.add(callId);
952
+ checkAndSubmitBatch();
484
953
  }
485
- }, [onExecutionChange, onToolResult]);
954
+ }, [onExecutionChange, checkAndSubmitBatch]);
486
955
  /**
487
956
  * 处理工具调用
488
957
  */
489
958
  const processToolCalls = useCallback(async () => {
490
959
  if (!tools) return;
960
+ if (isProcessingRef.current) return;
961
+ const frontendCalls = [];
491
962
  for (const call of toolCalls) {
492
963
  const callId = call.id;
493
964
  if (executedCallsRef.current.has(callId) || executingCallsRef.current.has(callId)) continue;
@@ -498,7 +969,7 @@ function useToolExecution({ tools, toolCalls, isLoading = false, onExecutionChan
498
969
  onExecutionChange?.({
499
970
  callId,
500
971
  name: call.name,
501
- arguments: call.arguments,
972
+ args: call.args,
502
973
  status: "pending"
503
974
  });
504
975
  }
@@ -508,19 +979,33 @@ function useToolExecution({ tools, toolCalls, isLoading = false, onExecutionChan
508
979
  onExecutionChange?.({
509
980
  callId,
510
981
  name: call.name,
511
- arguments: call.arguments,
982
+ args: call.args,
512
983
  status: "pending"
513
984
  });
514
985
  continue;
515
986
  }
516
- if (isFrontendTool(tool)) await executeFrontendTool(tool, call);
987
+ if (isFrontendTool(tool)) frontendCalls.push({
988
+ tool,
989
+ call
990
+ });
517
991
  else onExecutionChange?.({
518
992
  callId,
519
993
  name: call.name,
520
- arguments: call.arguments,
994
+ args: call.args,
521
995
  status: "pending"
522
996
  });
523
997
  }
998
+ if (frontendCalls.length > 0) {
999
+ batchSubmittedRef.current = false;
1000
+ isProcessingRef.current = true;
1001
+ batchCallIdsRef.current = new Set(frontendCalls.map(({ call }) => call.id));
1002
+ batchResultsRef.current = /* @__PURE__ */ new Map();
1003
+ try {
1004
+ await Promise.all(frontendCalls.map(({ tool, call }) => executeFrontendTool(tool, call)));
1005
+ } finally {
1006
+ isProcessingRef.current = false;
1007
+ }
1008
+ }
524
1009
  }, [
525
1010
  tools,
526
1011
  toolCalls,
@@ -543,7 +1028,7 @@ function extractToolCalls(message) {
543
1028
  if ("tool_calls" in message && Array.isArray(message.tool_calls)) return message.tool_calls.map((tc) => ({
544
1029
  id: tc.id || crypto.randomUUID(),
545
1030
  name: tc.name || tc.function?.name || "",
546
- arguments: typeof tc.function?.arguments === "string" ? JSON.parse(tc.function.arguments) : tc.args || {}
1031
+ args: typeof tc.function?.args === "string" ? JSON.parse(tc.function.args) : tc.args || {}
547
1032
  }));
548
1033
  }
549
1034
  /**
@@ -632,10 +1117,33 @@ const styles = `
632
1117
  justify-content: center;
633
1118
  }
634
1119
 
1120
+
1121
+
635
1122
  .agent-message-list .ant-think-status-text {
636
1123
  font-size: 13px;
637
1124
  }
638
1125
 
1126
+ .agent-message-list table {
1127
+ border-collapse: collapse;
1128
+ width: 100%;
1129
+ }
1130
+
1131
+ .agent-message-list th,
1132
+ .agent-message-list td {
1133
+ border: 1px solid rgba(128, 128, 128, 0.3);
1134
+ padding: 8px 12px;
1135
+ text-align: left;
1136
+ }
1137
+
1138
+ .agent-message-list th {
1139
+ background-color: rgba(128, 128, 128, 0.12);
1140
+ font-weight: 500;
1141
+ }
1142
+
1143
+ .agent-message-list tr:nth-child(even) {
1144
+ background-color: rgba(128, 128, 128, 0.06);
1145
+ }
1146
+
639
1147
  .agent-message-empty {
640
1148
  color: #8b8d91;
641
1149
  font-size: 14px;
@@ -715,7 +1223,7 @@ function injectStyles() {
715
1223
  //#endregion
716
1224
  //#region src/components/AgentChat.tsx
717
1225
  injectStyles();
718
- const AgentChat = forwardRef(({ apiUrl, assistantId, headers, threadId: externalThreadId, onThreadIdChange, className = "", tools, contexts, messageConfig, inputConfig, onError, interruptConfig, agentState, onAgentStateChange }, ref) => {
1226
+ const AgentChat = forwardRef(({ apiUrl, assistantId, headers, threadId: externalThreadId, onThreadIdChange, className = "", tools, contexts, messageConfig, inputConfig, onError, interruptConfig, agentState, onAgentStateChange, showDebug }, ref) => {
719
1227
  const [internalThreadId, setInternalThreadId] = useState(externalThreadId);
720
1228
  useEffect(() => {
721
1229
  setInternalThreadId(externalThreadId);
@@ -774,7 +1282,9 @@ const AgentChat = forwardRef(({ apiUrl, assistantId, headers, threadId: external
774
1282
  context: contexts
775
1283
  }
776
1284
  }, {
1285
+ streamSubgraphs: true,
777
1286
  streamResumable: true,
1287
+ config: { recursion_limit: 1e3 },
778
1288
  optimisticValues(prev) {
779
1289
  const newMessages = [...prev.messages ?? [], ...submitMessages];
780
1290
  return {
@@ -789,13 +1299,18 @@ const AgentChat = forwardRef(({ apiUrl, assistantId, headers, threadId: external
789
1299
  contexts,
790
1300
  agentState
791
1301
  ]);
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
- }]);
1302
+ const handleToolResultsBatch = useCallback((results) => {
1303
+ const toolMessages = results.map(({ callId, name, result }) => {
1304
+ return {
1305
+ type: "tool",
1306
+ content: typeof result === "string" ? result : JSON.stringify(result),
1307
+ tool_call_id: callId,
1308
+ name
1309
+ };
1310
+ });
1311
+ setTimeout(() => {
1312
+ submitToStream(toolMessages);
1313
+ }, 100);
799
1314
  }, [submitToStream]);
800
1315
  const handleSend = useCallback(async (params) => {
801
1316
  let messages = [];
@@ -818,38 +1333,46 @@ const AgentChat = forwardRef(({ apiUrl, assistantId, headers, threadId: external
818
1333
  toolCalls: allToolCalls,
819
1334
  isLoading: stream.isLoading,
820
1335
  onExecutionChange: handleExecutionChange,
821
- onToolResult: handleToolResult,
1336
+ onToolResultsBatch: handleToolResultsBatch,
822
1337
  completedToolResults: toolResults
823
1338
  });
824
1339
  return /* @__PURE__ */ jsxs("div", {
825
1340
  className: `agent-chat-container ${className}`,
826
- children: [/* @__PURE__ */ jsx(MessageList, {
827
- messages,
828
- isLoading: stream.isLoading,
829
- className: "agent-chat-messages",
830
- tools,
831
- toolExecutions,
832
- components: messageConfig?.components,
833
- securityConfig: {
834
- allowedTags: messageConfig?.allowedTags,
835
- literalTagContent: messageConfig?.literalTagContent
836
- },
837
- loadingColor: messageConfig?.loadingColor,
838
- interruptRender,
839
- emptyState: messageConfig?.emptyState
840
- }), /* @__PURE__ */ jsx(ChatInput, {
841
- ref: chatInputRef,
842
- onSend: handleSend,
843
- onStop: handleStop,
844
- isLoading: stream.isLoading,
845
- className: "agent-chat-input",
846
- footer,
847
- skill,
848
- slotConfig,
849
- header,
850
- prefix,
851
- placeholder
852
- })]
1341
+ children: [
1342
+ /* @__PURE__ */ jsx(MessageList, {
1343
+ messages,
1344
+ isLoading: stream.isLoading,
1345
+ className: "agent-chat-messages",
1346
+ tools,
1347
+ toolExecutions,
1348
+ components: messageConfig?.components,
1349
+ securityConfig: {
1350
+ allowedTags: messageConfig?.allowedTags,
1351
+ literalTagContent: messageConfig?.literalTagContent
1352
+ },
1353
+ loadingColor: messageConfig?.loadingColor,
1354
+ interruptRender,
1355
+ emptyState: messageConfig?.emptyState
1356
+ }),
1357
+ /* @__PURE__ */ jsx(ChatInput, {
1358
+ ref: chatInputRef,
1359
+ onSend: handleSend,
1360
+ onStop: handleStop,
1361
+ isLoading: stream.isLoading,
1362
+ className: "agent-chat-input",
1363
+ footer,
1364
+ skill,
1365
+ slotConfig,
1366
+ header,
1367
+ prefix,
1368
+ placeholder
1369
+ }),
1370
+ /* @__PURE__ */ jsx(DebugPanel, {
1371
+ messages: stream.messages,
1372
+ streamState: stream.values || {},
1373
+ visible: showDebug
1374
+ })
1375
+ ]
853
1376
  });
854
1377
  });
855
1378
  AgentChat.displayName = "AgentChat";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vegintech/langchain-react-agent",
3
- "version": "0.0.8",
3
+ "version": "0.0.10",
4
4
  "description": "LangChain Agent UI component library for React",
5
5
  "license": "MIT",
6
6
  "files": [
@@ -15,13 +15,6 @@
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
- },
25
18
  "dependencies": {
26
19
  "@ant-design/x": "^2.4.0",
27
20
  "@langchain/core": "^1.1.36",
@@ -43,5 +36,11 @@
43
36
  "peerDependencies": {
44
37
  "react": ">=18.0.0",
45
38
  "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"
46
45
  }
47
- }
46
+ }