@langgraph-js/ui 5.2.0 → 5.4.0

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.
Files changed (69) hide show
  1. package/dist/assets/{arc-k3dgSSjU.js → arc-DQUrjEml.js} +1 -1
  2. package/dist/assets/{architectureDiagram-VXUJARFQ-3N4Q3aZn.js → architectureDiagram-VXUJARFQ-HbPzZrlH.js} +1 -1
  3. package/dist/assets/{blockDiagram-VD42YOAC-gYWuEePF.js → blockDiagram-VD42YOAC-D_Idys33.js} +1 -1
  4. package/dist/assets/{c4Diagram-YG6GDRKO-d1wDBP0m.js → c4Diagram-YG6GDRKO-Cz8lYf2-.js} +1 -1
  5. package/dist/assets/channel-B6KFe5Ui.js +1 -0
  6. package/dist/assets/{chunk-4BX2VUAB-DFEs4FqT.js → chunk-4BX2VUAB-B-hX18OI.js} +1 -1
  7. package/dist/assets/{chunk-55IACEB6-CBZCFFSQ.js → chunk-55IACEB6-CDCucU6U.js} +1 -1
  8. package/dist/assets/{chunk-B4BG7PRW-MBLYLOpW.js → chunk-B4BG7PRW-B_bvOmJU.js} +1 -1
  9. package/dist/assets/{chunk-DI55MBZ5-CKIqbMAk.js → chunk-DI55MBZ5-BqSop0cW.js} +1 -1
  10. package/dist/assets/{chunk-FMBD7UC4-CKvuUqu2.js → chunk-FMBD7UC4-C54-XKR7.js} +1 -1
  11. package/dist/assets/{chunk-QN33PNHL-C7W3Jof4.js → chunk-QN33PNHL-BPnnBJ3E.js} +1 -1
  12. package/dist/assets/{chunk-QZHKN3VN-DTp6PsnU.js → chunk-QZHKN3VN-WNR9TOch.js} +1 -1
  13. package/dist/assets/{chunk-TZMSLE5B-CbzLkt3f.js → chunk-TZMSLE5B-7H_0y6Jq.js} +1 -1
  14. package/dist/assets/classDiagram-2ON5EDUG-By_JhIEM.js +1 -0
  15. package/dist/assets/classDiagram-v2-WZHVMYZB-By_JhIEM.js +1 -0
  16. package/dist/assets/clone-BuF8XBFq.js +1 -0
  17. package/dist/assets/{cose-bilkent-S5V4N54A-DIvheiGX.js → cose-bilkent-S5V4N54A-DYYnco-F.js} +1 -1
  18. package/dist/assets/{dagre-6UL2VRFP-DZhx7t5Y.js → dagre-6UL2VRFP-BuN5tYoM.js} +1 -1
  19. package/dist/assets/{diagram-PSM6KHXK-DGAecazr.js → diagram-PSM6KHXK-BqLZHOD_.js} +1 -1
  20. package/dist/assets/{diagram-QEK2KX5R-B_5MlwT9.js → diagram-QEK2KX5R-D6ihm09C.js} +1 -1
  21. package/dist/assets/{diagram-S2PKOQOG-DbnmA7e2.js → diagram-S2PKOQOG-Z4kDGPqZ.js} +1 -1
  22. package/dist/assets/{erDiagram-Q2GNP2WA-Br7xZhkv.js → erDiagram-Q2GNP2WA-tT_ZIA6k.js} +1 -1
  23. package/dist/assets/{flowDiagram-NV44I4VS-B0eGR7-f.js → flowDiagram-NV44I4VS-DP4XZFAl.js} +1 -1
  24. package/dist/assets/{ganttDiagram-LVOFAZNH-1DUzINqD.js → ganttDiagram-LVOFAZNH-D8dtKJe7.js} +1 -1
  25. package/dist/assets/{gitGraphDiagram-NY62KEGX-BoWt4frQ.js → gitGraphDiagram-NY62KEGX-CHTqjWSe.js} +1 -1
  26. package/dist/assets/{graph-DzatdHCT.js → graph-BuzEybDF.js} +1 -1
  27. package/dist/assets/index-C2dvtkqk.css +1 -0
  28. package/dist/assets/{index-BKT1JcWk.js → index-C7NPwb1W.js} +71 -64
  29. package/dist/assets/{index-DW8ErvlV.js → index-CRO3NlHm.js} +1 -1
  30. package/dist/assets/{infoDiagram-F6ZHWCRC-DLQslVcT.js → infoDiagram-F6ZHWCRC-DxSSCHjJ.js} +1 -1
  31. package/dist/assets/{isUndefined-BgBSNL7h.js → isUndefined-CMHFQ84G.js} +1 -1
  32. package/dist/assets/{journeyDiagram-XKPGCS4Q-CM8hW-Dy.js → journeyDiagram-XKPGCS4Q-BevCzXS0.js} +1 -1
  33. package/dist/assets/{kanban-definition-3W4ZIXB7-BV_KMoL8.js → kanban-definition-3W4ZIXB7-Datdpuzc.js} +1 -1
  34. package/dist/assets/{layout-DI2Ek4ZX.js → layout-CRoaNhL7.js} +1 -1
  35. package/dist/assets/{linear-B70jw5Te.js → linear-CZCbyP3Y.js} +1 -1
  36. package/dist/assets/{mermaid.core-CGLWRVx8.js → mermaid.core-oWlc3CvG.js} +5 -5
  37. package/dist/assets/{min-IW_t5ZIc.js → min-B-Brdcc-.js} +1 -1
  38. package/dist/assets/{mindmap-definition-VGOIOE7T-DKmAom6R.js → mindmap-definition-VGOIOE7T-C2cLH6vf.js} +1 -1
  39. package/dist/assets/{pieDiagram-ADFJNKIX-2BvdVtwh.js → pieDiagram-ADFJNKIX-lHSlzymq.js} +1 -1
  40. package/dist/assets/{quadrantDiagram-AYHSOK5B-tZqJeg84.js → quadrantDiagram-AYHSOK5B-Cu4iHHyE.js} +1 -1
  41. package/dist/assets/{requirementDiagram-UZGBJVZJ-BDVfQky-.js → requirementDiagram-UZGBJVZJ-DsXoYXUv.js} +1 -1
  42. package/dist/assets/{sankeyDiagram-TZEHDZUN-DL3nHaW_.js → sankeyDiagram-TZEHDZUN-BR0Y-EwJ.js} +1 -1
  43. package/dist/assets/{sequenceDiagram-WL72ISMW-Ccci1BhL.js → sequenceDiagram-WL72ISMW-DI7AT-qP.js} +1 -1
  44. package/dist/assets/{stateDiagram-FKZM4ZOC-D72rbhg9.js → stateDiagram-FKZM4ZOC-oxL9LRWa.js} +1 -1
  45. package/dist/assets/stateDiagram-v2-4FDKWEC3-C9dX-PKL.js +1 -0
  46. package/dist/assets/{timeline-definition-IT6M3QCI-BJz3pzov.js → timeline-definition-IT6M3QCI-Dj8UUbRz.js} +1 -1
  47. package/dist/assets/{treemap-KMMF4GRG-C6FRo8sN.js → treemap-KMMF4GRG-oIIgGlOX.js} +1 -1
  48. package/dist/assets/{xychartDiagram-PRI3JC2R-B1dvrGNn.js → xychartDiagram-PRI3JC2R-tBcCr-MQ.js} +1 -1
  49. package/dist/index.html +3 -3
  50. package/index.html +1 -1
  51. package/package.json +11 -7
  52. package/server/graph/debugAgent.ts +42 -0
  53. package/server/index.ts +6 -1
  54. package/src/chat/Chat.tsx +56 -25
  55. package/src/chat/components/MessageHuman.tsx +83 -8
  56. package/src/chat/components/UsageMetadata.tsx +2 -2
  57. package/src/debugPanel/Context.tsx +45 -0
  58. package/src/debugPanel/DebugPanel.tsx +237 -0
  59. package/src/debugPanel/index.ts +1 -0
  60. package/src/index.ts +1 -0
  61. package/src/monitor/index.tsx +1 -4
  62. package/test/App.tsx +8 -2
  63. package/tsconfig.json +1 -1
  64. package/dist/assets/channel-vUExqIsh.js +0 -1
  65. package/dist/assets/classDiagram-2ON5EDUG-BR8LFF3q.js +0 -1
  66. package/dist/assets/classDiagram-v2-WZHVMYZB-BR8LFF3q.js +0 -1
  67. package/dist/assets/clone-DFFpaJQA.js +0 -1
  68. package/dist/assets/index-DfuXfoTh.css +0 -1
  69. package/dist/assets/stateDiagram-v2-4FDKWEC3-yiVP38kq.js +0 -1
@@ -1,7 +1,8 @@
1
1
  import { RenderMessage } from "@langgraph-js/sdk";
2
2
  import { useChat } from "@langgraph-js/sdk/react";
3
- import React from "react";
4
- import { RotateCcw, Undo } from "lucide-react";
3
+ import React, { useState } from "react";
4
+ import { RotateCcw, Undo, ChevronDown, ChevronUp, Edit3, Check, X } from "lucide-react";
5
+ import { useExtraParams } from "../context/ExtraParamsContext";
5
6
 
6
7
  interface MessageHumanProps {
7
8
  message: RenderMessage;
@@ -54,9 +55,76 @@ const parseFileTags = (text: string): any[] => {
54
55
 
55
56
  const MessageHuman: React.FC<MessageHumanProps> = ({ message, content }) => {
56
57
  const chat = useChat();
58
+ const { extraParams } = useExtraParams();
59
+ const [isExpanded, setIsExpanded] = useState(false);
60
+ const [isEditing, setIsEditing] = useState(false);
61
+ const [editedContent, setEditedContent] = useState<string>("");
62
+
63
+ // 获取纯文本内容用于编辑
64
+ const getTextContent = (content: string | any[]): string => {
65
+ if (typeof content === "string") {
66
+ return content;
67
+ }
68
+ if (Array.isArray(content)) {
69
+ return content
70
+ .filter((item) => item.type === "text" && typeof item.text === "string")
71
+ .map((item) => item.text)
72
+ .join("");
73
+ }
74
+ return "";
75
+ };
76
+
77
+ // 处理双击进入编辑模式
78
+ const handleDoubleClick = () => {
79
+ setIsEditing(true);
80
+ setEditedContent(getTextContent(content));
81
+ };
82
+
83
+ // 确认编辑并调用 revertChatTo + sendMessage
84
+ const handleEditConfirm = async () => {
85
+ if (editedContent.trim() !== getTextContent(content)) {
86
+ await chat.revertChatTo(message.id!, false, { includeMessageId: true }); // 先回退到该消息
87
+ await chat.sendMessage([{ type: "human", content: editedContent }], { extraParams }); // 发送编辑的内容
88
+ }
89
+ setIsEditing(false);
90
+ setEditedContent("");
91
+ };
92
+
93
+ // 取消编辑
94
+ const handleEditCancel = () => {
95
+ setIsEditing(false);
96
+ setEditedContent("");
97
+ };
98
+
57
99
  const renderContent = () => {
100
+ if (isEditing) {
101
+ return (
102
+ <div className="flex flex-col gap-2">
103
+ <textarea
104
+ value={editedContent}
105
+ onChange={(e) => setEditedContent(e.target.value)}
106
+ className="w-full bg-transparent text-white rounded px-3 py-2 min-h-[120px] resize-none border-2 border-blue-400 outline-none"
107
+ placeholder="编辑消息内容..."
108
+ autoFocus
109
+ />
110
+ <div className="flex gap-2 justify-end">
111
+ <button onClick={handleEditConfirm} className="px-3 py-1 bg-green-600 text-white rounded text-sm hover:bg-green-700 transition-colors" title="确认编辑">
112
+ <Check size={14} />
113
+ </button>
114
+ <button onClick={handleEditCancel} className="px-3 py-1 bg-gray-600 text-white rounded text-sm hover:bg-gray-700 transition-colors" title="取消编辑">
115
+ <X size={14} />
116
+ </button>
117
+ </div>
118
+ </div>
119
+ );
120
+ }
121
+
58
122
  if (typeof content === "string") {
59
- return <div className="text-white whitespace-pre-wrap">{content}</div>;
123
+ return (
124
+ <div className="text-white whitespace-pre-wrap cursor-pointer" onDoubleClick={handleDoubleClick} title="双击编辑">
125
+ {content}
126
+ </div>
127
+ );
60
128
  }
61
129
 
62
130
  if (Array.isArray(content)) {
@@ -78,7 +146,7 @@ const MessageHuman: React.FC<MessageHumanProps> = ({ message, content }) => {
78
146
  switch (item.type) {
79
147
  case "text":
80
148
  return (
81
- <div key={index} className="text-white whitespace-pre-wrap">
149
+ <div key={index} className="text-white whitespace-pre-wrap cursor-pointer" onDoubleClick={handleDoubleClick} title="双击编辑">
82
150
  {item.text}
83
151
  </div>
84
152
  );
@@ -129,16 +197,23 @@ const MessageHuman: React.FC<MessageHumanProps> = ({ message, content }) => {
129
197
  };
130
198
 
131
199
  return (
132
- <div className="flex flex-row w-full justify-end group/message-human">
200
+ <div className="flex flex-row w-full justify-end group/message-human ">
133
201
  <div className="hidden group-hover/message-human:flex gap-2 mx-2">
134
202
  <button onClick={() => chat.revertChatTo(message.id!, true)} className="p-2 text-gray-700 transition-colors cursor-pointer" title="重试">
135
203
  <RotateCcw size={16} />
136
204
  </button>
137
- <button onClick={() => chat.revertChatTo(message.id!, false)} className="p-2 text-gray-700 transition-colors cursor-pointer" title="回退">
138
- <Undo size={16} />
205
+ <button onClick={handleDoubleClick} className="p-2 text-gray-700 transition-colors cursor-pointer" title="编辑">
206
+ <Edit3 size={16} />
139
207
  </button>
208
+ <button onClick={() => setIsExpanded(!isExpanded)} className="p-2 text-gray-700 transition-colors cursor-pointer" title={isExpanded ? "收起" : "展开"}>
209
+ {isExpanded ? <ChevronUp size={16} /> : <ChevronDown size={16} />}
210
+ </button>
211
+ </div>
212
+ <div
213
+ className={`flex flex-col w-fit bg-blue-500/90 rounded-2xl text-white px-4 py-3 max-w-[80%] ${isExpanded || isEditing ? "" : "max-h-40 overflow-y-auto"} ${isEditing ? "w-full h-52" : ""}`}
214
+ >
215
+ {renderContent()}
140
216
  </div>
141
- <div className="flex flex-col w-fit bg-blue-500/90 rounded-2xl text-white px-4 py-3 max-w-[80%]">{renderContent()}</div>
142
217
  </div>
143
218
  );
144
219
  };
@@ -25,7 +25,7 @@ export const UsageMetadata: React.FC<UsageMetadataProps> = ({ usage_metadata, sp
25
25
  const speed = spend_time ? ((usage_metadata.output_tokens || 0) * 1000) / (spend_time || 1) : 0;
26
26
  spend_time = spend_time && !isNaN(spend_time) ? spend_time : 0;
27
27
  const { currentChatId } = useChat();
28
- const { openMonitorWithChat } = useMonitor();
28
+ const monitor = useMonitor();
29
29
  return (
30
30
  <div className="flex items-center justify-between text-xs text-gray-400 mt-3">
31
31
  <div className="flex items-center gap-3">
@@ -37,7 +37,7 @@ export const UsageMetadata: React.FC<UsageMetadataProps> = ({ usage_metadata, sp
37
37
  {response_metadata?.model_name && <span className="text-gray-500">{response_metadata.model_name}</span>}
38
38
  {tool_call_id && <span className="text-gray-400">Tool: {tool_call_id}</span>}
39
39
  {id && (
40
- <span className="text-gray-400" onClick={() => openMonitorWithChat(currentChatId!, id)}>
40
+ <span className="text-gray-400" onClick={() => monitor?.openMonitorWithChat(currentChatId!, id)}>
41
41
  ID: {id}
42
42
  </span>
43
43
  )}
@@ -0,0 +1,45 @@
1
+ import { RenderMessage } from "@langgraph-js/sdk";
2
+ import React, { createContext, useContext, useState, ReactNode } from "react";
3
+
4
+ interface DebugPanelContextType {
5
+ isDebugPanelVisible: boolean;
6
+ setIsDebugPanelVisible: (visible: boolean) => void;
7
+ toggleDebugPanel: () => void;
8
+ messagesContext: string;
9
+ setMessagesContext: (messages: RenderMessage[]) => void;
10
+ }
11
+
12
+ const DebugPanelContext = createContext<DebugPanelContextType | undefined>(undefined);
13
+
14
+ export const useDebugPanel = () => {
15
+ const context = useContext(DebugPanelContext);
16
+ if (context === undefined) {
17
+ throw new Error("useDebugPanel must be used within a DebugPanelProvider");
18
+ }
19
+ return context;
20
+ };
21
+
22
+ interface DebugPanelProviderProps {
23
+ children: ReactNode;
24
+ }
25
+
26
+ export const DebugPanelProvider: React.FC<DebugPanelProviderProps> = ({ children }) => {
27
+ const [messagesContext, setMessagesContext] = useState<string>("");
28
+ const [isDebugPanelVisible, setIsDebugPanelVisible] = useState(false);
29
+
30
+ const toggleDebugPanel = () => {
31
+ setIsDebugPanelVisible((prev) => !prev);
32
+ };
33
+
34
+ const value = {
35
+ isDebugPanelVisible,
36
+ setIsDebugPanelVisible,
37
+ toggleDebugPanel,
38
+ messagesContext,
39
+ setMessagesContext(messages: RenderMessage[]) {
40
+ setMessagesContext(JSON.stringify(messages));
41
+ },
42
+ };
43
+
44
+ return <DebugPanelContext.Provider value={value}>{children}</DebugPanelContext.Provider>;
45
+ };
@@ -0,0 +1,237 @@
1
+ import React, { useState, useRef, useEffect } from "react";
2
+ import { ChatProvider, useChat } from "@langgraph-js/sdk/react";
3
+ import { Message } from "@langgraph-js/sdk";
4
+ import { Send, Bug, Bot, Plus, Trash } from "lucide-react";
5
+ import { toast } from "sonner";
6
+ import { useDebugPanel } from "./Context";
7
+ import ModelTesterPopup from "../chat/components/ModelTesterPopup";
8
+ import { MessagesBox } from "../chat/components/MessageBox";
9
+ import { ExtraParamsProvider } from "@/chat/context/ExtraParamsContext";
10
+
11
+ const DebugMessages: React.FC = () => {
12
+ const { renderMessages, loading, inChatError, client } = useChat();
13
+ const messagesEndRef = useRef<HTMLDivElement>(null);
14
+
15
+ const scrollToBottom = () => {
16
+ messagesEndRef.current?.scrollIntoView({ behavior: "instant" });
17
+ };
18
+
19
+ useEffect(() => {
20
+ if (renderMessages.length > 0 && !loading) {
21
+ scrollToBottom();
22
+ }
23
+ }, [renderMessages, loading]);
24
+
25
+ return (
26
+ <div className="flex-1 overflow-y-auto overflow-x-hidden p-4 bg-gray-50 [&::-webkit-scrollbar]:w-1.5 [&::-webkit-scrollbar]:h-1.5 [&::-webkit-scrollbar-track]:bg-transparent [&::-webkit-scrollbar-thumb]:bg-gray-300 [&::-webkit-scrollbar-thumb]:rounded [&::-webkit-scrollbar-thumb:hover]:bg-gray-400">
27
+ {renderMessages.length === 0 && !loading ? (
28
+ <div className="flex items-center justify-center h-full">
29
+ <div className="text-center">
30
+ <div className="flex items-center justify-center">
31
+ <div className="text-4xl mb-4">🐛</div>
32
+ </div>
33
+ <h1 className="text-2xl font-bold text-gray-700 mb-2">Debug Panel</h1>
34
+ <div className="text-sm text-gray-500">调试助手控制台</div>
35
+ </div>
36
+ </div>
37
+ ) : (
38
+ <div className="flex flex-col gap-5 w-full">
39
+ <MessagesBox renderMessages={renderMessages} collapsedTools={[]} toggleToolCollapse={() => {}} client={client!} />
40
+ </div>
41
+ )}
42
+ {loading && (
43
+ <div className="flex items-center justify-center py-4 text-gray-500">
44
+ <div className="animate-spin rounded-full h-4 w-4 border-2 border-blue-500 border-t-transparent mr-2"></div>
45
+ 调试中...
46
+ </div>
47
+ )}
48
+ {inChatError && <div className="px-4 py-3 text-sm text-red-600 bg-red-50 rounded-lg">{JSON.stringify(inChatError)}</div>}
49
+ <div ref={messagesEndRef} />
50
+ </div>
51
+ );
52
+ };
53
+
54
+ const DebugInput: React.FC = () => {
55
+ const { userInput, setUserInput, loading, sendMessage, stopGeneration, createNewChat, renderMessages } = useChat();
56
+ const { messagesContext } = useDebugPanel();
57
+ const textareaRef = useRef<HTMLTextAreaElement>(null);
58
+
59
+ const sendDebugMessage = () => {
60
+ const item = localStorage.getItem("model-tester-config");
61
+ if (!item) {
62
+ toast.error("请先配置模型测试器");
63
+ return;
64
+ }
65
+
66
+ let config;
67
+ try {
68
+ config = JSON.parse(item);
69
+ } catch (error) {
70
+ toast.error("模型测试器配置格式错误,请重新配置");
71
+ console.error("Failed to parse model-tester-config:", error);
72
+ return;
73
+ }
74
+
75
+ // 验证配置对象的有效性
76
+ if (!config || typeof config !== "object") {
77
+ toast.error("模型测试器配置无效,请重新配置");
78
+ return;
79
+ }
80
+
81
+ // 检查必要的配置项
82
+ if (!config.token || typeof config.token !== "string" || config.token.trim() === "") {
83
+ toast.error("API Token 未配置或为空,请检查模型测试器配置");
84
+ return;
85
+ }
86
+ const isFirstMessage = renderMessages.length === 0;
87
+ const content: Message[] = [
88
+ {
89
+ type: "human",
90
+ content: userInput,
91
+ },
92
+ ];
93
+ if (isFirstMessage && messagesContext)
94
+ content.push({
95
+ type: "human",
96
+ content: "这些是我们的数据\n\n" + messagesContext,
97
+ });
98
+ sendMessage(content, {
99
+ extraParams: {
100
+ model_name: config.model_name || "qwen-plus",
101
+ api_key: config.token.trim(),
102
+ api_host: config.base_url,
103
+ },
104
+ });
105
+ };
106
+
107
+ const handleKeyPress = (event: React.KeyboardEvent) => {
108
+ if (event.key === "Enter" && !event.shiftKey) {
109
+ event.preventDefault();
110
+ sendDebugMessage();
111
+ }
112
+ };
113
+
114
+ const [isExpanded, setIsExpanded] = useState(false);
115
+
116
+ useEffect(() => {
117
+ if (userInput && (userInput.length > 50 || userInput.includes("\n"))) {
118
+ setIsExpanded(true);
119
+ } else {
120
+ setIsExpanded(false);
121
+ }
122
+ }, [userInput]);
123
+
124
+ useEffect(() => {
125
+ if (textareaRef.current) {
126
+ textareaRef.current.style.height = "auto";
127
+ if (isExpanded) {
128
+ textareaRef.current.style.height = Math.min(textareaRef.current.scrollHeight, 96) + "px";
129
+ }
130
+ }
131
+ }, [userInput, isExpanded]);
132
+
133
+ return (
134
+ <div className="p-4 bg-white border-t border-gray-200">
135
+ <div className={`bg-gray-50 border border-gray-200 rounded-lg px-3 py-3 ${isExpanded ? "rounded-xl" : "rounded-full"}`}>
136
+ <div className={`flex gap-3 ${isExpanded ? "items-end" : "items-center"}`}>
137
+ <button
138
+ onClick={() => createNewChat()}
139
+ className="w-8 h-8 flex items-center justify-center rounded-full focus:outline-none transition-colors bg-blue-500 hover:bg-blue-600 text-white disabled:opacity-40"
140
+ title="创建新对话"
141
+ >
142
+ <Trash className="w-4 h-4" />
143
+ </button>
144
+ <textarea
145
+ ref={textareaRef}
146
+ className="flex-1 text-sm resize-none active:outline-none focus:outline-none bg-transparent"
147
+ rows={1}
148
+ value={userInput}
149
+ onChange={(e) => setUserInput(e.target.value)}
150
+ onKeyDown={handleKeyPress}
151
+ placeholder="输入调试命令..."
152
+ disabled={loading}
153
+ style={{
154
+ maxHeight: isExpanded ? "6rem" : "2rem",
155
+ fontFamily: "inherit",
156
+ lineHeight: isExpanded ? "inherit" : "2rem",
157
+ }}
158
+ />
159
+ <button
160
+ className={`w-8 h-8 flex items-center justify-center rounded-full focus:outline-none transition-colors disabled:cursor-not-allowed ${
161
+ loading ? "bg-red-500 hover:bg-red-600 text-white" : "bg-blue-500 hover:bg-blue-600 text-white disabled:opacity-40"
162
+ }`}
163
+ onClick={() => (loading ? stopGeneration() : sendDebugMessage())}
164
+ disabled={!loading && !userInput.trim()}
165
+ >
166
+ {loading ? (
167
+ <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
168
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
169
+ </svg>
170
+ ) : (
171
+ <Send className="w-4 h-4" />
172
+ )}
173
+ </button>
174
+ </div>
175
+ </div>
176
+ </div>
177
+ );
178
+ };
179
+
180
+ const DebugPanel: React.FC = () => {
181
+ const [isModelTesterOpen, setIsModelTesterOpen] = useState(false);
182
+
183
+ return (
184
+ <div className="flex h-full w-full max-w-xl overflow-hidden bg-gray-50">
185
+ <section className="flex-1 flex flex-col overflow-hidden">
186
+ <header className="flex items-center gap-2 px-4 py-3 justify-between bg-white border-b border-gray-200">
187
+ <div className="flex items-center gap-2">
188
+ <Bug className="w-5 h-5 text-gray-600" />
189
+ <h1 className="text-lg font-semibold text-gray-900">调试面板</h1>
190
+ </div>
191
+ <div className="flex items-center gap-2">
192
+ <button
193
+ onClick={() => setIsModelTesterOpen(true)}
194
+ className="px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-200 cursor-pointer rounded-xl hover:bg-gray-100 focus:outline-none transition-colors flex items-center gap-2"
195
+ >
196
+ <Bot className="w-4 h-4" />
197
+ 模型测试器
198
+ </button>
199
+ </div>
200
+ </header>
201
+ <main className="flex-1 overflow-hidden flex flex-col">
202
+ <DebugMessages />
203
+ <DebugInput />
204
+ </main>
205
+ <ModelTesterPopup isOpen={isModelTesterOpen} onClose={() => setIsModelTesterOpen(false)} />
206
+ </section>
207
+ </div>
208
+ );
209
+ };
210
+
211
+ const DebugPanelWrapper: React.FC = () => {
212
+ const { isDebugPanelVisible } = useDebugPanel();
213
+ if (!isDebugPanelVisible) {
214
+ return null;
215
+ }
216
+ return (
217
+ <ChatProvider
218
+ defaultAgent={"graph"}
219
+ apiUrl={new URL("/api/open-smith/graph", window.location.origin.toString()).href}
220
+ defaultHeaders={{}}
221
+ withCredentials={false}
222
+ showHistory={false}
223
+ fallbackToAvailableAssistants={false}
224
+ onInitError={(err, currentAgent) => {
225
+ toast.error("调试面板连接失败: " + currentAgent + "\n" + err, {
226
+ duration: 5000,
227
+ });
228
+ }}
229
+ >
230
+ <ExtraParamsProvider>
231
+ <DebugPanel />
232
+ </ExtraParamsProvider>
233
+ </ChatProvider>
234
+ );
235
+ };
236
+
237
+ export default DebugPanelWrapper;
@@ -0,0 +1 @@
1
+ export { default } from "./DebugPanel";
package/src/index.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  import "./index.css";
2
2
  export { default as Chat } from "./chat/Chat";
3
+ export { default as DebugPanel } from "./debugPanel";
@@ -45,15 +45,12 @@ export const MonitorProvider: React.FC<{ children: ReactNode }> = ({ children })
45
45
  // Hook 用于在组件中使用 modal
46
46
  export const useMonitor = () => {
47
47
  const context = useContext(MonitorContext);
48
- if (context === undefined) {
49
- throw new Error("useModal must be used within a ModalProvider");
50
- }
51
48
  return context;
52
49
  };
53
50
 
54
51
  // Modal 组件
55
52
  export const Monitor: React.FC = () => {
56
- const { isOpen, url, closeModal } = useMonitor();
53
+ const { isOpen, url, closeModal } = useMonitor()!;
57
54
 
58
55
  if (!isOpen) return null;
59
56
 
package/test/App.tsx CHANGED
@@ -1,7 +1,13 @@
1
- import { Chat } from "../src/index";
1
+ import { Chat, DebugPanel } from "../src/index";
2
+ import { DebugPanelProvider } from "../src/debugPanel/Context";
2
3
 
3
4
  function App() {
4
- return <Chat />;
5
+ return (
6
+ <DebugPanelProvider>
7
+ <Chat />
8
+ <DebugPanel />
9
+ </DebugPanelProvider>
10
+ );
5
11
  }
6
12
 
7
13
  export default App;
package/tsconfig.json CHANGED
@@ -20,5 +20,5 @@
20
20
  "@/*": ["./src/*"]
21
21
  }
22
22
  },
23
- "include": ["test", "test/**/*.tsx"]
23
+ "include": ["test", "test/**/*.tsx", "src/debugPanel/Context.tsx"]
24
24
  }
@@ -1 +0,0 @@
1
- import{U as a,C as n}from"./mermaid.core-CGLWRVx8.js";const t=(r,o)=>a.lang.round(n.parse(r)[o]);export{t as c};
@@ -1 +0,0 @@
1
- import{s as a,c as s,a as e,C as t}from"./chunk-B4BG7PRW-MBLYLOpW.js";import{_ as i}from"./mermaid.core-CGLWRVx8.js";import"./index-BKT1JcWk.js";import"./chunk-FMBD7UC4-CKvuUqu2.js";import"./chunk-55IACEB6-CBZCFFSQ.js";import"./chunk-QN33PNHL-C7W3Jof4.js";var u={parser:e,get db(){return new t},renderer:s,styles:a,init:i(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{u as diagram};
@@ -1 +0,0 @@
1
- import{s as a,c as s,a as e,C as t}from"./chunk-B4BG7PRW-MBLYLOpW.js";import{_ as i}from"./mermaid.core-CGLWRVx8.js";import"./index-BKT1JcWk.js";import"./chunk-FMBD7UC4-CKvuUqu2.js";import"./chunk-55IACEB6-CBZCFFSQ.js";import"./chunk-QN33PNHL-C7W3Jof4.js";var u={parser:e,get db(){return new t},renderer:s,styles:a,init:i(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{u as diagram};
@@ -1 +0,0 @@
1
- import{b as r}from"./index-BKT1JcWk.js";var e=4;function a(o){return r(o,e)}export{a as c};