@aiscene/aiserver 1.2.5 → 1.2.6
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/config/index.d.ts.map +1 -1
- package/dist/config/index.js +6 -49
- package/dist/config/index.js.map +1 -1
- package/dist/config/schema.d.ts +1 -7
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/debug/websocket-server.d.ts.map +1 -1
- package/dist/debug/websocket-server.js +3 -8
- package/dist/debug/websocket-server.js.map +1 -1
- package/dist/task/scheduler.d.ts.map +1 -1
- package/dist/task/scheduler.js +18 -17
- package/dist/task/scheduler.js.map +1 -1
- package/dist/web/debug-page.d.ts.map +1 -1
- package/dist/web/debug-page.js +325 -196
- package/dist/web/debug-page.js.map +1 -1
- package/dist/web/server.d.ts +3 -0
- package/dist/web/server.d.ts.map +1 -1
- package/dist/web/server.js +313 -203
- package/dist/web/server.js.map +1 -1
- package/package.json +1 -1
package/dist/web/debug-page.js
CHANGED
|
@@ -38,7 +38,7 @@ html,body{overflow:hidden;margin:0;padding:0}
|
|
|
38
38
|
.debug-terminal-body .log-error{color:#dc2626}
|
|
39
39
|
.debug-terminal-body .log-success{color:#16a34a}
|
|
40
40
|
/* 实时报告面板 - 右侧独立列(白色主题) */
|
|
41
|
-
.debug-report-panel{width:
|
|
41
|
+
.debug-report-panel{width:640px;min-width:520px;max-width:800px;border-left:1px solid #e2e8f0;display:flex;flex-direction:column;background:#fff}
|
|
42
42
|
.debug-report-panel.hidden{display:none}
|
|
43
43
|
.debug-report-header{padding:8px 16px;border-bottom:1px solid #e2e8f0;display:flex;align-items:center;justify-content:space-between;background:#fff}
|
|
44
44
|
.debug-report-header .title{font-size:11px;color:#1e293b;display:flex;align-items:center;gap:6px;font-weight:600}
|
|
@@ -88,10 +88,11 @@ html,body{overflow:hidden;margin:0;padding:0}
|
|
|
88
88
|
.rrd-progress-fill{height:100%;background:#2563eb;border-radius:2px;transition:width .3s}
|
|
89
89
|
.rrd-empty{text-align:center;color:#94a3b8;padding:40px 20px;font-size:13px}
|
|
90
90
|
/* 截图展示 */
|
|
91
|
-
.rrd-screenshot{margin-top:8px;border-radius:6px;overflow:hidden;border:1px solid #e2e8f0;background:#fff}
|
|
91
|
+
.rrd-screenshot{margin-top:8px;border-radius:6px;overflow:hidden;border:1px solid #e2e8f0;background:#fff;position:relative}
|
|
92
92
|
.rrd-screenshot img{width:100%;display:block;cursor:pointer;transition:opacity .2s}
|
|
93
93
|
.rrd-screenshot img:hover{opacity:0.9}
|
|
94
94
|
.rrd-screenshot-label{font-size:10px;color:#64748b;padding:4px 8px;text-align:center}
|
|
95
|
+
.rrd-screenshot-canvas{position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none}
|
|
95
96
|
.debug-statusbar{height:28px;background:#fff;border-top:1px solid #e2e8f0;display:flex;align-items:center;justify-content:space-between;padding:0 16px}
|
|
96
97
|
.debug-statusbar .left{display:flex;align-items:center;gap:12px;font-size:11px;color:#64748b}
|
|
97
98
|
.debug-statusbar .right{display:flex;align-items:center;gap:8px}
|
|
@@ -163,27 +164,20 @@ html,body{overflow:hidden;margin:0;padding:0}
|
|
|
163
164
|
.btn-blue:hover{background:#1d4ed8}
|
|
164
165
|
|
|
165
166
|
/* AI生成脚本对话框样式 */
|
|
166
|
-
.ai-generator-panel{
|
|
167
|
-
.ai-generator-
|
|
168
|
-
.ai-generator-header{padding:10px 16px;display:flex;align-items:center;justify-content:space-between;cursor:pointer;background:#f8fafc}
|
|
167
|
+
.ai-generator-panel{border-top:1px solid #e2e8f0;background:#fff;display:flex;flex-direction:column}
|
|
168
|
+
.ai-generator-header{padding:8px 16px;display:flex;align-items:center;justify-content:space-between;cursor:pointer;background:#f8fafc;border-bottom:1px solid #e2e8f0}
|
|
169
169
|
.ai-generator-header:hover{background:#f1f5f9}
|
|
170
170
|
.ai-generator-header .title{font-size:13px;font-weight:600;color:#1e293b;display:flex;align-items:center;gap:8px}
|
|
171
171
|
.ai-generator-header .toggle-icon{font-size:12px;color:#64748b;transition:transform .2s}
|
|
172
|
-
.ai-generator-panel.
|
|
173
|
-
.ai-generator-
|
|
174
|
-
.ai-generator-
|
|
175
|
-
.ai-generator-input-row
|
|
172
|
+
.ai-generator-panel.collapsed .ai-generator-body{display:none}
|
|
173
|
+
.ai-generator-panel.collapsed .toggle-icon{transform:rotate(180deg)}
|
|
174
|
+
.ai-generator-body{padding:12px 16px;overflow-y:auto;flex:1;min-height:0}
|
|
175
|
+
.ai-generator-input-row{display:flex;gap:8px;margin-bottom:8px}
|
|
176
|
+
.ai-generator-input-row input{flex:1;padding:8px 12px;border:1px solid #cbd5e1;border-radius:6px;font-size:13px;color:#1e293b;outline:none}
|
|
176
177
|
.ai-generator-input-row input:focus{border-color:#38bdf8}
|
|
177
|
-
.ai-generator-input-row button{padding:
|
|
178
|
+
.ai-generator-input-row button{padding:8px 16px;background:#2563eb;color:#fff;border:none;border-radius:6px;font-size:13px;font-weight:600;cursor:pointer;transition:background .2s;white-space:nowrap}
|
|
178
179
|
.ai-generator-input-row button:hover{background:#1d4ed8}
|
|
179
180
|
.ai-generator-input-row button:disabled{background:#94a3b8;cursor:not-allowed}
|
|
180
|
-
.ai-generator-hint{font-size:11px;color:#94a3b8;margin-bottom:12px}
|
|
181
|
-
.ai-generator-result{border:1px solid #e2e8f0;border-radius:8px;overflow:hidden}
|
|
182
|
-
.ai-generator-result-header{padding:8px 12px;background:#f8fafc;border-bottom:1px solid #e2e8f0;font-size:12px;font-weight:600;color:#64748b;display:flex;align-items:center;justify-content:space-between}
|
|
183
|
-
.ai-generator-result-body{padding:12px;font-size:12px;line-height:1.6;color:#1e293b;white-space:pre-wrap;font-family:'SF Mono',Monaco,Consolas,monospace;max-height:200px;overflow-y:auto;background:#fff}
|
|
184
|
-
.ai-generator-result-actions{display:flex;gap:8px;padding:8px 12px;background:#f8fafc;border-top:1px solid #e2e8f0}
|
|
185
|
-
.ai-generator-loading{display:flex;align-items:center;gap:8px;padding:12px;color:#64748b;font-size:12px}
|
|
186
|
-
.ai-generator-loading .spinner{width:16px;height:16px;border:2px solid #e2e8f0;border-top-color:#38bdf8;border-radius:50%;animation:spin 1s linear infinite}
|
|
187
181
|
</style>
|
|
188
182
|
|
|
189
183
|
<div class="debug-page" id="debug-page-content">
|
|
@@ -240,11 +234,6 @@ html,body{overflow:hidden;margin:0;padding:0}
|
|
|
240
234
|
</div>
|
|
241
235
|
</div>
|
|
242
236
|
|
|
243
|
-
<div class="checkbox-group">
|
|
244
|
-
<input type="checkbox" id="debug-skipAppium" checked>
|
|
245
|
-
<label for="debug-skipAppium">跳过 Appium 服务</label>
|
|
246
|
-
</div>
|
|
247
|
-
|
|
248
237
|
<div class="form-group">
|
|
249
238
|
<label>登录用户名(选填)</label>
|
|
250
239
|
<input id="debug-loginUser" placeholder="输入用户名">
|
|
@@ -270,7 +259,6 @@ html,body{overflow:hidden;margin:0;padding:0}
|
|
|
270
259
|
<div class="actions">
|
|
271
260
|
<button class="btn-icon btn-ghost" onclick="openCaseSelector()">📁 选择用例</button>
|
|
272
261
|
<button class="btn-icon btn-ghost" onclick="openSaveCaseDialog()">💾 保存用例</button>
|
|
273
|
-
<button class="btn-icon btn-ghost" onclick="handleGenerateScript()">⚡ 生成脚本</button>
|
|
274
262
|
<button class="btn-icon btn-ghost" onclick="handleClearEditor()">🗑 清空</button>
|
|
275
263
|
</div>
|
|
276
264
|
</div>
|
|
@@ -280,6 +268,24 @@ html,body{overflow:hidden;margin:0;padding:0}
|
|
|
280
268
|
<textarea id="debug-script" placeholder="// 在此编写调试脚本\n\n" spellcheck="false"></textarea>
|
|
281
269
|
</div>
|
|
282
270
|
|
|
271
|
+
<!-- AI脚本生成器 - 内嵌在脚本编辑器下方 -->
|
|
272
|
+
<div class="ai-generator-panel" id="ai-generator-panel">
|
|
273
|
+
<div class="ai-generator-header" onclick="toggleAiGenerator()">
|
|
274
|
+
<div class="title">
|
|
275
|
+
<span style="font-size:14px">⚡</span>
|
|
276
|
+
<span>AI 脚本生成器</span>
|
|
277
|
+
<span style="font-size:11px;color:#94a3b8;font-weight:400">- 输入自然语言描述,生成脚本直接写入编辑器</span>
|
|
278
|
+
</div>
|
|
279
|
+
<div class="toggle-icon">▲</div>
|
|
280
|
+
</div>
|
|
281
|
+
<div class="ai-generator-body">
|
|
282
|
+
<div class="ai-generator-input-row">
|
|
283
|
+
<input type="text" id="ai-generator-input" placeholder="输入自然语言描述,例如:点击搜索按钮,输入'京东',然后点击搜索" onkeypress="if(event.key==='Enter')handleAiGenerate()">
|
|
284
|
+
<button id="ai-generator-btn" onclick="handleAiGenerate()">⚡ 生成</button>
|
|
285
|
+
</div>
|
|
286
|
+
</div>
|
|
287
|
+
</div>
|
|
288
|
+
|
|
283
289
|
<!-- 终端面板 -->
|
|
284
290
|
<div class="debug-terminal" id="debug-terminal">
|
|
285
291
|
<div class="debug-terminal-header">
|
|
@@ -310,28 +316,6 @@ html,body{overflow:hidden;margin:0;padding:0}
|
|
|
310
316
|
</div>
|
|
311
317
|
</div>
|
|
312
318
|
|
|
313
|
-
<!-- AI生成脚本对话框 -->
|
|
314
|
-
<div class="ai-generator-panel" id="ai-generator-panel">
|
|
315
|
-
<div class="ai-generator-header" onclick="toggleAiGenerator()">
|
|
316
|
-
<div class="title">
|
|
317
|
-
<span style="font-size:16px">⚡</span>
|
|
318
|
-
<span>AI 脚本生成器</span>
|
|
319
|
-
<span style="font-size:11px;color:#94a3b8;font-weight:400">- 输入自然语言描述生成自动化脚本</span>
|
|
320
|
-
</div>
|
|
321
|
-
<div class="toggle-icon">▲</div>
|
|
322
|
-
</div>
|
|
323
|
-
<div class="ai-generator-body">
|
|
324
|
-
<div class="ai-generator-input-row">
|
|
325
|
-
<input type="text" id="ai-generator-input" placeholder="输入自然语言描述,例如:点击搜索按钮,输入"京东",然后点击搜索" onkeypress="if(event.key==='Enter')handleAiGenerate()">
|
|
326
|
-
<button id="ai-generator-btn" onclick="handleAiGenerate()">⚡ 生成脚本</button>
|
|
327
|
-
</div>
|
|
328
|
-
<div class="ai-generator-hint">
|
|
329
|
-
💡 提示:描述越详细,生成的脚本越准确。支持描述页面操作、数据验证、流程步骤等
|
|
330
|
-
</div>
|
|
331
|
-
<div id="ai-generator-result"></div>
|
|
332
|
-
</div>
|
|
333
|
-
</div>
|
|
334
|
-
|
|
335
319
|
<!-- 实时报告面板 - 右侧独立列 -->
|
|
336
320
|
<div class="debug-report-panel hidden" id="debug-report-panel">
|
|
337
321
|
<div class="debug-report-header">
|
|
@@ -420,7 +404,7 @@ html,body{overflow:hidden;margin:0;padding:0}
|
|
|
420
404
|
</div>
|
|
421
405
|
<div class="modal-footer">
|
|
422
406
|
<button class="btn btn-cancel" onclick="closeSaveCaseDialog()">取消</button>
|
|
423
|
-
<button class="btn btn-primary" onclick="handleSaveCase()">保存</button>
|
|
407
|
+
<button class="btn btn-primary" id="save-case-submit-btn" onclick="handleSaveCase()">保存</button>
|
|
424
408
|
</div>
|
|
425
409
|
</div>
|
|
426
410
|
</div>
|
|
@@ -953,14 +937,24 @@ function renderStepDetail() {
|
|
|
953
937
|
if(task.usage) {
|
|
954
938
|
html += '<div style="margin-top:4px;font-size:10px;color:#64748b">模型: ' + escHtml(task.usage.model_name || task.usage.model_description || '-') + ' | Token: ' + (task.usage.prompt_tokens || 0) + '+' + (task.usage.completion_tokens || 0) + '=' + (task.usage.total_tokens || 0) + '</div>';
|
|
955
939
|
}
|
|
956
|
-
// 截图展示 -
|
|
940
|
+
// 截图展示 - 图片可点击在新窗口打开,并在图片上绘制定位矩形
|
|
957
941
|
if(task.uiContext && task.uiContext.screenshot) {
|
|
958
942
|
const ss = task.uiContext.screenshot;
|
|
943
|
+
// 提取 element 定位信息(从 task.output.element 或 task.log 中)
|
|
944
|
+
let elementInfo = null;
|
|
945
|
+
if(task.output && task.output.element) {
|
|
946
|
+
elementInfo = task.output.element;
|
|
947
|
+
} else if(task.log && task.log.data && task.log.data.element) {
|
|
948
|
+
elementInfo = task.log.data.element;
|
|
949
|
+
}
|
|
950
|
+
const elementJson = elementInfo ? escHtml(JSON.stringify(elementInfo)) : '';
|
|
959
951
|
if(ss.base64) {
|
|
960
952
|
const sizeLabel = ss.width ? ss.width + "x" + (ss.height || "") : "";
|
|
961
|
-
|
|
953
|
+
const canvasId = 'ss-canvas-' + index + '-' + (exec.tasks || []).indexOf(task);
|
|
954
|
+
html += '<div class="rrd-screenshot"><img src="' + escHtml(ss.base64) + '" title="点击查看大图" data-canvas-id="' + canvasId + '" data-element="' + elementJson + '" onload="drawElementRect(this)" /><canvas id="' + canvasId + '" class="rrd-screenshot-canvas"></canvas><div class="rrd-screenshot-label">UI截图 ' + sizeLabel + '</div></div>';
|
|
962
955
|
} else if(ss.url) {
|
|
963
|
-
|
|
956
|
+
const canvasId = 'ss-canvas-' + index + '-' + (exec.tasks || []).indexOf(task);
|
|
957
|
+
html += '<div class="rrd-screenshot"><img src="' + escHtml(ss.url) + '" title="点击查看大图" data-canvas-id="' + canvasId + '" data-element="' + elementJson + '" onload="drawElementRect(this)" /><canvas id="' + canvasId + '" class="rrd-screenshot-canvas"></canvas><div class="rrd-screenshot-label">UI截图</div></div>';
|
|
964
958
|
}
|
|
965
959
|
}
|
|
966
960
|
html += '</div></div>';
|
|
@@ -976,6 +970,112 @@ function openReport() {
|
|
|
976
970
|
}
|
|
977
971
|
}
|
|
978
972
|
|
|
973
|
+
// 在截图上绘制元素定位矩形
|
|
974
|
+
function drawElementRect(imgEl) {
|
|
975
|
+
const canvasId = imgEl.dataset.canvasId;
|
|
976
|
+
const elementJsonStr = imgEl.dataset.element;
|
|
977
|
+
if(!elementJsonStr) return;
|
|
978
|
+
let element;
|
|
979
|
+
try { element = JSON.parse(elementJsonStr); } catch(e) { return; }
|
|
980
|
+
if(!element.rect && !element.center) return;
|
|
981
|
+
|
|
982
|
+
const canvas = document.getElementById(canvasId);
|
|
983
|
+
if(!canvas) return;
|
|
984
|
+
|
|
985
|
+
// 等待图片加载完成后获取显示尺寸
|
|
986
|
+
const draw = () => {
|
|
987
|
+
const displayW = imgEl.clientWidth;
|
|
988
|
+
const displayH = imgEl.clientHeight;
|
|
989
|
+
if(!displayW || !displayH) return;
|
|
990
|
+
|
|
991
|
+
// 获取原始截图尺寸(dpr相关)
|
|
992
|
+
const dpr = element.dpr || 1;
|
|
993
|
+
const origW = imgEl.naturalWidth / dpr;
|
|
994
|
+
const origH = imgEl.naturalHeight / dpr;
|
|
995
|
+
|
|
996
|
+
// 设置 canvas 尺寸与图片显示区域一致
|
|
997
|
+
canvas.width = displayW;
|
|
998
|
+
canvas.height = displayH;
|
|
999
|
+
canvas.style.width = displayW + 'px';
|
|
1000
|
+
canvas.style.height = displayH + 'px';
|
|
1001
|
+
|
|
1002
|
+
const ctx = canvas.getContext('2d');
|
|
1003
|
+
if(!ctx) return;
|
|
1004
|
+
|
|
1005
|
+
// 缩放比例:从原始坐标到显示坐标
|
|
1006
|
+
const scaleX = displayW / origW;
|
|
1007
|
+
const scaleY = displayH / origH;
|
|
1008
|
+
|
|
1009
|
+
if(element.rect) {
|
|
1010
|
+
const r = element.rect;
|
|
1011
|
+
const x = r.left * scaleX;
|
|
1012
|
+
const y = r.top * scaleY;
|
|
1013
|
+
const w = r.width * scaleX;
|
|
1014
|
+
const h = r.height * scaleY;
|
|
1015
|
+
|
|
1016
|
+
// 半透明填充
|
|
1017
|
+
ctx.fillStyle = 'rgba(37, 99, 235, 0.12)';
|
|
1018
|
+
ctx.fillRect(x, y, w, h);
|
|
1019
|
+
|
|
1020
|
+
// 边框
|
|
1021
|
+
ctx.strokeStyle = '#2563eb';
|
|
1022
|
+
ctx.lineWidth = 2.5;
|
|
1023
|
+
ctx.strokeRect(x, y, w, h);
|
|
1024
|
+
|
|
1025
|
+
// 角标(左上角小方块)
|
|
1026
|
+
const cornerSize = 6;
|
|
1027
|
+
ctx.fillStyle = '#2563eb';
|
|
1028
|
+
ctx.fillRect(x - 1, y - 1, cornerSize, cornerSize);
|
|
1029
|
+
ctx.fillRect(x + w - cornerSize + 1, y - 1, cornerSize, cornerSize);
|
|
1030
|
+
ctx.fillRect(x - 1, y + h - cornerSize + 1, cornerSize, cornerSize);
|
|
1031
|
+
ctx.fillRect(x + w - cornerSize + 1, y + h - cornerSize + 1, cornerSize, cornerSize);
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
if(element.center) {
|
|
1035
|
+
const cx = element.center[0] * scaleX;
|
|
1036
|
+
const cy = element.center[1] * scaleY;
|
|
1037
|
+
|
|
1038
|
+
// 十字标记
|
|
1039
|
+
ctx.strokeStyle = '#dc2626';
|
|
1040
|
+
ctx.lineWidth = 2;
|
|
1041
|
+
ctx.beginPath();
|
|
1042
|
+
ctx.moveTo(cx - 10, cy);
|
|
1043
|
+
ctx.lineTo(cx + 10, cy);
|
|
1044
|
+
ctx.moveTo(cx, cy - 10);
|
|
1045
|
+
ctx.lineTo(cx, cy + 10);
|
|
1046
|
+
ctx.stroke();
|
|
1047
|
+
|
|
1048
|
+
// 中心圆点
|
|
1049
|
+
ctx.fillStyle = '#dc2626';
|
|
1050
|
+
ctx.beginPath();
|
|
1051
|
+
ctx.arc(cx, cy, 4, 0, Math.PI * 2);
|
|
1052
|
+
ctx.fill();
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
// 标签描述
|
|
1056
|
+
if(element.description) {
|
|
1057
|
+
const labelX = element.rect ? element.rect.left * scaleX : (element.center ? element.center[0] * scaleX - 30 : 4);
|
|
1058
|
+
const labelY = element.rect ? element.rect.top * scaleY - 6 : (element.center ? element.center[1] * scaleY - 16 : 4);
|
|
1059
|
+
ctx.font = '600 11px -apple-system, sans-serif';
|
|
1060
|
+
const textW = ctx.measureText(element.description).width + 10;
|
|
1061
|
+
// 标签背景
|
|
1062
|
+
ctx.fillStyle = '#2563eb';
|
|
1063
|
+
const labelDrawY = labelY > 18 ? labelY - 14 : labelY + (element.rect ? element.rect.height * scaleY + 14 : 0);
|
|
1064
|
+
ctx.fillRect(labelX, labelDrawY, textW, 16);
|
|
1065
|
+
// 标签文字
|
|
1066
|
+
ctx.fillStyle = '#fff';
|
|
1067
|
+
ctx.fillText(element.description, labelX + 5, labelDrawY + 12);
|
|
1068
|
+
}
|
|
1069
|
+
};
|
|
1070
|
+
|
|
1071
|
+
// 确保 img 已加载
|
|
1072
|
+
if(imgEl.complete && imgEl.naturalWidth > 0) {
|
|
1073
|
+
draw();
|
|
1074
|
+
} else {
|
|
1075
|
+
imgEl.addEventListener('load', draw);
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
|
|
979
1079
|
// ===== 历史记录 =====
|
|
980
1080
|
function saveToHistory(status) {
|
|
981
1081
|
const item = {
|
|
@@ -1077,17 +1177,26 @@ async function loadFolderTree() {
|
|
|
1077
1177
|
|
|
1078
1178
|
function renderFolderTree() {
|
|
1079
1179
|
const list = document.getElementById('case-folder-list');
|
|
1180
|
+
list.innerHTML = '';
|
|
1080
1181
|
if(caseSelectorState.folders.length === 0) {
|
|
1081
1182
|
list.innerHTML = '<div style="text-align:center;color:#64748b;padding:20px">暂无文件夹</div>';
|
|
1082
1183
|
return;
|
|
1083
1184
|
}
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1185
|
+
// 全部用例
|
|
1186
|
+
const allItem = document.createElement('div');
|
|
1187
|
+
allItem.className = 'folder-item';
|
|
1188
|
+
allItem.style.color = '#22c55e';
|
|
1189
|
+
allItem.innerHTML = '📄 全部用例';
|
|
1190
|
+
allItem.onclick = function() { loadAllCases(); };
|
|
1191
|
+
list.appendChild(allItem);
|
|
1192
|
+
|
|
1193
|
+
caseSelectorState.folders.forEach(function(folder, idx) {
|
|
1194
|
+
const node = buildFolderNode(folder, 0, 'root-' + idx);
|
|
1195
|
+
list.appendChild(node);
|
|
1087
1196
|
});
|
|
1088
1197
|
}
|
|
1089
1198
|
|
|
1090
|
-
function
|
|
1199
|
+
function buildFolderNode(node, level, key) {
|
|
1091
1200
|
const info = node.folder || {};
|
|
1092
1201
|
const folderId = info.folderId || info.id;
|
|
1093
1202
|
const name = (!info.folderName || info.folderName === '未命名文件夹') ? '未分类' : info.folderName;
|
|
@@ -1096,29 +1205,56 @@ function renderFolderNode(node, level, key) {
|
|
|
1096
1205
|
const isActive = caseSelectorState.selectedFolderId === folderId;
|
|
1097
1206
|
const indent = level * 16 + 8;
|
|
1098
1207
|
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
html += '📁 ' + escHtml(name);
|
|
1103
|
-
if(count > 0) html += ' <span style="color:#64748b;font-size:11px">(' + count + ')</span>';
|
|
1104
|
-
html += '</div>';
|
|
1208
|
+
const item = document.createElement('div');
|
|
1209
|
+
item.className = 'folder-item' + (isActive ? ' active' : '');
|
|
1210
|
+
item.style.paddingLeft = indent + 'px';
|
|
1105
1211
|
|
|
1106
1212
|
if(hasChildren) {
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1213
|
+
const arrow = document.createElement('span');
|
|
1214
|
+
arrow.className = 'arrow';
|
|
1215
|
+
arrow.innerHTML = '▶';
|
|
1216
|
+
arrow.onclick = function(e) {
|
|
1217
|
+
e.stopPropagation();
|
|
1218
|
+
arrow.classList.toggle('expanded');
|
|
1219
|
+
const childrenDiv = item.nextElementSibling;
|
|
1220
|
+
if(childrenDiv && childrenDiv.classList.contains('folder-children')) {
|
|
1221
|
+
childrenDiv.style.display = childrenDiv.style.display === 'none' ? '' : 'none';
|
|
1222
|
+
}
|
|
1223
|
+
};
|
|
1224
|
+
item.appendChild(arrow);
|
|
1225
|
+
} else {
|
|
1226
|
+
const spacer = document.createElement('span');
|
|
1227
|
+
spacer.style.cssText = 'width:10px;display:inline-block';
|
|
1228
|
+
item.appendChild(spacer);
|
|
1112
1229
|
}
|
|
1113
|
-
return html;
|
|
1114
|
-
}
|
|
1115
1230
|
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
if(
|
|
1120
|
-
|
|
1231
|
+
const text = document.createTextNode('\u{1F4C1} ' + name);
|
|
1232
|
+
item.appendChild(text);
|
|
1233
|
+
|
|
1234
|
+
if(count > 0) {
|
|
1235
|
+
const countSpan = document.createElement('span');
|
|
1236
|
+
countSpan.style.cssText = 'color:#64748b;font-size:11px';
|
|
1237
|
+
countSpan.textContent = ' (' + count + ')';
|
|
1238
|
+
item.appendChild(countSpan);
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
item.onclick = function() { selectFolder(String(folderId)); };
|
|
1242
|
+
|
|
1243
|
+
// 容器:先放item,再放children
|
|
1244
|
+
const wrapper = document.createDocumentFragment();
|
|
1245
|
+
wrapper.appendChild(item);
|
|
1246
|
+
|
|
1247
|
+
if(hasChildren) {
|
|
1248
|
+
const childrenDiv = document.createElement('div');
|
|
1249
|
+
childrenDiv.className = 'folder-children';
|
|
1250
|
+
childrenDiv.style.display = 'none';
|
|
1251
|
+
node.children.forEach(function(child, idx) {
|
|
1252
|
+
childrenDiv.appendChild(buildFolderNode(child, level + 1, key + '-' + idx));
|
|
1253
|
+
});
|
|
1254
|
+
wrapper.appendChild(childrenDiv);
|
|
1121
1255
|
}
|
|
1256
|
+
|
|
1257
|
+
return wrapper;
|
|
1122
1258
|
}
|
|
1123
1259
|
|
|
1124
1260
|
async function selectFolder(folderId) {
|
|
@@ -1245,15 +1381,22 @@ function confirmCaseSelection() {
|
|
|
1245
1381
|
}
|
|
1246
1382
|
|
|
1247
1383
|
// ===== 保存用例 =====
|
|
1248
|
-
function openSaveCaseDialog() {
|
|
1384
|
+
async function openSaveCaseDialog() {
|
|
1385
|
+
const saveBtn = document.getElementById('save-case-submit-btn');
|
|
1249
1386
|
if(debugState.currentTestCase) {
|
|
1250
1387
|
document.getElementById('save-case-title').textContent = '修改测试用例';
|
|
1251
1388
|
document.getElementById('save-case-name').value = debugState.currentTestCase.configName || '';
|
|
1389
|
+
saveBtn.textContent = '更新';
|
|
1252
1390
|
} else {
|
|
1253
1391
|
document.getElementById('save-case-title').textContent = '保存测试用例';
|
|
1254
1392
|
document.getElementById('save-case-name').value = '';
|
|
1393
|
+
saveBtn.textContent = '保存';
|
|
1255
1394
|
}
|
|
1256
1395
|
document.getElementById('save-case-modal').style.display = '';
|
|
1396
|
+
// 确保文件夹树已加载
|
|
1397
|
+
if(!debugState.folderTree || debugState.folderTree.length === 0) {
|
|
1398
|
+
await loadFolderTree();
|
|
1399
|
+
}
|
|
1257
1400
|
loadSaveFolderOptions();
|
|
1258
1401
|
}
|
|
1259
1402
|
|
|
@@ -1284,6 +1427,10 @@ function loadSaveFolderOptions() {
|
|
|
1284
1427
|
});
|
|
1285
1428
|
}
|
|
1286
1429
|
addFolders(debugState.folderTree, '');
|
|
1430
|
+
// 如果有当前用例,自动选中其所在文件夹
|
|
1431
|
+
if(debugState.currentTestCase && debugState.currentTestCase.folderId) {
|
|
1432
|
+
sel.value = debugState.currentTestCase.folderId;
|
|
1433
|
+
}
|
|
1287
1434
|
}
|
|
1288
1435
|
|
|
1289
1436
|
async function handleSaveCase() {
|
|
@@ -1423,159 +1570,141 @@ function handleClearEditor() {
|
|
|
1423
1570
|
// ===== AI脚本生成器 =====
|
|
1424
1571
|
function toggleAiGenerator() {
|
|
1425
1572
|
const panel = document.getElementById('ai-generator-panel');
|
|
1426
|
-
panel.classList.toggle('
|
|
1573
|
+
panel.classList.toggle('collapsed');
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1576
|
+
// 清理Markdown代码块标记
|
|
1577
|
+
function cleanCodeBlock(str) {
|
|
1578
|
+
var bt = String.fromCharCode(96);
|
|
1579
|
+
var pattern1 = bt+bt+bt+'javascript';
|
|
1580
|
+
var pattern2 = bt+bt+bt;
|
|
1581
|
+
return str.split(pattern1).join('').split(pattern2).join('').trim();
|
|
1427
1582
|
}
|
|
1428
1583
|
|
|
1429
1584
|
async function handleAiGenerate() {
|
|
1430
1585
|
const input = document.getElementById('ai-generator-input');
|
|
1431
1586
|
const btn = document.getElementById('ai-generator-btn');
|
|
1432
|
-
const
|
|
1433
|
-
const naturalLanguage = input.value.trim();
|
|
1587
|
+
const keyword = input.value.trim();
|
|
1434
1588
|
|
|
1435
|
-
if(!
|
|
1436
|
-
|
|
1589
|
+
if (!keyword) {
|
|
1590
|
+
addDebugLog('请输入代码描述', 'warn');
|
|
1437
1591
|
return;
|
|
1438
1592
|
}
|
|
1439
1593
|
|
|
1440
1594
|
// 显示加载状态
|
|
1441
1595
|
btn.disabled = true;
|
|
1442
|
-
btn.innerHTML = '<span class="spinner"></span> 生成中...';
|
|
1443
|
-
|
|
1596
|
+
btn.innerHTML = '<span class="spinner" style="width:14px;height:14px;border:2px solid #fef3c7;border-top-color:#d97706;border-radius:50%;display:inline-block;vertical-align:middle;animation:spin 1s linear infinite"></span> 生成中...';
|
|
1597
|
+
addDebugLog('正在生成脚本...', 'info');
|
|
1444
1598
|
|
|
1445
1599
|
try {
|
|
1446
1600
|
// 获取当前配置信息
|
|
1447
|
-
const runMode = document.getElementById('debug-runMode').value;
|
|
1448
1601
|
const platform = document.getElementById('debug-platform').value;
|
|
1449
|
-
const
|
|
1450
|
-
const
|
|
1451
|
-
const
|
|
1602
|
+
const packageName = getPackageName();
|
|
1603
|
+
const testUrl = document.getElementById('debug-url').value || '';
|
|
1604
|
+
const erp = 'system';
|
|
1605
|
+
|
|
1606
|
+
// 构建请求参数
|
|
1607
|
+
const body = {
|
|
1608
|
+
traceId: Date.now().toString() + Math.random().toString(36).substr(2, 9),
|
|
1609
|
+
reqId: Date.now().toString(),
|
|
1610
|
+
erp: erp,
|
|
1611
|
+
keyword: keyword,
|
|
1612
|
+
platform: platform,
|
|
1613
|
+
packageName: packageName,
|
|
1614
|
+
testUrl: testUrl
|
|
1615
|
+
};
|
|
1452
1616
|
|
|
1453
|
-
|
|
1454
|
-
|
|
1617
|
+
const headers = {
|
|
1618
|
+
'Content-Type': 'application/json',
|
|
1619
|
+
'Accept': 'text/event-stream',
|
|
1620
|
+
'autobots-agent-id': '30323',
|
|
1621
|
+
'autobots-token': '60e7b3f68f654e9992ae0d403d2d90e5'
|
|
1622
|
+
};
|
|
1623
|
+
|
|
1624
|
+
// 直接发送fetch请求处理SSE
|
|
1625
|
+
const response = await fetch('http://autobots-bk.jd.local/autobots/api/v1/searchAiSse', {
|
|
1455
1626
|
method: 'POST',
|
|
1456
|
-
headers:
|
|
1457
|
-
body: JSON.stringify(
|
|
1458
|
-
naturalLanguage,
|
|
1459
|
-
runMode,
|
|
1460
|
-
platform,
|
|
1461
|
-
url,
|
|
1462
|
-
deviceId,
|
|
1463
|
-
packageName
|
|
1464
|
-
})
|
|
1627
|
+
headers: headers,
|
|
1628
|
+
body: JSON.stringify(body),
|
|
1465
1629
|
});
|
|
1466
1630
|
|
|
1467
|
-
|
|
1631
|
+
if (!response.ok) {
|
|
1632
|
+
throw new Error('HTTP ' + response.status);
|
|
1633
|
+
}
|
|
1468
1634
|
|
|
1469
|
-
if(
|
|
1470
|
-
|
|
1471
|
-
resultDiv.innerHTML = '<div class="ai-generator-result" id="ai-result-container" data-script="' + btoa(encodeURIComponent(data.script)) + '">' +
|
|
1472
|
-
'<div class="ai-generator-result-header">' +
|
|
1473
|
-
'<span>✅ 脚本已生成</span>' +
|
|
1474
|
-
'<span style="font-size:10px;color:#94a3b8;font-weight:400">点击"添加到脚本框"将脚本加入编辑器</span>' +
|
|
1475
|
-
'</div>' +
|
|
1476
|
-
'<div class="ai-generator-result-body">' + escHtml(data.script) + '</div>' +
|
|
1477
|
-
'<div class="ai-generator-result-actions">' +
|
|
1478
|
-
'<button class="btn-icon btn-blue" style="padding:6px 12px;font-size:12px" onclick="handleAppendScript()">→ 添加到脚本框</button>' +
|
|
1479
|
-
'<button class="btn-icon btn-ghost" style="padding:6px 12px;font-size:12px" onclick="handleCopyScript()">📋 复制</button>' +
|
|
1480
|
-
'<button class="btn-icon btn-ghost" style="padding:6px 12px;font-size:12px" onclick="clearAiResult()">✕ 清空</button>' +
|
|
1481
|
-
'</div>' +
|
|
1482
|
-
'</div>';
|
|
1483
|
-
addDebugLog('AI脚本生成成功', 'success');
|
|
1484
|
-
} else {
|
|
1485
|
-
// API不支持或失败,使用本地生成
|
|
1486
|
-
const localScript = generateScriptFromNaturalLanguage(naturalLanguage);
|
|
1487
|
-
resultDiv.innerHTML = '<div class="ai-generator-result" id="ai-result-container" data-script="' + btoa(encodeURIComponent(localScript)) + '">' +
|
|
1488
|
-
'<div class="ai-generator-result-header">' +
|
|
1489
|
-
'<span>✅ 脚本已生成(本地模式)</span>' +
|
|
1490
|
-
'<span style="font-size:10px;color:#94a3b8;font-weight:400">提示:配置AI服务可获得更精准的脚本生成</span>' +
|
|
1491
|
-
'</div>' +
|
|
1492
|
-
'<div class="ai-generator-result-body">' + escHtml(localScript) + '</div>' +
|
|
1493
|
-
'<div class="ai-generator-result-actions">' +
|
|
1494
|
-
'<button class="btn-icon btn-blue" style="padding:6px 12px;font-size:12px" onclick="handleAppendScript()">→ 添加到脚本框</button>' +
|
|
1495
|
-
'<button class="btn-icon btn-ghost" style="padding:6px 12px;font-size:12px" onclick="handleCopyScript()">📋 复制</button>' +
|
|
1496
|
-
'<button class="btn-icon btn-ghost" style="padding:6px 12px;font-size:12px" onclick="clearAiResult()">✕ 清空</button>' +
|
|
1497
|
-
'</div>' +
|
|
1498
|
-
'</div>';
|
|
1499
|
-
addDebugLog('使用本地模式生成脚本', 'info');
|
|
1635
|
+
if (!response.body) {
|
|
1636
|
+
throw new Error('No response body');
|
|
1500
1637
|
}
|
|
1501
|
-
} catch(error) {
|
|
1502
|
-
// 发生错误,使用本地生成
|
|
1503
|
-
console.error('AI脚本生成失败:', error);
|
|
1504
|
-
const localScript = generateScriptFromNaturalLanguage(naturalLanguage);
|
|
1505
|
-
resultDiv.innerHTML = '<div class="ai-generator-result" id="ai-result-container" data-script="' + btoa(encodeURIComponent(localScript)) + '">' +
|
|
1506
|
-
'<div class="ai-generator-result-header">' +
|
|
1507
|
-
'<span>⚠ 使用本地模式生成</span>' +
|
|
1508
|
-
'<span style="font-size:10px;color:#f59e0b;font-weight:400">AI服务不可用</span>' +
|
|
1509
|
-
'</div>' +
|
|
1510
|
-
'<div class="ai-generator-result-body">' + escHtml(localScript) + '</div>' +
|
|
1511
|
-
'<div class="ai-generator-result-actions">' +
|
|
1512
|
-
'<button class="btn-icon btn-blue" style="padding:6px 12px;font-size:12px" onclick="handleAppendScript()">→ 添加到脚本框</button>' +
|
|
1513
|
-
'<button class="btn-icon btn-ghost" style="padding:6px 12px;font-size:12px" onclick="handleCopyScript()">📋 复制</button>' +
|
|
1514
|
-
'<button class="btn-icon btn-ghost" style="padding:6px 12px;font-size:12px" onclick="clearAiResult()">✕ 清空</button>' +
|
|
1515
|
-
'</div>' +
|
|
1516
|
-
'</div>';
|
|
1517
|
-
addDebugLog('AI服务不可用,使用本地模式生成脚本', 'warn');
|
|
1518
|
-
}
|
|
1519
1638
|
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1639
|
+
const reader = response.body.getReader();
|
|
1640
|
+
const decoder = new TextDecoder();
|
|
1641
|
+
let buffer = '';
|
|
1642
|
+
let finalContent = '';
|
|
1524
1643
|
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
try {
|
|
1529
|
-
const encoded = container.getAttribute('data-script');
|
|
1530
|
-
return decodeURIComponent(atob(encoded));
|
|
1531
|
-
} catch(e) {
|
|
1532
|
-
return '';
|
|
1533
|
-
}
|
|
1534
|
-
}
|
|
1644
|
+
// 脚本编辑器 - 记住原有内容
|
|
1645
|
+
const editor = document.getElementById('debug-script');
|
|
1646
|
+
const existingContent = editor.value.trim();
|
|
1535
1647
|
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
}
|
|
1648
|
+
while (true) {
|
|
1649
|
+
const { done, value } = await reader.read();
|
|
1650
|
+
if (done) {
|
|
1651
|
+
addDebugLog('脚本生成完成', 'success');
|
|
1652
|
+
break;
|
|
1653
|
+
}
|
|
1542
1654
|
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1655
|
+
buffer += decoder.decode(value, { stream: true });
|
|
1656
|
+
|
|
1657
|
+
// 按行分割处理
|
|
1658
|
+
const lines = buffer.split('\\n');
|
|
1659
|
+
buffer = lines.pop() || '';
|
|
1660
|
+
|
|
1661
|
+
for (const line of lines) {
|
|
1662
|
+
if (line.startsWith('data:')) {
|
|
1663
|
+
try {
|
|
1664
|
+
const jsonStr = line.slice(5).trim();
|
|
1665
|
+
const parsedData = JSON.parse(jsonStr);
|
|
1666
|
+
const responseContent = parsedData.data?.response || parsedData.response || '';
|
|
1667
|
+
const responseAll = parsedData.data?.responseAll || parsedData.responseAll || '';
|
|
1668
|
+
|
|
1669
|
+
if (responseContent) {
|
|
1670
|
+
const cleanContent = cleanCodeBlock(responseContent);
|
|
1671
|
+
finalContent = cleanContent;
|
|
1672
|
+
}
|
|
1673
|
+
|
|
1674
|
+
if (parsedData.data?.status === 'finished' || parsedData.status === 'finished') {
|
|
1675
|
+
if (responseAll) {
|
|
1676
|
+
const cleanContent = cleanCodeBlock(responseAll);
|
|
1677
|
+
finalContent = cleanContent;
|
|
1678
|
+
}
|
|
1679
|
+
}
|
|
1680
|
+
|
|
1681
|
+
// 流式过程中实时预览生成内容
|
|
1682
|
+
if (finalContent) {
|
|
1683
|
+
editor.value = finalContent;
|
|
1684
|
+
editor.scrollTop = editor.scrollHeight;
|
|
1685
|
+
}
|
|
1686
|
+
} catch (error) {
|
|
1687
|
+
// 忽略解析错误
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1549
1692
|
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1693
|
+
// 生成完成,将最终内容追加到编辑器原有内容后面
|
|
1694
|
+
if (finalContent) {
|
|
1695
|
+
editor.value = existingContent ? existingContent + '\\n\\n' + finalContent : finalContent;
|
|
1696
|
+
}
|
|
1697
|
+
input.value = '';
|
|
1698
|
+
addDebugLog('脚本已追加到编辑器', 'success');
|
|
1554
1699
|
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
editor.value = script;
|
|
1700
|
+
} catch(error) {
|
|
1701
|
+
console.error('AI脚本生成失败:', error);
|
|
1702
|
+
addDebugLog('脚本生成失败: ' + error.message, 'error');
|
|
1559
1703
|
}
|
|
1560
1704
|
|
|
1561
|
-
//
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
addDebugLog('脚本已添加到编辑器', 'success');
|
|
1566
|
-
}
|
|
1567
|
-
|
|
1568
|
-
function copyScriptToClipboard(text) {
|
|
1569
|
-
navigator.clipboard.writeText(text).then(() => {
|
|
1570
|
-
addDebugLog('脚本已复制到剪贴板', 'success');
|
|
1571
|
-
}).catch(err => {
|
|
1572
|
-
console.error('复制失败:', err);
|
|
1573
|
-
addDebugLog('复制失败,请手动复制', 'error');
|
|
1574
|
-
});
|
|
1575
|
-
}
|
|
1576
|
-
|
|
1577
|
-
function clearAiResult() {
|
|
1578
|
-
document.getElementById('ai-generator-result').innerHTML = '';
|
|
1705
|
+
// 恢复按钮状态
|
|
1706
|
+
btn.disabled = false;
|
|
1707
|
+
btn.innerHTML = '⚡ 生成';
|
|
1579
1708
|
}
|
|
1580
1709
|
|
|
1581
1710
|
// ===== 工具函数 =====
|