aicodeman 0.5.12 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/web/public/api-client.3adebdc2.js.gz +0 -0
- package/dist/web/public/{app.cea3ccb3.js → app.8b544e9f.js} +14 -13
- package/dist/web/public/app.8b544e9f.js.br +0 -0
- package/dist/web/public/app.8b544e9f.js.gz +0 -0
- package/dist/web/public/{constants.573fac38.js → constants.193cefd7.js} +3 -0
- package/dist/web/public/constants.193cefd7.js.br +0 -0
- package/dist/web/public/{constants.573fac38.js.gz → constants.193cefd7.js.gz} +0 -0
- package/dist/web/public/index.html +8 -8
- package/dist/web/public/index.html.br +0 -0
- package/dist/web/public/index.html.gz +0 -0
- package/dist/web/public/input-cjk.88082175.js.gz +0 -0
- package/dist/web/public/keyboard-accessory.9bb75102.js.gz +0 -0
- package/dist/web/public/mobile-handlers.1e2a8ef8.js.gz +0 -0
- package/dist/web/public/mobile.225b595d.css.gz +0 -0
- package/dist/web/public/notification-manager.2d5ea8ec.js.gz +0 -0
- package/dist/web/public/orchestrator-panel.js.gz +0 -0
- package/dist/web/public/{panels-ui.8cdc0c1f.js → panels-ui.07e4dee7.js} +16 -16
- package/dist/web/public/panels-ui.07e4dee7.js.br +0 -0
- package/dist/web/public/panels-ui.07e4dee7.js.gz +0 -0
- package/dist/web/public/ralph-panel.61076370.js.gz +0 -0
- package/dist/web/public/ralph-wizard.f31ab90e.js.gz +0 -0
- package/dist/web/public/respawn-ui.60be6ef5.js.gz +0 -0
- package/dist/web/public/session-ui.15c8b22a.js +36 -0
- package/dist/web/public/session-ui.15c8b22a.js.br +0 -0
- package/dist/web/public/session-ui.15c8b22a.js.gz +0 -0
- package/dist/web/public/settings-ui.45cbf627.js.gz +0 -0
- package/dist/web/public/styles.2e748af7.css +1 -0
- package/dist/web/public/styles.2e748af7.css.br +0 -0
- package/dist/web/public/styles.2e748af7.css.gz +0 -0
- package/dist/web/public/subagent-windows.a366a4ad.js.gz +0 -0
- package/dist/web/public/sw.js.gz +0 -0
- package/dist/web/public/terminal-ui.74920567.js +3 -0
- package/dist/web/public/terminal-ui.74920567.js.br +0 -0
- package/dist/web/public/terminal-ui.74920567.js.gz +0 -0
- package/dist/web/public/upload.html.gz +0 -0
- package/dist/web/public/vendor/marked.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-fit.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-unicode11.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-webgl.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-zerolag-input.137ad9f0.js.gz +0 -0
- package/dist/web/public/vendor/xterm.css.gz +0 -0
- package/dist/web/public/vendor/xterm.min.js.gz +0 -0
- package/dist/web/public/voice-input.085e9e73.js.gz +0 -0
- package/dist/web/routes/clipboard-routes.d.ts +8 -0
- package/dist/web/routes/clipboard-routes.d.ts.map +1 -0
- package/dist/web/routes/clipboard-routes.js +21 -0
- package/dist/web/routes/clipboard-routes.js.map +1 -0
- package/dist/web/routes/index.d.ts +1 -0
- package/dist/web/routes/index.d.ts.map +1 -1
- package/dist/web/routes/index.js +1 -0
- package/dist/web/routes/index.js.map +1 -1
- package/dist/web/server.d.ts.map +1 -1
- package/dist/web/server.js +2 -1
- package/dist/web/server.js.map +1 -1
- package/dist/web/sse-events.d.ts +3 -0
- package/dist/web/sse-events.d.ts.map +1 -1
- package/dist/web/sse-events.js +5 -0
- package/dist/web/sse-events.js.map +1 -1
- package/package.json +1 -1
- package/dist/web/public/app.cea3ccb3.js.br +0 -0
- package/dist/web/public/app.cea3ccb3.js.gz +0 -0
- package/dist/web/public/constants.573fac38.js.br +0 -0
- package/dist/web/public/panels-ui.8cdc0c1f.js.br +0 -0
- package/dist/web/public/panels-ui.8cdc0c1f.js.gz +0 -0
- package/dist/web/public/session-ui.8c9638ab.js +0 -36
- package/dist/web/public/session-ui.8c9638ab.js.br +0 -0
- package/dist/web/public/session-ui.8c9638ab.js.gz +0 -0
- package/dist/web/public/styles.a2a030fc.css +0 -1
- package/dist/web/public/styles.a2a030fc.css.br +0 -0
- package/dist/web/public/styles.a2a030fc.css.gz +0 -0
- package/dist/web/public/terminal-ui.e59d86d5.js +0 -3
- package/dist/web/public/terminal-ui.e59d86d5.js.br +0 -0
- package/dist/web/public/terminal-ui.e59d86d5.js.gz +0 -0
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
<span class="stat-card-value">${this.formatTokens(d+u)}</span>
|
|
15
15
|
<span class="stat-card-cost">~$${h.toFixed(2)}</span>
|
|
16
16
|
</div>
|
|
17
|
-
`;const p=document.getElementById("statsChart"),f=document.getElementById("statsChartDays"),
|
|
17
|
+
`;const p=document.getElementById("statsChart"),f=document.getElementById("statsChartDays"),w=[];for(let v=6;v>=0;v--){const S=new Date;S.setDate(S.getDate()-v);const $=S.toISOString().split("T")[0],y=t.find(D=>D.date===$);w.push({date:$,dayName:S.toLocaleDateString("en-US",{weekday:"short"}),tokens:y?y.inputTokens+y.outputTokens:0,cost:y?y.estimatedCost:0})}const g=Math.max(...w.map(v=>v.tokens),1);p.innerHTML=w.map(v=>{const S=Math.max(v.tokens/g*100,3),$=`${v.dayName}: ${this.formatTokens(v.tokens)} (~$${v.cost.toFixed(2)})`;return`<div class="bar" style="height: ${S}%" data-tooltip="${$}"></div>`}).join(""),f.innerHTML=w.map(v=>`<span>${v.dayName}</span>`).join("");const b=document.getElementById("statsTable"),T=t.slice(0,14);T.length===0?b.innerHTML='<div class="stats-no-data">No usage data recorded yet</div>':b.innerHTML=`
|
|
18
18
|
<div class="stats-table-header">
|
|
19
19
|
<span>Date</span>
|
|
20
20
|
<span>Input</span>
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
<span class="cell cell-cost">$${v.estimatedCost.toFixed(2)}</span>
|
|
30
30
|
</div>
|
|
31
31
|
`).join("")}
|
|
32
|
-
`},closeTokenStats(){const e=document.getElementById("tokenStatsModal");e&&e.classList.remove("active")},async toggleMonitorPanel(){const e=document.getElementById("monitorPanel"),t=document.getElementById("monitorToggleBtn");e.classList.toggle("open"),e.classList.contains("open")?(await this.loadMuxSessions(),await fetch("/api/mux-sessions/stats/start",{method:"POST"}),this.renderTaskPanel(),t&&(t.innerHTML="▼")):(await fetch("/api/mux-sessions/stats/stop",{method:"POST"}),t&&(t.innerHTML="▲"))},toggleTaskPanel(){this.toggleMonitorPanel()},toggleMonitorDetach(){const e=document.getElementById("monitorPanel"),t=document.getElementById("monitorDetachBtn");e.classList.contains("detached")?(e.classList.remove("detached"),e.style.top="",e.style.left="",e.style.width="",e.style.height="",t&&(t.innerHTML="⧉",t.title="Detach panel")):(e.classList.add("detached"),e.classList.add("open"),t&&(t.innerHTML="⊞",t.title="Attach panel"),this.setupMonitorDrag())},setupMonitorDrag(){const e=document.getElementById("monitorPanel"),t=document.getElementById("monitorPanelHeader");if(!e||!t)return;let s=!1,n,a,o,r;const i=d=>{if(d.target.closest("button")||!e.classList.contains("detached"))return;s=!0;const u=getEventCoords(d);n=u.clientX,a=u.clientY;const h=e.getBoundingClientRect();o=h.left,r=h.top,document.addEventListener("mousemove",l),document.addEventListener("mouseup",c),document.addEventListener("touchmove",l,{passive:!1}),document.addEventListener("touchend",c),d.preventDefault()},l=d=>{if(!s)return;const u=getEventCoords(d),h=u.clientX-n,m=u.clientY-a;let p=o+h,f=r+m;const
|
|
32
|
+
`},closeTokenStats(){const e=document.getElementById("tokenStatsModal");e&&e.classList.remove("active")},async toggleMonitorPanel(){const e=document.getElementById("monitorPanel"),t=document.getElementById("monitorToggleBtn");e.classList.toggle("open"),e.classList.contains("open")?(await this.loadMuxSessions(),await fetch("/api/mux-sessions/stats/start",{method:"POST"}),this.renderTaskPanel(),t&&(t.innerHTML="▼")):(await fetch("/api/mux-sessions/stats/stop",{method:"POST"}),t&&(t.innerHTML="▲"))},toggleTaskPanel(){this.toggleMonitorPanel()},toggleMonitorDetach(){const e=document.getElementById("monitorPanel"),t=document.getElementById("monitorDetachBtn");e.classList.contains("detached")?(e.classList.remove("detached"),e.style.top="",e.style.left="",e.style.width="",e.style.height="",t&&(t.innerHTML="⧉",t.title="Detach panel")):(e.classList.add("detached"),e.classList.add("open"),t&&(t.innerHTML="⊞",t.title="Attach panel"),this.setupMonitorDrag())},setupMonitorDrag(){const e=document.getElementById("monitorPanel"),t=document.getElementById("monitorPanelHeader");if(!e||!t)return;let s=!1,n,a,o,r;const i=d=>{if(d.target.closest("button")||!e.classList.contains("detached"))return;s=!0;const u=getEventCoords(d);n=u.clientX,a=u.clientY;const h=e.getBoundingClientRect();o=h.left,r=h.top,document.addEventListener("mousemove",l),document.addEventListener("mouseup",c),document.addEventListener("touchmove",l,{passive:!1}),document.addEventListener("touchend",c),d.preventDefault()},l=d=>{if(!s)return;const u=getEventCoords(d),h=u.clientX-n,m=u.clientY-a;let p=o+h,f=r+m;const w=e.getBoundingClientRect();p=Math.max(0,Math.min(window.innerWidth-w.width,p)),f=Math.max(0,Math.min(window.innerHeight-w.height,f)),e.style.left=p+"px",e.style.top=f+"px"},c=()=>{s=!1,document.removeEventListener("mousemove",l),document.removeEventListener("mouseup",c),document.removeEventListener("touchmove",l),document.removeEventListener("touchend",c)};t.removeEventListener("mousedown",t._dragHandler),t.removeEventListener("touchstart",t._touchDragHandler),t._dragHandler=i,t._touchDragHandler=i,t.addEventListener("mousedown",i),t.addEventListener("touchstart",i,{passive:!1})},toggleSubagentsDetach(){const e=document.getElementById("subagentsPanel"),t=document.getElementById("subagentsDetachBtn");e.classList.contains("detached")?(e.classList.remove("detached"),e.style.top="",e.style.left="",e.style.width="",e.style.height="",t&&(t.innerHTML="⧉",t.title="Detach panel")):(e.classList.add("detached"),e.classList.add("open"),t&&(t.innerHTML="⊞",t.title="Attach panel"),this.setupSubagentsDrag())},setupSubagentsDrag(){const e=document.getElementById("subagentsPanel"),t=document.getElementById("subagentsPanelHeader");if(!e||!t)return;let s=!1,n,a,o,r;const i=d=>{if(d.target.closest("button")||!e.classList.contains("detached"))return;s=!0;const u=getEventCoords(d);n=u.clientX,a=u.clientY;const h=e.getBoundingClientRect();o=h.left,r=h.top,document.addEventListener("mousemove",l),document.addEventListener("mouseup",c),document.addEventListener("touchmove",l,{passive:!1}),document.addEventListener("touchend",c),d.preventDefault()},l=d=>{if(!s)return;const u=getEventCoords(d),h=u.clientX-n,m=u.clientY-a;let p=o+h,f=r+m;const w=e.getBoundingClientRect();p=Math.max(0,Math.min(window.innerWidth-w.width,p)),f=Math.max(0,Math.min(window.innerHeight-w.height,f)),e.style.left=p+"px",e.style.top=f+"px"},c=()=>{s=!1,document.removeEventListener("mousemove",l),document.removeEventListener("mouseup",c),document.removeEventListener("touchmove",l),document.removeEventListener("touchend",c)};t.removeEventListener("mousedown",t._dragHandler),t.removeEventListener("touchstart",t._touchDragHandler),t._dragHandler=i,t._touchDragHandler=i,t.addEventListener("mousedown",i),t.addEventListener("touchstart",i,{passive:!1})},renderTaskPanel(){this._debouncedCall("taskPanel",this._renderTaskPanelImmediate)},_renderTaskPanelImmediate(){const e=this.sessions.get(this.activeSessionId),t=document.getElementById("backgroundTasksBody"),s=document.getElementById("taskPanelStats"),n=document.getElementById("backgroundTasksSection");if(!e||!e.taskTree||e.taskTree.length===0){n&&(n.style.display="none"),t.innerHTML="",s.textContent="0 tasks";return}n&&(n.style.display="");const a=e.taskStats||{running:0,completed:0,failed:0,total:0};s.textContent=`${a.running} running, ${a.completed} done`;const o=(l,c)=>{const d=l.status==="running"?"":l.status==="completed"?"✓":"✗",u=l.endTime?`${((l.endTime-l.startTime)/1e3).toFixed(1)}s`:`${((Date.now()-l.startTime)/1e3).toFixed(0)}s...`;let h="";if(l.children&&l.children.length>0){h='<div class="task-children">';for(const m of l.children){const p=c.find(f=>f.id===m);p&&(h+=`<div class="task-node">${o(p,c)}</div>`)}h+="</div>"}return`
|
|
33
33
|
<div class="task-item">
|
|
34
34
|
<span class="task-status-icon ${l.status}">${d}</span>
|
|
35
35
|
<div class="task-info">
|
|
@@ -134,10 +134,10 @@
|
|
|
134
134
|
<span class="tool-detail">${escapeHtml(a)}</span>
|
|
135
135
|
</div>`}else if(e.type==="message"){const s=e.text.length>150?e.text.substring(0,150)+"...":e.text;return`<div class="message-line">
|
|
136
136
|
<span class="time">${t}</span> \u{1F4AC} ${escapeHtml(s)}
|
|
137
|
-
</div>`}return""},updateSubagentWindows(){for(const e of this.subagentWindows.keys())this.renderSubagentWindowContent(e),this.updateSubagentWindowHeader(e)},updateSubagentWindowHeader(e){const t=this.subagents.get(e);if(!t)return;const s=document.getElementById(`subagent-window-${e}`);if(!s)return;const n=s.querySelector(".subagent-window-title .id");if(n){const c=this.getTeammateInfo(t),d=c?c.name:t.description||e.substring(0,7),u=d.length>50?d.substring(0,50)+"...":d;n.textContent=u}let a=s.querySelector(".teammate-badge");const o=this.getTeammateInfo(t);if(o&&!a){const c=s.querySelector(".subagent-window-title");if(c){const d=document.createElement("span");d.className=`teammate-badge teammate-color-${o.color}`,d.title=`Team: ${o.teamName}`,d.textContent=`@${o.name}`;const u=c.querySelector(".status");u&&u.insertAdjacentElement("beforebegin",d)}}const r=s.querySelector(".subagent-window-title");r&&(r.title=t.description||e);let i=s.querySelector(".subagent-window-title .subagent-model-badge");if(t.modelShort){if(!i){i=document.createElement("span"),i.className=`subagent-model-badge ${t.modelShort}`;const c=s.querySelector(".subagent-window-title .status");c&&c.insertAdjacentElement("beforebegin",i)}i.className=`subagent-model-badge ${t.modelShort}`,i.textContent=t.modelShort}const l=s.querySelector(".subagent-window-title .status");l&&(l.className=`status ${t.status}`,l.textContent=t.status)},openAllActiveSubagentWindows(){for(const[e,t]of this.subagents)t.status==="active"&&!this.subagentWindows.has(e)&&this.openSubagentWindow(e)},initTeammateTerminal(e,t,s){const n=s.querySelector(".subagent-window-body");if(!n)return;n.innerHTML="",n.classList.add("teammate-terminal-body"),s.classList.add("has-terminal");const a=t.sessionId,o=[];this.teammateTerminals.set(e,{terminal:null,fitAddon:null,paneTarget:t.paneTarget,sessionId:a,resizeObserver:null,pendingData:o}),requestAnimationFrame(()=>{if(!document.contains(n)){this.teammateTerminals.delete(e);return}const r=new Terminal({theme:{background:"#0d0d0d",foreground:"#e0e0e0",cursor:"#e0e0e0",cursorAccent:"#0d0d0d",selection:"rgba(255, 255, 255, 0.3)",black:"#0d0d0d",red:"#ff6b6b",green:"#51cf66",yellow:"#ffd43b",blue:"#339af0",magenta:"#cc5de8",cyan:"#22b8cf",white:"#e0e0e0",brightBlack:"#495057",brightRed:"#ff8787",brightGreen:"#69db7c",brightYellow:"#ffe066",brightBlue:"#5c7cfa",brightMagenta:"#da77f2",brightCyan:"#66d9e8",brightWhite:"#ffffff"},fontFamily:'"Fira Code", "Cascadia Code", "JetBrains Mono", "SF Mono", Monaco, monospace',fontSize:12,lineHeight:1.2,cursorBlink:!0,cursorStyle:"block",scrollback:5e3,allowTransparency:!0,allowProposedApi:!0}),i=new FitAddon.FitAddon;if(r.loadAddon(i),typeof Unicode11Addon<"u")try{const d=new Unicode11Addon.Unicode11Addon;r.loadAddon(d),r.unicode.activeVersion="11"}catch{}try{r.open(n)}catch(d){console.warn("[TeammateTerminal] Failed to open terminal:",d),this.teammateTerminals.delete(e);return}setTimeout(()=>{try{i.fit()}catch{}fetch(`/api/sessions/${a}/teammate-pane-buffer/${encodeURIComponent(t.paneTarget)}`).then(d=>d.json()).then(d=>{if(d.success&&d.data?.buffer)try{r.write(d.data.buffer)}catch{}}).catch(d=>console.error("[TeammateTerminal] Failed to fetch buffer:",d));for(const d of o)try{r.write(d)}catch{}o.length=0},100),r.onData(d=>{fetch(`/api/sessions/${a}/teammate-pane-input`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({paneTarget:t.paneTarget,input:d})}).catch(u=>console.error("[TeammateTerminal] Failed to send input:",u))});const l=new ResizeObserver(()=>{requestAnimationFrame(()=>{try{i.fit()}catch{}})});l.observe(n);const c=this.teammateTerminals.get(e);c&&(c.terminal=r,c.fitAddon=i,c.resizeObserver=l)})},openTeammateTerminalWindow(e){if(!this.sessions.has(e.sessionId))return;const t=`pane-${e.paneTarget}`;if(this.subagentWindows.has(t)){const y=this.subagentWindows.get(t);y.hidden&&(y.element.style.display="flex",y.hidden=!1),y.element.style.zIndex=++this.subagentWindowZIndex,y.minimized&&this.restoreSubagentWindow(t);return}const s=this.subagentWindows.size,n=550,a=400,o=20,r=window.innerWidth,i=window.innerHeight,l=50,c=120,d=Math.floor((r-l-50)/(n+o))||1,u=Math.floor((i-c-50)/(a+o))||1,h=s%d,m=Math.floor(s/d)%u;let p=l+h*(n+o),f=c+m*(a+o);p=Math.max(10,Math.min(p,r-n-10)),f=Math.max(10,Math.min(f,i-a-10));const
|
|
137
|
+
</div>`}return""},updateSubagentWindows(){for(const e of this.subagentWindows.keys())this.renderSubagentWindowContent(e),this.updateSubagentWindowHeader(e)},updateSubagentWindowHeader(e){const t=this.subagents.get(e);if(!t)return;const s=document.getElementById(`subagent-window-${e}`);if(!s)return;const n=s.querySelector(".subagent-window-title .id");if(n){const c=this.getTeammateInfo(t),d=c?c.name:t.description||e.substring(0,7),u=d.length>50?d.substring(0,50)+"...":d;n.textContent=u}let a=s.querySelector(".teammate-badge");const o=this.getTeammateInfo(t);if(o&&!a){const c=s.querySelector(".subagent-window-title");if(c){const d=document.createElement("span");d.className=`teammate-badge teammate-color-${o.color}`,d.title=`Team: ${o.teamName}`,d.textContent=`@${o.name}`;const u=c.querySelector(".status");u&&u.insertAdjacentElement("beforebegin",d)}}const r=s.querySelector(".subagent-window-title");r&&(r.title=t.description||e);let i=s.querySelector(".subagent-window-title .subagent-model-badge");if(t.modelShort){if(!i){i=document.createElement("span"),i.className=`subagent-model-badge ${t.modelShort}`;const c=s.querySelector(".subagent-window-title .status");c&&c.insertAdjacentElement("beforebegin",i)}i.className=`subagent-model-badge ${t.modelShort}`,i.textContent=t.modelShort}const l=s.querySelector(".subagent-window-title .status");l&&(l.className=`status ${t.status}`,l.textContent=t.status)},openAllActiveSubagentWindows(){for(const[e,t]of this.subagents)t.status==="active"&&!this.subagentWindows.has(e)&&this.openSubagentWindow(e)},initTeammateTerminal(e,t,s){const n=s.querySelector(".subagent-window-body");if(!n)return;n.innerHTML="",n.classList.add("teammate-terminal-body"),s.classList.add("has-terminal");const a=t.sessionId,o=[];this.teammateTerminals.set(e,{terminal:null,fitAddon:null,paneTarget:t.paneTarget,sessionId:a,resizeObserver:null,pendingData:o}),requestAnimationFrame(()=>{if(!document.contains(n)){this.teammateTerminals.delete(e);return}const r=new Terminal({theme:{background:"#0d0d0d",foreground:"#e0e0e0",cursor:"#e0e0e0",cursorAccent:"#0d0d0d",selection:"rgba(255, 255, 255, 0.3)",black:"#0d0d0d",red:"#ff6b6b",green:"#51cf66",yellow:"#ffd43b",blue:"#339af0",magenta:"#cc5de8",cyan:"#22b8cf",white:"#e0e0e0",brightBlack:"#495057",brightRed:"#ff8787",brightGreen:"#69db7c",brightYellow:"#ffe066",brightBlue:"#5c7cfa",brightMagenta:"#da77f2",brightCyan:"#66d9e8",brightWhite:"#ffffff"},fontFamily:'"Fira Code", "Cascadia Code", "JetBrains Mono", "SF Mono", Monaco, monospace',fontSize:12,lineHeight:1.2,cursorBlink:!0,cursorStyle:"block",scrollback:5e3,allowTransparency:!0,allowProposedApi:!0}),i=new FitAddon.FitAddon;if(r.loadAddon(i),typeof Unicode11Addon<"u")try{const d=new Unicode11Addon.Unicode11Addon;r.loadAddon(d),r.unicode.activeVersion="11"}catch{}try{r.open(n)}catch(d){console.warn("[TeammateTerminal] Failed to open terminal:",d),this.teammateTerminals.delete(e);return}setTimeout(()=>{try{i.fit()}catch{}fetch(`/api/sessions/${a}/teammate-pane-buffer/${encodeURIComponent(t.paneTarget)}`).then(d=>d.json()).then(d=>{if(d.success&&d.data?.buffer)try{r.write(d.data.buffer)}catch{}}).catch(d=>console.error("[TeammateTerminal] Failed to fetch buffer:",d));for(const d of o)try{r.write(d)}catch{}o.length=0},100),r.onData(d=>{fetch(`/api/sessions/${a}/teammate-pane-input`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({paneTarget:t.paneTarget,input:d})}).catch(u=>console.error("[TeammateTerminal] Failed to send input:",u))});const l=new ResizeObserver(()=>{requestAnimationFrame(()=>{try{i.fit()}catch{}})});l.observe(n);const c=this.teammateTerminals.get(e);c&&(c.terminal=r,c.fitAddon=i,c.resizeObserver=l)})},openTeammateTerminalWindow(e){if(!this.sessions.has(e.sessionId))return;const t=`pane-${e.paneTarget}`;if(this.subagentWindows.has(t)){const y=this.subagentWindows.get(t);y.hidden&&(y.element.style.display="flex",y.hidden=!1),y.element.style.zIndex=++this.subagentWindowZIndex,y.minimized&&this.restoreSubagentWindow(t);return}const s=this.subagentWindows.size,n=550,a=400,o=20,r=window.innerWidth,i=window.innerHeight,l=50,c=120,d=Math.floor((r-l-50)/(n+o))||1,u=Math.floor((i-c-50)/(a+o))||1,h=s%d,m=Math.floor(s/d)%u;let p=l+h*(n+o),f=c+m*(a+o);p=Math.max(10,Math.min(p,r-n-10)),f=Math.max(10,Math.min(f,i-a-10));const w=e.color||"blue",g=document.createElement("div");g.className="subagent-window has-terminal",g.id=`subagent-window-${t}`,g.style.zIndex=++this.subagentWindowZIndex,g.style.left=`${p}px`,g.style.top=`${f}px`,g.style.width=`${n}px`,g.style.height=`${a}px`,g.innerHTML=`
|
|
138
138
|
<div class="subagent-window-header">
|
|
139
139
|
<div class="subagent-window-title" title="Teammate terminal: ${escapeHtml(e.teammateName)} (pane ${e.paneTarget})">
|
|
140
|
-
<span class="icon" style="color: var(--team-color-${
|
|
140
|
+
<span class="icon" style="color: var(--team-color-${w}, #339af0)">\u2B24</span>
|
|
141
141
|
<span class="id">${escapeHtml(e.teammateName)}</span>
|
|
142
142
|
<span class="status running">terminal</span>
|
|
143
143
|
</div>
|
|
@@ -147,10 +147,10 @@
|
|
|
147
147
|
</div>
|
|
148
148
|
<div class="subagent-window-body teammate-terminal-body" id="subagent-window-body-${t}">
|
|
149
149
|
</div>
|
|
150
|
-
`,document.body.appendChild(g);const
|
|
150
|
+
`,document.body.appendChild(g);const b=this.makeWindowDraggable(g,g.querySelector(".subagent-window-header"));typeof this.makeWindowResizable=="function"&&this.makeWindowResizable(g);const S=(this.loadAppSettingsFromStorage().subagentActiveTabOnly??!0)&&e.sessionId!==this.activeSessionId;this.subagentWindows.set(t,{element:g,minimized:!1,hidden:S,dragListeners:b,description:`Teammate: ${e.teammateName}`}),this.subagentParentMap.set(t,e.sessionId),S&&(g.style.display="none"),g.addEventListener("mousedown",()=>{g.style.zIndex=++this.subagentWindowZIndex});const $=new ResizeObserver(()=>{this.updateConnectionLines()});if($.observe(g),this.subagentWindows.get(t).resizeObserver=$,S){const y=this.subagentWindows.get(t);y&&(y._lazyTerminal=!0,y._lazyPaneTarget=e.paneTarget,y._lazySessionId=e.sessionId)}else this.initTeammateTerminal(t,e,g);requestAnimationFrame(()=>{g.style.transition="transform 0.3s ease, opacity 0.3s ease",g.style.transform="scale(1)",g.style.opacity="1"})},rebuildTeammateMap(){this.teammateMap.clear();for(const[e,t]of this.teams)for(const s of t.members)s.agentType!=="team-lead"&&this.teammateMap.set(s.name,{name:s.name,color:s.color||"blue",teamName:e,agentId:s.agentId})},getTeammateInfo(e){if(!e?.description)return null;const t=e.description.match(/<teammate-message\s+teammate_id="?([^">\s]+)/);if(!t)return null;const n=t[1].split("@")[0];return this.teammateMap.get(n)||{name:n,color:"blue",teamName:"unknown"}},getTeammateBadgeHtml(e){const t=this.getTeammateInfo(e);return t?`<span class="teammate-badge teammate-color-${t.color}" title="Team: ${escapeHtml(t.teamName)}">@${escapeHtml(t.name)}</span>`:""},renderTeamTasksPanel(){const e=document.getElementById("teamTasksPanel");if(!e)return;let t=null,s=null;if(this.activeSessionId){for(const[m,p]of this.teams)if(p.leadSessionId===this.activeSessionId){t=p,s=m;break}}if(!t){e.style.display="none";return}const n=e.style.display==="none";if(e.style.display="flex",n&&!this.teamTasksDragListeners){e.style.left=`${Math.max(10,window.innerWidth-360-20)}px`,e.style.top=`${Math.max(10,window.innerHeight-300-70)}px`;const f=e.querySelector(".team-tasks-header");f&&(this.teamTasksDragListeners=this.makeWindowDraggable(e,f))}const a=this.teamTasks.get(s)||[],o=a.filter(m=>m.status==="completed").length,r=a.length,i=r>0?Math.round(o/r*100):0,l=e.querySelector(".team-tasks-header-text");if(l){const m=t.members.filter(p=>p.agentType!=="team-lead").length;l.textContent=`Team Tasks (${m} teammates)`}const c=e.querySelector(".team-tasks-progress-fill");c&&(c.style.width=`${i}%`);const d=e.querySelector(".team-tasks-progress-text");d&&(d.textContent=`${o}/${r}`);const u=e.querySelector(".team-tasks-list");if(!u)return;if(a.length===0){u.innerHTML='<div class="team-task-empty">No tasks yet</div>';return}const h=a.map(m=>{const p=m.status==="completed"?"\u2713":m.status==="in_progress"?"\u25C9":"\u25CB",f=m.status.replace("_","-"),w=m.owner?`<span class="team-task-owner teammate-color-${this.getTeammateColor(m.owner)}">${escapeHtml(m.owner)}</span>`:"";return`<div class="team-task-item ${f}">
|
|
151
151
|
<span class="team-task-status">${p}</span>
|
|
152
152
|
<span class="team-task-subject">${escapeHtml(m.subject)}</span>
|
|
153
|
-
${
|
|
153
|
+
${w}
|
|
154
154
|
</div>`}).join("");u.innerHTML=h},hideTeamTasksPanel(){const e=document.getElementById("teamTasksPanel");e&&(e.style.display="none"),this.teamTasksDragListeners&&(document.removeEventListener("mousemove",this.teamTasksDragListeners.move),document.removeEventListener("mouseup",this.teamTasksDragListeners.up),this.teamTasksDragListeners.touchMove&&(document.removeEventListener("touchmove",this.teamTasksDragListeners.touchMove),document.removeEventListener("touchend",this.teamTasksDragListeners.up),document.removeEventListener("touchcancel",this.teamTasksDragListeners.up)),this.teamTasksDragListeners.handle&&(this.teamTasksDragListeners.handle.removeEventListener("mousedown",this.teamTasksDragListeners.handleMouseDown),this.teamTasksDragListeners.handle.removeEventListener("touchstart",this.teamTasksDragListeners.handleTouchStart)),this.teamTasksDragListeners=null)},getTeammateColor(e){return this.teammateMap.get(e)?.color||"blue"},normalizeFilePath(e,t){if(!e)return"";let s=e.trim();const n="/home/"+(window.USER||"user");s.startsWith("~/")?s=n+s.slice(1):s==="~"&&(s=n),!s.startsWith("/")&&t&&(s=t+"/"+s);const a=s.split("/"),o=[];for(const r of a)r===""||r==="."||(r===".."?o.length>1&&o.pop():o.push(r));return"/"+o.join("/")},getFilename(e){const t=e.split("/");return t[t.length-1]||""},isShallowRootPath(e){return e.startsWith("/")?e.split("/").filter(s=>s!=="").length===1:!1},isPathInWorkingDir(e,t){if(!t)return!1;const s=this.normalizeFilePath(e,t);return s.startsWith(t+"/")||s===t},pathsAreEquivalent(e,t,s){const n=this.normalizeFilePath(e,s),a=this.normalizeFilePath(t,s);if(n===a)return!0;const o=this.getFilename(n),r=this.getFilename(a);if(o!==r)return!1;const i=this.isShallowRootPath(e),l=this.isShallowRootPath(t),c=this.isPathInWorkingDir(n,s),d=this.isPathInWorkingDir(a,s);return!!(i&&d||l&&c)},pickBetterPath(e,t,s){if(s){const o=this.isPathInWorkingDir(e,s),r=this.isPathInWorkingDir(t,s);if(o&&!r)return e;if(r&&!o)return t}const n=e.startsWith("/"),a=t.startsWith("/");return n&&!a?e:a&&!n?t:e.length!==t.length?e.length>t.length?e:t:!e.includes("~")&&t.includes("~")?e:!t.includes("~")&&e.includes("~")?t:e},deduplicateProjectInsightPaths(e,t){const s=[];for(const o of e)for(const r of o.filePaths)s.push({rawPath:r,toolId:o.id});if(s.length<=1){const o=new Map;for(const r of s)o.set(this.normalizeFilePath(r.rawPath,t),r);return o}s.sort((o,r)=>{const i=this.isPathInWorkingDir(o.rawPath,t),l=this.isPathInWorkingDir(r.rawPath,t);return i&&!l?-1:l&&!i?1:r.rawPath.length-o.rawPath.length});const n=new Map,a=new Set;for(const{rawPath:o,toolId:r}of s){const i=this.normalizeFilePath(o,t);let l=!1;for(const[,c]of n)if(this.pathsAreEquivalent(o,c.rawPath,t)){l=!0;break}!l&&!a.has(i)&&(n.set(i,{rawPath:o,toolId:r}),a.add(i))}return n},handleBashToolStart(e,t){let s=this.projectInsights.get(e)||[];s=s.filter(n=>n.id!==t.id),s.push(t),this.projectInsights.set(e,s),this.renderProjectInsightsPanel()},handleBashToolEnd(e,t){const n=(this.projectInsights.get(e)||[]).find(a=>a.id===t.id);n&&(n.status="completed"),this.renderProjectInsightsPanel(),setTimeout(()=>{const a=this.projectInsights.get(e)||[];this.projectInsights.set(e,a.filter(o=>o.id!==t.id)),this.renderProjectInsightsPanel()},2e3)},handleBashToolsUpdate(e,t){this.projectInsights.set(e,t),this.renderProjectInsightsPanel()},renderProjectInsightsPanel(){const e=this.$("projectInsightsPanel"),t=this.$("projectInsightsList");if(!e||!t)return;if(!(this.loadAppSettingsFromStorage().showProjectInsights??!1)){e.classList.remove("visible"),this.projectInsightsPanelVisible=!1;return}const o=(this.projectInsights.get(this.activeSessionId)||[]).filter(u=>u.status==="running");if(o.length===0){e.classList.remove("visible"),this.projectInsightsPanelVisible=!1;return}e.classList.add("visible"),this.projectInsightsPanelVisible=!0;const i=this.sessions.get(this.activeSessionId)?.workingDir||this.currentSessionWorkingDir,l=this.deduplicateProjectInsightPaths(o,i),c=new Set(Array.from(l.values()).map(u=>u.rawPath)),d=[];for(const u of o){const h=u.filePaths.filter(p=>c.has(p));if(h.length===0)continue;const m=u.command.length>50?u.command.substring(0,50)+"...":u.command;d.push(`
|
|
155
155
|
<div class="project-insight-item" data-tool-id="${u.id}">
|
|
156
156
|
<div class="project-insight-command">
|
|
@@ -167,15 +167,15 @@
|
|
|
167
167
|
`)}d.push(`
|
|
168
168
|
</div>
|
|
169
169
|
</div>
|
|
170
|
-
`)}t.innerHTML=d.join("")},closeProjectInsightsPanel(){const e=this.$("projectInsightsPanel");e&&(e.classList.remove("visible"),this.projectInsightsPanelVisible=!1)},async loadFileBrowser(e){if(!e)return;const t=this.$("fileBrowserTree"),s=this.$("fileBrowserStatus");if(t){t.innerHTML='<div class="file-browser-loading">Loading files...</div>';try{const n=await fetch(`/api/sessions/${e}/files?depth=5&showHidden=false`);if(!n.ok)throw new Error("Failed to load files");const a=await n.json();if(!a.success)throw new Error(a.error||"Failed to load files");if(this.fileBrowserData=a.data,this.renderFileBrowserTree(),s){const{totalFiles:o,totalDirectories:r,truncated:i}=a.data;s.textContent=`${o} files, ${r} dirs${i?" (truncated)":""}`}}catch(n){console.error("Failed to load file browser:",n),t.innerHTML=`<div class="file-browser-empty">Failed to load files: ${escapeHtml(n.message)}</div>`}}},renderFileBrowserTree(){const e=this.$("fileBrowserTree");if(!e||!this.fileBrowserData)return;const{tree:t}=this.fileBrowserData;if(!t||t.length===0){e.innerHTML='<div class="file-browser-empty">No files found</div>';return}const s=[],n=this.fileBrowserFilter.toLowerCase(),a=(o,r)=>{const i=o.type==="directory",l=this.fileBrowserExpandedDirs.has(o.path),c=!n||o.name.toLowerCase().includes(n);let d=!1;i&&n&&o.children&&(d=this.hasMatchingChild(o,n));const h=!(c||d)&&n?" hidden-by-filter":"",m=i?l?"\u{1F4C2}":"\u{1F4C1}":this.getFileIcon(o.extension),p=i?`<span class="file-tree-expand${l?" expanded":""}">\u25B6</span>`:'<span class="file-tree-expand"></span>',f=!i&&o.size!==void 0?`<span class="file-tree-size">${this.formatFileSize(o.size)}</span>`:"",
|
|
170
|
+
`)}t.innerHTML=d.join("")},closeProjectInsightsPanel(){const e=this.$("projectInsightsPanel");e&&(e.classList.remove("visible"),this.projectInsightsPanelVisible=!1)},async loadFileBrowser(e){if(!e)return;const t=this.$("fileBrowserTree"),s=this.$("fileBrowserStatus");if(t){t.innerHTML='<div class="file-browser-loading">Loading files...</div>';try{const n=await fetch(`/api/sessions/${e}/files?depth=5&showHidden=false`);if(!n.ok)throw new Error("Failed to load files");const a=await n.json();if(!a.success)throw new Error(a.error||"Failed to load files");if(this.fileBrowserData=a.data,this.renderFileBrowserTree(),s){const{totalFiles:o,totalDirectories:r,truncated:i}=a.data;s.textContent=`${o} files, ${r} dirs${i?" (truncated)":""}`}}catch(n){console.error("Failed to load file browser:",n),t.innerHTML=`<div class="file-browser-empty">Failed to load files: ${escapeHtml(n.message)}</div>`}}},renderFileBrowserTree(){const e=this.$("fileBrowserTree");if(!e||!this.fileBrowserData)return;const{tree:t}=this.fileBrowserData;if(!t||t.length===0){e.innerHTML='<div class="file-browser-empty">No files found</div>';return}const s=[],n=this.fileBrowserFilter.toLowerCase(),a=(o,r)=>{const i=o.type==="directory",l=this.fileBrowserExpandedDirs.has(o.path),c=!n||o.name.toLowerCase().includes(n);let d=!1;i&&n&&o.children&&(d=this.hasMatchingChild(o,n));const h=!(c||d)&&n?" hidden-by-filter":"",m=i?l?"\u{1F4C2}":"\u{1F4C1}":this.getFileIcon(o.extension),p=i?`<span class="file-tree-expand${l?" expanded":""}">\u25B6</span>`:'<span class="file-tree-expand"></span>',f=!i&&o.size!==void 0?`<span class="file-tree-size">${this.formatFileSize(o.size)}</span>`:"",w=i?"file-tree-name directory":"file-tree-name",g=i?"":`<a class="file-tree-download" href="/api/sessions/${this.activeSessionId}/file-raw?path=${encodeURIComponent(o.path)}&download=true" title="Download" onclick="event.stopPropagation()">⬇</a>`;if(s.push(`
|
|
171
171
|
<div class="file-tree-item${h}" data-path="${escapeHtml(o.path)}" data-type="${o.type}" data-depth="${r}">
|
|
172
172
|
${p}
|
|
173
173
|
<span class="file-tree-icon">${m}</span>
|
|
174
|
-
<span class="${
|
|
174
|
+
<span class="${w}">${escapeHtml(o.name)}</span>
|
|
175
175
|
${f}
|
|
176
176
|
${g}
|
|
177
177
|
</div>
|
|
178
|
-
`),i&&l&&o.children)for(const
|
|
178
|
+
`),i&&l&&o.children)for(const b of o.children)a(b,r+1)};for(const o of t)a(o,0);e.innerHTML=s.join(""),e.querySelectorAll(".file-tree-item").forEach(o=>{o.addEventListener("click",()=>{const r=o.dataset.path;o.dataset.type==="directory"?this.toggleFileBrowserFolder(r):this.openFilePreview(r)})})},hasMatchingChild(e,t){if(!e.children)return!1;for(const s of e.children)if(s.name.toLowerCase().includes(t)||s.type==="directory"&&this.hasMatchingChild(s,t))return!0;return!1},toggleFileBrowserFolder(e){this.fileBrowserExpandedDirs.has(e)?this.fileBrowserExpandedDirs.delete(e):this.fileBrowserExpandedDirs.add(e),this.renderFileBrowserTree()},filterFileBrowser(e){this.fileBrowserFilter=e,e&&this.expandAllDirectories(this.fileBrowserData?.tree||[]),this.renderFileBrowserTree()},expandAllDirectories(e){for(const t of e)t.type==="directory"&&(this.fileBrowserExpandedDirs.add(t.path),t.children&&this.expandAllDirectories(t.children))},collapseAllDirectories(){this.fileBrowserExpandedDirs.clear()},toggleFileBrowserExpand(){this.fileBrowserAllExpanded=!this.fileBrowserAllExpanded;const e=this.$("fileBrowserExpandBtn");this.fileBrowserAllExpanded?(this.expandAllDirectories(this.fileBrowserData?.tree||[]),e&&(e.innerHTML="\u229F")):(this.collapseAllDirectories(),e&&(e.innerHTML="\u229E")),this.renderFileBrowserTree()},refreshFileBrowser(){if(this.activeSessionId){this.fileBrowserExpandedDirs.clear(),this.fileBrowserFilter="",this.fileBrowserAllExpanded=!1;const e=this.$("fileBrowserSearch");e&&(e.value=""),this.loadFileBrowser(this.activeSessionId)}},closeFileBrowserPanel(){const e=this.$("fileBrowserPanel");if(e&&(e.classList.remove("visible"),e.style.left="",e.style.top="",e.style.bottom="",e.style.right=""),this.fileBrowserDragListeners){const s=this.fileBrowserDragListeners;document.removeEventListener("mousemove",s.move),document.removeEventListener("mouseup",s.up),document.removeEventListener("touchmove",s.touchMove),document.removeEventListener("touchend",s.up),document.removeEventListener("touchcancel",s.up),s.handle&&(s.handle.removeEventListener("mousedown",s.handleMouseDown),s.handle.removeEventListener("touchstart",s.handleTouchStart),s._onFirstDrag&&(s.handle.removeEventListener("mousedown",s._onFirstDrag),s.handle.removeEventListener("touchstart",s._onFirstDrag))),this.fileBrowserDragListeners=null}const t=this.loadAppSettingsFromStorage();t.showFileBrowser=!1,this.saveAppSettingsToStorage(t)},async openFilePreview(e){if(!this.activeSessionId||!e)return;const t=this.$("filePreviewOverlay"),s=this.$("filePreviewTitle"),n=this.$("filePreviewBody"),a=this.$("filePreviewFooter");if(!(!t||!n)){t.classList.add("visible"),s.textContent=e,n.innerHTML='<div class="binary-message">Loading...</div>',a.textContent="";try{const o=await fetch(`/api/sessions/${this.activeSessionId}/file-content?path=${encodeURIComponent(e)}&lines=500`);if(!o.ok)throw new Error("Failed to load file");const r=await o.json();if(!r.success)throw new Error(r.error||"Failed to load file");const i=r.data;if(i.type==="image")n.innerHTML=`<img src="${i.url}" alt="${escapeHtml(e)}">`,a.textContent=`${this.formatFileSize(i.size)} \u2022 ${i.extension}`;else if(i.type==="video")n.innerHTML=`<video src="${i.url}" controls autoplay></video>`,a.textContent=`${this.formatFileSize(i.size)} \u2022 ${i.extension}`;else if(i.type==="binary")n.innerHTML=`<div class="binary-message">Binary file (${this.formatFileSize(i.size)})<br>Cannot preview</div>`,a.textContent=i.extension||"binary";else{this.filePreviewContent=i.content,n.innerHTML=`<pre><code>${escapeHtml(i.content)}</code></pre>`;const l=i.truncated?` (showing 500/${i.totalLines} lines)`:"";a.textContent=`${i.totalLines} lines \u2022 ${this.formatFileSize(i.size)}${l}`}}catch(o){console.error("Failed to preview file:",o),n.innerHTML=`<div class="binary-message">Error: ${escapeHtml(o.message)}</div>`}}},closeFilePreview(){const e=this.$("filePreviewOverlay");e&&e.classList.remove("visible"),this.filePreviewContent=""},copyFilePreviewContent(){this.filePreviewContent&&navigator.clipboard.writeText(this.filePreviewContent).then(()=>{this.showToast("Copied to clipboard","success")}).catch(()=>{this.showToast("Failed to copy","error")})},getFileIcon(e){return e&&{ts:"\u{1F4D8}",tsx:"\u{1F4D8}",js:"\u{1F4D2}",jsx:"\u{1F4D2}",mjs:"\u{1F4D2}",cjs:"\u{1F4D2}",py:"\u{1F40D}",pyx:"\u{1F40D}",pyw:"\u{1F40D}",rs:"\u{1F980}",go:"\u{1F439}",c:"\u2699\uFE0F",cpp:"\u2699\uFE0F",h:"\u2699\uFE0F",hpp:"\u2699\uFE0F",html:"\u{1F310}",htm:"\u{1F310}",css:"\u{1F3A8}",scss:"\u{1F3A8}",sass:"\u{1F3A8}",less:"\u{1F3A8}",json:"\u{1F4CB}",yaml:"\u{1F4CB}",yml:"\u{1F4CB}",xml:"\u{1F4CB}",toml:"\u{1F4CB}",csv:"\u{1F4CB}",md:"\u{1F4DD}",markdown:"\u{1F4DD}",txt:"\u{1F4DD}",rst:"\u{1F4DD}",png:"\u{1F5BC}\uFE0F",jpg:"\u{1F5BC}\uFE0F",jpeg:"\u{1F5BC}\uFE0F",gif:"\u{1F5BC}\uFE0F",svg:"\u{1F5BC}\uFE0F",webp:"\u{1F5BC}\uFE0F",ico:"\u{1F5BC}\uFE0F",bmp:"\u{1F5BC}\uFE0F",mp4:"\u{1F3AC}",webm:"\u{1F3AC}",mov:"\u{1F3AC}",mp3:"\u{1F3B5}",wav:"\u{1F3B5}",ogg:"\u{1F3B5}",sh:"\u{1F4BB}",bash:"\u{1F4BB}",zsh:"\u{1F4BB}",env:"\u{1F510}",gitignore:"\u{1F6AB}",dockerfile:"\u{1F433}",lock:"\u{1F512}"}[e.toLowerCase()]||"\u{1F4C4}"},formatFileSize(e){return e==null?"":e<1024?`${e} B`:e<1024*1024?`${(e/1024).toFixed(1)} KB`:e<1024*1024*1024?`${(e/(1024*1024)).toFixed(1)} MB`:`${(e/(1024*1024*1024)).toFixed(1)} GB`},openLogViewerWindow(e,t){if(t=t||this.activeSessionId,!t)return;const s=`${t}-${e.replace(/[^a-zA-Z0-9]/g,"_")}`;if(this.logViewerWindows.has(s)){const d=this.logViewerWindows.get(s);d.element.style.zIndex=++this.logViewerWindowZIndex;return}const n=this.logViewerWindows.size,a=100+n%5*30,o=100+n%5*30,r=e.split("/").pop(),i=document.createElement("div");i.className="log-viewer-window",i.id=`log-viewer-window-${s}`,i.style.left=`${a}px`,i.style.top=`${o}px`,i.style.zIndex=++this.logViewerWindowZIndex,i.innerHTML=`
|
|
179
179
|
<div class="log-viewer-window-header">
|
|
180
180
|
<div class="log-viewer-window-title" title="${escapeHtml(e)}">
|
|
181
181
|
<span class="icon">\u{1F4C4}</span>
|
|
@@ -189,13 +189,13 @@
|
|
|
189
189
|
<div class="log-viewer-window-body" id="log-viewer-body-${s}">
|
|
190
190
|
<div class="log-info">Connecting to ${escapeHtml(e)}...</div>
|
|
191
191
|
</div>
|
|
192
|
-
`,document.body.appendChild(i);const l=this.makeWindowDraggable(i,i.querySelector(".log-viewer-window-header")),c=new EventSource(`/api/sessions/${t}/tail-file?path=${encodeURIComponent(e)}&lines=50`);c.onmessage=d=>{const u=JSON.parse(d.data),h=document.getElementById(`log-viewer-body-${s}`);if(h)switch(u.type){case"connected":h.innerHTML="";break;case"data":const m=h.scrollTop+h.clientHeight>=h.scrollHeight-10,p=escapeHtml(u.content);h.innerHTML+=p,m&&(h.scrollTop=h.scrollHeight),h.innerHTML.length>5e5&&(h.innerHTML=h.innerHTML.slice(-4e5));break;case"end":this.updateLogViewerStatus(s,"disconnected","ended");break;case"error":h.innerHTML+=`<div class="log-error">${escapeHtml(u.error)}</div>`,this.updateLogViewerStatus(s,"error","error");break}},c.onerror=()=>{this.updateLogViewerStatus(s,"disconnected","connection error")},this.logViewerWindows.set(s,{element:i,eventSource:c,filePath:e,sessionId:t,dragListeners:l})},updateLogViewerStatus(e,t,s){const n=document.querySelector(`#log-viewer-window-${e} .status`);n&&(n.className=`status ${t}`,n.textContent=s)},closeLogViewerWindow(e){const t=this.logViewerWindows.get(e);t&&(t.eventSource&&t.eventSource.close(),t.dragListeners&&(document.removeEventListener("mousemove",t.dragListeners.move),document.removeEventListener("mouseup",t.dragListeners.up),t.dragListeners.handle&&(t.dragListeners.handle.removeEventListener("mousedown",t.dragListeners.handleMouseDown),t.dragListeners.handle.removeEventListener("touchstart",t.dragListeners.handleTouchStart))),t.element.remove(),this.logViewerWindows.delete(e))},closeSessionLogViewerWindows(e){const t=[];for(const[s,n]of this.logViewerWindows)n.sessionId===e&&t.push(s);for(const s of t)this.closeLogViewerWindow(s)},openImagePopup(e){const{sessionId:t,filePath:s,relativePath:n,fileName:a,timestamp:o,size:r}=e,i=`${t}-${o}`;if(this.imagePopups.has(i)){const v=this.imagePopups.get(i);v.element.style.zIndex=++this.imagePopupZIndex;return}if(this.imagePopups.size>=20){const v=this.imagePopups.keys().next().value;v&&this.closeImagePopup(v)}const c=this.imagePopups.size,d=(window.innerWidth-600)/2,u=(window.innerHeight-500)/2,h=d+c%5*30,m=u+c%5*30,f=this.sessions.get(t)?.name||t.substring(0,8),
|
|
192
|
+
`,document.body.appendChild(i);const l=this.makeWindowDraggable(i,i.querySelector(".log-viewer-window-header")),c=new EventSource(`/api/sessions/${t}/tail-file?path=${encodeURIComponent(e)}&lines=50`);c.onmessage=d=>{const u=JSON.parse(d.data),h=document.getElementById(`log-viewer-body-${s}`);if(h)switch(u.type){case"connected":h.innerHTML="";break;case"data":const m=h.scrollTop+h.clientHeight>=h.scrollHeight-10,p=escapeHtml(u.content);h.innerHTML+=p,m&&(h.scrollTop=h.scrollHeight),h.innerHTML.length>5e5&&(h.innerHTML=h.innerHTML.slice(-4e5));break;case"end":this.updateLogViewerStatus(s,"disconnected","ended");break;case"error":h.innerHTML+=`<div class="log-error">${escapeHtml(u.error)}</div>`,this.updateLogViewerStatus(s,"error","error");break}},c.onerror=()=>{this.updateLogViewerStatus(s,"disconnected","connection error")},this.logViewerWindows.set(s,{element:i,eventSource:c,filePath:e,sessionId:t,dragListeners:l})},updateLogViewerStatus(e,t,s){const n=document.querySelector(`#log-viewer-window-${e} .status`);n&&(n.className=`status ${t}`,n.textContent=s)},closeLogViewerWindow(e){const t=this.logViewerWindows.get(e);t&&(t.eventSource&&t.eventSource.close(),t.dragListeners&&(document.removeEventListener("mousemove",t.dragListeners.move),document.removeEventListener("mouseup",t.dragListeners.up),t.dragListeners.handle&&(t.dragListeners.handle.removeEventListener("mousedown",t.dragListeners.handleMouseDown),t.dragListeners.handle.removeEventListener("touchstart",t.dragListeners.handleTouchStart))),t.element.remove(),this.logViewerWindows.delete(e))},closeSessionLogViewerWindows(e){const t=[];for(const[s,n]of this.logViewerWindows)n.sessionId===e&&t.push(s);for(const s of t)this.closeLogViewerWindow(s)},openImagePopup(e){const{sessionId:t,filePath:s,relativePath:n,fileName:a,timestamp:o,size:r}=e,i=`${t}-${o}`;if(this.imagePopups.has(i)){const v=this.imagePopups.get(i);v.element.style.zIndex=++this.imagePopupZIndex;return}if(this.imagePopups.size>=20){const v=this.imagePopups.keys().next().value;v&&this.closeImagePopup(v)}const c=this.imagePopups.size,d=(window.innerWidth-600)/2,u=(window.innerHeight-500)/2,h=d+c%5*30,m=u+c%5*30,f=this.sessions.get(t)?.name||t.substring(0,8),w=(r/1024).toFixed(1),g=`/api/sessions/${t}/file-raw?path=${encodeURIComponent(n||a)}`,b=document.createElement("div");b.className="image-popup-window",b.id=`image-popup-${i}`,b.style.left=`${h}px`,b.style.top=`${m}px`,b.style.zIndex=++this.imagePopupZIndex,b.innerHTML=`
|
|
193
193
|
<div class="image-popup-header">
|
|
194
194
|
<div class="image-popup-title" title="${escapeHtml(s)}">
|
|
195
195
|
<span class="icon">\u{1F5BC}\uFE0F</span>
|
|
196
196
|
<span class="filename">${escapeHtml(a)}</span>
|
|
197
197
|
<span class="session-badge">${escapeHtml(f)}</span>
|
|
198
|
-
<span class="size-badge">${
|
|
198
|
+
<span class="size-badge">${w} KB</span>
|
|
199
199
|
</div>
|
|
200
200
|
<div class="image-popup-actions">
|
|
201
201
|
<button onclick="app.openImageInNewTab('${escapeHtml(g)}')" title="Open in new tab">\u2197</button>
|
|
@@ -207,21 +207,21 @@
|
|
|
207
207
|
onerror="this.parentElement.innerHTML='<div class=\\'image-error\\'>Failed to load image</div>'"
|
|
208
208
|
onclick="app.openImageInNewTab('${escapeHtml(g)}')" />
|
|
209
209
|
</div>
|
|
210
|
-
`,document.body.appendChild(
|
|
211
|
-
<div class="process-item process-item-clickable" onclick="app.selectSession('${
|
|
210
|
+
`,document.body.appendChild(b);const T=this.makeWindowDraggable(b,b.querySelector(".image-popup-header"));b.addEventListener("mousedown",()=>{b.style.zIndex=++this.imagePopupZIndex}),this.imagePopups.set(i,{element:b,sessionId:t,filePath:s,dragListeners:T})},closeImagePopup(e){const t=this.imagePopups.get(e);t&&(t.dragListeners&&(document.removeEventListener("mousemove",t.dragListeners.move),document.removeEventListener("mouseup",t.dragListeners.up),t.dragListeners.touchMove&&(document.removeEventListener("touchmove",t.dragListeners.touchMove),document.removeEventListener("touchend",t.dragListeners.up),document.removeEventListener("touchcancel",t.dragListeners.up)),t.dragListeners.handle&&(t.dragListeners.handle.removeEventListener("mousedown",t.dragListeners.handleMouseDown),t.dragListeners.handle.removeEventListener("touchstart",t.dragListeners.handleTouchStart))),t.element.remove(),this.imagePopups.delete(e))},openImageInNewTab(e){window.open(e,"_blank")},closeSessionImagePopups(e){const t=[];for(const[s,n]of this.imagePopups)n.sessionId===e&&t.push(s);for(const s of t)this.closeImagePopup(s)},async loadMuxSessions(){try{const t=await(await fetch("/api/mux-sessions")).json();this.muxSessions=t.sessions||[],this.renderMuxSessions()}catch(e){console.error("Failed to load mux sessions:",e)}},killAllMuxSessions(){const e=this.muxSessions?.length||0;if(e===0){alert("No sessions to kill");return}document.getElementById("killAllCount").textContent=e;const t=document.getElementById("killAllModal");t.classList.add("active"),this.activeFocusTrap=new FocusTrap(t),this.activeFocusTrap.activate()},closeKillAllModal(){document.getElementById("killAllModal").classList.remove("active"),this.activeFocusTrap&&(this.activeFocusTrap.deactivate(),this.activeFocusTrap=null)},async confirmKillAll(e){this.closeKillAllModal();try{if(e){if((await(await fetch("/api/sessions",{method:"DELETE"})).json()).success){this.sessions.clear(),this.muxSessions=[],this.activeSessionId=null;try{localStorage.removeItem("codeman-active-session")}catch{}this.renderSessionTabs(),this.renderMuxSessions(),this.terminal.clear(),this.terminal.reset(),this.toast("All sessions and tmux killed","success")}}else{this.sessions.clear(),this.activeSessionId=null;try{localStorage.removeItem("codeman-active-session")}catch{}this.renderSessionTabs(),this.terminal.clear(),this.terminal.reset(),this.toast("All tabs removed, tmux still running","info")}}catch(t){console.error("Failed to kill sessions:",t),this.toast("Failed to kill sessions: "+t.message,"error")}},renderMuxSessions(){this._debouncedCall("muxSessions",this._renderMuxSessionsImmediate)},_renderMuxSessionsImmediate(){const e=document.getElementById("muxSessionsBody");if(!this.muxSessions||this.muxSessions.length===0){e.innerHTML='<div class="monitor-empty">No mux sessions</div>';return}let t="";for(const s of this.muxSessions){const n=s.stats||{memoryMB:0,cpuPercent:0,childCount:0},a=this.sessions.get(s.sessionId),o=a?a.status:"unknown",r=a?a.isWorking:!1;let i,l;o==="idle"&&!r?(i="IDLE",l="status-idle"):o==="busy"||r?(i="WORKING",l="status-working"):o==="stopped"?(i="STOPPED",l="status-stopped"):(i=o.toUpperCase(),l="");const c=a&&a.tokens?a.tokens:null,d=a?a.totalCost:0,u=a&&a.cliModel||"",h=u.includes("opus")?"opus":u.includes("sonnet")?"sonnet":u.includes("haiku")?"haiku":"",m=a?a.ralphTodoStats:null;let p="";if(m&&m.total>0){const T=Math.round(m.completed/m.total*100);p=`<span class="process-stat todo-progress">${m.completed}/${m.total} (${T}%)</span>`}let f="";c&&c.total>0&&(f=`<span class="process-stat tokens">${(c.total/1e3).toFixed(1)}k tok</span>`);let w="";d>0&&(w=`<span class="process-stat cost">$${d.toFixed(2)}</span>`);let g="";h&&(g=`<span class="monitor-model-badge ${h}">${h}</span>`);const b=escapeHtml(s.sessionId);t+=`
|
|
211
|
+
<div class="process-item process-item-clickable" onclick="app.selectSession('${b}')" title="Switch to session">
|
|
212
212
|
<span class="monitor-status-badge ${l}">${i}</span>
|
|
213
213
|
<div class="process-info">
|
|
214
214
|
<div class="process-name">${g} ${escapeHtml(s.name||s.muxName)}</div>
|
|
215
215
|
<div class="process-meta">
|
|
216
216
|
${f}
|
|
217
|
-
${
|
|
217
|
+
${w}
|
|
218
218
|
${p}
|
|
219
219
|
<span class="process-stat memory">${n.memoryMB}MB</span>
|
|
220
220
|
<span class="process-stat cpu">${n.cpuPercent}%</span>
|
|
221
221
|
</div>
|
|
222
222
|
</div>
|
|
223
223
|
<div class="process-actions">
|
|
224
|
-
<button class="btn-toolbar btn-sm btn-danger" onclick="event.stopPropagation(); app.killMuxSession('${
|
|
224
|
+
<button class="btn-toolbar btn-sm btn-danger" onclick="event.stopPropagation(); app.killMuxSession('${b}')" title="Kill session">Kill</button>
|
|
225
225
|
</div>
|
|
226
226
|
</div>
|
|
227
227
|
`}e.innerHTML=t},renderMonitorSubagents(){const e=document.getElementById("monitorSubagentsBody"),t=document.getElementById("monitorSubagentStats");if(!e)return;const s=Array.from(this.subagents.values()),n=s.filter(o=>o.status==="active"||o.status==="idle").length;if(t&&(t.textContent=`${s.length} tracked`+(n>0?`, ${n} active`:"")),s.length===0){e.innerHTML='<div class="monitor-empty">No background agents</div>';return}let a="";for(const o of s){const r=o.status==="active"?"active":o.status==="idle"?"idle":"completed",i=o.modelShort?`<span class="model-badge ${o.modelShort}">${o.modelShort}</span>`:"",l=o.description?escapeHtml(o.description.substring(0,40)):o.agentId;a+=`
|
|
@@ -238,4 +238,4 @@
|
|
|
238
238
|
${o.status!=="completed"?`<button class="btn-toolbar btn-sm btn-danger" onclick="app.killSubagent('${escapeHtml(o.agentId)}')" title="Kill agent">Kill</button>`:""}
|
|
239
239
|
</div>
|
|
240
240
|
</div>
|
|
241
|
-
`}e.innerHTML=a},async killMuxSession(e){if(confirm("Kill this mux session?")){try{await this.closeSession(e,!0)}catch{try{await fetch(`/api/mux-sessions/${e}`,{method:"DELETE"})}catch{}this.showToast("Tmux session killed","success")}this.muxSessions=this.muxSessions.filter(t=>t.sessionId!==e),this.renderMuxSessions()}},async reconcileMuxSessions(){try{const t=await(await fetch("/api/mux-sessions/reconcile",{method:"POST"})).json();t.dead&&t.dead.length>0?(this.showToast(`Found ${t.dead.length} dead mux session(s)`,"warning"),await this.loadMuxSessions()):this.showToast("All mux sessions are alive","success")}catch{this.showToast("Failed to reconcile mux sessions","error")}},toggleNotifications(){this.notificationManager?.toggleDrawer()},toast(e,t="info"){return this.showToast(e,t)},showToast(e,t="info",s={}){const{duration:n=3e3,action:a}=s,o=document.createElement("div");o.className=`toast toast-${t}`;const r=document.createElement("span");if(r.textContent=e,o.appendChild(r),a){const i=document.createElement("button");i.textContent=a.label,i.style.cssText="margin-left:12px;padding:2px 10px;background:rgba(255,255,255,0.15);border:1px solid rgba(255,255,255,0.3);border-radius:3px;color:inherit;cursor:pointer;font-size:12px",i.onclick=l=>{l.stopPropagation(),a.onClick(),o.remove()},o.appendChild(i)}this._toastContainer||(this._toastContainer=document.querySelector(".toast-container"),this._toastContainer||(this._toastContainer=document.createElement("div"),this._toastContainer.className="toast-container",document.body.appendChild(this._toastContainer))),this._toastContainer.appendChild(o),requestAnimationFrame(()=>o.classList.add("show")),setTimeout(()=>{o.classList.remove("show"),setTimeout(()=>o.remove(),200)},n)},startSystemStatsPolling(){this.stopSystemStatsPolling(),this.fetchSystemStats(),this.systemStatsInterval=setInterval(()=>{this.fetchSystemStats()},2e3)},stopSystemStatsPolling(){this.systemStatsInterval&&(clearInterval(this.systemStatsInterval),this.systemStatsInterval=null)},async fetchSystemStats(){const e=document.getElementById("headerSystemStats");if(!(!e||e.style.display==="none"))try{const s=await(await fetch("/api/system/stats")).json();this.updateSystemStatsDisplay(s)}catch{}},updateSystemStatsDisplay(e){const t=this.$("statCpu"),s=this.$("statCpuBar"),n=this.$("statMem"),a=this.$("statMemBar");if(t&&s&&(t.textContent=`${e.cpu}%`,s.style.width=`${Math.min(100,e.cpu)}%`,s.classList.remove("medium","high"),t.classList.remove("high"),e.cpu>80?(s.classList.add("high"),t.classList.add("high")):e.cpu>50&&s.classList.add("medium")),n&&a){const o=(e.memory.usedMB/1024).toFixed(1);n.textContent=`${o}G`,a.style.width=`${Math.min(100,e.memory.percent)}%`,a.classList.remove("medium","high"),n.classList.remove("high"),e.memory.percent>80?(a.classList.add("high"),n.classList.add("high")):e.memory.percent>50&&a.classList.add("medium")}}});
|
|
241
|
+
`}e.innerHTML=a},async killMuxSession(e){if(confirm("Kill this mux session?")){try{await this.closeSession(e,!0)}catch{try{await fetch(`/api/mux-sessions/${e}`,{method:"DELETE"})}catch{}this.showToast("Tmux session killed","success")}this.muxSessions=this.muxSessions.filter(t=>t.sessionId!==e),this.renderMuxSessions()}},async reconcileMuxSessions(){try{const t=await(await fetch("/api/mux-sessions/reconcile",{method:"POST"})).json();t.dead&&t.dead.length>0?(this.showToast(`Found ${t.dead.length} dead mux session(s)`,"warning"),await this.loadMuxSessions()):this.showToast("All mux sessions are alive","success")}catch{this.showToast("Failed to reconcile mux sessions","error")}},toggleNotifications(){this.notificationManager?.toggleDrawer()},toast(e,t="info"){return this.showToast(e,t)},showToast(e,t="info",s={}){const{duration:n=3e3,action:a}=s,o=document.createElement("div");o.className=`toast toast-${t}`;const r=document.createElement("span");if(r.textContent=e,o.appendChild(r),a){const i=document.createElement("button");i.textContent=a.label,i.style.cssText="margin-left:12px;padding:2px 10px;background:rgba(255,255,255,0.15);border:1px solid rgba(255,255,255,0.3);border-radius:3px;color:inherit;cursor:pointer;font-size:12px",i.onclick=l=>{l.stopPropagation(),a.onClick(),o.remove()},o.appendChild(i)}this._toastContainer||(this._toastContainer=document.querySelector(".toast-container"),this._toastContainer||(this._toastContainer=document.createElement("div"),this._toastContainer.className="toast-container",document.body.appendChild(this._toastContainer))),this._toastContainer.appendChild(o),requestAnimationFrame(()=>o.classList.add("show")),setTimeout(()=>{o.classList.remove("show"),setTimeout(()=>o.remove(),200)},n)},startSystemStatsPolling(){this.stopSystemStatsPolling(),this.fetchSystemStats(),this.systemStatsInterval=setInterval(()=>{this.fetchSystemStats()},2e3)},stopSystemStatsPolling(){this.systemStatsInterval&&(clearInterval(this.systemStatsInterval),this.systemStatsInterval=null)},async fetchSystemStats(){const e=document.getElementById("headerSystemStats");if(!(!e||e.style.display==="none"))try{const s=await(await fetch("/api/system/stats")).json();this.updateSystemStatsDisplay(s)}catch{}},updateSystemStatsDisplay(e){const t=this.$("statCpu"),s=this.$("statCpuBar"),n=this.$("statMem"),a=this.$("statMemBar");if(t&&s&&(t.textContent=`${e.cpu}%`,s.style.width=`${Math.min(100,e.cpu)}%`,s.classList.remove("medium","high"),t.classList.remove("high"),e.cpu>80?(s.classList.add("high"),t.classList.add("high")):e.cpu>50&&s.classList.add("medium")),n&&a){const o=(e.memory.usedMB/1024).toFixed(1);n.textContent=`${o}G`,a.style.width=`${Math.min(100,e.memory.percent)}%`,a.classList.remove("medium","high"),n.classList.remove("high"),e.memory.percent>80?(a.classList.add("high"),n.classList.add("high")):e.memory.percent>50&&a.classList.add("medium")}},async _onClipboardWrite(e){const t=e?.text;if(typeof t=="string")try{await navigator.clipboard.writeText(t),this.showToast(`Copied to clipboard (${t.length} chars)`,"success")}catch{this._showClipboardFallback(t)}},_showClipboardFallback(e){const t=document.createElement("div");t.style.cssText="position:fixed;inset:0;background:rgba(0,0,0,0.6);z-index:10000;display:flex;align-items:center;justify-content:center";const s=document.createElement("div");s.style.cssText="background:#1e1e2e;border:1px solid #444;border-radius:8px;padding:16px;max-width:600px;width:90%;max-height:60vh;display:flex;flex-direction:column;gap:12px";const n=document.createElement("div");n.style.cssText="display:flex;justify-content:space-between;align-items:center";const a=document.createElement("span");a.style.cssText="color:#cdd6f4;font-weight:600",a.textContent="Clipboard (browser blocked auto-copy)";const o=document.createElement("button");o.style.cssText="background:none;border:none;color:#cdd6f4;font-size:18px;cursor:pointer",o.textContent="\xD7",n.appendChild(a),n.appendChild(o);const r=document.createElement("textarea");r.readOnly=!0,r.style.cssText="background:#181825;color:#cdd6f4;border:1px solid #555;border-radius:4px;padding:8px;font-family:monospace;font-size:13px;resize:none;height:200px;width:100%",r.value=e;const i=document.createElement("button");i.style.cssText="background:#89b4fa;color:#1e1e2e;border:none;border-radius:4px;padding:8px 16px;cursor:pointer;font-weight:600",i.textContent="Copy to Clipboard",s.appendChild(n),s.appendChild(r),s.appendChild(i),t.appendChild(s),document.body.appendChild(t),i.onclick=async()=>{try{await navigator.clipboard.writeText(e),this.showToast("Copied to clipboard","success"),t.remove()}catch{r.select(),document.execCommand("copy"),this.showToast("Copied (fallback)","success"),t.remove()}};const l=()=>t.remove();o.onclick=l,t.onclick=c=>{c.target===t&&l()}}});
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";Object.assign(CodemanApp.prototype,{async loadQuickStartCases(e=null,t=null){try{let s=null;try{const d=t?await t:await fetch("/api/settings").then(l=>l.ok?l.json():null);d&&(s=d.lastUsedCase||null)}catch{}const n=await(await fetch("/api/cases")).json();this.cases=n,console.log("[loadQuickStartCases] Loaded cases:",n.map(d=>d.name),"lastUsedCase:",s);const i=document.getElementById("quickStartCase");let o="";const m=n.some(d=>d.name==="testcase"),r=MobileDetection.getDeviceType()==="mobile"?8:20;if(n.forEach(d=>{const l=d.name.length>r?d.name.substring(0,r)+"\u2026":d.name;o+=`<option value="${escapeHtml(d.name)}">${escapeHtml(l)}</option>`}),m||(o='<option value="testcase">testcase</option>'+o),i.innerHTML=o,console.log("[loadQuickStartCases] Set options:",i.innerHTML.substring(0,200)),e)i.value=e,this.updateDirDisplayForCase(e),this.updateMobileCaseLabel(e);else if(s&&n.some(d=>d.name===s))i.value=s,this.updateDirDisplayForCase(s),this.updateMobileCaseLabel(s);else if(n.length>0){const d=n.find(l=>l.name==="testcase")||n[0];i.value=d.name,this.updateDirDisplayForCase(d.name),this.updateMobileCaseLabel(d.name)}else i.value="testcase",document.getElementById("dirDisplay").textContent="~/codeman-cases/testcase",this.updateMobileCaseLabel("testcase");i.dataset.listenerAdded||(i.addEventListener("change",()=>{this.updateDirDisplayForCase(i.value),this.saveLastUsedCase(i.value),this.updateMobileCaseLabel(i.value)}),i.dataset.listenerAdded="true")}catch(s){console.error("Failed to load cases:",s)}},async updateDirDisplayForCase(e){try{const s=await(await fetch(`/api/cases/${e}`)).json();s.path&&(document.getElementById("dirDisplay").textContent=s.path,document.getElementById("dirInput").value=s.path)}catch{document.getElementById("dirDisplay").textContent=e}},async saveLastUsedCase(e){try{await fetch("/api/settings",{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({lastUsedCase:e})})}catch(t){console.error("Failed to save last used case:",t)}},async quickStart(){return this.run()},async run(){return(this._runMode||"claude")==="opencode"?this.runOpenCode():this.runClaude()},get runMode(){return this._runMode||"claude"},setRunMode(e){this._runMode=e;try{localStorage.setItem("codeman_runMode",e)}catch{}this._applyRunMode(),this._apiPut("/api/settings",{runMode:e}).catch(()=>{}),document.getElementById("runModeMenu")?.classList.remove("active")},toggleRunModeMenu(e){e?.stopPropagation();const t=document.getElementById("runModeMenu");if(t&&(t.classList.toggle("active"),t.querySelectorAll(".run-mode-option").forEach(s=>{s.classList.toggle("selected",s.dataset.mode===this.runMode)}),t.classList.contains("active"))){this._loadRunModeHistory();const s=a=>{t.contains(a.target)||(t.classList.remove("active"),document.removeEventListener("click",s))};setTimeout(()=>document.addEventListener("click",s),0)}},async _loadRunModeHistory(){const e=document.getElementById("runModeHistory");if(e){e.innerHTML='<div class="run-mode-hist-empty">Loading...</div>';try{const t=await this._fetchHistorySessions(10);if(t.length===0){e.innerHTML='<div class="run-mode-hist-empty">No history</div>';return}e.replaceChildren();for(const s of t){const a=new Date(s.lastModified),n=a.toLocaleDateString("en",{month:"short",day:"numeric"})+" "+a.toLocaleTimeString("en",{hour:"2-digit",minute:"2-digit",hour12:!1}),i=s.workingDir.replace(/^\/home\/[^/]+\//,"~/"),o=document.createElement("button");o.className="run-mode-option",o.title=s.workingDir,o.dataset.sessionId=s.sessionId,o.dataset.workingDir=s.workingDir;const m=document.createElement("span");m.className="hist-dir",m.textContent=i;const c=document.createElement("span");c.className="hist-meta",c.textContent=n,o.append(m,c),o.addEventListener("click",r=>{r.stopPropagation(),this.resumeHistorySession(s.sessionId,s.workingDir)}),e.appendChild(o)}}catch{e.innerHTML='<div class="run-mode-hist-empty">Failed to load</div>'}}},_applyRunMode(){const e=this.runMode,t=document.getElementById("runBtn"),s=t?.nextElementSibling,a=document.getElementById("runBtnLabel");t&&(t.className=`btn-toolbar btn-run mode-${e}`),s&&(s.className=`btn-toolbar btn-run-gear mode-${e}`),a&&(a.textContent=e==="opencode"?"Run OC":"Run")},_initRunMode(){try{this._runMode=localStorage.getItem("codeman_runMode")||"claude"}catch{this._runMode="claude"}this._applyRunMode()},incrementTabCount(){const e=document.getElementById("tabCount"),t=parseInt(e.value)||1;e.value=Math.min(20,t+1)},decrementTabCount(){const e=document.getElementById("tabCount"),t=parseInt(e.value)||1;e.value=Math.max(1,t-1)},incrementShellCount(){const e=document.getElementById("shellCount"),t=parseInt(e.value)||1;e.value=Math.min(20,t+1)},decrementShellCount(){const e=document.getElementById("shellCount"),t=parseInt(e.value)||1;e.value=Math.max(1,t-1)},async runClaude(){const e=document.getElementById("quickStartCase").value||"testcase",t=Math.min(20,Math.max(1,parseInt(document.getElementById("tabCount").value)||1));this.terminal.clear(),this.terminal.writeln(`\x1B[1;32m Starting ${t} Claude session(s) in ${e}...\x1B[0m`),this.terminal.writeln(""),this.terminal.focus();try{let a=await(await fetch(`/api/cases/${e}`)).json();if(!a.path){const g=await(await fetch("/api/cases",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:e,description:""})})).json();if(!g.success)throw new Error(g.error||"Failed to create case");a=g.data.case}const n=a.path;if(!n)throw new Error("Case path not found");let i=null,o=1;for(const[,u]of this.sessions){const g=u.name&&u.name.match(/^w(\d+)-([a-zA-Z0-9_-]+)/);if(g&&g[2]===e){const b=parseInt(g[1]);b>=o&&(o=b+1)}}const m=this.isRalphTrackerEnabledByDefault(),c=[];for(let u=0;u<t;u++)c.push(`w${o+u}-${e}`);const r=this.getCaseSettings(e),d=this.loadAppSettingsFromStorage(),l={};(r.agentTeams||d.agentTeamsEnabled)&&(l.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS="1");const h=Object.keys(l).length>0,C=r.opusContext1m||d.opusContext1mEnabled?"opus[1m]":"";this.terminal.writeln(`\x1B[90m Creating ${t} session(s)...\x1B[0m`);const p=c.map(u=>fetch("/api/sessions",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({workingDir:n,name:u,...h?{envOverrides:l}:{},...C!==void 0?{modelOverride:C}:{}})}).then(g=>g.json())),w=await Promise.all(p),f=[];for(const u of w){if(!u.success)throw new Error(u.error);f.push(u.session.id)}i=f[0],await Promise.all(f.map(u=>fetch(`/api/sessions/${u}/ralph-config`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({enabled:m,disableAutoEnable:!m})}))),this.terminal.writeln(`\x1B[90m Starting ${t} session(s) in parallel...\x1B[0m`),await Promise.all(f.map(u=>fetch(`/api/sessions/${u}/interactive`,{method:"POST"}))),this.terminal.writeln(`\x1B[90m All ${t} sessions ready\x1B[0m`),i&&(await this.selectSession(i),this.loadQuickStartCases()),this.terminal.focus()}catch(s){this.terminal.writeln(`\x1B[1;31m Error: ${s.message}\x1B[0m`)}},stopClaude(){if(!this.activeSessionId)return;const e=document.querySelector(".btn-toolbar.btn-stop");e&&(this._stopConfirmTimer?(clearTimeout(this._stopConfirmTimer),this._stopConfirmTimer=null,e.innerHTML=e.dataset.origHtml,delete e.dataset.origHtml,e.classList.remove("confirming"),fetch(`/api/sessions/${this.activeSessionId}/input`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({input:""})})):(e.dataset.origHtml=e.innerHTML,e.textContent="Tap again",e.classList.add("confirming"),this._stopConfirmTimer=setTimeout(()=>{this._stopConfirmTimer=null,e.dataset.origHtml&&(e.innerHTML=e.dataset.origHtml,delete e.dataset.origHtml),e.classList.remove("confirming")},2e3)))},async runShell(){const e=document.getElementById("quickStartCase").value||"testcase",t=Math.min(20,Math.max(1,parseInt(document.getElementById("shellCount").value)||1));this.terminal.clear(),this.terminal.writeln(`\x1B[1;33m Starting ${t} Shell session(s) in ${e}...\x1B[0m`),this.terminal.writeln("");try{let a=await(await fetch(`/api/cases/${e}`)).json();if(!a.path){const h=await(await fetch("/api/cases",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:e,description:""})})).json();if(!h.success)throw new Error(h.error||"Failed to create case");a=h.data.case}const n=a.path;if(!n)throw new Error("Case path not found");let i=1;for(const[,l]of this.sessions){const h=l.name&&l.name.match(/^s(\d+)-([a-zA-Z0-9_-]+)/);if(h&&h[2]===e){const y=parseInt(h[1]);y>=i&&(i=y+1)}}const o=[];for(let l=0;l<t;l++)o.push(`s${i+l}-${e}`);const m=o.map(l=>fetch("/api/sessions",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({workingDir:n,mode:"shell",name:l})}).then(h=>h.json())),c=await Promise.all(m),r=[];for(const l of c){if(!l.success)throw new Error(l.error);r.push(l.session.id)}await Promise.all(r.map(l=>fetch(`/api/sessions/${l}/shell`,{method:"POST"})));const d=this.getTerminalDimensions();d&&await Promise.all(r.map(l=>fetch(`/api/sessions/${l}/resize`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(d)}))),r.length>0&&(this.activeSessionId=r[0],await this.selectSession(r[0])),this.terminal.focus()}catch(s){this.terminal.writeln(`\x1B[1;31m Error: ${s.message}\x1B[0m`)}},async runOpenCode(){const e=document.getElementById("quickStartCase").value||"testcase";this.terminal.clear(),this.terminal.writeln(`\x1B[1;32m Starting OpenCode session in ${e}...\x1B[0m`),this.terminal.writeln(""),this.terminal.focus();try{if(!(await(await fetch("/api/opencode/status")).json()).available){this.terminal.writeln("\x1B[1;31m OpenCode CLI not found.\x1B[0m"),this.terminal.writeln("\x1B[90m Install with: curl -fsSL https://opencode.ai/install | bash\x1B[0m");return}const n=await(await fetch("/api/quick-start",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({caseName:e,mode:"opencode",openCodeConfig:{autoAllowTools:!0}})})).json();if(!n.success)throw new Error(n.error||"Failed to start OpenCode");n.sessionId&&await this.selectSession(n.sessionId),this.terminal.focus()}catch(t){this.terminal.writeln(`\x1B[1;31m Error: ${t.message}\x1B[0m`)}},openSessionOptions(e){const t=this.sessions.get(e);if(!t)return;this.editingSessionId=e,this.switchOptionsTab(t.mode==="opencode"?"summary":"respawn");const s=document.getElementById("sessionRespawnStatus"),a=document.getElementById("modalEnableRespawnBtn"),n=document.getElementById("modalStopRespawnBtn");this.respawnStatus[e]?(s.classList.add("active"),s.querySelector(".respawn-status-text").textContent=this.respawnStatus[e].state||"Active",a.style.display="none",n.style.display=""):(s.classList.remove("active"),s.querySelector(".respawn-status-text").textContent="Not active",a.style.display="",n.style.display="none");const i=document.getElementById("sessionRespawnSection");t.mode==="claude"&&t.pid?i.style.display="":i.style.display="none";const o=t.mode==="opencode";document.querySelectorAll("[data-claude-only]").forEach(p=>{p.style.display=o?"none":""}),this.selectDurationPreset(""),this.loadSavedRespawnConfig(e),document.getElementById("modalAutoCompactEnabled").checked=t.autoCompactEnabled??!1,document.getElementById("modalAutoCompactThreshold").value=t.autoCompactThreshold??11e4,document.getElementById("modalAutoCompactPrompt").value=t.autoCompactPrompt??"",document.getElementById("modalAutoClearEnabled").checked=t.autoClearEnabled??!1,document.getElementById("modalAutoClearThreshold").value=t.autoClearThreshold??14e4,document.getElementById("modalImageWatcherEnabled").checked=t.imageWatcherEnabled??!0,document.getElementById("modalFlickerFilterEnabled").checked=t.flickerFilterEnabled??!1;const c=parseSessionPrefix(t.name),r=document.getElementById("modalSessionPrefix");c?(r.textContent=c.prefix+": ",r.style.display="",document.getElementById("modalSessionName").value=c.suffix,document.getElementById("modalSessionName").placeholder="Add description..."):(r.style.display="none",r.textContent="",document.getElementById("modalSessionName").value=t.name||"",document.getElementById("modalSessionName").placeholder="Auto (directory name)");const d=t.color||"default";document.getElementById("sessionColorPicker")?.querySelectorAll(".color-swatch").forEach(p=>{p.classList.toggle("selected",p.dataset.color===d)}),this.renderPresetDropdown(),document.getElementById("respawnPresetSelect").value="",document.getElementById("presetDescriptionHint").textContent="";const h=document.querySelector('#sessionOptionsModal .modal-tab-btn[data-tab="ralph"]'),y=document.querySelector('#sessionOptionsModal .modal-tab-btn[data-tab="respawn"]');if(o?(h&&(h.style.display="none"),y&&(y.style.display="none"),this.switchOptionsTab("context")):(h&&(h.style.display=""),y&&(y.style.display="")),!o){const p=this.ralphStates.get(e);this.populateRalphForm({enabled:p?.loop?.enabled??t.ralphLoop?.enabled??!1,completionPhrase:p?.loop?.completionPhrase||t.ralphLoop?.completionPhrase||"",maxIterations:p?.loop?.maxIterations||t.ralphLoop?.maxIterations||0})}const C=document.getElementById("sessionOptionsModal");C.classList.add("active"),this.activeFocusTrap=new FocusTrap(C),this.activeFocusTrap.activate()},async saveSessionName(){if(!this.editingSessionId)return;const e=this.sessions.get(this.editingSessionId),t=e?parseSessionPrefix(e.name):null,s=document.getElementById("modalSessionName").value.trim();let a;t?a=t.prefix+(s?": "+s:""):a=s;try{await this._apiPut(`/api/sessions/${this.editingSessionId}/name`,{name:a})}catch(n){this.showToast("Failed to save session name: "+n.message,"error")}},async autoSaveAutoCompact(){if(this.editingSessionId)try{await this._apiPost(`/api/sessions/${this.editingSessionId}/auto-compact`,{enabled:document.getElementById("modalAutoCompactEnabled").checked,threshold:parseInt(document.getElementById("modalAutoCompactThreshold").value)||11e4,prompt:document.getElementById("modalAutoCompactPrompt").value.trim()||void 0})}catch{}},async autoSaveAutoClear(){if(this.editingSessionId)try{await this._apiPost(`/api/sessions/${this.editingSessionId}/auto-clear`,{enabled:document.getElementById("modalAutoClearEnabled").checked,threshold:parseInt(document.getElementById("modalAutoClearThreshold").value)||14e4})}catch{}},async toggleSessionImageWatcher(){if(!this.editingSessionId)return;const e=document.getElementById("modalImageWatcherEnabled").checked;try{await this._apiPost(`/api/sessions/${this.editingSessionId}/image-watcher`,{enabled:e});const t=this.sessions.get(this.editingSessionId);t&&(t.imageWatcherEnabled=e),this.showToast(`Image watcher ${e?"enabled":"disabled"}`,"success")}catch{this.showToast("Failed to toggle image watcher","error")}},async toggleFlickerFilter(){if(!this.editingSessionId)return;const e=document.getElementById("modalFlickerFilterEnabled").checked;try{await this._apiPost(`/api/sessions/${this.editingSessionId}/flicker-filter`,{enabled:e});const t=this.sessions.get(this.editingSessionId);t&&(t.flickerFilterEnabled=e),this.showToast(`Flicker filter ${e?"enabled":"disabled"}`,"success")}catch{this.showToast("Failed to toggle flicker filter","error")}},async autoSaveRespawnConfig(){if(!this.editingSessionId)return;const e={updatePrompt:document.getElementById("modalRespawnPrompt").value,sendClear:document.getElementById("modalRespawnSendClear").checked,sendInit:document.getElementById("modalRespawnSendInit").checked,kickstartPrompt:document.getElementById("modalRespawnKickstart").value.trim()||void 0,autoAcceptPrompts:document.getElementById("modalRespawnAutoAccept").checked};try{await this._apiPut(`/api/sessions/${this.editingSessionId}/respawn/config`,e)}catch{}},async loadSavedRespawnConfig(e){try{const s=await(await fetch(`/api/sessions/${e}/respawn/config`)).json();if(s.success&&s.config){const a=s.config;document.getElementById("modalRespawnPrompt").value=a.updatePrompt||"update all the docs and CLAUDE.md",document.getElementById("modalRespawnSendClear").checked=a.sendClear??!0,document.getElementById("modalRespawnSendInit").checked=a.sendInit??!0,document.getElementById("modalRespawnKickstart").value=a.kickstartPrompt||"",document.getElementById("modalRespawnAutoAccept").checked=a.autoAcceptPrompts??!0,a.durationMinutes&&(document.querySelector(`.duration-preset-btn[data-minutes="${a.durationMinutes}"]`)?this.selectDurationPreset(String(a.durationMinutes)):(this.selectDurationPreset("custom"),document.getElementById("modalRespawnDuration").value=a.durationMinutes))}}catch{}},selectDurationPreset(e){document.querySelectorAll(".duration-preset-btn").forEach(n=>n.classList.remove("active"));const t=document.querySelector(`.duration-preset-btn[data-minutes="${e}"]`);t&&t.classList.add("active");const s=document.querySelector(".duration-custom-input"),a=document.getElementById("modalRespawnDuration");e==="custom"?(s.classList.add("visible"),a.focus()):(s.classList.remove("visible"),a.value="")},getSelectedDuration(){const e=document.querySelector(".duration-custom-input"),t=document.getElementById("modalRespawnDuration");if(e.classList.contains("visible"))return t.value?parseInt(t.value):null;{const a=document.querySelector(".duration-preset-btn.active")?.dataset.minutes;return a?parseInt(a):null}},switchOptionsTab(e){document.querySelectorAll("#sessionOptionsModal .modal-tab-btn").forEach(t=>{t.classList.toggle("active",t.dataset.tab===e)}),document.getElementById("respawn-tab").classList.toggle("hidden",e!=="respawn"),document.getElementById("context-tab").classList.toggle("hidden",e!=="context"),document.getElementById("ralph-tab").classList.toggle("hidden",e!=="ralph"),document.getElementById("summary-tab").classList.toggle("hidden",e!=="summary"),e==="summary"&&this.editingSessionId&&this.loadRunSummary(this.editingSessionId)},getRalphConfig(){return{enabled:document.getElementById("modalRalphEnabled").checked,completionPhrase:document.getElementById("modalRalphPhrase").value.trim(),maxIterations:parseInt(document.getElementById("modalRalphMaxIterations").value)||0,maxTodos:parseInt(document.getElementById("modalRalphMaxTodos").value)||50,todoExpirationMinutes:parseInt(document.getElementById("modalRalphTodoExpiration").value)||60}},populateRalphForm(e){document.getElementById("modalRalphEnabled").checked=e?.enabled??!1,document.getElementById("modalRalphPhrase").value=e?.completionPhrase||"",document.getElementById("modalRalphMaxIterations").value=e?.maxIterations||0,document.getElementById("modalRalphMaxTodos").value=e?.maxTodos||50,document.getElementById("modalRalphTodoExpiration").value=e?.todoExpirationMinutes||60},async saveRalphConfig(){if(!this.editingSessionId){this.showToast("No session selected","warning");return}const e=this.getRalphConfig();e.enabled&&this.ralphClosedSessions.delete(this.editingSessionId);try{const s=await(await fetch(`/api/sessions/${this.editingSessionId}/ralph-config`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)})).json();if(s.error)throw new Error(s.error);this.showToast("Ralph config saved","success")}catch(t){this.showToast("Failed to save Ralph config: "+t.message,"error")}},startInlineRename(e){const t=this.sessions.get(e);if(!t)return;const s=document.querySelector(`.tab-name[data-session-id="${e}"]`);if(!s)return;const a=this.getSessionName(t),n=parseSessionPrefix(t.name),i=s.textContent;if(s.textContent="",s.innerHTML="",n){const c=document.createElement("span");c.textContent=n.prefix+": ",c.style.cssText="color: var(--text-muted); font-size: 0.75rem; white-space: nowrap;",s.appendChild(c)}const o=document.createElement("input");o.type="text",o.value=n?n.suffix:t.name||"",o.placeholder=n?"Add description...":a,o.className="tab-rename-input",o.style.cssText="width: 80px; font-size: 0.75rem; padding: 2px 4px; background: var(--bg-input); border: 1px solid var(--accent); border-radius: 3px; color: var(--text); outline: none;",s.appendChild(o),o.focus(),o.select();const m=async()=>{const c=o.value.trim();let r;if(n?r=n.prefix+(c?": "+c:""):r=c,s.textContent=r||i,r!==t.name)try{await fetch(`/api/sessions/${e}/name`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:r})})}catch{s.textContent=i,this.showToast("Failed to rename","error")}};o.addEventListener("blur",m),o.addEventListener("keydown",c=>{c.key==="Enter"?(c.preventDefault(),o.blur()):c.key==="Escape"&&(o.value="",o.blur())})},toggleCaseSettings(){const e=document.getElementById("caseSettingsPopover");if(e.classList.contains("hidden")){const t=document.getElementById("quickStartCase").value||"testcase",s=this.getCaseSettings(t);document.getElementById("caseAgentTeams").checked=s.agentTeams,document.getElementById("caseOpusContext1m").checked=s.opusContext1m,e.classList.remove("hidden");const a=n=>{!e.contains(n.target)&&!n.target.classList.contains("btn-case-settings")&&(e.classList.add("hidden"),document.removeEventListener("click",a))};setTimeout(()=>document.addEventListener("click",a),0)}else e.classList.add("hidden")},getCaseSettings(e){try{const t=localStorage.getItem("caseSettings_"+e);if(t)return JSON.parse(t)}catch{}return{agentTeams:!1,opusContext1m:!0}},saveCaseSettings(e,t){localStorage.setItem("caseSettings_"+e,JSON.stringify(t))},onCaseSettingChanged(){const e=document.getElementById("quickStartCase").value||"testcase",t=this.getCaseSettings(e);t.agentTeams=document.getElementById("caseAgentTeams").checked,t.opusContext1m=document.getElementById("caseOpusContext1m").checked,this.saveCaseSettings(e,t);const s=document.getElementById("caseAgentTeamsMobile");s&&(s.checked=t.agentTeams);const a=document.getElementById("caseOpusContext1mMobile");a&&(a.checked=t.opusContext1m)},toggleCaseSettingsMobile(){const e=document.getElementById("caseSettingsPopoverMobile");if(e.classList.contains("hidden")){const t=document.getElementById("quickStartCase").value||"testcase",s=this.getCaseSettings(t);document.getElementById("caseAgentTeamsMobile").checked=s.agentTeams,document.getElementById("caseOpusContext1mMobile").checked=s.opusContext1m,e.classList.remove("hidden");const a=n=>{!e.contains(n.target)&&!n.target.classList.contains("btn-case-settings-mobile")&&(e.classList.add("hidden"),document.removeEventListener("click",a))};setTimeout(()=>document.addEventListener("click",a),0)}else e.classList.add("hidden")},onCaseSettingChangedMobile(){const e=document.getElementById("quickStartCase").value||"testcase",t=this.getCaseSettings(e);t.agentTeams=document.getElementById("caseAgentTeamsMobile").checked,t.opusContext1m=document.getElementById("caseOpusContext1mMobile").checked,this.saveCaseSettings(e,t);const s=document.getElementById("caseAgentTeams");s&&(s.checked=t.agentTeams);const a=document.getElementById("caseOpusContext1m");a&&(a.checked=t.opusContext1m)},showCreateCaseModal(){document.getElementById("newCaseName").value="",document.getElementById("newCaseDescription").value="",document.getElementById("linkCaseName").value="",document.getElementById("linkCasePath").value="",this.caseModalTab="case-create",this.switchCaseModalTab("case-create");const e=document.getElementById("createCaseModal");e.querySelectorAll(".modal-tabs .modal-tab-btn").forEach(t=>{t.onclick=()=>this.switchCaseModalTab(t.dataset.tab)}),e.querySelectorAll('input[type="text"]').forEach(t=>{t._mobileScrollWired||(t._mobileScrollWired=!0,t.addEventListener("focus",()=>{window.innerWidth<=430&&setTimeout(()=>t.scrollIntoView({behavior:"smooth",block:"center"}),300)}))}),e.classList.add("active"),document.getElementById("newCaseName").focus()},switchCaseModalTab(e){this.caseModalTab=e;const t=document.getElementById("createCaseModal");t.querySelectorAll(".modal-tabs .modal-tab-btn").forEach(a=>{a.classList.toggle("active",a.dataset.tab===e)}),t.querySelectorAll(".modal-tab-content").forEach(a=>{a.classList.toggle("hidden",a.id!==e)});const s=document.getElementById("caseModalSubmit");e==="case-manage"?(s.style.display="none",this.renderCaseManageList()):(s.style.display="",s.textContent=e==="case-create"?"Create":"Link"),e==="case-create"?document.getElementById("newCaseName").focus():e==="case-link"&&document.getElementById("linkCaseName").focus()},closeCreateCaseModal(){document.getElementById("createCaseModal").classList.remove("active")},async submitCaseModal(){const e=document.getElementById("caseModalSubmit"),t=e.textContent;e.classList.add("loading"),e.textContent=this.caseModalTab==="case-create"?"Creating...":"Linking...";try{this.caseModalTab==="case-create"?await this.createCase():await this.linkCase()}finally{e.classList.remove("loading"),e.textContent=t}},async createCase(){const e=document.getElementById("newCaseName").value.trim(),t=document.getElementById("newCaseDescription").value.trim();if(!e){this.showToast("Please enter a case name","error");return}if(!/^[a-zA-Z0-9_-]+$/.test(e)){this.showToast("Invalid name. Use only letters, numbers, hyphens, underscores.","error");return}try{const a=await(await fetch("/api/cases",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:e,description:t})})).json();a.success?(this.closeCreateCaseModal(),this.showToast(`Case "${e}" created`,"success"),await this.loadQuickStartCases(e),await this.saveLastUsedCase(e)):this.showToast(a.error||"Failed to create case","error")}catch(s){console.error("Failed to create case:",s),this.showToast("Failed to create case: "+s.message,"error")}},async linkCase(){const e=document.getElementById("linkCaseName").value.trim(),t=document.getElementById("linkCasePath").value.trim();if(!e){this.showToast("Please enter a case name","error");return}if(!/^[a-zA-Z0-9_-]+$/.test(e)){this.showToast("Invalid name. Use only letters, numbers, hyphens, underscores.","error");return}if(!t){this.showToast("Please enter a folder path","error");return}try{const a=await(await fetch("/api/cases/link",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:e,path:t})})).json();a.success?(this.closeCreateCaseModal(),this.showToast(`Case "${e}" linked to ${t}`,"success"),await this.loadQuickStartCases(e),await this.saveLastUsedCase(e)):this.showToast(a.error||"Failed to link case","error")}catch(s){console.error("Failed to link case:",s),this.showToast("Failed to link case: "+s.message,"error")}},renderCaseManageList(){const e=document.getElementById("caseManageList"),t=this.cases||[];if(t.length===0){e.innerHTML='<div class="form-hint" style="text-align: center; padding: 2rem 0;">No cases yet</div>';return}let s="";t.forEach((a,n)=>{const i=n===0,o=n===t.length-1,m=a.path?a.path.replace(/^\/Users\/[^/]+/,"~"):"";s+=`
|
|
2
|
+
<div class="case-manage-item" data-case="${escapeHtml(a.name)}">
|
|
3
|
+
<div class="case-manage-info">
|
|
4
|
+
<span class="case-manage-name">${escapeHtml(a.name)}</span>
|
|
5
|
+
<span class="case-manage-path">${escapeHtml(m)}</span>
|
|
6
|
+
</div>
|
|
7
|
+
<div class="case-manage-actions">
|
|
8
|
+
<button class="case-manage-btn" onclick="app.moveCaseUp('${escapeHtml(a.name)}')"
|
|
9
|
+
title="Move up" ${i?"disabled":""}>▲</button>
|
|
10
|
+
<button class="case-manage-btn" onclick="app.moveCaseDown('${escapeHtml(a.name)}')"
|
|
11
|
+
title="Move down" ${o?"disabled":""}>▼</button>
|
|
12
|
+
<button class="case-manage-btn case-manage-btn-delete" onclick="app.deleteCase('${escapeHtml(a.name)}')"
|
|
13
|
+
title="Delete case">✕</button>
|
|
14
|
+
</div>
|
|
15
|
+
</div>
|
|
16
|
+
`}),e.innerHTML=s},async moveCaseUp(e){const t=this.cases||[],s=t.findIndex(n=>n.name===e);if(s<=0)return;const a=[...t];[a[s-1],a[s]]=[a[s],a[s-1]],this.cases=a,this.renderCaseManageList(),await this.saveCaseOrder(a.map(n=>n.name))},async moveCaseDown(e){const t=this.cases||[],s=t.findIndex(n=>n.name===e);if(s<0||s>=t.length-1)return;const a=[...t];[a[s],a[s+1]]=[a[s+1],a[s]],this.cases=a,this.renderCaseManageList(),await this.saveCaseOrder(a.map(n=>n.name))},async deleteCase(e){if(confirm(`Delete case "${e}"? Linked cases will only be unlinked (folder preserved). Created cases will be permanently deleted.`))try{const s=await(await fetch(`/api/cases/${encodeURIComponent(e)}`,{method:"DELETE"})).json();if(s.success){this.showToast(`Case "${e}" ${s.data?.type==="unlinked"?"unlinked":"deleted"}`,"success"),this.cases=(this.cases||[]).filter(i=>i.name!==e),this.renderCaseManageList();const n=document.getElementById("quickStartCase").value;await this.loadQuickStartCases(n===e?null:n)}else this.showToast(s.error||"Failed to delete case","error")}catch(t){this.showToast("Failed to delete case: "+t.message,"error")}},async saveCaseOrder(e){try{await fetch("/api/cases/order",{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({order:e})});const s=document.getElementById("quickStartCase").value;await this.loadQuickStartCases(s)}catch(t){this.showToast("Failed to save case order: "+t.message,"error")}},showMobileCasePicker(){const e=document.getElementById("mobileCasePickerModal"),t=document.getElementById("mobileCaseList"),a=document.getElementById("quickStartCase").value;let n="";const i=this.cases||[],m=i.some(c=>c.name==="testcase")?i:[{name:"testcase"},...i];for(const c of m){const r=c.name===a;n+=`
|
|
17
|
+
<button class="mobile-case-item ${r?"selected":""}"
|
|
18
|
+
onclick="app.selectMobileCase('${escapeHtml(c.name)}')">
|
|
19
|
+
<span class="mobile-case-item-icon">
|
|
20
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
21
|
+
<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/>
|
|
22
|
+
</svg>
|
|
23
|
+
</span>
|
|
24
|
+
<span class="mobile-case-item-name">${escapeHtml(c.name)}</span>
|
|
25
|
+
<span class="mobile-case-item-delete" onclick="event.stopPropagation(); app.deleteCaseMobile('${escapeHtml(c.name)}')" title="Delete">
|
|
26
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
27
|
+
<line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>
|
|
28
|
+
</svg>
|
|
29
|
+
</span>
|
|
30
|
+
<span class="mobile-case-item-check">
|
|
31
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
|
|
32
|
+
<polyline points="20 6 9 17 4 12"/>
|
|
33
|
+
</svg>
|
|
34
|
+
</span>
|
|
35
|
+
</button>
|
|
36
|
+
`}t.innerHTML=n,e.classList.add("active")},closeMobileCasePicker(){document.getElementById("mobileCasePickerModal").classList.remove("active")},selectMobileCase(e){const t=document.getElementById("quickStartCase");t.value=e,this.updateMobileCaseLabel(e),this.updateDirDisplayForCase(e),this.saveLastUsedCase(e),this.closeMobileCasePicker(),this.showToast(`Selected: ${e}`,"success")},updateMobileCaseLabel(e){const t=document.getElementById("mobileCaseName");t&&(t.textContent=e)},async deleteCaseMobile(e){if(confirm(`Delete case "${e}"?`))try{const s=await(await fetch(`/api/cases/${encodeURIComponent(e)}`,{method:"DELETE"})).json();s.success?(this.showToast(`Case "${e}" ${s.data?.type==="unlinked"?"unlinked":"deleted"}`,"success"),this.cases=(this.cases||[]).filter(a=>a.name!==e),this.closeMobileCasePicker(),await this.loadQuickStartCases()):this.showToast(s.error||"Failed to delete case","error")}catch(t){this.showToast("Failed to delete case: "+t.message,"error")}},showCreateCaseFromMobile(){this.closeMobileCasePicker(),this.showCreateCaseModal();const e=document.getElementById("createCaseModal");e.classList.add("from-mobile"),setTimeout(()=>e.classList.remove("from-mobile"),300)}});
|
|
Binary file
|
|
Binary file
|
|
Binary file
|