@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.
@@ -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:480px;min-width:400px;max-width:600px;border-left:1px solid #e2e8f0;display:flex;flex-direction:column;background:#fff}
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()">&#128193; 选择用例</button>
249
261
  <button class="btn-icon btn-ghost" onclick="openSaveCaseDialog()">&#128190; 保存用例</button>
250
- <button class="btn-icon btn-ghost" onclick="handleGenerateScript()">&#9889; 生成脚本</button>
251
262
  <button class="btn-icon btn-ghost" onclick="handleClearEditor()">&#128465; 清空</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">&#9889;</span>
276
+ <span>AI 脚本生成器</span>
277
+ <span style="font-size:11px;color:#94a3b8;font-weight:400">- 输入自然语言描述,生成脚本直接写入编辑器</span>
278
+ </div>
279
+ <div class="toggle-icon">&#9650;</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()">&#9889; 生成</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
- html += '<div class="rrd-screenshot"><img src="' + escHtml(ss.base64) + '" title="点击查看大图" /><div class="rrd-screenshot-label">UI截图 ' + sizeLabel + '</div></div>';
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
- html += '<div class="rrd-screenshot"><img src="' + escHtml(ss.url) + '" title="点击查看大图" /><div class="rrd-screenshot-label">UI截图</div></div>';
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
- list.innerHTML = '<div class="folder-item" onclick="loadAllCases()" style="color:#22c55e">&#128196; 全部用例</div>';
1040
- caseSelectorState.folders.forEach((folder, idx) => {
1041
- list.innerHTML += renderFolderNode(folder, 0, 'root-' + idx);
1185
+ // 全部用例
1186
+ const allItem = document.createElement('div');
1187
+ allItem.className = 'folder-item';
1188
+ allItem.style.color = '#22c55e';
1189
+ allItem.innerHTML = '&#128196; 全部用例';
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 renderFolderNode(node, level, key) {
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
- let html = '<div class="folder-item' + (isActive ? ' active' : '') + '" style="padding-left:' + indent + 'px" onclick="selectFolder(&apos;' + folderId + '&apos;, this)">';
1055
- if(hasChildren) html += '<span class="arrow" onclick="event.stopPropagation();toggleFolder(this)">&#9654;</span>';
1056
- else html += '<span style="width:10px;display:inline-block"></span>';
1057
- html += '&#128193; ' + 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
- html += '<div class="folder-children" style="display:none">';
1063
- node.children.forEach((child, idx) => {
1064
- html += renderFolderNode(child, level + 1, key + '-' + idx);
1065
- });
1066
- html += '</div>';
1213
+ const arrow = document.createElement('span');
1214
+ arrow.className = 'arrow';
1215
+ arrow.innerHTML = '&#9654;';
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
- function toggleFolder(arrow) {
1072
- arrow.classList.toggle('expanded');
1073
- const children = arrow.closest('.folder-item').nextElementSibling;
1074
- if(children && children.classList.contains('folder-children')) {
1075
- children.style.display = children.style.display === 'none' ? '' : 'none';
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 = '&#9889; 生成';
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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAm2CR,CAAC;AACF,CAAC"}
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"}
@@ -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
  }
@@ -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;AAIxD,qBAAa,SAAS;IACpB,OAAO,CAAC,GAAG,CAAsB;IACjC,OAAO,CAAC,MAAM,CAAgB;;IAS9B,OAAO,CAAC,eAAe;IAUvB,OAAO,CAAC,cAAc;IAuKtB,OAAO,CAAC,gBAAgB;IAYxB,OAAO,CAAC,eAAe;IAqgBvB,KAAK,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI;IAOjC,KAAK,IAAI,IAAI;CAGd;AAED,eAAO,MAAM,SAAS,WAAkB,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"}