@@ -304,11 +304,9 @@ export class SidebarProvider implements vscode.WebviewViewProvider {
304304 }
305305
306306 private async _handleScanBlueprints ( ) : Promise < void > {
307- // 按项目文件夹分组扫描蓝本
308- type BlueprintEntry = { path : string ; mtime : number ; appName : string ; description : string ; platform : string ; scenarioCount : number ; stepCount : number } ;
309- type ProjectGroup = { projectDir : string ; projectName : string ; platform : string ; blueprints : BlueprintEntry [ ] } ;
307+ type BpEntry = { path : string ; mtime : number ; appName : string ; description : string ; platform : string ; scenarioCount : number ; stepCount : number } ;
308+ type ProjectGroup = { projectDir : string ; projectName : string ; platform : string ; blueprints : BpEntry [ ] } ;
310309 const projectMap = new Map < string , ProjectGroup > ( ) ;
311-
312310 const folders = vscode . workspace . workspaceFolders ;
313311 if ( folders ) {
314312 for ( const folder of folders ) {
@@ -339,43 +337,26 @@ export class SidebarProvider implements vscode.WebviewViewProvider {
339337 scenarioCount = ( json . pages || [ ] ) . reduce ( ( n : number , p : any ) => n + ( p . scenarios || [ ] ) . length , 0 ) ;
340338 stepCount = ( json . pages || [ ] ) . reduce ( ( n : number , p : any ) => n + ( p . scenarios || [ ] ) . reduce ( ( m : number , s : any ) => m + ( s . steps || [ ] ) . length , 0 ) , 0 ) ;
341339 } catch { /* ignore parse errors */ }
342-
343- // 确定项目目录:蓝本文件的父目录(如果在testpilot/子目录里则取上一级)
344- const filePath = f . fsPath . replace ( / \\ / g, "/" ) ;
345- const parts = filePath . split ( "/" ) ;
346- const fileName = parts . pop ( ) || "" ;
347- let dir = parts . join ( "/" ) ;
348- // 如果蓝本在 testpilot/ 子目录里,项目目录取上一级
349- if ( parts . length > 0 && parts [ parts . length - 1 ] === "testpilot" ) {
350- parts . pop ( ) ;
351- dir = parts . join ( "/" ) ;
352- }
353-
354- const entry : BlueprintEntry = { path : f . fsPath , mtime, appName, description, platform, scenarioCount, stepCount } ;
355-
340+ // 确定项目目录(testpilot/子目录取上一级)
341+ const fPath = f . fsPath . replace ( / \\ / g, "/" ) ;
342+ const parts = fPath . split ( "/" ) ;
343+ parts . pop ( ) ; // 移除文件名
344+ if ( parts . length > 0 && parts [ parts . length - 1 ] === "testpilot" ) { parts . pop ( ) ; }
345+ const dir = parts . join ( "/" ) ;
346+ const entry : BpEntry = { path : f . fsPath , mtime, appName, description, platform, scenarioCount, stepCount } ;
356347 if ( projectMap . has ( dir ) ) {
357348 projectMap . get ( dir ) ! . blueprints . push ( entry ) ;
358349 } else {
359- // 项目名优先用蓝本的app_name,兜底用目录名
360350 const dirName = dir . split ( "/" ) . pop ( ) || dir ;
361- projectMap . set ( dir , {
362- projectDir : dir ,
363- projectName : appName || dirName ,
364- platform : platform ,
365- blueprints : [ entry ] ,
366- } ) ;
351+ projectMap . set ( dir , { projectDir : dir , projectName : appName || dirName , platform, blueprints : [ entry ] } ) ;
367352 }
368353 }
369354 }
370355 }
371356 }
372-
373- // 转为数组,按蓝本总数降序排列
374357 const projects = Array . from ( projectMap . values ( ) ) ;
375358 projects . sort ( ( a , b ) => b . blueprints . length - a . blueprints . length ) ;
376- // 每个项目内蓝本按修改时间降序
377359 projects . forEach ( p => p . blueprints . sort ( ( a , b ) => b . mtime - a . mtime ) ) ;
378-
379360 this . _postMessage ( { command : "blueprintList" , data : projects } ) ;
380361 }
381362
@@ -593,12 +574,10 @@ export class SidebarProvider implements vscode.WebviewViewProvider {
593574 <select id="projectSelect" style="flex:1;font-size:12px;padding:4px 6px" disabled>
594575 <option value="">暂无测试项目</option>
595576 </select>
596- <button id="btnRefreshProjects" class="btn-secondary" style="width:32px;min-width:32px;margin:0;padding:4px;font-size:14px" title="刷新项目列表">🔄</button>
597- </div>
598- <div class="btn-row">
599- <button class="btn-secondary" id="btnCheckEngine" style="flex:1">检查连接</button>
600- <button id="btnLaunchEngine" style="flex:1;background:#22c55e">🚀 启动引擎</button>
577+ <button id="btnRefreshProjects" class="btn-secondary" style="width:28px;min-width:28px;margin:0;padding:3px;font-size:13px" title="刷新项目列表">🔄</button>
601578 </div>
579+ <button class="btn-secondary" id="btnCheckEngine">检查连接</button>
580+ <button id="btnLaunchEngine" class="hidden" style="background:#22c55e;margin-top:6px">🚀 一键启动引擎</button>
602581 <button id="btnStopEngine" class="hidden" style="background:#ef4444;margin-top:6px">⏹ 断开引擎</button>
603582 </div>
604583
@@ -615,6 +594,10 @@ export class SidebarProvider implements vscode.WebviewViewProvider {
615594 <span style="font-size:11px;color:var(--muted);margin-left:4px">(勾选要测试的蓝本)</span>
616595 </label>
617596 <div id="blueprintList" style="max-height:150px;overflow-y:auto;border:1px solid var(--border);border-radius:3px;padding:4px;font-size:11px;margin-bottom:4px"></div>
597+ <div class="btn-row" style="margin-top:4px">
598+ <button class="btn-secondary" id="btnScanBp" style="flex:1;font-size:11px;padding:4px">🔍 扫描蓝本</button>
599+ <button class="btn-secondary" id="btnBrowseBp" style="flex:1;font-size:11px;padding:4px">📂 浏览文件</button>
600+ </div>
618601 <input type="text" id="inputBlueprintPath" placeholder="或手动输入路径..." style="font-size:11px;margin-top:4px" />
619602
620603 <label>覆盖 base_url(可选)</label>
@@ -748,14 +731,14 @@ export class SidebarProvider implements vscode.WebviewViewProvider {
748731 const blueprintListEl = document.getElementById("blueprintList");
749732 const inputBlueprintPath = document.getElementById("inputBlueprintPath");
750733 const projectSelect = document.getElementById("projectSelect");
751- let allProjects = []; // 存储分组后的项目数据
752- let currentProjectIdx = 0; // 当前选中项目索引
734+ let allProjects = [];
735+ let currentProjectIdx = 0;
753736 let blueprintEntries = []; // 当前项目的蓝本列表
754737
755- // 获取当前项目的平台
738+ // 获取当前项目平台
756739 function getCurrentPlatform() {
757740 if (allProjects.length === 0) { return "web"; }
758- return allProjects[currentProjectIdx]? .platform || "web";
741+ return allProjects[currentProjectIdx] ? allProjects[currentProjectIdx] .platform : "web";
759742 }
760743
761744 // 获取所有选中的蓝本路径
@@ -772,17 +755,17 @@ export class SidebarProvider implements vscode.WebviewViewProvider {
772755 projectSelect.addEventListener("change", () => {
773756 currentProjectIdx = projectSelect.selectedIndex;
774757 if (allProjects.length > 0 && allProjects[currentProjectIdx]) {
775- blueprintEntries = allProjects[currentProjectIdx].blueprints;
758+ blueprintEntries = allProjects[currentProjectIdx].blueprints || [] ;
776759 renderBlueprintList(blueprintEntries);
777760 }
778761 });
779762
780- // 刷新按钮
763+ // 刷新项目按钮
781764 document.getElementById("btnRefreshProjects").addEventListener("click", () => {
782765 vscode.postMessage({ command: "scanBlueprints" });
783766 });
784767
785- // 扫描按钮(蓝本区域内的)
768+ // 扫描按钮
786769 document.getElementById("btnScanBp").addEventListener("click", () => {
787770 vscode.postMessage({ command: "scanBlueprints" });
788771 });
@@ -975,37 +958,30 @@ export class SidebarProvider implements vscode.WebviewViewProvider {
975958
976959 function onBlueprintList(projects) {
977960 allProjects = projects || [];
978-
979961 // 更新项目下拉框
980962 projectSelect.innerHTML = "";
981963 if (allProjects.length === 0) {
982964 projectSelect.innerHTML = '<option value="">暂无测试项目</option>';
983965 projectSelect.disabled = true;
984- blueprintListEl.innerHTML = '<div style="color:var(--muted);padding:12px;text-align:center;font-size:12px;line-height:1.6">暂无测试项目<br><br>请在项目中创建蓝本文件,<br>或点击下方「📋 复制蓝本生成提示词」<br>让编程AI自动生成。</div>';
985966 blueprintEntries = [];
967+ blueprintListEl.innerHTML = '<div style="color:var(--muted);padding:12px;text-align:center;font-size:12px;line-height:1.6">暂无测试项目<br><br>请在项目中创建蓝本文件,<br>或点击下方「📋 复制蓝本生成提示词」<br>让编程AI自动生成。</div>';
986968 return;
987969 }
988-
989- const platformBadges = {web:"🌐",android:"📱",ios:"📱",miniprogram:"💬",desktop:"🖥️"};
990- allProjects.forEach((proj, i) => {
991- const opt = document.createElement("option");
992- opt.value = i;
993- const badge = platformBadges[proj.platform] || "📄";
994- const bpCount = proj.blueprints ? proj.blueprints.length : 0;
970+ var platformBadges = {web:"🌐",android:"📱",ios:"📱",miniprogram:"💬",desktop:"🖥️"};
971+ allProjects.forEach(function(proj, i) {
972+ var opt = document.createElement("option");
973+ opt.value = String(i);
974+ var badge = platformBadges[proj.platform] || "📄";
975+ var bpCount = proj.blueprints ? proj.blueprints.length : 0;
995976 opt.textContent = badge + " " + proj.projectName + " (" + bpCount + ")";
996977 projectSelect.appendChild(opt);
997978 });
998-
999- // 单项目时锁定不可选
1000979 projectSelect.disabled = allProjects.length <= 1;
1001980 currentProjectIdx = 0;
1002981 projectSelect.selectedIndex = 0;
1003-
1004- // 渲染第一个项目的蓝本
1005982 blueprintEntries = allProjects[0].blueprints || [];
1006983 renderBlueprintList(blueprintEntries);
1007-
1008- const totalBp = allProjects.reduce((n, p) => n + (p.blueprints ? p.blueprints.length : 0), 0);
984+ var totalBp = allProjects.reduce(function(n, p) { return n + (p.blueprints ? p.blueprints.length : 0); }, 0);
1009985 addLog("找到 " + allProjects.length + " 个项目,共 " + totalBp + " 个蓝本", "info");
1010986 }
1011987
@@ -1017,63 +993,63 @@ export class SidebarProvider implements vscode.WebviewViewProvider {
1017993 }
1018994
1019995 // 全局蓝本选项(固定在顶部)
1020- const globalItem = document.createElement("label");
996+ var globalItem = document.createElement("label");
1021997 globalItem.style.cssText = "display:flex;align-items:center;gap:4px;padding:4px 4px;border-radius:4px;cursor:pointer;font-size:12px;font-weight:600;background:var(--bg-secondary,#1e1e1e);border:1px solid var(--border);margin-bottom:4px";
1022998 globalItem.title = "勾选后将测试当前项目所有蓝本";
1023999
1024- const globalCb = document.createElement("input");
1000+ var globalCb = document.createElement("input");
10251001 globalCb.type = "checkbox";
10261002 globalCb.id = "cbGlobalBlueprint";
10271003 globalCb.checked = true;
10281004 globalCb.style.cssText = "flex-shrink:0;width:14px;height:14px";
10291005
1030- const globalLabel = document.createElement("span");
1006+ var globalLabel = document.createElement("span");
10311007 globalLabel.textContent = "🌍 全局蓝本(测试所有)";
10321008 globalLabel.style.cssText = "flex:1;color:var(--fg)";
10331009
10341010 globalItem.appendChild(globalCb);
10351011 globalItem.appendChild(globalLabel);
10361012 blueprintListEl.appendChild(globalItem);
10371013
1038- const divider = document.createElement("div");
1014+ var divider = document.createElement("div");
10391015 divider.style.cssText = "height:1px;background:var(--border);margin:4px 0";
10401016 blueprintListEl.appendChild(divider);
10411017
1042- const platformBadges = {web:"🌐",android:"📱",ios:"📱",miniprogram:"💬",desktop:"🖥️"};
1018+ var platformBadges = {web:"🌐",android:"📱",ios:"📱",miniprogram:"💬",desktop:"🖥️"};
10431019
1044- entries.forEach((entry, i) => {
1045- const path = typeof entry === "string" ? entry : entry.path;
1046- const appName = entry.appName || "";
1047- const desc = entry.description || "";
1048- const platform = entry.platform || "web";
1049- const scenarios = entry.scenarioCount || 0;
1050- const steps = entry.stepCount || 0;
1020+ entries.forEach(function (entry, i) {
1021+ var path = typeof entry === "string" ? entry : entry.path;
1022+ var appName = entry.appName || "";
1023+ var desc = entry.description || "";
1024+ var platform = entry.platform || "web";
1025+ var scenarios = entry.scenarioCount || 0;
1026+ var steps = entry.stepCount || 0;
10511027
1052- const parts = path.replace(/\\\\/g, "/").split("/");
1053- const shortPath = parts.slice(-3).join("/");
1054- const displayName = appName || shortPath;
1028+ var parts = path.replace(/\\\\/g, "/").split("/");
1029+ var shortPath = parts.slice(-3).join("/");
1030+ var displayName = appName || shortPath;
10551031
1056- const platformBadge = platformBadges[platform] || "📄";
1057- const tooltipText = desc ? (desc + "\\n场景:" + scenarios + " 步骤:" + steps + "\\n" + path) : ("场景:" + scenarios + " 步骤:" + steps + "\\n" + path);
1032+ var platformBadge = platformBadges[platform] || "📄";
1033+ var tooltipText = desc ? (desc + "\\n场景:" + scenarios + " 步骤:" + steps + "\\n" + path) : ("场景:" + scenarios + " 步骤:" + steps + "\\n" + path);
10581034
1059- const item = document.createElement("label");
1035+ var item = document.createElement("label");
10601036 item.className = "local-blueprint-item";
10611037 item.style.cssText = "display:flex;align-items:flex-start;gap:4px;padding:3px 4px;border-radius:4px;cursor:pointer;font-size:12px;line-height:1.4;overflow:hidden;width:100%;box-sizing:border-box";
10621038 item.title = tooltipText;
1063- item.addEventListener("mouseenter", () => { item.style.background = "var(--hover-bg,#2a2d2e)"; });
1064- item.addEventListener("mouseleave", () => { item.style.background = "transparent"; });
1039+ item.addEventListener("mouseenter", function() { item.style.background = "var(--hover-bg,#2a2d2e)"; });
1040+ item.addEventListener("mouseleave", function() { item.style.background = "transparent"; });
10651041
1066- const cb = document.createElement("input");
1042+ var cb = document.createElement("input");
10671043 cb.type = "checkbox";
10681044 cb.className = "local-blueprint-cb";
10691045 cb.value = path;
10701046 cb.checked = false;
10711047 cb.disabled = true;
10721048 cb.style.cssText = "margin-top:2px;flex-shrink:0;width:14px;height:14px";
10731049
1074- const fileName = path.replace(/\\\\/g, "/").split("/").pop() || path;
1050+ var fileName = path.replace(/\\\\/g, "/").split("/").pop() || path;
10751051
1076- const info = document.createElement("div");
1052+ var info = document.createElement("div");
10771053 info.style.cssText = "flex:1;min-width:0;overflow:hidden";
10781054 info.innerHTML = '<div style="font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">' + platformBadge + ' ' + displayName + '</div>'
10791055 + (desc ? '<div style="color:var(--muted);font-size:11px;margin-top:1px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">' + desc + '</div>' : '')
@@ -1085,31 +1061,32 @@ export class SidebarProvider implements vscode.WebviewViewProvider {
10851061 });
10861062
10871063 // 全局蓝本checkbox交互逻辑
1088- globalCb.addEventListener("change", () => {
1089- const localItems = blueprintListEl.querySelectorAll(".local-blueprint-item");
1090- const localCbs = blueprintListEl.querySelectorAll(".local-blueprint-cb");
1064+ globalCb.addEventListener("change", function() {
1065+ var localItems = blueprintListEl.querySelectorAll(".local-blueprint-item");
1066+ var localCbs = blueprintListEl.querySelectorAll(".local-blueprint-cb");
10911067 if (globalCb.checked) {
1092- localCbs.forEach(cb => { cb.disabled = true; cb.checked = false; });
1093- localItems.forEach(item => {
1068+ localCbs.forEach(function(cb) { cb.disabled = true; cb.checked = false; });
1069+ localItems.forEach(function( item) {
10941070 item.style.opacity = "0.5";
10951071 item.title = "已勾选全局蓝本,无需勾选局部。如需局部测试,请取消全局勾选。";
10961072 });
10971073 } else {
1098- localCbs.forEach(cb => { cb.disabled = false; });
1099- localItems.forEach((item, i) => {
1074+ localCbs.forEach(function(cb) { cb.disabled = false; });
1075+ localItems.forEach(function (item, i) {
11001076 item.style.opacity = "1";
1101- const entry = entries[i];
1102- const desc = entry.description || "";
1103- const scenarios = entry.scenarioCount || 0;
1104- const steps = entry.stepCount || 0;
1105- const path = entry.path;
1077+ var entry = entries[i];
1078+ if (!entry) return;
1079+ var desc = entry.description || "";
1080+ var scenarios = entry.scenarioCount || 0;
1081+ var steps = entry.stepCount || 0;
1082+ var path = entry.path;
11061083 item.title = desc ? (desc + "\\n场景:" + scenarios + " 步骤:" + steps + "\\n" + path) : ("场景:" + scenarios + " 步骤:" + steps + "\\n" + path);
11071084 });
11081085 }
11091086 });
11101087
11111088 // 填入第一个路径到手动输入框
1112- const firstPath = typeof entries[0] === "string" ? entries[0] : entries[0].path;
1089+ var firstPath = typeof entries[0] === "string" ? entries[0] : entries[0].path;
11131090 inputBlueprintPath.value = firstPath;
11141091 }
11151092
@@ -1142,7 +1119,6 @@ export class SidebarProvider implements vscode.WebviewViewProvider {
11421119 statusDot.className = "status-dot connected";
11431120 engineStatus.textContent = "v" + (data.version || "?");
11441121 btnLaunchEngine.classList.add("hidden");
1145- document.getElementById("btnCheckEngine").classList.add("hidden");
11461122 btnStopEngine.classList.remove("hidden");
11471123 btnStopEngine.textContent = "⏹ 断开引擎";
11481124 btnStopEngine.disabled = false;
@@ -1153,11 +1129,11 @@ export class SidebarProvider implements vscode.WebviewViewProvider {
11531129 statusDot.className = "status-dot disconnected";
11541130 engineStatus.textContent = isStarting ? "启动中..." : "未连接";
11551131 if (!isStarting) {
1132+ // 只有不在启动中时才恢复启动按钮,避免闪烁
11561133 btnLaunchEngine.classList.remove("hidden");
1157- document.getElementById("btnCheckEngine").classList.remove("hidden");
11581134 btnStopEngine.classList.add("hidden");
11591135 }
1160- addLog(isStarting ? "引擎尚未就绪,继续等待..." : "引擎未连接", isStarting ? "warn" : "error");
1136+ addLog(isStarting ? "引擎尚未就绪,继续等待..." : "引擎未连接,点击「一键启动引擎」按钮启动 ", isStarting ? "warn" : "error");
11611137 }
11621138 }
11631139
0 commit comments