@aiscene/aiserver 1.2.4 → 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 -28
- package/dist/config/index.js.map +1 -1
- package/dist/config/schema.d.ts +11 -7
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/core/autobots-client.d.ts +37 -0
- package/dist/core/autobots-client.d.ts.map +1 -0
- package/dist/core/autobots-client.js +132 -0
- package/dist/core/autobots-client.js.map +1 -0
- 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 +19 -6
- package/dist/task/scheduler.js.map +1 -1
- package/dist/web/debug-page.d.ts.map +1 -1
- package/dist/web/debug-page.js +367 -35
- 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 +586 -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}
|
|
@@ -161,6 +162,22 @@ html,body{overflow:hidden;margin:0;padding:0}
|
|
|
161
162
|
.btn-danger:hover{background:#ef4444}
|
|
162
163
|
.btn-blue{background:#2563eb;color:#fff}
|
|
163
164
|
.btn-blue:hover{background:#1d4ed8}
|
|
165
|
+
|
|
166
|
+
/* AI生成脚本对话框样式 */
|
|
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
|
+
.ai-generator-header:hover{background:#f1f5f9}
|
|
170
|
+
.ai-generator-header .title{font-size:13px;font-weight:600;color:#1e293b;display:flex;align-items:center;gap:8px}
|
|
171
|
+
.ai-generator-header .toggle-icon{font-size:12px;color:#64748b;transition:transform .2s}
|
|
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}
|
|
177
|
+
.ai-generator-input-row input:focus{border-color:#38bdf8}
|
|
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}
|
|
179
|
+
.ai-generator-input-row button:hover{background:#1d4ed8}
|
|
180
|
+
.ai-generator-input-row button:disabled{background:#94a3b8;cursor:not-allowed}
|
|
164
181
|
</style>
|
|
165
182
|
|
|
166
183
|
<div class="debug-page" id="debug-page-content">
|
|
@@ -217,11 +234,6 @@ html,body{overflow:hidden;margin:0;padding:0}
|
|
|
217
234
|
</div>
|
|
218
235
|
</div>
|
|
219
236
|
|
|
220
|
-
<div class="checkbox-group">
|
|
221
|
-
<input type="checkbox" id="debug-skipAppium" checked>
|
|
222
|
-
<label for="debug-skipAppium">跳过 Appium 服务</label>
|
|
223
|
-
</div>
|
|
224
|
-
|
|
225
237
|
<div class="form-group">
|
|
226
238
|
<label>登录用户名(选填)</label>
|
|
227
239
|
<input id="debug-loginUser" placeholder="输入用户名">
|
|
@@ -247,7 +259,6 @@ html,body{overflow:hidden;margin:0;padding:0}
|
|
|
247
259
|
<div class="actions">
|
|
248
260
|
<button class="btn-icon btn-ghost" onclick="openCaseSelector()">📁 选择用例</button>
|
|
249
261
|
<button class="btn-icon btn-ghost" onclick="openSaveCaseDialog()">💾 保存用例</button>
|
|
250
|
-
<button class="btn-icon btn-ghost" onclick="handleGenerateScript()">⚡ 生成脚本</button>
|
|
251
262
|
<button class="btn-icon btn-ghost" onclick="handleClearEditor()">🗑 清空</button>
|
|
252
263
|
</div>
|
|
253
264
|
</div>
|
|
@@ -257,6 +268,24 @@ html,body{overflow:hidden;margin:0;padding:0}
|
|
|
257
268
|
<textarea id="debug-script" placeholder="// 在此编写调试脚本\n\n" spellcheck="false"></textarea>
|
|
258
269
|
</div>
|
|
259
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
|
+
|
|
260
289
|
<!-- 终端面板 -->
|
|
261
290
|
<div class="debug-terminal" id="debug-terminal">
|
|
262
291
|
<div class="debug-terminal-header">
|
|
@@ -375,7 +404,7 @@ html,body{overflow:hidden;margin:0;padding:0}
|
|
|
375
404
|
</div>
|
|
376
405
|
<div class="modal-footer">
|
|
377
406
|
<button class="btn btn-cancel" onclick="closeSaveCaseDialog()">取消</button>
|
|
378
|
-
<button class="btn btn-primary" onclick="handleSaveCase()">保存</button>
|
|
407
|
+
<button class="btn btn-primary" id="save-case-submit-btn" onclick="handleSaveCase()">保存</button>
|
|
379
408
|
</div>
|
|
380
409
|
</div>
|
|
381
410
|
</div>
|
|
@@ -908,14 +937,24 @@ function renderStepDetail() {
|
|
|
908
937
|
if(task.usage) {
|
|
909
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>';
|
|
910
939
|
}
|
|
911
|
-
// 截图展示 -
|
|
940
|
+
// 截图展示 - 图片可点击在新窗口打开,并在图片上绘制定位矩形
|
|
912
941
|
if(task.uiContext && task.uiContext.screenshot) {
|
|
913
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)) : '';
|
|
914
951
|
if(ss.base64) {
|
|
915
952
|
const sizeLabel = ss.width ? ss.width + "x" + (ss.height || "") : "";
|
|
916
|
-
|
|
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>';
|
|
917
955
|
} else if(ss.url) {
|
|
918
|
-
|
|
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>';
|
|
919
958
|
}
|
|
920
959
|
}
|
|
921
960
|
html += '</div></div>';
|
|
@@ -931,6 +970,112 @@ function openReport() {
|
|
|
931
970
|
}
|
|
932
971
|
}
|
|
933
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
|
+
|
|
934
1079
|
// ===== 历史记录 =====
|
|
935
1080
|
function saveToHistory(status) {
|
|
936
1081
|
const item = {
|
|
@@ -1032,17 +1177,26 @@ async function loadFolderTree() {
|
|
|
1032
1177
|
|
|
1033
1178
|
function renderFolderTree() {
|
|
1034
1179
|
const list = document.getElementById('case-folder-list');
|
|
1180
|
+
list.innerHTML = '';
|
|
1035
1181
|
if(caseSelectorState.folders.length === 0) {
|
|
1036
1182
|
list.innerHTML = '<div style="text-align:center;color:#64748b;padding:20px">暂无文件夹</div>';
|
|
1037
1183
|
return;
|
|
1038
1184
|
}
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
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);
|
|
1042
1196
|
});
|
|
1043
1197
|
}
|
|
1044
1198
|
|
|
1045
|
-
function
|
|
1199
|
+
function buildFolderNode(node, level, key) {
|
|
1046
1200
|
const info = node.folder || {};
|
|
1047
1201
|
const folderId = info.folderId || info.id;
|
|
1048
1202
|
const name = (!info.folderName || info.folderName === '未命名文件夹') ? '未分类' : info.folderName;
|
|
@@ -1051,29 +1205,56 @@ function renderFolderNode(node, level, key) {
|
|
|
1051
1205
|
const isActive = caseSelectorState.selectedFolderId === folderId;
|
|
1052
1206
|
const indent = level * 16 + 8;
|
|
1053
1207
|
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
html += '📁 ' + escHtml(name);
|
|
1058
|
-
if(count > 0) html += ' <span style="color:#64748b;font-size:11px">(' + count + ')</span>';
|
|
1059
|
-
html += '</div>';
|
|
1208
|
+
const item = document.createElement('div');
|
|
1209
|
+
item.className = 'folder-item' + (isActive ? ' active' : '');
|
|
1210
|
+
item.style.paddingLeft = indent + 'px';
|
|
1060
1211
|
|
|
1061
1212
|
if(hasChildren) {
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
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);
|
|
1067
1229
|
}
|
|
1068
|
-
return html;
|
|
1069
|
-
}
|
|
1070
1230
|
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
if(
|
|
1075
|
-
|
|
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);
|
|
1076
1255
|
}
|
|
1256
|
+
|
|
1257
|
+
return wrapper;
|
|
1077
1258
|
}
|
|
1078
1259
|
|
|
1079
1260
|
async function selectFolder(folderId) {
|
|
@@ -1200,15 +1381,22 @@ function confirmCaseSelection() {
|
|
|
1200
1381
|
}
|
|
1201
1382
|
|
|
1202
1383
|
// ===== 保存用例 =====
|
|
1203
|
-
function openSaveCaseDialog() {
|
|
1384
|
+
async function openSaveCaseDialog() {
|
|
1385
|
+
const saveBtn = document.getElementById('save-case-submit-btn');
|
|
1204
1386
|
if(debugState.currentTestCase) {
|
|
1205
1387
|
document.getElementById('save-case-title').textContent = '修改测试用例';
|
|
1206
1388
|
document.getElementById('save-case-name').value = debugState.currentTestCase.configName || '';
|
|
1389
|
+
saveBtn.textContent = '更新';
|
|
1207
1390
|
} else {
|
|
1208
1391
|
document.getElementById('save-case-title').textContent = '保存测试用例';
|
|
1209
1392
|
document.getElementById('save-case-name').value = '';
|
|
1393
|
+
saveBtn.textContent = '保存';
|
|
1210
1394
|
}
|
|
1211
1395
|
document.getElementById('save-case-modal').style.display = '';
|
|
1396
|
+
// 确保文件夹树已加载
|
|
1397
|
+
if(!debugState.folderTree || debugState.folderTree.length === 0) {
|
|
1398
|
+
await loadFolderTree();
|
|
1399
|
+
}
|
|
1212
1400
|
loadSaveFolderOptions();
|
|
1213
1401
|
}
|
|
1214
1402
|
|
|
@@ -1239,6 +1427,10 @@ function loadSaveFolderOptions() {
|
|
|
1239
1427
|
});
|
|
1240
1428
|
}
|
|
1241
1429
|
addFolders(debugState.folderTree, '');
|
|
1430
|
+
// 如果有当前用例,自动选中其所在文件夹
|
|
1431
|
+
if(debugState.currentTestCase && debugState.currentTestCase.folderId) {
|
|
1432
|
+
sel.value = debugState.currentTestCase.folderId;
|
|
1433
|
+
}
|
|
1242
1434
|
}
|
|
1243
1435
|
|
|
1244
1436
|
async function handleSaveCase() {
|
|
@@ -1375,6 +1567,146 @@ function handleClearEditor() {
|
|
|
1375
1567
|
addDebugLog('编辑器已清空', 'info');
|
|
1376
1568
|
}
|
|
1377
1569
|
|
|
1570
|
+
// ===== AI脚本生成器 =====
|
|
1571
|
+
function toggleAiGenerator() {
|
|
1572
|
+
const panel = document.getElementById('ai-generator-panel');
|
|
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();
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
async function handleAiGenerate() {
|
|
1585
|
+
const input = document.getElementById('ai-generator-input');
|
|
1586
|
+
const btn = document.getElementById('ai-generator-btn');
|
|
1587
|
+
const keyword = input.value.trim();
|
|
1588
|
+
|
|
1589
|
+
if (!keyword) {
|
|
1590
|
+
addDebugLog('请输入代码描述', 'warn');
|
|
1591
|
+
return;
|
|
1592
|
+
}
|
|
1593
|
+
|
|
1594
|
+
// 显示加载状态
|
|
1595
|
+
btn.disabled = true;
|
|
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');
|
|
1598
|
+
|
|
1599
|
+
try {
|
|
1600
|
+
// 获取当前配置信息
|
|
1601
|
+
const platform = document.getElementById('debug-platform').value;
|
|
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
|
+
};
|
|
1616
|
+
|
|
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', {
|
|
1626
|
+
method: 'POST',
|
|
1627
|
+
headers: headers,
|
|
1628
|
+
body: JSON.stringify(body),
|
|
1629
|
+
});
|
|
1630
|
+
|
|
1631
|
+
if (!response.ok) {
|
|
1632
|
+
throw new Error('HTTP ' + response.status);
|
|
1633
|
+
}
|
|
1634
|
+
|
|
1635
|
+
if (!response.body) {
|
|
1636
|
+
throw new Error('No response body');
|
|
1637
|
+
}
|
|
1638
|
+
|
|
1639
|
+
const reader = response.body.getReader();
|
|
1640
|
+
const decoder = new TextDecoder();
|
|
1641
|
+
let buffer = '';
|
|
1642
|
+
let finalContent = '';
|
|
1643
|
+
|
|
1644
|
+
// 脚本编辑器 - 记住原有内容
|
|
1645
|
+
const editor = document.getElementById('debug-script');
|
|
1646
|
+
const existingContent = editor.value.trim();
|
|
1647
|
+
|
|
1648
|
+
while (true) {
|
|
1649
|
+
const { done, value } = await reader.read();
|
|
1650
|
+
if (done) {
|
|
1651
|
+
addDebugLog('脚本生成完成', 'success');
|
|
1652
|
+
break;
|
|
1653
|
+
}
|
|
1654
|
+
|
|
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
|
+
}
|
|
1692
|
+
|
|
1693
|
+
// 生成完成,将最终内容追加到编辑器原有内容后面
|
|
1694
|
+
if (finalContent) {
|
|
1695
|
+
editor.value = existingContent ? existingContent + '\\n\\n' + finalContent : finalContent;
|
|
1696
|
+
}
|
|
1697
|
+
input.value = '';
|
|
1698
|
+
addDebugLog('脚本已追加到编辑器', 'success');
|
|
1699
|
+
|
|
1700
|
+
} catch(error) {
|
|
1701
|
+
console.error('AI脚本生成失败:', error);
|
|
1702
|
+
addDebugLog('脚本生成失败: ' + error.message, 'error');
|
|
1703
|
+
}
|
|
1704
|
+
|
|
1705
|
+
// 恢复按钮状态
|
|
1706
|
+
btn.disabled = false;
|
|
1707
|
+
btn.innerHTML = '⚡ 生成';
|
|
1708
|
+
}
|
|
1709
|
+
|
|
1378
1710
|
// ===== 工具函数 =====
|
|
1379
1711
|
function escHtml(s) {
|
|
1380
1712
|
return String(s || '').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"debug-page.js","sourceRoot":"","sources":["../../src/web/debug-page.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,UAAU,gBAAgB;IAC9B,OAAO
|
|
1
|
+
{"version":3,"file":"debug-page.js","sourceRoot":"","sources":["../../src/web/debug-page.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,UAAU,gBAAgB;IAC9B,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+qDR,CAAC;AACF,CAAC"}
|
package/dist/web/server.d.ts
CHANGED
|
@@ -5,8 +5,11 @@ export declare class WebServer {
|
|
|
5
5
|
constructor();
|
|
6
6
|
private setupMiddleware;
|
|
7
7
|
private setupApiRoutes;
|
|
8
|
+
private setupDetailRoutes;
|
|
8
9
|
private setupStaticFiles;
|
|
9
10
|
private getFallbackHtml;
|
|
11
|
+
private getTaskDetailHtml;
|
|
12
|
+
private getDebugDetailHtml;
|
|
10
13
|
start(config: ServerConfig): void;
|
|
11
14
|
close(): void;
|
|
12
15
|
}
|
package/dist/web/server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/web/server.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/web/server.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AA6NxD,qBAAa,SAAS;IACpB,OAAO,CAAC,GAAG,CAAsB;IACjC,OAAO,CAAC,MAAM,CAAgB;;IAU9B,OAAO,CAAC,eAAe;IAUvB,OAAO,CAAC,cAAc;IAyPtB,OAAO,CAAC,iBAAiB;IAezB,OAAO,CAAC,gBAAgB;IAYxB,OAAO,CAAC,eAAe;IAwbvB,OAAO,CAAC,iBAAiB;IAwFzB,OAAO,CAAC,kBAAkB;IAgF1B,KAAK,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI;IAOjC,KAAK,IAAI,IAAI;CAGd;AAED,eAAO,MAAM,SAAS,WAAkB,CAAC"}
|