@vegintech/langchain-react-agent 0.0.25 → 0.0.26

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.mjs CHANGED
@@ -456,803 +456,336 @@ const MessageList = ({ messages, isLoading = false, className = "", tools, toolE
456
456
  });
457
457
  };
458
458
  //#endregion
459
- //#region src/components/DebugPanel.tsx
460
- const isDevelopment = () => {
461
- if (typeof process !== "undefined" && process.env) return process.env.NODE_ENV === "development";
462
- const viteEnv = import.meta.env;
463
- if (typeof import.meta !== "undefined" && viteEnv) return viteEnv.DEV === true || viteEnv.MODE === "development";
464
- return false;
465
- };
466
- function DebugPanel({ messages, streamState, visible = true }) {
467
- if (!isDevelopment() || !visible) return null;
468
- const [isOpen, setIsOpen] = useState(false);
469
- const [activeTab, setActiveTab] = useState("messages");
470
- const [expandedRows, setExpandedRows] = useState(/* @__PURE__ */ new Set());
471
- const [buttonPos, setButtonPos] = useState({
472
- x: 0,
473
- y: 0
474
- });
475
- const [isDraggingButton, setIsDraggingButton] = useState(false);
476
- const buttonDragRef = useRef(null);
477
- const [dialogPos, setDialogPos] = useState({
478
- x: 0,
479
- y: 0
480
- });
481
- const [isDraggingDialog, setIsDraggingDialog] = useState(false);
482
- const dialogDragRef = useRef(null);
483
- const handleButtonMouseDown = useCallback((e) => {
484
- e.preventDefault();
485
- buttonDragRef.current = {
486
- startX: e.clientX,
487
- startY: e.clientY,
488
- initialX: buttonPos.x,
489
- initialY: buttonPos.y
490
- };
491
- setIsDraggingButton(true);
492
- }, [buttonPos]);
493
- const handleDialogMouseDown = useCallback((e) => {
494
- if (!e.target.closest(".debug-panel-header")) return;
495
- e.preventDefault();
496
- dialogDragRef.current = {
497
- startX: e.clientX,
498
- startY: e.clientY,
499
- initialX: dialogPos.x,
500
- initialY: dialogPos.y
501
- };
502
- setIsDraggingDialog(true);
503
- }, [dialogPos]);
459
+ //#region src/hooks/useInterrupt.tsx
460
+ /**
461
+ * InterruptManager - 管理 Interrupt 状态的 Hook
462
+ *
463
+ * 封装 Interrupt 状态管理和处理逻辑,避免在 AgentChat 组件中臃肿
464
+ */
465
+ function useInterrupt({ interrupt, config, onSubmit }) {
466
+ const [activeInterrupt, setActiveInterrupt] = useState(null);
467
+ const processedInterruptIdRef = useRef(null);
504
468
  useEffect(() => {
505
- const handleMouseMove = (e) => {
506
- if (isDraggingButton && buttonDragRef.current) {
507
- const dx = e.clientX - buttonDragRef.current.startX;
508
- const dy = e.clientY - buttonDragRef.current.startY;
509
- setButtonPos({
510
- x: buttonDragRef.current.initialX + dx,
511
- y: buttonDragRef.current.initialY + dy
512
- });
513
- }
514
- if (isDraggingDialog && dialogDragRef.current) {
515
- const dx = e.clientX - dialogDragRef.current.startX;
516
- const dy = e.clientY - dialogDragRef.current.startY;
517
- setDialogPos({
518
- x: dialogDragRef.current.initialX + dx,
519
- y: dialogDragRef.current.initialY + dy
520
- });
469
+ if (interrupt) {
470
+ const interruptEvent = interrupt;
471
+ if (interruptEvent.id !== processedInterruptIdRef.current) {
472
+ processedInterruptIdRef.current = interruptEvent.id;
473
+ setActiveInterrupt(interruptEvent);
521
474
  }
522
- };
523
- const handleMouseUp = () => {
524
- setIsDraggingButton(false);
525
- setIsDraggingDialog(false);
526
- buttonDragRef.current = null;
527
- dialogDragRef.current = null;
528
- };
529
- if (isDraggingButton || isDraggingDialog) {
530
- document.addEventListener("mousemove", handleMouseMove);
531
- document.addEventListener("mouseup", handleMouseUp);
532
- }
533
- return () => {
534
- document.removeEventListener("mousemove", handleMouseMove);
535
- document.removeEventListener("mouseup", handleMouseUp);
536
- };
537
- }, [isDraggingButton, isDraggingDialog]);
538
- const formatJson = (data) => {
539
- try {
540
- return JSON.stringify(data, null, 2);
541
- } catch {
542
- return String(data);
475
+ } else {
476
+ processedInterruptIdRef.current = null;
477
+ setActiveInterrupt(null);
543
478
  }
479
+ }, [interrupt?.id]);
480
+ const handleResolveInterrupt = useCallback((response) => {
481
+ onSubmit(null, { command: { resume: response } });
482
+ setActiveInterrupt(null);
483
+ }, [onSubmit]);
484
+ return {
485
+ activeInterrupt,
486
+ handleResolveInterrupt,
487
+ renderInterrupt: useCallback(() => {
488
+ if (!activeInterrupt || !config?.render) return null;
489
+ return /* @__PURE__ */ jsx("div", {
490
+ className: "agent-chat-interrupt",
491
+ children: config.render({
492
+ event: activeInterrupt,
493
+ resolve: handleResolveInterrupt
494
+ })
495
+ });
496
+ }, [
497
+ activeInterrupt,
498
+ config,
499
+ handleResolveInterrupt
500
+ ])
544
501
  };
545
- const toggleRow = (index) => {
546
- setExpandedRows((prev) => {
547
- const next = new Set(prev);
548
- if (next.has(index)) next.delete(index);
549
- else next.add(index);
550
- return next;
551
- });
552
- };
553
- const getTypeTagStyle = (type) => {
554
- const color = {
555
- human: {
556
- bg: "#e6f4ff",
557
- color: "#0958d9"
558
- },
559
- ai: {
560
- bg: "#f6ffed",
561
- color: "#389e0d"
562
- },
563
- tool: {
564
- bg: "#fff7e6",
565
- color: "#d46b08"
566
- },
567
- system: {
568
- bg: "#f9f0ff",
569
- color: "#722ed1"
570
- },
571
- function: {
572
- bg: "#fff2f0",
573
- color: "#cf1322"
574
- }
575
- }[type || ""] || {
576
- bg: "#f5f5f5",
577
- color: "#666"
578
- };
579
- return {
580
- display: "inline-block",
581
- padding: "2px 8px",
582
- borderRadius: "4px",
583
- fontSize: "12px",
584
- fontWeight: 500,
585
- background: color.bg,
586
- color: color.color
587
- };
588
- };
589
- const formatContent = (content) => {
590
- if (!content) return "";
591
- if (Array.isArray(content)) return content.map((item) => typeof item === "string" ? item : JSON.stringify(item)).join("");
592
- return String(content);
593
- };
594
- const renderMessageContent = (message) => {
595
- const hasToolCalls = message.tool_calls && Array.isArray(message.tool_calls) && message.tool_calls.length > 0;
596
- return /* @__PURE__ */ jsxs("div", {
597
- style: {
598
- display: "flex",
599
- flexDirection: "column",
600
- gap: "8px"
601
- },
602
- children: [message.content && /* @__PURE__ */ jsx("div", {
603
- style: {
604
- color: "#333",
605
- lineHeight: 1.5,
606
- wordBreak: "break-all"
607
- },
608
- children: formatContent(message.content)
609
- }), hasToolCalls && /* @__PURE__ */ jsxs("div", {
610
- style: {
611
- marginTop: "4px",
612
- padding: "8px",
613
- background: "#f6ffed",
614
- border: "1px solid #b7eb8f",
615
- borderRadius: "6px"
616
- },
617
- children: [/* @__PURE__ */ jsxs("div", {
618
- style: {
619
- fontSize: "11px",
620
- fontWeight: 600,
621
- color: "#389e0d",
622
- marginBottom: "6px",
623
- display: "flex",
624
- alignItems: "center",
625
- gap: "4px"
626
- },
627
- children: [/* @__PURE__ */ jsx("span", { children: "🔧" }), /* @__PURE__ */ jsxs("span", { children: [
628
- "Tool Calls (",
629
- message.tool_calls.length,
630
- ")"
631
- ] })]
632
- }), /* @__PURE__ */ jsx("pre", {
633
- style: {
634
- ...preStyle,
635
- background: "#fff",
636
- border: "1px solid #d9f7be",
637
- maxHeight: "200px",
638
- fontSize: "11px"
639
- },
640
- children: formatJson(message.tool_calls)
641
- })]
642
- })]
502
+ }
503
+ //#endregion
504
+ //#region src/hooks/useToolExecution.ts
505
+ /**
506
+ * useToolExecution - 管理前端工具执行的 Hook
507
+ *
508
+ * 职责:
509
+ * 1. 管理工具执行状态
510
+ * 2. 自动执行前端工具(避免重复执行)
511
+ * 3. 通知外部执行状态变化
512
+ * 4. 支持批量提交工具结果
513
+ */
514
+ function useToolExecution({ tools, toolCalls, isLoading = false, onExecutionChange, onToolResultsBatch, completedToolResults }) {
515
+ const executedCallsRef = useRef(/* @__PURE__ */ new Set());
516
+ const executingCallsRef = useRef(/* @__PURE__ */ new Set());
517
+ const pendingNotifiedRef = useRef(/* @__PURE__ */ new Set());
518
+ const notifiedCompletedRef = useRef(/* @__PURE__ */ new Set());
519
+ const batchCallIdsRef = useRef(/* @__PURE__ */ new Set());
520
+ const batchResultsRef = useRef(/* @__PURE__ */ new Map());
521
+ const isProcessingRef = useRef(false);
522
+ const batchSubmittedRef = useRef(false);
523
+ useEffect(() => {
524
+ if (!completedToolResults || completedToolResults.size === 0) return;
525
+ completedToolResults.forEach((result, callId) => {
526
+ if (notifiedCompletedRef.current.has(callId)) return;
527
+ notifiedCompletedRef.current.add(callId);
528
+ executedCallsRef.current.add(callId);
529
+ const call = toolCalls.find((c) => c.id === callId);
530
+ if (call) onExecutionChange?.({
531
+ callId,
532
+ name: call.name,
533
+ args: call.args,
534
+ status: "success",
535
+ result
536
+ });
643
537
  });
538
+ }, [completedToolResults, toolCalls]);
539
+ /**
540
+ * 检查批次是否完成并提交结果
541
+ */
542
+ const checkAndSubmitBatch = useCallback(() => {
543
+ if (batchSubmittedRef.current) return;
544
+ if (Array.from(batchCallIdsRef.current).every((callId) => executedCallsRef.current.has(callId)) && batchCallIdsRef.current.size > 0) {
545
+ batchSubmittedRef.current = true;
546
+ const results = Array.from(batchResultsRef.current.values());
547
+ if (results.length > 0) onToolResultsBatch?.(results);
548
+ batchCallIdsRef.current.clear();
549
+ batchResultsRef.current.clear();
550
+ }
551
+ }, [onToolResultsBatch]);
552
+ /**
553
+ * 执行单个前端工具
554
+ */
555
+ const executeFrontendTool = useCallback(async (tool, call) => {
556
+ const callId = call.id;
557
+ executingCallsRef.current.add(callId);
558
+ onExecutionChange?.({
559
+ callId,
560
+ name: call.name,
561
+ args: call.args,
562
+ status: "running"
563
+ });
564
+ try {
565
+ const result = await tool.execute(call.args);
566
+ onExecutionChange?.({
567
+ callId,
568
+ name: call.name,
569
+ args: call.args,
570
+ status: "success",
571
+ result
572
+ });
573
+ batchResultsRef.current.set(callId, {
574
+ callId,
575
+ name: call.name,
576
+ result
577
+ });
578
+ } catch (error) {
579
+ const errorMessage = error instanceof Error ? error.message : String(error);
580
+ onExecutionChange?.({
581
+ callId,
582
+ name: call.name,
583
+ args: call.args,
584
+ status: "error",
585
+ error: errorMessage
586
+ });
587
+ batchResultsRef.current.set(callId, {
588
+ callId,
589
+ name: call.name,
590
+ result: { error: errorMessage }
591
+ });
592
+ } finally {
593
+ executingCallsRef.current.delete(callId);
594
+ executedCallsRef.current.add(callId);
595
+ checkAndSubmitBatch();
596
+ }
597
+ }, [onExecutionChange, checkAndSubmitBatch]);
598
+ /**
599
+ * 处理工具调用
600
+ */
601
+ const processToolCalls = useCallback(async () => {
602
+ if (!tools) return;
603
+ if (isProcessingRef.current) return;
604
+ const frontendCalls = [];
605
+ for (const call of toolCalls) {
606
+ const callId = call.id;
607
+ if (executedCallsRef.current.has(callId) || executingCallsRef.current.has(callId)) continue;
608
+ const tool = findTool(tools, call.name);
609
+ if (isLoading) {
610
+ if (!pendingNotifiedRef.current.has(callId)) {
611
+ pendingNotifiedRef.current.add(callId);
612
+ onExecutionChange?.({
613
+ callId,
614
+ name: call.name,
615
+ args: call.args,
616
+ status: "pending"
617
+ });
618
+ }
619
+ continue;
620
+ }
621
+ if (!tool) {
622
+ onExecutionChange?.({
623
+ callId,
624
+ name: call.name,
625
+ args: call.args,
626
+ status: "pending"
627
+ });
628
+ continue;
629
+ }
630
+ if (isFrontendTool(tool)) frontendCalls.push({
631
+ tool,
632
+ call
633
+ });
634
+ else onExecutionChange?.({
635
+ callId,
636
+ name: call.name,
637
+ args: call.args,
638
+ status: "pending"
639
+ });
640
+ }
641
+ if (frontendCalls.length > 0) {
642
+ batchSubmittedRef.current = false;
643
+ isProcessingRef.current = true;
644
+ batchCallIdsRef.current = new Set(frontendCalls.map(({ call }) => call.id));
645
+ batchResultsRef.current = /* @__PURE__ */ new Map();
646
+ try {
647
+ await Promise.all(frontendCalls.map(({ tool, call }) => executeFrontendTool(tool, call)));
648
+ } finally {
649
+ isProcessingRef.current = false;
650
+ }
651
+ }
652
+ }, [
653
+ tools,
654
+ toolCalls,
655
+ isLoading,
656
+ executeFrontendTool,
657
+ onExecutionChange
658
+ ]);
659
+ useEffect(() => {
660
+ processToolCalls();
661
+ }, [useMemo(() => {
662
+ return toolCalls.filter((call) => !executedCallsRef.current.has(call.id) && !executingCallsRef.current.has(call.id)).map((call) => call.id).sort().join(",");
663
+ }, [toolCalls]), isLoading]);
664
+ }
665
+ //#endregion
666
+ //#region src/utils/messageUtils.ts
667
+ /**
668
+ * 从 BaseMessage 中提取 tool_calls
669
+ */
670
+ function extractToolCalls(message) {
671
+ if ("tool_calls" in message && Array.isArray(message.tool_calls)) return message.tool_calls.map((tc) => ({
672
+ id: tc.id || crypto.randomUUID(),
673
+ name: tc.name || tc.function?.name || "",
674
+ args: typeof tc.function?.args === "string" ? JSON.parse(tc.function.args) : tc.args || {}
675
+ }));
676
+ }
677
+ /**
678
+ * 从 BaseMessage 中提取原始内容
679
+ * 保留原始结构:字符串或内容块数组
680
+ */
681
+ function extractContent(message) {
682
+ if (typeof message.content === "string") return message.content;
683
+ if (Array.isArray(message.content)) return message.content.map((c) => {
684
+ if (typeof c === "object" && c !== null) return c;
685
+ return {
686
+ type: "text",
687
+ text: String(c)
688
+ };
689
+ });
690
+ return "";
691
+ }
692
+ /**
693
+ * 从 MessageContent 中提取纯文本内容(用于渲染)
694
+ */
695
+ function extractTextFromContent(content) {
696
+ if (typeof content === "string") return content;
697
+ return content.map((block) => {
698
+ if (block.type === "text") return block.text;
699
+ return "";
700
+ }).join("");
701
+ }
702
+ /**
703
+ * 将 BaseMessage 转换为 ChatMessage
704
+ */
705
+ function toChatMessage(message, toolResults) {
706
+ const additionalKwargs = message.additional_kwargs || {};
707
+ if (message.type === "tool") return null;
708
+ if (message.type === "system") return null;
709
+ let msgType = "ai";
710
+ if (message.type === "human") msgType = "human";
711
+ const toolCalls = extractToolCalls(message);
712
+ let toolCallsWithResult = toolCalls;
713
+ if (toolCalls) toolCallsWithResult = toolCalls.map((tc) => ({
714
+ ...tc,
715
+ result: toolResults.get(tc.id)
716
+ }));
717
+ const reasoningContent = additionalKwargs.reasoning_content;
718
+ return {
719
+ id: message.id || crypto.randomUUID(),
720
+ type: msgType,
721
+ content: extractContent(message),
722
+ name: message.name,
723
+ additional_kwargs: additionalKwargs,
724
+ reasoningContent,
725
+ toolCalls: toolCallsWithResult
644
726
  };
645
- const buttonStyle = {
646
- position: "fixed",
647
- right: `${20 - buttonPos.x}px`,
648
- bottom: `${20 - buttonPos.y}px`,
649
- width: "48px",
650
- height: "48px",
651
- borderRadius: "50%",
652
- background: "#1677ff",
653
- color: "#fff",
654
- border: "none",
655
- cursor: isDraggingButton ? "grabbing" : "grab",
656
- boxShadow: "0 4px 12px rgba(0, 0, 0, 0.15)",
657
- display: "flex",
658
- alignItems: "center",
659
- justifyContent: "center",
660
- fontSize: "20px",
661
- zIndex: 9998,
662
- transition: isDraggingButton ? "none" : "box-shadow 0.2s",
663
- userSelect: "none"
664
- };
665
- const dialogStyle = {
666
- position: "fixed",
667
- left: "50%",
668
- top: "50%",
669
- transform: `translate(calc(-50% + ${dialogPos.x}px), calc(-50% + ${dialogPos.y}px))`,
670
- width: "1000px",
671
- maxWidth: "90vw",
672
- height: "650px",
673
- maxHeight: "85vh",
674
- background: "#fff",
675
- borderRadius: "12px",
676
- boxShadow: "0 8px 32px rgba(0, 0, 0, 0.2)",
677
- zIndex: 9999,
678
- display: "flex",
679
- flexDirection: "column",
680
- overflow: "hidden",
681
- cursor: isDraggingDialog ? "grabbing" : "default"
682
- };
683
- const headerStyle = {
684
- padding: "12px 16px",
685
- background: "#f5f5f5",
686
- borderBottom: "1px solid #e8e8e8",
687
- display: "flex",
688
- alignItems: "center",
689
- justifyContent: "space-between",
690
- cursor: isDraggingDialog ? "grabbing" : "grab"
727
+ }
728
+ /**
729
+ * 预处理消息列表:建立 tool_call_id -> result 映射,并过滤 ToolMessage
730
+ */
731
+ function processMessages(rawMessages) {
732
+ const toolResults = /* @__PURE__ */ new Map();
733
+ for (const message of rawMessages) if (message.type === "tool") {
734
+ const toolCallId = message.tool_call_id || message.additional_kwargs?.tool_call_id;
735
+ if (toolCallId) {
736
+ const textContent = extractTextFromContent(extractContent(message));
737
+ try {
738
+ toolResults.set(toolCallId, JSON.parse(textContent));
739
+ } catch {
740
+ toolResults.set(toolCallId, textContent);
741
+ }
742
+ }
743
+ }
744
+ const messages = [];
745
+ for (const message of rawMessages) {
746
+ const chatMessage = toChatMessage(message, toolResults);
747
+ if (chatMessage) messages.push(chatMessage);
748
+ }
749
+ return {
750
+ messages,
751
+ toolResults
691
752
  };
692
- const tabContainerStyle = {
693
- display: "flex",
694
- gap: "8px"
695
- };
696
- const getTabStyle = (isActive) => ({
697
- padding: "6px 16px",
698
- borderRadius: "6px",
699
- border: "none",
700
- background: isActive ? "#1677ff" : "transparent",
701
- color: isActive ? "#fff" : "#666",
702
- cursor: "pointer",
703
- fontSize: "14px",
704
- transition: "all 0.2s"
705
- });
706
- const closeButtonStyle = {
707
- width: "28px",
708
- height: "28px",
709
- borderRadius: "50%",
710
- border: "none",
711
- background: "#ff4d4f",
712
- color: "#fff",
713
- cursor: "pointer",
714
- display: "flex",
715
- alignItems: "center",
716
- justifyContent: "center",
717
- fontSize: "16px",
718
- transition: "background 0.2s"
719
- };
720
- const contentStyle = {
721
- flex: 1,
722
- overflow: "auto",
723
- padding: "16px",
724
- background: "#fafafa"
725
- };
726
- const preStyle = {
727
- margin: 0,
728
- padding: "12px",
729
- background: "#f8f9fa",
730
- color: "#333",
731
- borderRadius: "8px",
732
- fontSize: "12px",
733
- fontFamily: "\"Fira Code\", \"Monaco\", \"Consolas\", monospace",
734
- lineHeight: 1.5,
735
- overflow: "auto",
736
- whiteSpace: "pre-wrap",
737
- wordBreak: "break-word",
738
- border: "1px solid #e8e8e8"
739
- };
740
- const statsStyle = {
741
- padding: "8px 12px",
742
- background: "#e6f4ff",
743
- borderRadius: "6px",
744
- marginBottom: "12px",
745
- fontSize: "13px",
746
- color: "#0958d9"
747
- };
748
- const tableStyle = {
749
- width: "100%",
750
- borderCollapse: "collapse",
751
- fontSize: "13px",
752
- background: "#fff",
753
- borderRadius: "8px",
754
- overflow: "hidden",
755
- boxShadow: "0 1px 2px rgba(0, 0, 0, 0.05)"
756
- };
757
- const tableHeaderStyle = {
758
- background: "#f5f5f5",
759
- fontWeight: 600,
760
- color: "#333",
761
- textAlign: "left",
762
- padding: "10px 12px",
763
- borderBottom: "1px solid #e8e8e8",
764
- whiteSpace: "nowrap"
765
- };
766
- const tableCellStyle = {
767
- padding: "10px 12px",
768
- borderBottom: "1px solid #f0f0f0",
769
- verticalAlign: "top"
770
- };
771
- const expandButtonStyle = {
772
- width: "20px",
773
- height: "20px",
774
- border: "none",
775
- background: "transparent",
776
- cursor: "pointer",
777
- display: "flex",
778
- alignItems: "center",
779
- justifyContent: "center",
780
- fontSize: "12px",
781
- color: "#666",
782
- borderRadius: "4px",
783
- transition: "background 0.2s"
784
- };
785
- const expandedRowStyle = { background: "#fafafa" };
786
- const jsonContainerStyle = {
787
- padding: "12px",
788
- background: "#fff",
789
- borderRadius: "6px",
790
- margin: "8px 0",
791
- border: "1px solid #e8e8e8"
792
- };
793
- return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("button", {
794
- style: buttonStyle,
795
- onMouseDown: handleButtonMouseDown,
796
- onClick: () => !isDraggingButton && setIsOpen(true),
797
- title: "打开调试面板",
798
- children: "🐛"
799
- }), isOpen && /* @__PURE__ */ jsxs("div", {
800
- style: dialogStyle,
801
- onMouseDown: handleDialogMouseDown,
802
- children: [/* @__PURE__ */ jsxs("div", {
803
- className: "debug-panel-header",
804
- style: headerStyle,
805
- children: [/* @__PURE__ */ jsxs("div", {
806
- style: tabContainerStyle,
807
- children: [/* @__PURE__ */ jsxs("button", {
808
- style: getTabStyle(activeTab === "messages"),
809
- onClick: () => setActiveTab("messages"),
810
- children: [
811
- "Messages (",
812
- messages.length,
813
- ")"
814
- ]
815
- }), /* @__PURE__ */ jsx("button", {
816
- style: getTabStyle(activeTab === "state"),
817
- onClick: () => setActiveTab("state"),
818
- children: "State"
819
- })]
820
- }), /* @__PURE__ */ jsx("button", {
821
- style: closeButtonStyle,
822
- onClick: () => setIsOpen(false),
823
- title: "关闭",
824
- children: "×"
825
- })]
826
- }), /* @__PURE__ */ jsx("div", {
827
- style: contentStyle,
828
- children: activeTab === "messages" ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("div", {
829
- style: statsStyle,
830
- children: [
831
- "共 ",
832
- messages.length,
833
- " 条消息"
834
- ]
835
- }), /* @__PURE__ */ jsxs("table", {
836
- style: tableStyle,
837
- children: [/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
838
- /* @__PURE__ */ jsx("th", { style: {
839
- ...tableHeaderStyle,
840
- width: "30px"
841
- } }),
842
- /* @__PURE__ */ jsx("th", {
843
- style: {
844
- ...tableHeaderStyle,
845
- width: "60px"
846
- },
847
- children: "类型"
848
- }),
849
- /* @__PURE__ */ jsx("th", {
850
- style: tableHeaderStyle,
851
- children: "内容"
852
- })
853
- ] }) }), /* @__PURE__ */ jsx("tbody", { children: messages.map((msg, index) => {
854
- const message = msg;
855
- const isExpanded = expandedRows.has(index);
856
- return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("tr", {
857
- style: {
858
- cursor: "pointer",
859
- transition: "background 0.2s"
860
- },
861
- onClick: () => toggleRow(index),
862
- onMouseEnter: (e) => {
863
- e.currentTarget.style.background = "#f5f5f5";
864
- },
865
- onMouseLeave: (e) => {
866
- e.currentTarget.style.background = "transparent";
867
- },
868
- children: [
869
- /* @__PURE__ */ jsx("td", {
870
- style: tableCellStyle,
871
- children: /* @__PURE__ */ jsx("button", {
872
- style: expandButtonStyle,
873
- onClick: (e) => {
874
- e.stopPropagation();
875
- toggleRow(index);
876
- },
877
- children: isExpanded ? "▼" : "▶"
878
- })
879
- }),
880
- /* @__PURE__ */ jsx("td", {
881
- style: tableCellStyle,
882
- children: /* @__PURE__ */ jsx("span", {
883
- style: getTypeTagStyle(message.type),
884
- children: message.type || "unknown"
885
- })
886
- }),
887
- /* @__PURE__ */ jsx("td", {
888
- style: tableCellStyle,
889
- children: renderMessageContent(message)
890
- })
891
- ]
892
- }, index), isExpanded && /* @__PURE__ */ jsx("tr", {
893
- style: expandedRowStyle,
894
- children: /* @__PURE__ */ jsx("td", {
895
- colSpan: 3,
896
- style: {
897
- padding: 0,
898
- borderBottom: "1px solid #e8e8e8"
899
- },
900
- children: /* @__PURE__ */ jsx("div", {
901
- style: jsonContainerStyle,
902
- children: /* @__PURE__ */ jsx("pre", {
903
- style: {
904
- ...preStyle,
905
- margin: 0,
906
- maxHeight: "300px",
907
- overflow: "auto"
908
- },
909
- children: formatJson(message)
910
- })
911
- })
912
- })
913
- })] });
914
- }) })]
915
- })] }) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("div", {
916
- style: statsStyle,
917
- children: ["State 键数量: ", Object.keys(streamState).length]
918
- }), /* @__PURE__ */ jsx("pre", {
919
- style: preStyle,
920
- children: formatJson(streamState)
921
- })] })
922
- })]
923
- })] });
924
753
  }
925
754
  //#endregion
926
- //#region src/hooks/useInterrupt.tsx
927
- /**
928
- * InterruptManager - 管理 Interrupt 状态的 Hook
929
- *
930
- * 封装 Interrupt 状态管理和处理逻辑,避免在 AgentChat 组件中臃肿
931
- */
932
- function useInterrupt({ interrupt, config, onSubmit }) {
933
- const [activeInterrupt, setActiveInterrupt] = useState(null);
934
- const processedInterruptIdRef = useRef(null);
935
- useEffect(() => {
936
- if (interrupt) {
937
- const interruptEvent = interrupt;
938
- if (interruptEvent.id !== processedInterruptIdRef.current) {
939
- processedInterruptIdRef.current = interruptEvent.id;
940
- setActiveInterrupt(interruptEvent);
941
- }
942
- } else {
943
- processedInterruptIdRef.current = null;
944
- setActiveInterrupt(null);
945
- }
946
- }, [interrupt?.id]);
947
- const handleResolveInterrupt = useCallback((response) => {
948
- onSubmit(null, { command: { resume: response } });
949
- setActiveInterrupt(null);
950
- }, [onSubmit]);
951
- return {
952
- activeInterrupt,
953
- handleResolveInterrupt,
954
- renderInterrupt: useCallback(() => {
955
- if (!activeInterrupt || !config?.render) return null;
956
- return /* @__PURE__ */ jsx("div", {
957
- className: "agent-chat-interrupt",
958
- children: config.render({
959
- event: activeInterrupt,
960
- resolve: handleResolveInterrupt
961
- })
962
- });
963
- }, [
964
- activeInterrupt,
965
- config,
966
- handleResolveInterrupt
967
- ])
968
- };
755
+ //#region src/utils/injectStyles.ts
756
+ const styles = `
757
+ /* Agent Chat Container */
758
+ .agent-chat-container {
759
+ display: flex;
760
+ flex-direction: column;
761
+ height: 100%;
762
+ background-color: transparent;
763
+ overflow: hidden;
969
764
  }
970
- //#endregion
971
- //#region src/hooks/useToolExecution.ts
972
- /**
973
- * useToolExecution - 管理前端工具执行的 Hook
974
- *
975
- * 职责:
976
- * 1. 管理工具执行状态
977
- * 2. 自动执行前端工具(避免重复执行)
978
- * 3. 通知外部执行状态变化
979
- * 4. 支持批量提交工具结果
980
- */
981
- function useToolExecution({ tools, toolCalls, isLoading = false, onExecutionChange, onToolResultsBatch, completedToolResults }) {
982
- const executedCallsRef = useRef(/* @__PURE__ */ new Set());
983
- const executingCallsRef = useRef(/* @__PURE__ */ new Set());
984
- const pendingNotifiedRef = useRef(/* @__PURE__ */ new Set());
985
- const notifiedCompletedRef = useRef(/* @__PURE__ */ new Set());
986
- const batchCallIdsRef = useRef(/* @__PURE__ */ new Set());
987
- const batchResultsRef = useRef(/* @__PURE__ */ new Map());
988
- const isProcessingRef = useRef(false);
989
- const batchSubmittedRef = useRef(false);
990
- useEffect(() => {
991
- if (!completedToolResults || completedToolResults.size === 0) return;
992
- completedToolResults.forEach((result, callId) => {
993
- if (notifiedCompletedRef.current.has(callId)) return;
994
- notifiedCompletedRef.current.add(callId);
995
- executedCallsRef.current.add(callId);
996
- const call = toolCalls.find((c) => c.id === callId);
997
- if (call) onExecutionChange?.({
998
- callId,
999
- name: call.name,
1000
- args: call.args,
1001
- status: "success",
1002
- result
1003
- });
1004
- });
1005
- }, [completedToolResults, toolCalls]);
1006
- /**
1007
- * 检查批次是否完成并提交结果
1008
- */
1009
- const checkAndSubmitBatch = useCallback(() => {
1010
- if (batchSubmittedRef.current) return;
1011
- if (Array.from(batchCallIdsRef.current).every((callId) => executedCallsRef.current.has(callId)) && batchCallIdsRef.current.size > 0) {
1012
- batchSubmittedRef.current = true;
1013
- const results = Array.from(batchResultsRef.current.values());
1014
- if (results.length > 0) onToolResultsBatch?.(results);
1015
- batchCallIdsRef.current.clear();
1016
- batchResultsRef.current.clear();
1017
- }
1018
- }, [onToolResultsBatch]);
1019
- /**
1020
- * 执行单个前端工具
1021
- */
1022
- const executeFrontendTool = useCallback(async (tool, call) => {
1023
- const callId = call.id;
1024
- executingCallsRef.current.add(callId);
1025
- onExecutionChange?.({
1026
- callId,
1027
- name: call.name,
1028
- args: call.args,
1029
- status: "running"
1030
- });
1031
- try {
1032
- const result = await tool.execute(call.args);
1033
- onExecutionChange?.({
1034
- callId,
1035
- name: call.name,
1036
- args: call.args,
1037
- status: "success",
1038
- result
1039
- });
1040
- batchResultsRef.current.set(callId, {
1041
- callId,
1042
- name: call.name,
1043
- result
1044
- });
1045
- } catch (error) {
1046
- const errorMessage = error instanceof Error ? error.message : String(error);
1047
- onExecutionChange?.({
1048
- callId,
1049
- name: call.name,
1050
- args: call.args,
1051
- status: "error",
1052
- error: errorMessage
1053
- });
1054
- batchResultsRef.current.set(callId, {
1055
- callId,
1056
- name: call.name,
1057
- result: { error: errorMessage }
1058
- });
1059
- } finally {
1060
- executingCallsRef.current.delete(callId);
1061
- executedCallsRef.current.add(callId);
1062
- checkAndSubmitBatch();
1063
- }
1064
- }, [onExecutionChange, checkAndSubmitBatch]);
1065
- /**
1066
- * 处理工具调用
1067
- */
1068
- const processToolCalls = useCallback(async () => {
1069
- if (!tools) return;
1070
- if (isProcessingRef.current) return;
1071
- const frontendCalls = [];
1072
- for (const call of toolCalls) {
1073
- const callId = call.id;
1074
- if (executedCallsRef.current.has(callId) || executingCallsRef.current.has(callId)) continue;
1075
- const tool = findTool(tools, call.name);
1076
- if (isLoading) {
1077
- if (!pendingNotifiedRef.current.has(callId)) {
1078
- pendingNotifiedRef.current.add(callId);
1079
- onExecutionChange?.({
1080
- callId,
1081
- name: call.name,
1082
- args: call.args,
1083
- status: "pending"
1084
- });
1085
- }
1086
- continue;
1087
- }
1088
- if (!tool) {
1089
- onExecutionChange?.({
1090
- callId,
1091
- name: call.name,
1092
- args: call.args,
1093
- status: "pending"
1094
- });
1095
- continue;
1096
- }
1097
- if (isFrontendTool(tool)) frontendCalls.push({
1098
- tool,
1099
- call
1100
- });
1101
- else onExecutionChange?.({
1102
- callId,
1103
- name: call.name,
1104
- args: call.args,
1105
- status: "pending"
1106
- });
1107
- }
1108
- if (frontendCalls.length > 0) {
1109
- batchSubmittedRef.current = false;
1110
- isProcessingRef.current = true;
1111
- batchCallIdsRef.current = new Set(frontendCalls.map(({ call }) => call.id));
1112
- batchResultsRef.current = /* @__PURE__ */ new Map();
1113
- try {
1114
- await Promise.all(frontendCalls.map(({ tool, call }) => executeFrontendTool(tool, call)));
1115
- } finally {
1116
- isProcessingRef.current = false;
1117
- }
1118
- }
1119
- }, [
1120
- tools,
1121
- toolCalls,
1122
- isLoading,
1123
- executeFrontendTool,
1124
- onExecutionChange
1125
- ]);
1126
- useEffect(() => {
1127
- processToolCalls();
1128
- }, [useMemo(() => {
1129
- return toolCalls.filter((call) => !executedCallsRef.current.has(call.id) && !executingCallsRef.current.has(call.id)).map((call) => call.id).sort().join(",");
1130
- }, [toolCalls]), isLoading]);
765
+
766
+ /* Message List */
767
+ .agent-message-list {
768
+ flex: 1;
769
+ overflow-y: auto;
770
+ display: flex;
771
+ flex-direction: column;
1131
772
  }
1132
- //#endregion
1133
- //#region src/utils/messageUtils.ts
1134
- /**
1135
- * 从 BaseMessage 中提取 tool_calls
1136
- */
1137
- function extractToolCalls(message) {
1138
- if ("tool_calls" in message && Array.isArray(message.tool_calls)) return message.tool_calls.map((tc) => ({
1139
- id: tc.id || crypto.randomUUID(),
1140
- name: tc.name || tc.function?.name || "",
1141
- args: typeof tc.function?.args === "string" ? JSON.parse(tc.function.args) : tc.args || {}
1142
- }));
773
+
774
+ .agent-message-list .empty {
775
+ display: flex;
776
+ align-items: center;
777
+ justify-content: center;
1143
778
  }
1144
- /**
1145
- * 从 BaseMessage 中提取原始内容
1146
- * 保留原始结构:字符串或内容块数组
1147
- */
1148
- function extractContent(message) {
1149
- if (typeof message.content === "string") return message.content;
1150
- if (Array.isArray(message.content)) return message.content.map((c) => {
1151
- if (typeof c === "object" && c !== null) return c;
1152
- return {
1153
- type: "text",
1154
- text: String(c)
1155
- };
1156
- });
1157
- return "";
779
+
780
+
781
+
782
+ .agent-message-list .ant-think-status-text {
783
+ font-size: 13px;
1158
784
  }
1159
- /**
1160
- * MessageContent 中提取纯文本内容(用于渲染)
1161
- */
1162
- function extractTextFromContent(content) {
1163
- if (typeof content === "string") return content;
1164
- return content.map((block) => {
1165
- if (block.type === "text") return block.text;
1166
- return "";
1167
- }).join("");
1168
- }
1169
- /**
1170
- * 将 BaseMessage 转换为 ChatMessage
1171
- */
1172
- function toChatMessage(message, toolResults) {
1173
- const additionalKwargs = message.additional_kwargs || {};
1174
- if (message.type === "tool") return null;
1175
- if (message.type === "system") return null;
1176
- let msgType = "ai";
1177
- if (message.type === "human") msgType = "human";
1178
- const toolCalls = extractToolCalls(message);
1179
- let toolCallsWithResult = toolCalls;
1180
- if (toolCalls) toolCallsWithResult = toolCalls.map((tc) => ({
1181
- ...tc,
1182
- result: toolResults.get(tc.id)
1183
- }));
1184
- const reasoningContent = additionalKwargs.reasoning_content;
1185
- return {
1186
- id: message.id || crypto.randomUUID(),
1187
- type: msgType,
1188
- content: extractContent(message),
1189
- name: message.name,
1190
- additional_kwargs: additionalKwargs,
1191
- reasoningContent,
1192
- toolCalls: toolCallsWithResult
1193
- };
1194
- }
1195
- /**
1196
- * 预处理消息列表:建立 tool_call_id -> result 映射,并过滤 ToolMessage
1197
- */
1198
- function processMessages(rawMessages) {
1199
- const toolResults = /* @__PURE__ */ new Map();
1200
- for (const message of rawMessages) if (message.type === "tool") {
1201
- const toolCallId = message.tool_call_id || message.additional_kwargs?.tool_call_id;
1202
- if (toolCallId) {
1203
- const textContent = extractTextFromContent(extractContent(message));
1204
- try {
1205
- toolResults.set(toolCallId, JSON.parse(textContent));
1206
- } catch {
1207
- toolResults.set(toolCallId, textContent);
1208
- }
1209
- }
1210
- }
1211
- const messages = [];
1212
- for (const message of rawMessages) {
1213
- const chatMessage = toChatMessage(message, toolResults);
1214
- if (chatMessage) messages.push(chatMessage);
1215
- }
1216
- return {
1217
- messages,
1218
- toolResults
1219
- };
1220
- }
1221
- //#endregion
1222
- //#region src/utils/injectStyles.ts
1223
- const styles = `
1224
- /* Agent Chat Container */
1225
- .agent-chat-container {
1226
- display: flex;
1227
- flex-direction: column;
1228
- height: 100%;
1229
- background-color: transparent;
1230
- overflow: hidden;
1231
- }
1232
-
1233
- /* Message List */
1234
- .agent-message-list {
1235
- flex: 1;
1236
- overflow-y: auto;
1237
- display: flex;
1238
- flex-direction: column;
1239
- }
1240
-
1241
- .agent-message-list .empty {
1242
- display: flex;
1243
- align-items: center;
1244
- justify-content: center;
1245
- }
1246
-
1247
-
1248
-
1249
- .agent-message-list .ant-think-status-text {
1250
- font-size: 13px;
1251
- }
1252
-
1253
- .agent-message-list table {
1254
- border-collapse: collapse;
1255
- width: 100%;
785
+
786
+ .agent-message-list table {
787
+ border-collapse: collapse;
788
+ width: 100%;
1256
789
  }
1257
790
 
1258
791
  .agent-message-list th,
@@ -1349,7 +882,7 @@ function injectStyles() {
1349
882
  //#endregion
1350
883
  //#region src/components/AgentChat.tsx
1351
884
  injectStyles();
1352
- const AgentChat = forwardRef(({ stream, className = "", tools, contexts, messageConfig, inputConfig, interruptConfig, agentState, showDebug, welcome }, ref) => {
885
+ const AgentChat = forwardRef(({ stream, className = "", tools, contexts, messageConfig, inputConfig, interruptConfig, agentState, welcome }, ref) => {
1353
886
  const { onPreSend, ...chatInputConfig } = inputConfig || {};
1354
887
  const chatInputRef = useRef(null);
1355
888
  useImperativeHandle(ref, () => ({
@@ -1358,8 +891,12 @@ const AgentChat = forwardRef(({ stream, className = "", tools, contexts, message
1358
891
  clearInput: () => chatInputRef.current?.clear?.(),
1359
892
  focusInput: () => chatInputRef.current?.focus?.()
1360
893
  }));
894
+ const interruptEvent = stream.interrupt;
1361
895
  const { renderInterrupt: interruptRender } = useInterrupt({
1362
- interrupt: stream.interrupt,
896
+ interrupt: interruptEvent ? {
897
+ value: interruptEvent.value,
898
+ id: interruptEvent.id
899
+ } : null,
1363
900
  config: interruptConfig,
1364
901
  onSubmit: stream.submit
1365
902
  });
@@ -1451,37 +988,29 @@ const AgentChat = forwardRef(({ stream, className = "", tools, contexts, message
1451
988
  const shouldRenderWelcome = messages.length === 0 && !isLoading && welcome;
1452
989
  return /* @__PURE__ */ jsxs("div", {
1453
990
  className: `agent-chat-container ${className}`,
1454
- children: [
1455
- shouldRenderWelcome ? /* @__PURE__ */ jsx("div", {
1456
- className: "agent-message-list agent-chat-welcome",
1457
- children: welcome
1458
- }) : /* @__PURE__ */ jsx(MessageList, {
1459
- messages,
1460
- isLoading: stream.isLoading,
1461
- tools,
1462
- toolExecutions,
1463
- components: messageConfig?.components,
1464
- securityConfig: {
1465
- allowedTags: messageConfig?.allowedTags,
1466
- literalTagContent: messageConfig?.literalTagContent
1467
- },
1468
- loadingColor: messageConfig?.loadingColor,
1469
- interruptRender
1470
- }),
1471
- /* @__PURE__ */ jsx(ChatInput, {
1472
- ref: chatInputRef,
1473
- onSend: handleSend,
1474
- onStop: handleStop,
1475
- isLoading,
1476
- className: "agent-chat-input",
1477
- ...chatInputConfig
1478
- }),
1479
- /* @__PURE__ */ jsx(DebugPanel, {
1480
- messages: stream.messages,
1481
- streamState: stream.values || {},
1482
- visible: showDebug
1483
- })
1484
- ]
991
+ children: [shouldRenderWelcome ? /* @__PURE__ */ jsx("div", {
992
+ className: "agent-message-list agent-chat-welcome",
993
+ children: welcome
994
+ }) : /* @__PURE__ */ jsx(MessageList, {
995
+ messages,
996
+ isLoading: stream.isLoading,
997
+ tools,
998
+ toolExecutions,
999
+ components: messageConfig?.components,
1000
+ securityConfig: {
1001
+ allowedTags: messageConfig?.allowedTags,
1002
+ literalTagContent: messageConfig?.literalTagContent
1003
+ },
1004
+ loadingColor: messageConfig?.loadingColor,
1005
+ interruptRender
1006
+ }), /* @__PURE__ */ jsx(ChatInput, {
1007
+ ref: chatInputRef,
1008
+ onSend: handleSend,
1009
+ onStop: handleStop,
1010
+ isLoading,
1011
+ className: "agent-chat-input",
1012
+ ...chatInputConfig
1013
+ })]
1485
1014
  });
1486
1015
  });
1487
1016
  AgentChat.displayName = "AgentChat";
@@ -1502,6 +1031,474 @@ const ToolCard = ({ children, style, ...rest }) => {
1502
1031
  }), children] });
1503
1032
  };
1504
1033
  //#endregion
1034
+ //#region src/components/DebugPanel.tsx
1035
+ const isDevelopment = () => {
1036
+ if (typeof process !== "undefined" && process.env) return process.env.NODE_ENV === "development";
1037
+ const viteEnv = import.meta.env;
1038
+ if (typeof import.meta !== "undefined" && viteEnv) return viteEnv.DEV === true || viteEnv.MODE === "development";
1039
+ return false;
1040
+ };
1041
+ function DebugPanel({ streamState, visible = true }) {
1042
+ if (!isDevelopment() || !visible) return null;
1043
+ const streamMessages = streamState.messages || [];
1044
+ const [isOpen, setIsOpen] = useState(false);
1045
+ const [activeTab, setActiveTab] = useState("messages");
1046
+ const [expandedRows, setExpandedRows] = useState(/* @__PURE__ */ new Set());
1047
+ const [buttonPos, setButtonPos] = useState({
1048
+ x: 0,
1049
+ y: 0
1050
+ });
1051
+ const [isDraggingButton, setIsDraggingButton] = useState(false);
1052
+ const buttonDragRef = useRef(null);
1053
+ const [dialogPos, setDialogPos] = useState({
1054
+ x: 0,
1055
+ y: 0
1056
+ });
1057
+ const [isDraggingDialog, setIsDraggingDialog] = useState(false);
1058
+ const dialogDragRef = useRef(null);
1059
+ const handleButtonMouseDown = useCallback((e) => {
1060
+ e.preventDefault();
1061
+ buttonDragRef.current = {
1062
+ startX: e.clientX,
1063
+ startY: e.clientY,
1064
+ initialX: buttonPos.x,
1065
+ initialY: buttonPos.y
1066
+ };
1067
+ setIsDraggingButton(true);
1068
+ }, [buttonPos]);
1069
+ const handleDialogMouseDown = useCallback((e) => {
1070
+ if (!e.target.closest(".debug-panel-header")) return;
1071
+ e.preventDefault();
1072
+ dialogDragRef.current = {
1073
+ startX: e.clientX,
1074
+ startY: e.clientY,
1075
+ initialX: dialogPos.x,
1076
+ initialY: dialogPos.y
1077
+ };
1078
+ setIsDraggingDialog(true);
1079
+ }, [dialogPos]);
1080
+ useEffect(() => {
1081
+ const handleMouseMove = (e) => {
1082
+ if (isDraggingButton && buttonDragRef.current) {
1083
+ const dx = e.clientX - buttonDragRef.current.startX;
1084
+ const dy = e.clientY - buttonDragRef.current.startY;
1085
+ setButtonPos({
1086
+ x: buttonDragRef.current.initialX + dx,
1087
+ y: buttonDragRef.current.initialY + dy
1088
+ });
1089
+ }
1090
+ if (isDraggingDialog && dialogDragRef.current) {
1091
+ const dx = e.clientX - dialogDragRef.current.startX;
1092
+ const dy = e.clientY - dialogDragRef.current.startY;
1093
+ setDialogPos({
1094
+ x: dialogDragRef.current.initialX + dx,
1095
+ y: dialogDragRef.current.initialY + dy
1096
+ });
1097
+ }
1098
+ };
1099
+ const handleMouseUp = () => {
1100
+ setIsDraggingButton(false);
1101
+ setIsDraggingDialog(false);
1102
+ buttonDragRef.current = null;
1103
+ dialogDragRef.current = null;
1104
+ };
1105
+ if (isDraggingButton || isDraggingDialog) {
1106
+ document.addEventListener("mousemove", handleMouseMove);
1107
+ document.addEventListener("mouseup", handleMouseUp);
1108
+ }
1109
+ return () => {
1110
+ document.removeEventListener("mousemove", handleMouseMove);
1111
+ document.removeEventListener("mouseup", handleMouseUp);
1112
+ };
1113
+ }, [isDraggingButton, isDraggingDialog]);
1114
+ const formatJson = (data) => {
1115
+ try {
1116
+ return JSON.stringify(data, null, 2);
1117
+ } catch {
1118
+ return String(data);
1119
+ }
1120
+ };
1121
+ const toggleRow = (index) => {
1122
+ setExpandedRows((prev) => {
1123
+ const next = new Set(prev);
1124
+ if (next.has(index)) next.delete(index);
1125
+ else next.add(index);
1126
+ return next;
1127
+ });
1128
+ };
1129
+ const getTypeTagStyle = (type) => {
1130
+ const color = {
1131
+ human: {
1132
+ bg: "#e6f4ff",
1133
+ color: "#0958d9"
1134
+ },
1135
+ ai: {
1136
+ bg: "#f6ffed",
1137
+ color: "#389e0d"
1138
+ },
1139
+ tool: {
1140
+ bg: "#fff7e6",
1141
+ color: "#d46b08"
1142
+ },
1143
+ system: {
1144
+ bg: "#f9f0ff",
1145
+ color: "#722ed1"
1146
+ },
1147
+ function: {
1148
+ bg: "#fff2f0",
1149
+ color: "#cf1322"
1150
+ }
1151
+ }[type || ""] || {
1152
+ bg: "#f5f5f5",
1153
+ color: "#666"
1154
+ };
1155
+ return {
1156
+ display: "inline-block",
1157
+ padding: "2px 8px",
1158
+ borderRadius: "4px",
1159
+ fontSize: "12px",
1160
+ fontWeight: 500,
1161
+ background: color.bg,
1162
+ color: color.color
1163
+ };
1164
+ };
1165
+ const formatContent = (content) => {
1166
+ if (!content) return "";
1167
+ if (Array.isArray(content)) return content.map((item) => typeof item === "string" ? item : JSON.stringify(item)).join("");
1168
+ return String(content);
1169
+ };
1170
+ const renderMessageContent = (message) => {
1171
+ const hasToolCalls = message.tool_calls && Array.isArray(message.tool_calls) && message.tool_calls.length > 0;
1172
+ return /* @__PURE__ */ jsxs("div", {
1173
+ style: {
1174
+ display: "flex",
1175
+ flexDirection: "column",
1176
+ gap: "8px"
1177
+ },
1178
+ children: [message.content && /* @__PURE__ */ jsx("div", {
1179
+ style: {
1180
+ color: "#333",
1181
+ lineHeight: 1.5,
1182
+ wordBreak: "break-all"
1183
+ },
1184
+ children: formatContent(message.content)
1185
+ }), hasToolCalls && /* @__PURE__ */ jsxs("div", {
1186
+ style: {
1187
+ marginTop: "4px",
1188
+ padding: "8px",
1189
+ background: "#f6ffed",
1190
+ border: "1px solid #b7eb8f",
1191
+ borderRadius: "6px"
1192
+ },
1193
+ children: [/* @__PURE__ */ jsxs("div", {
1194
+ style: {
1195
+ fontSize: "11px",
1196
+ fontWeight: 600,
1197
+ color: "#389e0d",
1198
+ marginBottom: "6px",
1199
+ display: "flex",
1200
+ alignItems: "center",
1201
+ gap: "4px"
1202
+ },
1203
+ children: [/* @__PURE__ */ jsx("span", { children: "🔧" }), /* @__PURE__ */ jsxs("span", { children: [
1204
+ "Tool Calls (",
1205
+ message.tool_calls.length,
1206
+ ")"
1207
+ ] })]
1208
+ }), /* @__PURE__ */ jsx("pre", {
1209
+ style: {
1210
+ ...preStyle,
1211
+ background: "#fff",
1212
+ border: "1px solid #d9f7be",
1213
+ maxHeight: "200px",
1214
+ fontSize: "11px"
1215
+ },
1216
+ children: formatJson(message.tool_calls)
1217
+ })]
1218
+ })]
1219
+ });
1220
+ };
1221
+ const buttonStyle = {
1222
+ position: "fixed",
1223
+ right: `${20 - buttonPos.x}px`,
1224
+ bottom: `${20 - buttonPos.y}px`,
1225
+ width: "48px",
1226
+ height: "48px",
1227
+ borderRadius: "50%",
1228
+ background: "#1677ff",
1229
+ color: "#fff",
1230
+ border: "none",
1231
+ cursor: isDraggingButton ? "grabbing" : "grab",
1232
+ boxShadow: "0 4px 12px rgba(0, 0, 0, 0.15)",
1233
+ display: "flex",
1234
+ alignItems: "center",
1235
+ justifyContent: "center",
1236
+ fontSize: "20px",
1237
+ zIndex: 9998,
1238
+ transition: isDraggingButton ? "none" : "box-shadow 0.2s",
1239
+ userSelect: "none"
1240
+ };
1241
+ const dialogStyle = {
1242
+ position: "fixed",
1243
+ left: "50%",
1244
+ top: "50%",
1245
+ transform: `translate(calc(-50% + ${dialogPos.x}px), calc(-50% + ${dialogPos.y}px))`,
1246
+ width: "1000px",
1247
+ maxWidth: "90vw",
1248
+ height: "650px",
1249
+ maxHeight: "85vh",
1250
+ background: "#fff",
1251
+ borderRadius: "12px",
1252
+ boxShadow: "0 8px 32px rgba(0, 0, 0, 0.2)",
1253
+ zIndex: 9999,
1254
+ display: "flex",
1255
+ flexDirection: "column",
1256
+ overflow: "hidden",
1257
+ cursor: isDraggingDialog ? "grabbing" : "default"
1258
+ };
1259
+ const headerStyle = {
1260
+ padding: "12px 16px",
1261
+ background: "#f5f5f5",
1262
+ borderBottom: "1px solid #e8e8e8",
1263
+ display: "flex",
1264
+ alignItems: "center",
1265
+ justifyContent: "space-between",
1266
+ cursor: isDraggingDialog ? "grabbing" : "grab"
1267
+ };
1268
+ const tabContainerStyle = {
1269
+ display: "flex",
1270
+ gap: "8px"
1271
+ };
1272
+ const getTabStyle = (isActive) => ({
1273
+ padding: "6px 16px",
1274
+ borderRadius: "6px",
1275
+ border: "none",
1276
+ background: isActive ? "#1677ff" : "transparent",
1277
+ color: isActive ? "#fff" : "#666",
1278
+ cursor: "pointer",
1279
+ fontSize: "14px",
1280
+ transition: "all 0.2s"
1281
+ });
1282
+ const closeButtonStyle = {
1283
+ width: "28px",
1284
+ height: "28px",
1285
+ borderRadius: "50%",
1286
+ border: "none",
1287
+ background: "#ff4d4f",
1288
+ color: "#fff",
1289
+ cursor: "pointer",
1290
+ display: "flex",
1291
+ alignItems: "center",
1292
+ justifyContent: "center",
1293
+ fontSize: "16px",
1294
+ transition: "background 0.2s"
1295
+ };
1296
+ const contentStyle = {
1297
+ flex: 1,
1298
+ overflow: "auto",
1299
+ padding: "16px",
1300
+ background: "#fafafa"
1301
+ };
1302
+ const preStyle = {
1303
+ margin: 0,
1304
+ padding: "12px",
1305
+ background: "#f8f9fa",
1306
+ color: "#333",
1307
+ borderRadius: "8px",
1308
+ fontSize: "12px",
1309
+ fontFamily: "\"Fira Code\", \"Monaco\", \"Consolas\", monospace",
1310
+ lineHeight: 1.5,
1311
+ overflow: "auto",
1312
+ whiteSpace: "pre-wrap",
1313
+ wordBreak: "break-word",
1314
+ border: "1px solid #e8e8e8"
1315
+ };
1316
+ const statsStyle = {
1317
+ padding: "8px 12px",
1318
+ background: "#e6f4ff",
1319
+ borderRadius: "6px",
1320
+ marginBottom: "12px",
1321
+ fontSize: "13px",
1322
+ color: "#0958d9"
1323
+ };
1324
+ const tableStyle = {
1325
+ width: "100%",
1326
+ borderCollapse: "collapse",
1327
+ fontSize: "13px",
1328
+ background: "#fff",
1329
+ borderRadius: "8px",
1330
+ overflow: "hidden",
1331
+ boxShadow: "0 1px 2px rgba(0, 0, 0, 0.05)"
1332
+ };
1333
+ const tableHeaderStyle = {
1334
+ background: "#f5f5f5",
1335
+ fontWeight: 600,
1336
+ color: "#333",
1337
+ textAlign: "left",
1338
+ padding: "10px 12px",
1339
+ borderBottom: "1px solid #e8e8e8",
1340
+ whiteSpace: "nowrap"
1341
+ };
1342
+ const tableCellStyle = {
1343
+ padding: "10px 12px",
1344
+ borderBottom: "1px solid #f0f0f0",
1345
+ verticalAlign: "top"
1346
+ };
1347
+ const expandButtonStyle = {
1348
+ width: "20px",
1349
+ height: "20px",
1350
+ border: "none",
1351
+ background: "transparent",
1352
+ cursor: "pointer",
1353
+ display: "flex",
1354
+ alignItems: "center",
1355
+ justifyContent: "center",
1356
+ fontSize: "12px",
1357
+ color: "#666",
1358
+ borderRadius: "4px",
1359
+ transition: "background 0.2s"
1360
+ };
1361
+ const expandedRowStyle = { background: "#fafafa" };
1362
+ const jsonContainerStyle = {
1363
+ padding: "12px",
1364
+ background: "#fff",
1365
+ borderRadius: "6px",
1366
+ margin: "8px 0",
1367
+ border: "1px solid #e8e8e8"
1368
+ };
1369
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("button", {
1370
+ style: buttonStyle,
1371
+ onMouseDown: handleButtonMouseDown,
1372
+ onClick: () => !isDraggingButton && setIsOpen(true),
1373
+ title: "打开调试面板",
1374
+ children: "🐛"
1375
+ }), isOpen && /* @__PURE__ */ jsxs("div", {
1376
+ style: dialogStyle,
1377
+ onMouseDown: handleDialogMouseDown,
1378
+ children: [/* @__PURE__ */ jsxs("div", {
1379
+ className: "debug-panel-header",
1380
+ style: headerStyle,
1381
+ children: [/* @__PURE__ */ jsxs("div", {
1382
+ style: tabContainerStyle,
1383
+ children: [/* @__PURE__ */ jsxs("button", {
1384
+ style: getTabStyle(activeTab === "messages"),
1385
+ onClick: () => setActiveTab("messages"),
1386
+ children: [
1387
+ "Messages (",
1388
+ streamMessages.length,
1389
+ ")"
1390
+ ]
1391
+ }), /* @__PURE__ */ jsx("button", {
1392
+ style: getTabStyle(activeTab === "state"),
1393
+ onClick: () => setActiveTab("state"),
1394
+ children: "State"
1395
+ })]
1396
+ }), /* @__PURE__ */ jsx("button", {
1397
+ style: closeButtonStyle,
1398
+ onClick: () => setIsOpen(false),
1399
+ title: "关闭",
1400
+ children: "×"
1401
+ })]
1402
+ }), /* @__PURE__ */ jsx("div", {
1403
+ style: contentStyle,
1404
+ children: activeTab === "messages" ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("div", {
1405
+ style: statsStyle,
1406
+ children: [
1407
+ "共 ",
1408
+ streamMessages.length,
1409
+ " 条消息"
1410
+ ]
1411
+ }), /* @__PURE__ */ jsxs("table", {
1412
+ style: tableStyle,
1413
+ children: [/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
1414
+ /* @__PURE__ */ jsx("th", { style: {
1415
+ ...tableHeaderStyle,
1416
+ width: "30px"
1417
+ } }),
1418
+ /* @__PURE__ */ jsx("th", {
1419
+ style: {
1420
+ ...tableHeaderStyle,
1421
+ width: "60px"
1422
+ },
1423
+ children: "类型"
1424
+ }),
1425
+ /* @__PURE__ */ jsx("th", {
1426
+ style: tableHeaderStyle,
1427
+ children: "内容"
1428
+ })
1429
+ ] }) }), /* @__PURE__ */ jsx("tbody", { children: streamMessages.map((msg, index) => {
1430
+ const message = msg;
1431
+ const isExpanded = expandedRows.has(index);
1432
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("tr", {
1433
+ style: {
1434
+ cursor: "pointer",
1435
+ transition: "background 0.2s"
1436
+ },
1437
+ onClick: () => toggleRow(index),
1438
+ onMouseEnter: (e) => {
1439
+ e.currentTarget.style.background = "#f5f5f5";
1440
+ },
1441
+ onMouseLeave: (e) => {
1442
+ e.currentTarget.style.background = "transparent";
1443
+ },
1444
+ children: [
1445
+ /* @__PURE__ */ jsx("td", {
1446
+ style: tableCellStyle,
1447
+ children: /* @__PURE__ */ jsx("button", {
1448
+ style: expandButtonStyle,
1449
+ onClick: (e) => {
1450
+ e.stopPropagation();
1451
+ toggleRow(index);
1452
+ },
1453
+ children: isExpanded ? "▼" : "▶"
1454
+ })
1455
+ }),
1456
+ /* @__PURE__ */ jsx("td", {
1457
+ style: tableCellStyle,
1458
+ children: /* @__PURE__ */ jsx("span", {
1459
+ style: getTypeTagStyle(message.type),
1460
+ children: message.type || "unknown"
1461
+ })
1462
+ }),
1463
+ /* @__PURE__ */ jsx("td", {
1464
+ style: tableCellStyle,
1465
+ children: renderMessageContent(message)
1466
+ })
1467
+ ]
1468
+ }, index), isExpanded && /* @__PURE__ */ jsx("tr", {
1469
+ style: expandedRowStyle,
1470
+ children: /* @__PURE__ */ jsx("td", {
1471
+ colSpan: 3,
1472
+ style: {
1473
+ padding: 0,
1474
+ borderBottom: "1px solid #e8e8e8"
1475
+ },
1476
+ children: /* @__PURE__ */ jsx("div", {
1477
+ style: jsonContainerStyle,
1478
+ children: /* @__PURE__ */ jsx("pre", {
1479
+ style: {
1480
+ ...preStyle,
1481
+ margin: 0,
1482
+ maxHeight: "300px",
1483
+ overflow: "auto"
1484
+ },
1485
+ children: formatJson(message)
1486
+ })
1487
+ })
1488
+ })
1489
+ })] });
1490
+ }) })]
1491
+ })] }) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("div", {
1492
+ style: statsStyle,
1493
+ children: ["State 键数量: ", Object.keys(streamState).length]
1494
+ }), /* @__PURE__ */ jsx("pre", {
1495
+ style: preStyle,
1496
+ children: formatJson(streamState)
1497
+ })] })
1498
+ })]
1499
+ })] });
1500
+ }
1501
+ //#endregion
1505
1502
  //#region src/hooks/useAgentStream.ts
1506
1503
  /**
1507
1504
  * 包装 useStream hook,为 AgentChat 提供统一的 stream 实例创建方式
@@ -1520,4 +1517,4 @@ function useAgentStream(options) {
1520
1517
  return useStream(options);
1521
1518
  }
1522
1519
  //#endregion
1523
- export { AgentChat, MessageContentRenderer, ToolCard, useAgentStream };
1520
+ export { AgentChat, DebugPanel, MessageContentRenderer, ToolCard, useAgentStream };