@langgraph-js/ui 4.0.4 → 4.1.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.
- package/README.md +3 -2
- package/dist/assets/{arc-VHSz5mGH.js → arc-DuQmRVz4.js} +1 -1
- package/dist/assets/{architectureDiagram-VXUJARFQ-ByOANAgd.js → architectureDiagram-VXUJARFQ-BHRgv10c.js} +1 -1
- package/dist/assets/{blockDiagram-VD42YOAC-D9vvw2x2.js → blockDiagram-VD42YOAC-CXpgSveL.js} +1 -1
- package/dist/assets/{c4Diagram-YG6GDRKO-Cjgfea4R.js → c4Diagram-YG6GDRKO-Khi-Yvw6.js} +1 -1
- package/dist/assets/channel-Ew2s1lX1.js +1 -0
- package/dist/assets/{chunk-4BX2VUAB-DaetH3EV.js → chunk-4BX2VUAB-3oTU12qk.js} +1 -1
- package/dist/assets/{chunk-55IACEB6-HRZ91JWG.js → chunk-55IACEB6-CWnzzmoV.js} +1 -1
- package/dist/assets/{chunk-B4BG7PRW-BxOW4a9d.js → chunk-B4BG7PRW-BsPWJrC5.js} +1 -1
- package/dist/assets/{chunk-DI55MBZ5-CJNOQRsD.js → chunk-DI55MBZ5-CUa1-9hK.js} +1 -1
- package/dist/assets/{chunk-FMBD7UC4-BWhkxmUU.js → chunk-FMBD7UC4-D5m6iMBs.js} +1 -1
- package/dist/assets/{chunk-QN33PNHL-CuRPvllX.js → chunk-QN33PNHL-BYUiMPlY.js} +1 -1
- package/dist/assets/{chunk-QZHKN3VN-9fig0537.js → chunk-QZHKN3VN-CirEX4FJ.js} +1 -1
- package/dist/assets/{chunk-TZMSLE5B-C_WU0QiO.js → chunk-TZMSLE5B-MjmZVXkb.js} +1 -1
- package/dist/assets/classDiagram-2ON5EDUG-CjN0oJQE.js +1 -0
- package/dist/assets/classDiagram-v2-WZHVMYZB-CjN0oJQE.js +1 -0
- package/dist/assets/clone-C47_Gecf.js +1 -0
- package/dist/assets/{cose-bilkent-S5V4N54A-Bsptsgkr.js → cose-bilkent-S5V4N54A-WS9dK6bo.js} +1 -1
- package/dist/assets/{dagre-6UL2VRFP-Dp-vNAFo.js → dagre-6UL2VRFP-B7BbfwPb.js} +1 -1
- package/dist/assets/{diagram-PSM6KHXK-DydynADF.js → diagram-PSM6KHXK-CxxR7Qtf.js} +1 -1
- package/dist/assets/{diagram-QEK2KX5R-D9TeDN1X.js → diagram-QEK2KX5R-MLgtLPVV.js} +1 -1
- package/dist/assets/{diagram-S2PKOQOG-DSRjzc3q.js → diagram-S2PKOQOG-BwuJtiXA.js} +1 -1
- package/dist/assets/{erDiagram-Q2GNP2WA-DzdPHEjy.js → erDiagram-Q2GNP2WA-BrhNBK01.js} +1 -1
- package/dist/assets/{flowDiagram-NV44I4VS-CbxmajPf.js → flowDiagram-NV44I4VS-D2jtwnON.js} +1 -1
- package/dist/assets/{ganttDiagram-LVOFAZNH-Drha5fxW.js → ganttDiagram-LVOFAZNH-B_UxrnT0.js} +1 -1
- package/dist/assets/{gitGraphDiagram-NY62KEGX-LbHwve64.js → gitGraphDiagram-NY62KEGX-CX4A_Q4m.js} +1 -1
- package/dist/assets/{graph-DCf4xH9S.js → graph-D_WrSJyu.js} +1 -1
- package/dist/assets/{index-BLuMPbxE.js → index-CLaLu4pI.js} +171 -160
- package/dist/assets/{index-CDM1QYzI.js → index-COCJ8JMS.js} +1 -1
- package/dist/assets/index-DfYgTPQG.css +1 -0
- package/dist/assets/{infoDiagram-F6ZHWCRC-ClyhK0t7.js → infoDiagram-F6ZHWCRC-BafCiyFj.js} +1 -1
- package/dist/assets/{isUndefined-C8RsejfO.js → isUndefined-DtxdyV6U.js} +1 -1
- package/dist/assets/{journeyDiagram-XKPGCS4Q-D5-QQe5o.js → journeyDiagram-XKPGCS4Q-CuwRo9T6.js} +1 -1
- package/dist/assets/{kanban-definition-3W4ZIXB7-syv7L3k1.js → kanban-definition-3W4ZIXB7-BuynooBb.js} +1 -1
- package/dist/assets/{layout-BVJoT6aY.js → layout-CfWeKgEO.js} +1 -1
- package/dist/assets/{linear-CYugkV7n.js → linear-D4EorWqr.js} +1 -1
- package/dist/assets/{mermaid.core-C74BWk1B.js → mermaid.core-BNKR4f8-.js} +5 -5
- package/dist/assets/{min-DiXGSxOK.js → min-DG2tUQY2.js} +1 -1
- package/dist/assets/{mindmap-definition-VGOIOE7T-BvO9FTOF.js → mindmap-definition-VGOIOE7T-dIFLlQwW.js} +1 -1
- package/dist/assets/{pieDiagram-ADFJNKIX-DXn2hC-L.js → pieDiagram-ADFJNKIX-B_cDKG3M.js} +1 -1
- package/dist/assets/{quadrantDiagram-AYHSOK5B-v-ly1CnS.js → quadrantDiagram-AYHSOK5B-GctFd0Pf.js} +1 -1
- package/dist/assets/{requirementDiagram-UZGBJVZJ-CXfPn-H8.js → requirementDiagram-UZGBJVZJ-BokiqoFs.js} +1 -1
- package/dist/assets/{sankeyDiagram-TZEHDZUN-BsEZTHTS.js → sankeyDiagram-TZEHDZUN-DhsbfX_7.js} +1 -1
- package/dist/assets/{sequenceDiagram-WL72ISMW-Bism4Sux.js → sequenceDiagram-WL72ISMW-A17Kalx7.js} +1 -1
- package/dist/assets/{stateDiagram-FKZM4ZOC-ZURqOAAV.js → stateDiagram-FKZM4ZOC-CC5jSMs3.js} +1 -1
- package/dist/assets/stateDiagram-v2-4FDKWEC3-BxupysV8.js +1 -0
- package/dist/assets/{timeline-definition-IT6M3QCI-CrIOkZVS.js → timeline-definition-IT6M3QCI-dhXwDrLB.js} +1 -1
- package/dist/assets/{treemap-KMMF4GRG-CUO0dPou.js → treemap-KMMF4GRG-DssYo4UW.js} +1 -1
- package/dist/assets/{xychartDiagram-PRI3JC2R-CpUXJp3l.js → xychartDiagram-PRI3JC2R-CJOB16TX.js} +1 -1
- package/dist/index.html +2 -2
- package/package.json +5 -2
- package/server/index.ts +7 -0
- package/server/test-for-model.ts +49 -0
- package/server/type.ts +7 -0
- package/src/OpenSmithPlugin.ts +21 -4
- package/src/chat/Chat.tsx +17 -6
- package/src/chat/components/MessageTool.tsx +3 -3
- package/src/chat/components/ModelTesterPopup.tsx +424 -0
- package/src/chat/tools/show_form.tsx +1 -2
- package/tsconfig.json +1 -2
- package/dist/assets/channel-DVXmOWsH.js +0 -1
- package/dist/assets/classDiagram-2ON5EDUG-DaMtOxOJ.js +0 -1
- package/dist/assets/classDiagram-v2-WZHVMYZB-DaMtOxOJ.js +0 -1
- package/dist/assets/clone-n9nuTed8.js +0 -1
- package/dist/assets/index-BwQLftC0.css +0 -1
- package/dist/assets/stateDiagram-v2-4FDKWEC3-25vTwMmA.js +0 -1
- package/tsconfig.node.json +0 -9
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
import React, { useState, useEffect } from "react";
|
|
2
|
+
import { LLMModel } from "../../../server/type";
|
|
3
|
+
import { X } from "lucide-react";
|
|
4
|
+
|
|
5
|
+
interface ModelTesterPopupProps {
|
|
6
|
+
isOpen: boolean;
|
|
7
|
+
onClose: () => void;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface TestResult {
|
|
11
|
+
success: boolean;
|
|
12
|
+
error?: string;
|
|
13
|
+
response?: string;
|
|
14
|
+
timestamp?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const ModelTesterPopup: React.FC<ModelTesterPopupProps> = ({ isOpen, onClose }) => {
|
|
18
|
+
const [formData, setFormData] = useState<LLMModel>({
|
|
19
|
+
model_name: "",
|
|
20
|
+
provider: "",
|
|
21
|
+
base_url: "",
|
|
22
|
+
token: "",
|
|
23
|
+
messages: [{ role: "user", content: "Hello, how are you?" }],
|
|
24
|
+
});
|
|
25
|
+
const [isTesting, setIsTesting] = useState(false);
|
|
26
|
+
const [testResult, setTestResult] = useState<TestResult | null>(null);
|
|
27
|
+
const [streamingResponse, setStreamingResponse] = useState<any>(null);
|
|
28
|
+
const [modelNameHistory, setModelNameHistory] = useState<string[]>([]);
|
|
29
|
+
const [showHistory, setShowHistory] = useState(false);
|
|
30
|
+
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
if (isOpen) {
|
|
33
|
+
// 从 localStorage 加载保存的配置
|
|
34
|
+
const savedConfig = localStorage.getItem("model-tester-config");
|
|
35
|
+
if (savedConfig) {
|
|
36
|
+
try {
|
|
37
|
+
const config = JSON.parse(savedConfig);
|
|
38
|
+
setFormData((prev) => ({ ...prev, ...config }));
|
|
39
|
+
} catch (e) {
|
|
40
|
+
console.error("Failed to load saved config:", e);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// 从 localStorage 加载模型名称历史记录
|
|
45
|
+
const savedHistory = localStorage.getItem("model-tester-name-history");
|
|
46
|
+
if (savedHistory) {
|
|
47
|
+
try {
|
|
48
|
+
const history = JSON.parse(savedHistory);
|
|
49
|
+
setModelNameHistory(Array.isArray(history) ? history : []);
|
|
50
|
+
} catch (e) {
|
|
51
|
+
console.error("Failed to load model name history:", e);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}, [isOpen]);
|
|
56
|
+
|
|
57
|
+
const handleInputChange = (field: keyof LLMModel, value: any) => {
|
|
58
|
+
setFormData((prev) => {
|
|
59
|
+
const newState = { ...prev, [field]: value };
|
|
60
|
+
saveConfigToLocalStorage({ [field]: value });
|
|
61
|
+
return newState;
|
|
62
|
+
});
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const saveConfigToLocalStorage = (config: Partial<LLMModel>) => {
|
|
66
|
+
const currentConfig = JSON.parse(localStorage.getItem("model-tester-config") || "{}");
|
|
67
|
+
const updatedConfig = { ...currentConfig, ...config };
|
|
68
|
+
localStorage.setItem("model-tester-config", JSON.stringify(updatedConfig));
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const addToModelNameHistory = (modelName: string) => {
|
|
72
|
+
if (!modelName.trim()) return;
|
|
73
|
+
|
|
74
|
+
setModelNameHistory((prev) => {
|
|
75
|
+
const filtered = prev.filter((name) => name !== modelName);
|
|
76
|
+
const newHistory = [modelName, ...filtered].slice(0, 5);
|
|
77
|
+
localStorage.setItem("model-tester-name-history", JSON.stringify(newHistory));
|
|
78
|
+
return newHistory;
|
|
79
|
+
});
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const handleMessageChange = (index: number, field: string, value: string) => {
|
|
83
|
+
const newMessages = [...formData.messages];
|
|
84
|
+
(newMessages[index] as any)[field] = value;
|
|
85
|
+
const updatedMessages = [...newMessages];
|
|
86
|
+
setFormData((prev) => {
|
|
87
|
+
const newState = { ...prev, messages: updatedMessages };
|
|
88
|
+
saveConfigToLocalStorage({ messages: updatedMessages });
|
|
89
|
+
return newState;
|
|
90
|
+
});
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const addMessage = () => {
|
|
94
|
+
setFormData((prev) => {
|
|
95
|
+
const newMessages = [...prev.messages, { role: "user", content: "" }];
|
|
96
|
+
saveConfigToLocalStorage({ messages: newMessages });
|
|
97
|
+
return {
|
|
98
|
+
...prev,
|
|
99
|
+
messages: newMessages,
|
|
100
|
+
};
|
|
101
|
+
});
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const removeMessage = (index: number) => {
|
|
105
|
+
if (formData.messages.length > 1) {
|
|
106
|
+
setFormData((prev) => {
|
|
107
|
+
const newMessages = prev.messages.filter((_, i) => i !== index);
|
|
108
|
+
saveConfigToLocalStorage({ messages: newMessages });
|
|
109
|
+
return {
|
|
110
|
+
...prev,
|
|
111
|
+
messages: newMessages,
|
|
112
|
+
};
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const testModel = async () => {
|
|
118
|
+
if (!formData.model_name || !formData.token) {
|
|
119
|
+
setTestResult({
|
|
120
|
+
success: false,
|
|
121
|
+
error: "请填写模型名称和 API Token",
|
|
122
|
+
timestamp: new Date().toISOString(),
|
|
123
|
+
});
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
setIsTesting(true);
|
|
128
|
+
setTestResult(null);
|
|
129
|
+
setStreamingResponse(null);
|
|
130
|
+
|
|
131
|
+
try {
|
|
132
|
+
const response = await fetch("/api/open-smith/llm/test-model", {
|
|
133
|
+
method: "POST",
|
|
134
|
+
headers: {
|
|
135
|
+
"Content-Type": "application/json",
|
|
136
|
+
},
|
|
137
|
+
body: JSON.stringify(formData),
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// 使用 Web Stream API 处理响应流
|
|
141
|
+
const stream = response.body;
|
|
142
|
+
if (!stream) {
|
|
143
|
+
throw new Error("无法读取响应流");
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// 将原始流通过解析器管道,然后读取结果
|
|
147
|
+
await stream.pipeThrough(new TextDecoderStream()).pipeTo(
|
|
148
|
+
new WritableStream({
|
|
149
|
+
write(chunk) {
|
|
150
|
+
// 每个 chunk 都是完整的 SSE 事件,直接处理
|
|
151
|
+
const lines = chunk.split("\n");
|
|
152
|
+
let eventData = "";
|
|
153
|
+
|
|
154
|
+
for (const line of lines) {
|
|
155
|
+
if (line.startsWith("data: ")) {
|
|
156
|
+
eventData = line.slice(6); // 移除 'data: ' 前缀
|
|
157
|
+
break;
|
|
158
|
+
} else if (line.trim() && !line.startsWith(":")) {
|
|
159
|
+
// 如果不是注释行也不是 data 行,可能就是直接的数据
|
|
160
|
+
eventData = line;
|
|
161
|
+
break;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (eventData.trim()) {
|
|
166
|
+
try {
|
|
167
|
+
const data = JSON.parse(eventData);
|
|
168
|
+
setStreamingResponse(data);
|
|
169
|
+
} catch (e) {
|
|
170
|
+
console.error("Failed to parse SSE data:", eventData, e);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
close() {
|
|
175
|
+
// 流结束,无需额外处理
|
|
176
|
+
console.log("Stream closed");
|
|
177
|
+
},
|
|
178
|
+
abort(reason) {
|
|
179
|
+
console.log("Stream aborted:", reason);
|
|
180
|
+
},
|
|
181
|
+
})
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
setTestResult({
|
|
185
|
+
success: true,
|
|
186
|
+
response: "", // 响应内容已经通过 streamingResponse 显示
|
|
187
|
+
timestamp: new Date().toISOString(),
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
// 添加到模型名称历史记录
|
|
191
|
+
addToModelNameHistory(formData.model_name);
|
|
192
|
+
|
|
193
|
+
// 保存配置到 localStorage
|
|
194
|
+
saveConfigToLocalStorage({
|
|
195
|
+
model_name: formData.model_name,
|
|
196
|
+
provider: formData.provider,
|
|
197
|
+
base_url: formData.base_url,
|
|
198
|
+
token: formData.token,
|
|
199
|
+
messages: formData.messages,
|
|
200
|
+
});
|
|
201
|
+
} catch (error: any) {
|
|
202
|
+
setTestResult({
|
|
203
|
+
success: false,
|
|
204
|
+
error: error.message || "测试失败",
|
|
205
|
+
timestamp: new Date().toISOString(),
|
|
206
|
+
});
|
|
207
|
+
} finally {
|
|
208
|
+
setIsTesting(false);
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
if (!isOpen) return null;
|
|
213
|
+
|
|
214
|
+
return (
|
|
215
|
+
<div className="fixed inset-0 bg-black/30 backdrop-blur-sm flex items-center justify-center z-50">
|
|
216
|
+
<div className="bg-white rounded-2xl w-full max-w-4xl mx-4 flex flex-col overflow-hidden" style={{ height: "85vh" }}>
|
|
217
|
+
<div className="px-6 py-5 bg-white/80 backdrop-blur-sm border-b border-gray-200 flex items-start justify-between">
|
|
218
|
+
<div>
|
|
219
|
+
<h2 className="text-lg font-semibold text-gray-800">模型测试器</h2>
|
|
220
|
+
<p className="text-sm text-gray-500 mt-1">测试您的 LLM 模型配置是否正确工作</p>
|
|
221
|
+
</div>
|
|
222
|
+
<button onClick={onClose} className="ml-4 p-2 text-gray-400 hover:text-gray-600 hover:bg-gray-100 rounded-lg transition-colors focus:outline-none">
|
|
223
|
+
<X className="w-5 h-5" />
|
|
224
|
+
</button>
|
|
225
|
+
</div>
|
|
226
|
+
|
|
227
|
+
<div className="flex-1 overflow-y-auto p-6">
|
|
228
|
+
<div className="flex gap-6 h-full">
|
|
229
|
+
{/* 左侧配置栏 */}
|
|
230
|
+
<div className="w-80 flex-shrink-0">
|
|
231
|
+
<h3 className="text-md font-medium text-gray-700 mb-4">API 配置</h3>
|
|
232
|
+
<div className="space-y-4">
|
|
233
|
+
<div>
|
|
234
|
+
<label className="block text-sm font-medium text-gray-700 mb-2">提供商</label>
|
|
235
|
+
<select
|
|
236
|
+
value={formData.provider || "openai"}
|
|
237
|
+
onChange={(e) => handleInputChange("provider", e.target.value)}
|
|
238
|
+
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-white"
|
|
239
|
+
>
|
|
240
|
+
<option value="openai">OpenAI</option>
|
|
241
|
+
</select>
|
|
242
|
+
</div>
|
|
243
|
+
<div>
|
|
244
|
+
<label className="block text-sm font-medium text-gray-700 mb-2">Base URL</label>
|
|
245
|
+
<input
|
|
246
|
+
type="text"
|
|
247
|
+
value={formData.base_url || ""}
|
|
248
|
+
onChange={(e) => handleInputChange("base_url", e.target.value)}
|
|
249
|
+
placeholder="https://api.openai.com/v1"
|
|
250
|
+
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
|
251
|
+
/>
|
|
252
|
+
</div>
|
|
253
|
+
<div>
|
|
254
|
+
<label className="block text-sm font-medium text-gray-700 mb-2">API Token</label>
|
|
255
|
+
<input
|
|
256
|
+
type="password"
|
|
257
|
+
value={formData.token || ""}
|
|
258
|
+
onChange={(e) => handleInputChange("token", e.target.value)}
|
|
259
|
+
placeholder="您的 API Token"
|
|
260
|
+
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
|
261
|
+
/>
|
|
262
|
+
</div>
|
|
263
|
+
</div>
|
|
264
|
+
</div>
|
|
265
|
+
|
|
266
|
+
{/* 右侧内容栏 */}
|
|
267
|
+
<div className="flex-1 flex flex-col">
|
|
268
|
+
{/* 模型名称和历史记录 */}
|
|
269
|
+
<div className="mb-6">
|
|
270
|
+
<h3 className="text-md font-medium text-gray-700 mb-4">模型配置</h3>
|
|
271
|
+
<div className="relative">
|
|
272
|
+
<label className="block text-sm font-medium text-gray-700 mb-2">模型名称</label>
|
|
273
|
+
<div className="relative">
|
|
274
|
+
<input
|
|
275
|
+
type="text"
|
|
276
|
+
value={formData.model_name}
|
|
277
|
+
onChange={(e) => handleInputChange("model_name", e.target.value)}
|
|
278
|
+
onFocus={() => setShowHistory(true)}
|
|
279
|
+
onBlur={() => setTimeout(() => setShowHistory(false), 200)}
|
|
280
|
+
placeholder="例如: gpt-4, claude-3-sonnet"
|
|
281
|
+
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
|
282
|
+
/>
|
|
283
|
+
{modelNameHistory.length > 0 && (
|
|
284
|
+
<button type="button" onClick={() => setShowHistory(!showHistory)} className="absolute right-2 top-2 text-gray-400 hover:text-gray-600">
|
|
285
|
+
▼
|
|
286
|
+
</button>
|
|
287
|
+
)}
|
|
288
|
+
</div>
|
|
289
|
+
{showHistory && modelNameHistory.length > 0 && (
|
|
290
|
+
<div className="absolute z-10 w-full mt-1 bg-white border border-gray-300 rounded-lg shadow-lg max-h-40 overflow-y-auto">
|
|
291
|
+
{modelNameHistory.map((name, index) => (
|
|
292
|
+
<div
|
|
293
|
+
key={index}
|
|
294
|
+
className="px-3 py-2 hover:bg-gray-100 cursor-pointer text-sm"
|
|
295
|
+
onClick={() => {
|
|
296
|
+
handleInputChange("model_name", name);
|
|
297
|
+
setShowHistory(false);
|
|
298
|
+
}}
|
|
299
|
+
>
|
|
300
|
+
{name}
|
|
301
|
+
</div>
|
|
302
|
+
))}
|
|
303
|
+
</div>
|
|
304
|
+
)}
|
|
305
|
+
</div>
|
|
306
|
+
</div>
|
|
307
|
+
|
|
308
|
+
{/* 消息配置 */}
|
|
309
|
+
<div className="mb-6">
|
|
310
|
+
<div className="flex items-center justify-between mb-4">
|
|
311
|
+
<h3 className="text-md font-medium text-gray-700">测试消息</h3>
|
|
312
|
+
<button onClick={addMessage} className="px-3 py-1 text-sm bg-blue-50 text-blue-600 rounded-lg hover:bg-blue-100 transition-colors">
|
|
313
|
+
添加消息
|
|
314
|
+
</button>
|
|
315
|
+
</div>
|
|
316
|
+
<div className="space-y-3">
|
|
317
|
+
{formData.messages.map((message, index) => (
|
|
318
|
+
<div key={index} className="flex gap-3 items-start">
|
|
319
|
+
<select
|
|
320
|
+
value={(message as any).role || "user"}
|
|
321
|
+
onChange={(e) => handleMessageChange(index, "role", e.target.value)}
|
|
322
|
+
className="px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent min-w-24"
|
|
323
|
+
>
|
|
324
|
+
<option value="system">系统</option>
|
|
325
|
+
<option value="user">用户</option>
|
|
326
|
+
<option value="assistant">助手</option>
|
|
327
|
+
</select>
|
|
328
|
+
<textarea
|
|
329
|
+
value={(message as any).content || ""}
|
|
330
|
+
onChange={(e) => handleMessageChange(index, "content", e.target.value)}
|
|
331
|
+
placeholder="输入消息内容..."
|
|
332
|
+
rows={2}
|
|
333
|
+
className="flex-1 px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent resize-none"
|
|
334
|
+
/>
|
|
335
|
+
{formData.messages.length > 1 && (
|
|
336
|
+
<button onClick={() => removeMessage(index)} className="px-3 py-2 text-red-500 hover:text-red-700 transition-colors">
|
|
337
|
+
删除
|
|
338
|
+
</button>
|
|
339
|
+
)}
|
|
340
|
+
</div>
|
|
341
|
+
))}
|
|
342
|
+
</div>
|
|
343
|
+
</div>
|
|
344
|
+
|
|
345
|
+
{/* 测试按钮 */}
|
|
346
|
+
<div className="mb-6">
|
|
347
|
+
<button
|
|
348
|
+
onClick={testModel}
|
|
349
|
+
disabled={isTesting || !formData.model_name || !formData.token}
|
|
350
|
+
className="w-full px-4 py-3 bg-blue-500 text-white rounded-lg hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed transition-colors flex items-center justify-center gap-2"
|
|
351
|
+
>
|
|
352
|
+
{isTesting ? (
|
|
353
|
+
<>
|
|
354
|
+
<div className="animate-spin rounded-full h-4 w-4 border-2 border-white border-t-transparent"></div>
|
|
355
|
+
测试中...
|
|
356
|
+
</>
|
|
357
|
+
) : (
|
|
358
|
+
"开始测试"
|
|
359
|
+
)}
|
|
360
|
+
</button>
|
|
361
|
+
</div>
|
|
362
|
+
|
|
363
|
+
{/* 测试结果 */}
|
|
364
|
+
{(testResult || streamingResponse) && (
|
|
365
|
+
<div className="flex-1">
|
|
366
|
+
<h3 className="text-md font-medium text-gray-700 mb-4">测试结果</h3>
|
|
367
|
+
<div className="bg-gray-50 rounded-lg p-4 h-full overflow-y-auto">
|
|
368
|
+
{testResult && (
|
|
369
|
+
<div className={`mb-3 text-sm ${testResult.success ? "text-green-600" : "text-red-600"}`}>
|
|
370
|
+
{testResult.success ? "✓ 测试成功" : `✗ 测试失败: ${testResult.error}`}
|
|
371
|
+
{testResult.timestamp && <span className="text-gray-500 ml-2">{new Date(testResult.timestamp).toLocaleString()}</span>}
|
|
372
|
+
</div>
|
|
373
|
+
)}
|
|
374
|
+
{streamingResponse && (
|
|
375
|
+
<div className="text-sm text-gray-700 space-y-3">
|
|
376
|
+
<div>
|
|
377
|
+
<strong>响应内容:</strong>
|
|
378
|
+
<div className="mt-2 p-3 bg-white rounded border font-mono whitespace-pre-wrap">
|
|
379
|
+
{streamingResponse.data?.content || streamingResponse.content || "无内容"}
|
|
380
|
+
</div>
|
|
381
|
+
</div>
|
|
382
|
+
{streamingResponse.data?.response_metadata && (
|
|
383
|
+
<div>
|
|
384
|
+
<strong>响应元数据:</strong>
|
|
385
|
+
<div className="mt-2 p-3 bg-white rounded border">
|
|
386
|
+
<div className="space-y-1 text-xs">
|
|
387
|
+
<div>
|
|
388
|
+
<span className="font-medium">模型提供商:</span> {streamingResponse.data.response_metadata.model_provider}
|
|
389
|
+
</div>
|
|
390
|
+
<div>
|
|
391
|
+
<span className="font-medium">模型名称:</span> {streamingResponse.data.response_metadata.model_name}
|
|
392
|
+
</div>
|
|
393
|
+
<div>
|
|
394
|
+
<span className="font-medium">Token 使用:</span> {streamingResponse.data.response_metadata.usage?.total_tokens || 0} (输入:{" "}
|
|
395
|
+
{streamingResponse.data.response_metadata.usage?.prompt_tokens || 0}, 输出:{" "}
|
|
396
|
+
{streamingResponse.data.response_metadata.usage?.completion_tokens || 0})
|
|
397
|
+
</div>
|
|
398
|
+
<div>
|
|
399
|
+
<span className="font-medium">完成原因:</span> {streamingResponse.data.response_metadata.finish_reason}
|
|
400
|
+
</div>
|
|
401
|
+
</div>
|
|
402
|
+
</div>
|
|
403
|
+
</div>
|
|
404
|
+
)}
|
|
405
|
+
{streamingResponse.id && (
|
|
406
|
+
<div>
|
|
407
|
+
<strong>响应 ID:</strong>
|
|
408
|
+
<div className="mt-2 p-3 bg-white rounded border font-mono text-xs">{streamingResponse.id}</div>
|
|
409
|
+
</div>
|
|
410
|
+
)}
|
|
411
|
+
</div>
|
|
412
|
+
)}
|
|
413
|
+
</div>
|
|
414
|
+
</div>
|
|
415
|
+
)}
|
|
416
|
+
</div>
|
|
417
|
+
</div>
|
|
418
|
+
</div>
|
|
419
|
+
</div>
|
|
420
|
+
</div>
|
|
421
|
+
);
|
|
422
|
+
};
|
|
423
|
+
|
|
424
|
+
export default ModelTesterPopup;
|
|
@@ -483,7 +483,6 @@ export const show_form = createUITool({
|
|
|
483
483
|
SubmitButton: CustomSubmitButton,
|
|
484
484
|
},
|
|
485
485
|
};
|
|
486
|
-
|
|
487
486
|
return (
|
|
488
487
|
<div className="p-4 bg-white rounded-lg border border-gray-200">
|
|
489
488
|
<div className="flex items-center gap-1.5 text-gray-700 mb-3 font-bold">
|
|
@@ -493,7 +492,7 @@ export const show_form = createUITool({
|
|
|
493
492
|
<ErrorBoundary>
|
|
494
493
|
<Form
|
|
495
494
|
readonly={tool.state === "done"}
|
|
496
|
-
schema={formSchema}
|
|
495
|
+
schema={formSchema || {}}
|
|
497
496
|
formData={output}
|
|
498
497
|
onSubmit={(data: IChangeEvent<any>) => handleSubmit(data.formData)}
|
|
499
498
|
validator={validator}
|
package/tsconfig.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{U as a,C as n}from"./mermaid.core-C74BWk1B.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-BxOW4a9d.js";import{_ as i}from"./mermaid.core-C74BWk1B.js";import"./index-BLuMPbxE.js";import"./chunk-FMBD7UC4-BWhkxmUU.js";import"./chunk-55IACEB6-HRZ91JWG.js";import"./chunk-QN33PNHL-CuRPvllX.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-BxOW4a9d.js";import{_ as i}from"./mermaid.core-C74BWk1B.js";import"./index-BLuMPbxE.js";import"./chunk-FMBD7UC4-BWhkxmUU.js";import"./chunk-55IACEB6-HRZ91JWG.js";import"./chunk-QN33PNHL-CuRPvllX.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-BLuMPbxE.js";var e=4;function a(o){return r(o,e)}export{a as c};
|