@qiaolei81/copilot-session-viewer 0.3.6 → 0.3.8
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/server.min.js +39 -34
- package/package.json +10 -8
- package/public/js/homepage.min.js +5 -5
- package/public/js/session-detail.min.js +223 -35
- package/public/js/time-analyze.min.js +1 -1
- package/views/index.ejs +253 -0
- package/views/session-vue.ejs +487 -182
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qiaolei81/copilot-session-viewer",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.8",
|
|
4
4
|
"description": "Web UI for viewing GitHub Copilot CLI session logs",
|
|
5
5
|
"author": "Lei Qiao <qiaolei81@gmail.com>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -33,14 +33,14 @@
|
|
|
33
33
|
"scripts": {
|
|
34
34
|
"build": "node scripts/build.mjs",
|
|
35
35
|
"build:watch": "node scripts/build.mjs --watch",
|
|
36
|
-
"start": "node server.js",
|
|
37
|
-
"dev": "nodemon server.js",
|
|
36
|
+
"start": "cross-env DISABLE_TELEMETRY=true node server.js",
|
|
37
|
+
"dev": "cross-env DISABLE_TELEMETRY=true nodemon server.js",
|
|
38
38
|
"test": "jest",
|
|
39
39
|
"test:watch": "jest --watch",
|
|
40
40
|
"test:coverage": "jest --coverage",
|
|
41
|
-
"test:e2e": "playwright test",
|
|
42
|
-
"test:e2e:headed": "playwright test --headed",
|
|
43
|
-
"test:e2e:debug": "playwright test --debug",
|
|
41
|
+
"test:e2e": "cross-env DISABLE_TELEMETRY=true playwright test",
|
|
42
|
+
"test:e2e:headed": "cross-env DISABLE_TELEMETRY=true playwright test --headed",
|
|
43
|
+
"test:e2e:debug": "cross-env DISABLE_TELEMETRY=true playwright test --debug",
|
|
44
44
|
"test:all": "npm test && npm run test:e2e",
|
|
45
45
|
"test:coverage:all": "npm run test:coverage && npm run test:e2e",
|
|
46
46
|
"lint": "eslint .",
|
|
@@ -54,6 +54,7 @@
|
|
|
54
54
|
"prepublishOnly": "npm run build:prod"
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
|
+
"adm-zip": "^0.5.16",
|
|
57
58
|
"applicationinsights": "^2.9.8",
|
|
58
59
|
"compression": "^1.8.1",
|
|
59
60
|
"dompurify": "^3.3.1",
|
|
@@ -61,9 +62,9 @@
|
|
|
61
62
|
"express": "^4.18.2",
|
|
62
63
|
"express-rate-limit": "^8.2.1",
|
|
63
64
|
"helmet": "^8.1.0",
|
|
65
|
+
"js-yaml": "^4.1.1",
|
|
64
66
|
"multer": "^2.0.2",
|
|
65
|
-
"zod": "^4.3.6"
|
|
66
|
-
"adm-zip": "^0.5.16"
|
|
67
|
+
"zod": "^4.3.6"
|
|
67
68
|
},
|
|
68
69
|
"devDependencies": {
|
|
69
70
|
"@eslint/css": "^0.14.1",
|
|
@@ -73,6 +74,7 @@
|
|
|
73
74
|
"@istanbuljs/nyc-config-typescript": "^1.0.2",
|
|
74
75
|
"@playwright/test": "^1.58.2",
|
|
75
76
|
"@types/jest": "^30.0.0",
|
|
77
|
+
"cross-env": "^10.1.0",
|
|
76
78
|
"esbuild": "^0.27.3",
|
|
77
79
|
"eslint": "^10.0.0",
|
|
78
80
|
"eslint-plugin-vue": "^10.8.0",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
(()=>{var
|
|
1
|
+
(()=>{var y=window.__PAGE_DATA||{},A=y.sessions||[],G=y.totalSessions||0,D=y.hasMore||!1,k=y.sourceHints||{},m=[...A],u={};u.copilot={offset:A.length,hasMore:D};var f=!1,S="sessionViewer.sourceFilter",$;try{$=localStorage.getItem(S)}catch{$=null}var i=$||"copilot";function g(){return u[i]||(u[i]={offset:0,hasMore:!0}),u[i]}async function B(){let e=g();if(f||!e.hasMore)return;f=!0;let t=document.getElementById("loading-indicator");t.style.display="block",window.trackClick("LoadMoreClicked",{currentPage:Math.floor(e.offset/20)+1,offset:e.offset,source:i});try{let s=await fetch(`/api/sessions/load-more?offset=${g().offset}&limit=20&source=${encodeURIComponent(i)}`);if(!s.ok)throw new Error("Failed to load more sessions");let o=await s.json(),n=new Set(m.map(r=>r.id)),a=[];for(let r of o.sessions)n.has(r.id)||(m.push(r),a.push(r));g().offset+=o.sessions.length,g().hasMore=o.hasMore,await C(a),E()}catch(s){console.error("Error loading more sessions:",s)}finally{f=!1,t.style.display="none"}}function F(){return m.filter(e=>e.source===i)}function E(){let e=document.getElementById("sessions-container");e.innerHTML="";let t=F();if(t.length===0){e.innerHTML='<div style="text-align: center; color: #6e7681; padding: 40px; font-size: 14px;">No sessions found for this filter.</div>';return}let s=O(t);Object.keys(s).sort((n,a)=>a.localeCompare(n)).forEach(n=>{let a=document.createElement("div");a.className="date-group-header",a.textContent=N(s[n][0].createdAt),e.appendChild(a);let r=document.createElement("div");r.className="recent-list",s[n].forEach(h=>{r.innerHTML+=R(h)}),e.appendChild(r)})}function z(){let e=window.pageYOffset||document.documentElement.scrollTop,t=window.innerHeight,s=document.documentElement.scrollHeight;e+t>=s-500&&g().hasMore&&!f&&B()}var w;function P(){w||(w=setTimeout(()=>{z(),w=null},100))}function Z(e){e.preventDefault();let t=document.getElementById("sessionInput").value.trim();t&&(window.location.href=`/session/${t}`)}document.getElementById("sessionForm").addEventListener("submit",Z);var M=document.getElementById("fileInput"),d=document.getElementById("importLink"),L=document.getElementById("importStatus");d.addEventListener("click",e=>{e.preventDefault(),M.click()});M.addEventListener("change",async e=>{let t=e.target.files[0];if(t){if(!t.name.endsWith(".zip")){p("error","\u274C Please select a .zip file");return}d.style.pointerEvents="none",d.style.opacity="0.5",d.textContent="Importing...",p("loading","Uploading and extracting session...");try{let s=new FormData;s.append("sessionZip",t);let o=await fetch("/session/import",{method:"POST",body:s}),n=await o.json();o.ok?(p("success",`\u2705 Session ${n.sessionId} imported successfully!`),setTimeout(()=>{window.location.reload()},1500)):(p("error",`\u274C Import failed: ${n.error}`),d.style.pointerEvents="auto",d.style.opacity="1",d.textContent="Import session from zip")}catch(s){p("error",`\u274C Import failed: ${s.message}`),d.style.pointerEvents="auto",d.style.opacity="1",d.textContent="Import session from zip"}finally{M.value=""}}});function p(e,t){L.className=`import-status ${e}`,L.textContent=t}function _(e){if(!e||e<0)return"\u2014";let t=Math.floor(e/1e3),s=Math.floor(t/60),o=Math.floor(s/60);if(o>0){let n=s%60;return`${o}h ${n}m`}else if(s>0){let n=t%60;return`${s}m ${n}s`}else return`${t}s`}function N(e){let t=new Date(e),s=t.getFullYear(),o=String(t.getMonth()+1).padStart(2,"0"),n=String(t.getDate()).padStart(2,"0");return`${s}/${o}/${n}`}function j(e){if(!e)return"Unknown";let t=new Date(e),s=t.getFullYear(),o=String(t.getMonth()+1).padStart(2,"0"),n=String(t.getDate()).padStart(2,"0");return`${s}-${o}-${n}`}function O(e){let t={};return e.forEach(s=>{let o=j(s.createdAt);t[o]||(t[o]=[]),t[o].push(s)}),t}function R(e){let t="",s=e.sourceBadgeClass||"source-copilot",o=e.sourceName||"Copilot";if(t+=`<span class="status-badge ${s}" title="${o}">${o}</span>`,e.sessionStatus==="wip"&&(t+='<span class="status-badge wip" title="Session in progress">\u{1F504} WIP</span>'),e.isImported&&(t+='<span class="status-badge imported" title="Imported session">\u{1F4E5}</span>'),e.hasInsight&&(t+='<span class="status-badge insight" title="Has Agent Review">\u{1F4A1}</span>'),e.selectedModel){let v=e.selectedModel.replace("claude-","").replace("gpt-","").replace("gemini-",""),l="model-other";e.selectedModel.includes("claude")?l="model-claude":e.selectedModel.includes("gpt")?l="model-gpt":e.selectedModel.includes("gemini")&&(l="model-gemini"),t+=`<span class="status-badge model ${l}" title="Model: ${c(e.selectedModel)}">${c(v)}</span>`}e.source==="modernize"&&e.modernizeVersion?t+=`<span class="status-badge version" title="Modernize version">${c(e.modernizeVersion)}</span>`:e.copilotVersion&&(t+=`<span class="status-badge version" title="CLI version">${c(e.copilotVersion)}</span>`);let n="";if(e.summary&&e.summary!=="No summary"&&e.summary!=="Legacy session"){let v=e.summary.replace(/"/g,"""),l=c(e.summary).replace(/\n+/g," ");n=`<div class="session-summary" title="${v}">${l}</div>`}else n='<div class="session-summary" style="color: #6e7681; font-style: italic;">No summary available</div>';let a="";e.workspace&&e.workspace.cwd&&(a=`
|
|
2
2
|
<div class="session-info-item workspace" title="${c(e.workspace.cwd)}">
|
|
3
3
|
<svg viewBox="0 0 16 16" fill="currentColor"><path d="M1.75 1A1.75 1.75 0 000 2.75v10.5C0 14.216.784 15 1.75 15h12.5A1.75 1.75 0 0016 13.25v-8.5A1.75 1.75 0 0014.25 3H7.5a.25.25 0 01-.2-.1l-.9-1.2C6.07 1.26 5.55 1 5 1H1.75z"></path></svg>
|
|
4
4
|
<span class="session-info-value">${c(e.workspace.cwd)}</span>
|
|
@@ -6,16 +6,16 @@
|
|
|
6
6
|
`);let r=e.createdAt?new Date(e.createdAt).toLocaleString("en-US",{month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",hour12:!1}):"unknown",h="";e.duration&&(h=`
|
|
7
7
|
<div class="session-info-item">
|
|
8
8
|
<svg viewBox="0 0 16 16" fill="currentColor"><path d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8Zm8-6.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13ZM7.25 12.5v-5A.75.75 0 0 1 8 6.75h2.5a.75.75 0 0 1 0 1.5H8.75v4.25a.75.75 0 0 1-1.5 0Z"></path></svg>
|
|
9
|
-
<span class="session-info-value">${
|
|
9
|
+
<span class="session-info-value">${_(e.duration)}</span>
|
|
10
10
|
</div>
|
|
11
|
-
`);let T=e.sessionStatus==="wip"?" recent-item-wip":"",
|
|
11
|
+
`);let T=e.sessionStatus==="wip"?" recent-item-wip":"",I="";return e.tags&&e.tags.length>0&&(I=`<div class="session-tags">${e.tags.map(l=>`<span class="session-tag" style="background-color: ${V(l)}" title="${c(l)}">${c(l)}</span>`).join("")}</div>`),`
|
|
12
12
|
<a href="/session/${e.id}" class="recent-item${T}" onclick="trackClick('SessionCardClicked', { sessionId: '${c(e.id)}', source: '${c(e.source||"unknown")}' })">
|
|
13
13
|
<div class="session-id">
|
|
14
14
|
<span class="session-id-text" title="${c(e.id)}">${c(e.id)}</span>
|
|
15
15
|
</div>
|
|
16
16
|
<div class="session-badges-tags">
|
|
17
17
|
<div class="session-badges">${t}</div>
|
|
18
|
-
${
|
|
18
|
+
${I}
|
|
19
19
|
</div>
|
|
20
20
|
${n}
|
|
21
21
|
<div class="session-divider"></div>
|
|
@@ -32,4 +32,4 @@
|
|
|
32
32
|
</div>
|
|
33
33
|
</div>
|
|
34
34
|
</a>
|
|
35
|
-
`}function c(e){let t=document.createElement("div");return t.textContent=e,t.innerHTML}var
|
|
35
|
+
`}function c(e){let t=document.createElement("div");return t.textContent=e,t.innerHTML}var b=["#3b82f6","#10b981","#f59e0b","#ef4444","#8b5cf6","#ec4899","#06b6d4","#f97316"];function V(e){let t=0;for(let s=0;s<e.length;s++)t=e.charCodeAt(s)+((t<<5)-t);return b[Math.abs(t)%b.length]}async function U(e){try{let t=e.map(n=>fetch(`/api/sessions/${n}/tags`).then(a=>a.ok?a.json():{tags:[]}).then(a=>({id:n,tags:a.tags||[]})).catch(()=>({id:n,tags:[]}))),s=await Promise.all(t),o={};return s.forEach(({id:n,tags:a})=>{o[n]=a}),o}catch(t){return console.error("Error loading session tags:",t),{}}}async function C(e){let t=e.map(o=>o.id),s=await U(t);e.forEach(o=>{o.tags=s[o.id]||[]})}function H(e){let t=document.getElementById("sourceHint");t&&k[e]?t.innerHTML='Sessions from <span class="hint-code">'+k[e]+"</span>":t&&(t.textContent="")}async function x(e){if(u[e]||(u[e]={offset:0,hasMore:!0}),u[e].offset===0&&!f){f=!0;let t=document.getElementById("sessions-container");t.innerHTML='<div style="text-align: center; color: #6e7681; padding: 40px; font-size: 14px;">\u23F3 Loading...</div>',document.getElementById("loading-indicator").style.display="none";try{let s=await fetch(`/api/sessions/load-more?offset=0&limit=20&source=${encodeURIComponent(e)}`);if(s.ok){let o=await s.json(),n=new Set(m.map(r=>r.id)),a=[];for(let r of o.sessions||[])n.has(r.id)||(m.push(r),a.push(r));u[e].offset=(o.sessions||[]).length,u[e].hasMore=o.hasMore,await C(a)}}catch(s){console.error("Failed to load sessions for source:",e,s)}finally{f=!1}}E()}function Y(){let e=document.querySelectorAll(".filter-pill");if(!new Set([...e].map(s=>s.getAttribute("data-source"))).has(i)){i="copilot";try{localStorage.setItem(S,i)}catch{}}e.forEach(s=>{s.classList.toggle("active",s.getAttribute("data-source")===i)}),H(i),e.forEach(s=>{s.addEventListener("click",async()=>{e.forEach(o=>o.classList.remove("active")),s.classList.add("active"),i=s.getAttribute("data-source");try{localStorage.setItem(S,i)}catch{}H(i),window.trackClick("FilterPillClicked",{pillName:s.textContent.trim(),dataSource:i}),await x(i)})})}document.addEventListener("DOMContentLoaded",async function(){await C(m),window.addEventListener("scroll",P),Y(),i==="copilot"?E():await x(i)});})();
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
(()=>{(function(){if(typeof Vue>"u"){console.error("Vue is not loaded");return}if(typeof window.VueVirtualScroller>"u"){console.error("VueVirtualScroller is not loaded");return}console.log("Initializing Vue app...");let{createApp:
|
|
1
|
+
(()=>{var Ae=(d,g)=>()=>(g||d((g={exports:{}}).exports,g),g.exports);var Le=Ae((Bt,oe)=>{function qt(d){let g=new Map,f=new Map,y=0;for(let o of d)if(o.type==="subagent.started"){let u=o.data?.toolCallId;u&&f.set(u,{name:o.data?.agentDisplayName||o.data?.agentName||"SubAgent",colorIndex:y++})}for(let o of d)if(o.type==="assistant.message"&&o.data?.subAgentName&&o.data?.subAgentId){let u=o.data.subAgentId;f.has(u)||f.set(u,{name:o.data.subAgentName,colorIndex:y++}),g.set(o.stableId,u)}for(let o of d)if(o._subagent?.id){let u=o._subagent.id;f.has(u)||f.set(u,{name:o._subagent.name||"SubAgent",colorIndex:y++}),g.set(o.stableId,u)}if(f.size===0)return{ownerMap:g,subagentInfo:f};let B=new Map;for(let o of d)o.id&&B.set(o.id,o);for(let o of d)if(o.type==="assistant.message"){let u=o.data?.parentToolCallId;u&&f.has(u)&&g.set(o.stableId,u)}for(let o of d){if(o.type!=="reasoning")continue;let u=o.parentId,M=0;for(;u&&M<10;){let w=B.get(u);if(!w)break;if(w.type==="assistant.message"){let R=w.data?.parentToolCallId;R&&f.has(R)&&g.set(o.stableId,R);break}u=w.parentId,M++}}let m=new Map;for(let o of d){if(o.type!=="tool.execution_start")continue;let u=o.parentId,M=0;for(;u&&M<10;){let w=B.get(u);if(!w)break;if(w.type==="assistant.message"){let R=w.data?.parentToolCallId;if(R&&f.has(R)){g.set(o.stableId,R);let P=o.data?.toolCallId;P&&m.set(P,R)}break}u=w.parentId,M++}}for(let o of d){if(o.type!=="tool.execution_complete")continue;let u=o.data?.toolCallId;u&&m.has(u)&&g.set(o.stableId,m.get(u))}for(let o of d){if(o.type!=="tool.invocation")continue;let u=o.data?.parentToolCallId;u&&f.has(u)&&g.set(o.stableId,u)}let v=null;for(let o of d)o.type==="subagent.started"&&o.data?.toolCallId&&f.has(o.data.toolCallId)?v=o.data.toolCallId:(o.type==="subagent.completed"||o.type==="subagent.failed")&&o.data?.toolCallId===v?v=null:v&&!g.has(o.stableId)&&g.set(o.stableId,v);return{ownerMap:g,subagentInfo:f}}function Ft(d,g,f){return g?d.filter(y=>(y.type==="subagent.started"||y.type==="subagent.completed"||y.type==="subagent.failed")&&y.data?.toolCallId===g||f.get(y.stableId)===g||y._subagent?.id===g||y.data?.subAgentId===g):d}typeof oe<"u"&&oe.exports&&(oe.exports={computeSubagentOwnership:qt,filterBySubagent:Ft})});var Oe=Ae((Pt,ie)=>{function De(d){if(!d||typeof d!="object")return 0;let g=Number.isFinite(d.inputTokens)?d.inputTokens:0,f=Number.isFinite(d.cacheReadTokens)?d.cacheReadTokens:0,y=Number.isFinite(d.cacheWriteTokens)?d.cacheWriteTokens:0;return Math.max(g-f-y,0)}function Ut(d){if(!d||typeof d!="object")return null;let g=Number.isFinite(d.cacheReadTokens)?d.cacheReadTokens:0,f=De(d)+g;return g===0||f===0?null:Math.round(g/f*100)}typeof ie<"u"&&ie.exports&&(ie.exports={getDisplayInputTokens:De,getCacheHitRatio:Ut})});(function(){let{computeSubagentOwnership:d,filterBySubagent:g}=Le(),{getDisplayInputTokens:f,getCacheHitRatio:y}=Oe();if(typeof Vue>"u"){console.error("Vue is not loaded");return}if(typeof window.VueVirtualScroller>"u"){console.error("VueVirtualScroller is not loaded");return}console.log("Initializing Vue app...");let{createApp:B,ref:m,computed:v,onMounted:o,onBeforeUnmount:u,watch:M}=Vue,{DynamicScroller:w,DynamicScrollerItem:R}=window.VueVirtualScroller,P=B({components:{DynamicScroller:w,DynamicScrollerItem:R},setup(){let p=m(window.__PAGE_DATA.sessionId),x=m(window.__PAGE_DATA.metadata),re=m(!1),me=()=>window.innerWidth<=640,X=m(me()?!0:localStorage.getItem("sidebarCollapsed")==="true");M(X,t=>{me()||localStorage.setItem("sidebarCollapsed",t.toString())});let L=m({}),D=m({}),Y=50,ge=()=>{let t=Object.keys(L.value);t.length>Y&&t.slice(0,t.length-Y).forEach(s=>delete L.value[s]);let e=Object.keys(D.value);e.length>Y&&e.slice(0,e.length-Y).forEach(s=>delete D.value[s])},N=m("all"),H=m(""),F=m(""),pe=m(0),h=m(null),ve=m({start:0,end:0}),A=m(null),le=m(!1),_e=v(()=>{let t=0;return N.value!=="all"&&t++,A.value&&t++,H.value.trim()&&t++,t}),$e=()=>{N.value="all",A.value=null,H.value="",F.value="",le.value=!1},j=null,Z=null;M(H,t=>{clearTimeout(j),j=setTimeout(()=>{F.value=t,t.trim()&&window.trackClick&&window.trackClick("SearchUsed",{query:t.substring(0,50),resultCount:z.value.length,sessionId:p.value})},300)}),M(N,()=>{ge()}),M(F,()=>{ge()});let O=m([]),fe=m(!0),be=m(null),k=v(()=>O.value.filter(e=>e.type!=="assistant.turn_end"&&e.type!=="assistant.turn_complete").sort((e,a)=>{let s=e.timestamp?new Date(e.timestamp).getTime():0,n=a.timestamp?new Date(a.timestamp).getTime():0;return s!==n?s-n:(e._fileIndex??0)-(a._fileIndex??0)}).map((e,a)=>({...e,virtualIndex:a,stableId:e.id||`${e.timestamp}-${e.type}-${a}`}))),qe=t=>{if(!F.value.trim())return!0;let e=F.value.toLowerCase();return[t.data?.message,t.data?.text,t.data?.content,t.data?.reason,t.data?.reasoningText,t.data?.errorType,t.data?.previousModel,t.data?.newModel].filter(Boolean).join(" ").toLowerCase().includes(e)},z=v(()=>{let t=a=>{let s=a.type||"";return s!=="tool.execution_start"&&s!=="tool.execution_complete"},e=k.value.filter(t);return F.value.trim()&&(e=e.filter(qe)),e}),W=v(()=>{let t=z.value;if(A.value){let{ownerMap:s}=Q.value;t=g(t,A.value,s)}N.value!=="all"&&(t=t.filter(s=>s.type===N.value));let e=["assistant.turn_start","subagent.started","subagent.completed","subagent.failed"],a=t.length;return t.map((s,n)=>{let i=t[n+1],r=n===a-1,l=i&&e.includes(i.type);return{...s,filteredIndex:n,filteredTotal:a,isLastEvent:r||l}})}),Fe=v(()=>{let t={};return z.value.forEach(e=>{e.type&&(t[e.type]=(t[e.type]||0)+1)}),t}),Ue=v(()=>{if(!F.value.trim())return null;let t=z.value.length;return t>0?`${t} result${t!==1?"s":""}`:"No matches"}),ze=v(()=>{let t=Object.keys(L.value).filter(a=>L.value[a]).length,e=Object.keys(D.value).filter(a=>D.value[a]).length;return t+e}),Te=v(()=>{let t=z.value.length,e=[{type:"all",label:`All (${t})`,count:t}],a={};z.value.forEach(n=>{n.type&&(a[n.type]=(a[n.type]||0)+1)});let s=Object.entries(a).sort((n,i)=>i[1]-n[1]).map(([n,i])=>({type:n,label:`${n} (${i})`,count:i,disabled:!1}));return[...e,...s]}),G=v(()=>{let t=k.value.filter(a=>a.type==="assistant.turn_start"),e=k.value.filter(a=>a.type==="user.message");return t.map((a,s)=>{let n=s,i=new Date(a.timestamp).getTime(),r,l=t.indexOf(a)+1;l<t.length?r=new Date(t[l].timestamp).getTime():r=Date.now();let c=r-i,b=Math.floor(c/1e3),T=Math.floor(b/60),$=b%60,q=T>0?`${T}m ${$}s`:`${$}s`,V=k.value.slice(0,k.value.indexOf(a)).reverse().find(C=>C.type==="user.message"),te=V?e.indexOf(V)+1:0;return{id:n,index:a.virtualIndex,originalTurnId:a.data?.turnId,timestamp:a.timestamp,duration:q,message:V?.data?.content||V?.data?.transformedContent||"",userReqNumber:te}})}),Ve=v(()=>{let t=[],e=new Map;return G.value.forEach(a=>{let s=a.userReqNumber||0;if(!e.has(s)){let n={reqNumber:s,message:a.message,turns:[]};e.set(s,n),t.push(n)}e.get(s).turns.push(a)}),t}),Be=(t,e)=>t?t.length<=e?t:t.substring(0,e)+"\u2026":"",Q=v(()=>d(k.value)),Pe=v(()=>{let{subagentInfo:t}=Q.value;if(t.size===0)return[];let e=[];for(let[a,s]of t)e.push({toolCallId:a,name:s.name,colorIndex:s.colorIndex});return e}),He=v(()=>{if(!A.value)return null;let{ownerMap:t,subagentInfo:e}=Q.value,a=A.value;if(!e.has(a))return null;let s=0,n=null,i=null;for(let l of k.value){let c=(l.type==="subagent.started"||l.type==="subagent.completed"||l.type==="subagent.failed")&&l.data?.toolCallId===a,b=t.get(l.stableId)===a,T=l._subagent?.id===a,$=l.data?.subAgentId===a;if((c||b||T||$)&&(s++,l.timestamp!==null&&l.timestamp!==void 0)){let q=new Date(l.timestamp).getTime();(n===null||q<n)&&(n=q),(i===null||q>i)&&(i=q)}}let r=n===null||i===null?0:i-n;return{eventCount:s,durationMs:r}}),je=t=>{if(!t)return"";let e=new Date(t),a=String(e.getHours()).padStart(2,"0"),s=String(e.getMinutes()).padStart(2,"0"),n=String(e.getSeconds()).padStart(2,"0");return`${a}:${s}:${n}`},We=t=>{if(!t)return"";let e=new Date(t),a=String(e.getHours()).padStart(2,"0"),s=String(e.getMinutes()).padStart(2,"0"),n=String(e.getSeconds()).padStart(2,"0"),i=String(e.getMilliseconds()).padStart(3,"0");return`${a}:${s}:${n}.${i}`},S=new Map,he=200,Ge=t=>{if(!t)return"";if(S.has(t))return S.get(t);try{let e=t.replace(/\\r\\n/g,`
|
|
2
2
|
`).replace(/\\n/g,`
|
|
3
|
-
`).replace(/\\t/g," ").replace(/\\"/g,'"').replace(/\\\\/g,"\\"),a={ALLOWED_TAGS:["p","br","strong","em","code","pre","a","ul","ol","li","h1","h2","h3","h4","h5","h6","blockquote","table","thead","tbody","tr","th","td","hr","del","span","div","mark"],ALLOWED_ATTR:["href","style","class"],ALLOW_DATA_ATTR:!1,ALLOWED_URI_REGEXP:/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp):|[^a-z]|[a-z+.-]+(?:[^a-z+.\-:]|$))/i},s=e.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);if(s){let
|
|
4
|
-
`).
|
|
5
|
-
|
|
3
|
+
`).replace(/\\t/g," ").replace(/\\"/g,'"').replace(/\\\\/g,"\\"),a={ALLOWED_TAGS:["p","br","strong","em","code","pre","a","ul","ol","li","h1","h2","h3","h4","h5","h6","blockquote","table","thead","tbody","tr","th","td","hr","del","span","div","mark"],ALLOWED_ATTR:["href","style","class"],ALLOW_DATA_ATTR:!1,ALLOWED_URI_REGEXP:/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp):|[^a-z]|[a-z+.-]+(?:[^a-z+.\-:]|$))/i},s=e.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);if(s){let r=s[1],l=s[2],c=r.split(`
|
|
4
|
+
`),b=[],T=0;for(;T<c.length;){let C=c[T];if(!C.trim()||!C.includes(":")){T++;continue}let ae=C.indexOf(":"),se=C.substring(0,ae).trim(),ne=C.substring(ae+1).trim();if(ne==="|"||ne===">"){let Ne=[];for(T++;T<c.length&&(c[T].startsWith(" ")||c[T].startsWith(" ")||c[T].trim()==="");)Ne.push(c[T].trim()),T++;let _t=ne===">"?" ":`
|
|
5
|
+
`;b.push({key:se,value:Ne.filter($t=>$t).join(_t)})}else b.push({key:se,value:ne}),T++}let $='<table style="margin-bottom: 16px; border-collapse: collapse; width: 100%;"><tbody>';b.forEach(C=>{let ae=DOMPurify.sanitize(C.key,{ALLOWED_TAGS:[]}),se=DOMPurify.sanitize(C.value,{ALLOWED_TAGS:[]});$+=`<tr><td style="padding: 4px 12px; border: 1px solid #30363d; font-weight: 600; color: #7d8590;">${ae}</td><td style="padding: 4px 12px; border: 1px solid #30363d;">${se}</td></tr>`}),$+="</tbody></table>";let q=marked.parse(l),V=DOMPurify.sanitize(q,a),te=$+V;if(S.size>=he){let C=S.keys().next().value;S.delete(C)}return S.set(t,te),te}let n=marked.parse(e),i=DOMPurify.sanitize(n,a);if(S.size>=he){let r=S.keys().next().value;S.delete(r)}return S.set(t,i),i}catch{return t}},Ke=t=>{let e={...L.value},a=!!e[t];e[t]?delete e[t]:e[t]=!0,L.value=e,window.trackClick&&window.trackClick("EventExpanded",{eventType:"tool",action:a?"collapse":"expand",sessionId:p.value})},Je=(t,e)=>{if(!e||!e.trim()||!t)return t;let a=e.trim(),s=Ce(a).replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),n=document.createElement("div");n.innerHTML=t;let i=r=>{if(r.nodeType===Node.TEXT_NODE){let l=r.textContent,c=new RegExp(`(${s})`,"gi");if(c.test(l)){let b=l.replace(c,'<mark class="search-highlight">$1</mark>'),T=document.createElement("span");T.innerHTML=b,r.parentNode.replaceChild(T,r)}}else r.nodeType===Node.ELEMENT_NODE&&r.tagName!=="SCRIPT"&&r.tagName!=="STYLE"&&Array.from(r.childNodes).forEach(i)};return Array.from(n.childNodes).forEach(i),n.innerHTML},Xe=t=>{let e={...D.value},a=!!e[t];e[t]?delete e[t]:e[t]=!0,D.value=e,window.trackClick&&window.trackClick("EventExpanded",{eventType:"content",action:a?"collapse":"expand",sessionId:p.value})},Ye=t=>t?t.split(`
|
|
6
|
+
`).length>20||t.length>2e3:!1,Ze=t=>{let e=t.split(`
|
|
6
7
|
`);return e.length<=20?t:e.slice(0,20).join(`
|
|
7
8
|
`)+`
|
|
8
9
|
|
|
9
|
-
...`},Le=(t,e)=>{if(e?.data?.badgeLabel&&e?.data?.badgeClass)return{label:e.data.badgeLabel,class:e.data.badgeClass};if(t==="message"&&e?.data?.role==="toolResult")return{label:"TOOL RESULT",class:"badge-tool"};if(t==="session.model_change")return{label:"MODEL CHANGE",class:"badge-session"};if(t==="session.truncation")return{label:"TRUNCATION",class:"badge-truncation"};if(t==="session.compaction_start"||t==="session.compaction_complete")return{label:"COMPACTION",class:"badge-compaction"};if(t==="system.notification")return{label:"SYSTEM",class:"badge-system"};let s=(t||"").split(".")[0]||"unknown";return{user:{label:"USER",class:"badge-user"},assistant:{label:"ASSISTANT",class:"badge-assistant"},reasoning:{label:"REASONING",class:"badge-reasoning"},turn:{label:"TURN",class:"badge-turn"},tool:{label:"TOOL",class:"badge-tool"},subagent:{label:"SUBAGENT",class:"badge-subagent"},skill:{label:"SKILL",class:"badge-skill"},session:{label:"SESSION",class:"badge-session"},error:{label:"ERROR",class:"badge-error"},abort:{label:"ABORT",class:"badge-error"}}[s]||{label:s.toUpperCase(),class:"badge-info"}},Ae=t=>{if(!t.complete)return{icon:"\u23F3",color:"tool-status-running",text:""};let e=t.complete.data||{};return e.error||e.isError?{icon:"\u274C",color:"tool-status-error",text:""}:{icon:"\u2713",color:"tool-status-success",text:""}},Re=t=>{if(!t.complete?.data?.error)return"";let e=t.complete.data.error;if(typeof e=="object"&&e.message)return e.message;if(typeof e=="string"){try{let a=JSON.parse(e);if(a.message)return a.message}catch{}return e}return String(e)},De=t=>{if(!t.complete)return"";let e=new Date(t.start.timestamp).getTime(),s=new Date(t.complete.timestamp).getTime()-e;return s>=100?`${parseFloat((s/1e3).toPrecision(3))}s`:""},st=t=>{let e={};if(t.start?.timestamp&&(e.startTime=t.start.timestamp),t.complete?.timestamp&&(e.endTime=t.complete.timestamp),e.startTime&&e.endTime){let a=new Date(e.endTime).getTime()-new Date(e.startTime).getTime();a>=0&&(e.duration=`${parseFloat((a/1e3).toPrecision(3))}s (${a}ms)`)}return e},Oe=t=>{if(!t.start)return"";let e=t.start.data?.arguments||{},a=t.start.data?.toolName||t.tool||"",s="";if(a==="bash"||a==="exec")s=e.command||e.description||"";else if(a==="ask_user")s=e.question||e.message||"";else if(a==="read"||a==="write"||a==="edit")s=e.file_path||e.path||"";else if(a==="view")s=e.path||e.file||"";else if(a==="create")s=e.path||e.name||"";else if(a==="report_intent")s=e.intent||e.message||"";else if(a==="web_search")s=e.query||"";else if(a==="web_fetch")s=e.url||"";else if(a==="browser"){let n=e.action||"",i=e.targetUrl||e.url||"";s=i?`${n} ${i}`:n}else s=e.description||e.command||e.message||e.path||e.file_path||e.query||"";return s&&s.length>200&&(s=s.substring(0,200)+"..."),s},_e=t=>t.data?.tools&&t.data.tools.length>0,$e=t=>t.data?.tools&&Array.isArray(t.data.tools)?t.data.tools.filter(e=>e&&typeof e=="object"&&e.name).map(e=>{let a=e.result!==void 0||e.status==="completed"||e.status==="error",s={};if(e.startTime&&(s.startTime=e.startTime),e.endTime&&(s.endTime=e.endTime),s.startTime&&s.endTime){let n=new Date(s.endTime).getTime()-new Date(s.startTime).getTime();n>=0&&(s.duration=`${parseFloat((n/1e3).toPrecision(3))}s (${n}ms)`)}return{tool:e.name,timing:s,start:{timestamp:e.startTime,data:{toolName:e.name,arguments:e.input||e.arguments||{}}},complete:a?{timestamp:e.endTime,data:{result:e.result,error:e.status==="error"?e.error:null}}:null}}):[],ae=["#58a6ff","#f0883e","#a371f7","#3fb950","#f778ba","#79c0ff","#d29922","#56d4dd"],qe=t=>{let e=0;for(let a=0;a<t.length;a++){let s=t.charCodeAt(a);e=(e<<5)-e+s,e=e&e}return e},se=t=>{let{ownerMap:e,subagentInfo:a}=xe.value;if(t.type==="subagent.started"||t.type==="subagent.completed"||t.type==="subagent.failed"){let i=t.data?.toolCallId;if(i&&a.has(i)){let o=a.get(i);return{name:o.name,toolCallId:i,colorIndex:o.colorIndex}}return null}if(t._subagent){let i=t._subagent.id,o=t._subagent.name;if(a.has(i)){let r=a.get(i);return{name:r.name,toolCallId:i,colorIndex:r.colorIndex}}return{name:o,toolCallId:i,colorIndex:Math.abs(qe(i))}}if(t.data?.subAgentId){let i=t.data.subAgentId,o=a.get(i);if(o)return{name:o.name,toolCallId:i,colorIndex:o.colorIndex}}let s=e.get(t.stableId);if(!s)return null;let n=a.get(s);return n?{name:n.name,toolCallId:s,colorIndex:n.colorIndex}:null},Ue=t=>{let e=se(t);return e?ae[e.colorIndex%ae.length]:null},Pe=t=>{if(window.trackClick){let e=ee.value.find(a=>a.type===t);window.trackClick("EventFilterClicked",{filterType:t,filterLabel:e?e.label:t,sessionId:m.value})}k.value=t},oe=t=>{V.value="",k.value="all",X.value=t.id,Vue.nextTick(()=>{if(g.value){let e=A.value.findIndex(a=>a.virtualIndex===t.index);if(e>=0){let a=s=>{s<=0||!g.value||(g.value.scrollToItem(e),setTimeout(()=>a(s-1),100))};setTimeout(()=>a(3),50)}}})},Be=()=>{if(!g.value)return;let t=e=>{e<=0||!g.value||(g.value.scrollToItem(0),setTimeout(()=>t(e-1),100))};t(3)},ze=()=>{if(!g.value)return;let t=A.value.length-1,e=a=>{a<=0||!g.value||(g.value.scrollToItem(t),setTimeout(()=>e(a-1),100))};e(5)},ne=t=>{window.trackClick&&window.trackClick("TurnClicked",{turnNumber:t,sessionId:m.value});let e=R.value.find(a=>a.id===t);if(e){let a=`UserReq${e.userReqNumber}_Turn${e.id}`,s=`${window.location.pathname}?eventType=assistant.turn_start&eventName=${a}`;window.history.pushState({},"",s),oe(e)}},Ve=t=>{if(!t)return"";let e=t.replace(/\/$/,"").split("/");return e[e.length-1]||t},He=t=>{let e=R.value.find(s=>s.index===t);if(!e)return"?";let a=e.originalTurnId??e.id;return e.userReqNumber>0?`${e.userReqNumber} - Turn ${a}`:`Turn ${a}`},je=t=>R.value.find(a=>a.index===t)?.duration||null,ie=t=>{let e=document.createElement("div");return e.textContent=t,e.innerHTML},Fe=t=>t?new Date(t).toLocaleString():"N/A",Ge=async()=>{console.log("[Export] exportSession called"),window.trackClick&&window.trackClick("ExportClicked",{sessionId:m.value}),z.value=!0;try{console.log("[Export] Fetching:",`/session/${m.value}/export`);let t=await fetch(`/session/${m.value}/export`);if(console.log("[Export] Response received:",t.status,t.ok),console.log("[Export] Response received:",t.status,t.ok),!t.ok)throw new Error("Share failed");console.log("[Export] Creating blob...");let e=await t.blob();console.log("[Export] Blob size:",e.size,"type:",e.type);let a=window.URL.createObjectURL(e);console.log("[Export] Creating download link...");let s=document.createElement("a");s.href=a,s.download=`session-${m.value}.zip`,document.body.appendChild(s),s.click(),console.log("[Export] Download triggered"),window.URL.revokeObjectURL(a),document.body.removeChild(s),console.log("[Export] Showing success feedback...");let n="\u{1F4E4} Share Session",i="\u2713 Downloaded!",o=document.querySelector(".export-btn");o&&(o.textContent=i,o.style.background="#238636",console.log("[Export] Button text updated to:",o.textContent),setTimeout(()=>{o.textContent=n,o.style.background="",console.log("[Export] Button text restored")},2e3))}catch(t){console.error("[Export] Share session error:",t),alert("Failed to share session: "+t.message)}finally{z.value=!1,console.log("[Export] Export complete")}};F(async()=>{try{console.log("[Navigation] Starting event loading...");let a=await fetch(`/api/sessions/${m.value}/events`);if(!a.ok)throw new Error(`Failed to load events: ${a.statusText}`);let s=await a.json();if(Array.isArray(s))S.value=s;else if(s.events&&Array.isArray(s.events))S.value=s.events,console.log("[Navigation] Pagination:",s.pagination);else throw new Error("Invalid response format");if(console.log("[Navigation] Events loaded:",S.value.length),S.value.length>0){let c=S.value[S.value.length-1],l=c.timestamp||c.time||c.data?.timestamp;l&&(W.value.updated=new Date(l))}let n=new URLSearchParams(window.location.search),i=n.get("eventType"),o=n.get("eventName"),r=n.get("eventTimestamp");console.log("[Navigation] URL params:",i,o,r),i&&o&&(console.log("[Navigation] Waiting for Vue to render..."),Vue.nextTick(()=>{console.log("[Navigation] nextTick - flatEvents count:",v.value?.length);let c=null;if(i==="assistant.turn_start"){let l=o.match(/UserReq(\d+)_Turn(\d+)/);if(l){let d=parseInt(l[2],10);if(!isNaN(d)){console.log("[Navigation] Jumping to turn:",d),ne(d);return}}}else i==="subagent.started"?(console.log("[Navigation] Searching for subagent:",o,"timestamp:",r),r&&(c=v.value.find(l=>l.type==="subagent.started"&&l.timestamp===r)),c||(c=v.value.find(l=>l.type==="subagent.started"&&(l.data?.agentDisplayName===o||l.data?.agentName===o||l.data?.label===o))),console.log("[Navigation] Target event found:",c?"YES":"NO","virtualIndex:",c?.virtualIndex)):c=v.value.find(l=>l.type===i);if(c){let l=A.value.findIndex(d=>d.virtualIndex===c.virtualIndex);if(console.log("[Navigation] Target in filteredEvents at index:",l),l>=0&&g.value){console.log("[Navigation] Scrolling to index:",l);let d=y=>{y<=0||!g.value||(g.value.scrollToItem(l),setTimeout(()=>d(y-1),100))};setTimeout(()=>d(3),50)}else console.log("[Navigation] Failed - targetIndex:",l,"scrollerRef:",!!g.value)}else console.log("[Navigation] Target event not found")}))}catch(a){console.error("Error loading events:",a),Q.value=a.message}finally{Z.value=!1}window.addEventListener("keydown",a=>{a.ctrlKey&&a.key==="b"&&(a.preventDefault(),q.value=!q.value)}),window.marked&&marked.setOptions({breaks:!0,gfm:!0});let t=()=>{if(!g.value)return;let a=null;if(g.value.$el&&typeof g.value.$el.querySelector=="function"?a=g.value.$el.querySelector(".vue-recycle-scroller"):g.value.querySelector&&typeof g.value.querySelector=="function"&&(a=g.value.querySelector(".vue-recycle-scroller")),a||(a=document.querySelector(".vue-recycle-scroller")),a){let s=a.scrollTop,n=a.clientHeight,i=80,o=Math.floor(s/i),r=Math.ceil(n/i),c=Math.min(o+r,A.value.length),l=Math.max(1,o+1),d=Math.max(1,c);Y.value={start:Math.min(l,d),end:d}}},e=null;setTimeout(()=>{t();let a=document.querySelector(".vue-recycle-scroller");a&&(a.addEventListener("scroll",t),e=()=>{a.removeEventListener("scroll",t)})},500),me(()=>{L&&(clearTimeout(L),L=null),e&&e(),x.value={},w.value={},f.clear()})});let D=u([]),H=u([]),O=u(!1),b=u([]),C=u(""),j=u(null),T=u(""),I=u(!1),P=u([]),re=u(0),le=["#3b82f6","#10b981","#f59e0b","#ef4444","#8b5cf6","#ec4899","#06b6d4","#f97316"],We=t=>{let e=0;for(let a=0;a<t.length;a++)e=t.charCodeAt(a)+((e<<5)-e);return le[Math.abs(e)%le.length]},Ke=async()=>{try{let t=await fetch(`/api/sessions/${m.value}/tags`);if(t.ok){let e=await t.json();D.value=e.tags||[]}}catch(t){console.error("Error loading tags:",t)}},de=async()=>{try{let t=await fetch("/api/tags");if(t.ok){let e=await t.json();H.value=e.tags||[]}}catch(t){console.error("Error loading all tags:",t)}},Je=async t=>{try{window.trackClick&&t.filter(s=>!D.value.includes(s)).forEach(s=>{window.trackClick("TagAdded",{sessionId:m.value,tag:s})});let e=await fetch(`/api/sessions/${m.value}/tags`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({tags:t})});if(e.ok){let a=await e.json();return D.value=a.tags||[],T.value="",!0}else{let a=await e.json();return T.value=a.error||"Failed to save tags",!1}}catch(e){return console.error("Error saving tags:",e),T.value="Network error",!1}},Xe=()=>{b.value=[...D.value],O.value=!0,T.value="",setTimeout(()=>{j.value&&j.value.focus()},10)},Ye=()=>{O.value=!1,b.value=[],C.value="",I.value=!1,T.value=""},ce=()=>{let t=C.value.trim().toLowerCase();if(t){if(t.length>30){T.value="Tag must be 30 characters or less";return}if(b.value.length>=10){T.value="Maximum 10 tags per session";return}if(b.value.includes(t)){T.value="Tag already added",C.value="";return}b.value.push(t),C.value="",I.value=!1,T.value=""}},Ze=t=>{b.value=b.value.filter(e=>e!==t),T.value=""},Qe=()=>{let t=C.value.trim().toLowerCase();if(!t){I.value=!1,P.value=[];return}let e=H.value.filter(a=>a.toLowerCase().includes(t)&&!b.value.includes(a)).slice(0,5);e.length>0?(I.value=!0,P.value=e,re.value=0):(I.value=!1,P.value=[])},et=t=>{C.value=t,ce()},tt=async()=>{setTimeout(async()=>{if(!O.value)return;await Je(b.value)&&(O.value=!1,b.value=[],C.value="",I.value=!1,await de())},200)};return F(async()=>{await Ke(),await de()}),{sessionId:m,metadata:W,exporting:z,sidebarCollapsed:q,expandedTools:x,expandedContent:w,expansionCount:Te,currentFilter:k,searchText:V,currentTurnIndex:X,scrollerRef:g,visibleRange:Y,loadedEvents:S,eventsLoading:Z,eventsError:Q,flatEvents:v,filteredEvents:A,eventCounts:fe,filters:ee,turns:R,userReqs:he,truncateText:ye,formatTime:we,formatToolTime:Se,formatDateTime:Fe,renderMarkdown:Ce,highlightSearchText:ke,toggleTool:Ie,toggleContent:Ee,isContentTooLong:Me,truncateContent:Ne,getBadgeInfo:Le,getToolStatus:Ae,getToolErrorMessage:Re,getToolDuration:De,getToolCommand:Oe,hasTools:_e,getToolGroups:$e,getSubagentInfo:se,getSubagentColor:Ue,setFilter:Pe,scrollToTurn:oe,scrollToTop:Be,scrollToBottom:ze,jumpToTurn:ne,getTurnNumber:He,getTurnDuration:je,repoBasename:Ve,escapeHtml:ie,exportSession:Ge,searchResultCount:be,sessionTags:D,allTags:H,tagsEditing:O,editingTags:b,tagInputValue:C,tagInputRef:j,tagsError:T,showAutocomplete:I,autocompleteOptions:P,autocompleteSelectedIndex:re,getTagColor:We,startEditTags:Xe,cancelEditTags:Ye,addTag:ce,removeTagFromEdit:Ze,updateAutocomplete:Qe,selectAutocompleteOption:et,saveTagsOnBlur:tt}},template:`
|
|
10
|
+
...`},Qe=(t,e)=>{if(e?.data?.badgeLabel&&e?.data?.badgeClass)return{label:e.data.badgeLabel,class:e.data.badgeClass};if(t==="message"&&e?.data?.role==="toolResult")return{label:"TOOL RESULT",class:"badge-tool"};if(t==="session.model_change")return{label:"MODEL CHANGE",class:"badge-session"};if(t==="session.truncation")return{label:"TRUNCATION",class:"badge-truncation"};if(t==="session.compaction_start"||t==="session.compaction_complete")return{label:"COMPACTION",class:"badge-compaction"};if(t==="system.notification")return{label:"SYSTEM",class:"badge-system"};let s=(t||"").split(".")[0]||"unknown";return{user:{label:"USER",class:"badge-user"},assistant:{label:"ASSISTANT",class:"badge-assistant"},reasoning:{label:"REASONING",class:"badge-reasoning"},turn:{label:"TURN",class:"badge-turn"},tool:{label:"TOOL",class:"badge-tool"},subagent:{label:"SUBAGENT",class:"badge-subagent"},skill:{label:"SKILL",class:"badge-skill"},session:{label:"SESSION",class:"badge-session"},error:{label:"ERROR",class:"badge-error"},abort:{label:"ABORT",class:"badge-error"}}[s]||{label:s.toUpperCase(),class:"badge-info"}},et=t=>{if(!t.complete)return{icon:"\u23F3",color:"tool-status-running",text:""};let e=t.complete.data||{};return e.error||e.isError?{icon:"\u274C",color:"tool-status-error",text:""}:{icon:"\u2713",color:"tool-status-success",text:""}},tt=t=>{if(!t.complete?.data?.error)return"";let e=t.complete.data.error;if(typeof e=="object"&&e.message)return e.message;if(typeof e=="string"){try{let a=JSON.parse(e);if(a.message)return a.message}catch{}return e}return String(e)},at=t=>{if(!t.complete)return"";let e=new Date(t.start.timestamp).getTime(),s=new Date(t.complete.timestamp).getTime()-e;return s>=100?`${parseFloat((s/1e3).toPrecision(3))}s`:""},zt=t=>{let e={};if(t.start?.timestamp&&(e.startTime=t.start.timestamp),t.complete?.timestamp&&(e.endTime=t.complete.timestamp),e.startTime&&e.endTime){let a=new Date(e.endTime).getTime()-new Date(e.startTime).getTime();a>=0&&(e.duration=`${parseFloat((a/1e3).toPrecision(3))}s (${a}ms)`)}return e},st=t=>{if(!t.start)return"";let e=t.start.data?.arguments||{},a=t.start.data?.toolName||t.tool||"",s="";if(a==="bash"||a==="exec")s=e.command||e.description||"";else if(a==="ask_user")s=e.question||e.message||"";else if(a==="read"||a==="write"||a==="edit")s=e.file_path||e.path||"";else if(a==="view")s=e.path||e.file||"";else if(a==="create")s=e.path||e.name||"";else if(a==="report_intent")s=e.intent||e.message||"";else if(a==="web_search")s=e.query||"";else if(a==="web_fetch")s=e.url||"";else if(a==="browser"){let n=e.action||"",i=e.targetUrl||e.url||"";s=i?`${n} ${i}`:n}else s=e.description||e.command||e.message||e.path||e.file_path||e.query||"";return s&&s.length>200&&(s=s.substring(0,200)+"..."),s},nt=t=>t.data?.tools&&t.data.tools.length>0,ot=t=>t.data?.tools&&Array.isArray(t.data.tools)?t.data.tools.filter(e=>e&&typeof e=="object"&&e.name).map(e=>{let a=e.result!==void 0||e.status==="completed"||e.status==="error",s={};if(e.startTime&&(s.startTime=e.startTime),e.endTime&&(s.endTime=e.endTime),s.startTime&&s.endTime){let n=new Date(s.endTime).getTime()-new Date(s.startTime).getTime();n>=0&&(s.duration=`${parseFloat((n/1e3).toPrecision(3))}s (${n}ms)`)}return{tool:e.name,timing:s,start:{timestamp:e.startTime,data:{toolName:e.name,arguments:e.input||e.arguments||{}}},complete:a?{timestamp:e.endTime,data:{result:e.result,error:e.status==="error"?e.error:null}}:null}}):[],ce=["#58a6ff","#f0883e","#a371f7","#3fb950","#f778ba","#79c0ff","#d29922","#56d4dd"],it=t=>{let e=0;for(let a=0;a<t.length;a++){let s=t.charCodeAt(a);e=(e<<5)-e+s,e=e&e}return e},ye=t=>{let{ownerMap:e,subagentInfo:a}=Q.value;if(t.type==="subagent.started"||t.type==="subagent.completed"||t.type==="subagent.failed"){let i=t.data?.toolCallId;if(i&&a.has(i)){let r=a.get(i);return{name:r.name,toolCallId:i,colorIndex:r.colorIndex}}return null}if(t._subagent){let i=t._subagent.id,r=t._subagent.name;if(a.has(i)){let l=a.get(i);return{name:l.name,toolCallId:i,colorIndex:l.colorIndex}}return{name:r,toolCallId:i,colorIndex:Math.abs(it(i))}}if(t.data?.subAgentId){let i=t.data.subAgentId,r=a.get(i);if(r)return{name:r.name,toolCallId:i,colorIndex:r.colorIndex}}let s=e.get(t.stableId);if(!s)return null;let n=a.get(s);return n?{name:n.name,toolCallId:s,colorIndex:n.colorIndex}:null},rt=t=>{let e=ye(t);return e?ce[e.colorIndex%ce.length]:null},lt=t=>{if(window.trackClick){let e=Te.value.find(a=>a.type===t);window.trackClick("EventFilterClicked",{filterType:t,filterLabel:e?e.label:t,sessionId:p.value})}N.value=t},ct=t=>{A.value=t,t&&(N.value="all"),window.trackClick&&window.trackClick("SubagentSelected",{subagent:t,sessionId:p.value})},xe=t=>{H.value="",N.value="all",A.value=null,pe.value=t.id,Vue.nextTick(()=>{if(h.value){let e=W.value.findIndex(a=>a.virtualIndex===t.index);if(e>=0){let a=s=>{s<=0||!h.value||(h.value.scrollToItem(e),setTimeout(()=>a(s-1),100))};setTimeout(()=>a(3),50)}}})},dt=()=>{if(!h.value)return;let t=e=>{e<=0||!h.value||(h.value.scrollToItem(0),setTimeout(()=>t(e-1),100))};t(3)},ut=()=>{if(!h.value)return;let t=W.value.length-1,e=a=>{a<=0||!h.value||(h.value.scrollToItem(t),setTimeout(()=>e(a-1),100))};e(5)},ke=t=>{window.trackClick&&window.trackClick("TurnClicked",{turnNumber:t,sessionId:p.value});let e=G.value.find(a=>a.id===t);if(e){let a=`UserReq${e.userReqNumber}_Turn${e.id}`,s=`${window.location.pathname}?eventType=assistant.turn_start&eventName=${a}`;window.history.pushState({},"",s),xe(e)}},mt=t=>{if(!t)return"";let e=t.replace(/\/$/,"").split("/");return e[e.length-1]||t},gt=t=>{let e=G.value.find(s=>s.index===t);if(!e)return"?";let a=e.originalTurnId??e.id;return e.userReqNumber>0?`${e.userReqNumber} - Turn ${a}`:`Turn ${a}`},pt=t=>G.value.find(a=>a.index===t)?.duration||null,Ce=t=>{let e=document.createElement("div");return e.textContent=t,e.innerHTML},vt=t=>t?new Date(t).toLocaleString():"N/A",ft=async()=>{console.log("[Export] exportSession called"),window.trackClick&&window.trackClick("ExportClicked",{sessionId:p.value}),re.value=!0;try{console.log("[Export] Fetching:",`/session/${p.value}/export`);let t=await fetch(`/session/${p.value}/export`);if(console.log("[Export] Response received:",t.status,t.ok),console.log("[Export] Response received:",t.status,t.ok),!t.ok)throw new Error("Share failed");console.log("[Export] Creating blob...");let e=await t.blob();console.log("[Export] Blob size:",e.size,"type:",e.type);let a=window.URL.createObjectURL(e);console.log("[Export] Creating download link...");let s=document.createElement("a");s.href=a,s.download=`session-${p.value}.zip`,document.body.appendChild(s),s.click(),console.log("[Export] Download triggered"),window.URL.revokeObjectURL(a),document.body.removeChild(s),console.log("[Export] Showing success feedback...");let n="\u{1F4E4} Share Session",i="\u2713 Downloaded!",r=document.querySelector(".export-btn");r&&(r.textContent=i,r.style.background="#238636",console.log("[Export] Button text updated to:",r.textContent),setTimeout(()=>{r.textContent=n,r.style.background="",console.log("[Export] Button text restored")},2e3))}catch(t){console.error("[Export] Share session error:",t),alert("Failed to share session: "+t.message)}finally{re.value=!1,console.log("[Export] Export complete")}},we=t=>{let e=document.querySelector(".filter-type-wrapper");e&&!e.contains(t.target)&&(le.value=!1)},Se=t=>{t.ctrlKey&&t.key==="b"&&(t.preventDefault(),X.value=!X.value)};u(()=>{document.removeEventListener("click",we),window.removeEventListener("keydown",Se),j&&(clearTimeout(j),j=null),Z&&(Z(),Z=null),L.value={},D.value={},S.clear()}),o(async()=>{document.addEventListener("click",we);try{console.log("[Navigation] Starting event loading...");let e=await fetch(`/api/sessions/${p.value}/events`);if(!e.ok)throw new Error(`Failed to load events: ${e.statusText}`);let a=await e.json();if(Array.isArray(a))O.value=a;else if(a.events&&Array.isArray(a.events))O.value=a.events,console.log("[Navigation] Pagination:",a.pagination);else throw new Error("Invalid response format");if(console.log("[Navigation] Events loaded:",O.value.length),O.value.length>0){let l=O.value[O.value.length-1],c=l.timestamp||l.time||l.data?.timestamp;c&&(x.value.updated=new Date(c))}let s=new URLSearchParams(window.location.search),n=s.get("eventType"),i=s.get("eventName"),r=s.get("eventTimestamp");console.log("[Navigation] URL params:",n,i,r),n&&i&&(console.log("[Navigation] Waiting for Vue to render..."),Vue.nextTick(()=>{console.log("[Navigation] nextTick - flatEvents count:",k.value?.length);let l=null;if(n==="assistant.turn_start"){let c=i.match(/UserReq(\d+)_Turn(\d+)/);if(c){let b=parseInt(c[2],10);if(!isNaN(b)){console.log("[Navigation] Jumping to turn:",b),ke(b);return}}}else n==="subagent.started"?(console.log("[Navigation] Searching for subagent:",i,"timestamp:",r),r&&(l=k.value.find(c=>c.type==="subagent.started"&&c.timestamp===r)),l||(l=k.value.find(c=>c.type==="subagent.started"&&(c.data?.agentDisplayName===i||c.data?.agentName===i||c.data?.label===i))),console.log("[Navigation] Target event found:",l?"YES":"NO","virtualIndex:",l?.virtualIndex)):l=k.value.find(c=>c.type===n);if(l){let c=W.value.findIndex(b=>b.virtualIndex===l.virtualIndex);if(console.log("[Navigation] Target in filteredEvents at index:",c),c>=0&&h.value){console.log("[Navigation] Scrolling to index:",c);let b=T=>{T<=0||!h.value||(h.value.scrollToItem(c),setTimeout(()=>b(T-1),100))};setTimeout(()=>b(3),50)}else console.log("[Navigation] Failed - targetIndex:",c,"scrollerRef:",!!h.value)}else console.log("[Navigation] Target event not found")}))}catch(e){console.error("Error loading events:",e),be.value=e.message}finally{fe.value=!1}window.addEventListener("keydown",Se),window.marked&&marked.setOptions({breaks:!0,gfm:!0});let t=()=>{if(!h.value)return;let e=null;if(h.value.$el&&typeof h.value.$el.querySelector=="function"?e=h.value.$el.querySelector(".vue-recycle-scroller"):h.value.querySelector&&typeof h.value.querySelector=="function"&&(e=h.value.querySelector(".vue-recycle-scroller")),e||(e=document.querySelector(".vue-recycle-scroller")),e){let a=e.scrollTop,s=e.clientHeight,n=80,i=Math.floor(a/n),r=Math.ceil(s/n),l=Math.min(i+r,W.value.length),c=Math.max(1,i+1),b=Math.max(1,l);ve.value={start:Math.min(c,b),end:b}}};setTimeout(()=>{t();let e=document.querySelector(".vue-recycle-scroller");e&&(e.addEventListener("scroll",t),Z=()=>{e.removeEventListener("scroll",t)})},500)});let K=m([]),de=m([]),J=m(!1),I=m([]),_=m(""),ue=m(null),E=m(""),U=m(!1),ee=m([]),Ie=m(0),bt=t=>!t||t===0?"0":t<1e3?t.toString():Math.floor(t/1e3)+"K",Tt=t=>{if(!t||t===0)return"0s";let e=Math.floor(t/1e3);if(e<60)return(t/1e3).toFixed(1)+"s";let a=Math.floor(e/60),s=e%60;return`${a}m ${s}s`},ht=v(()=>{if(!x.value.usage||!x.value.usage.modelMetrics)return 0;let t=0;for(let e in x.value.usage.modelMetrics){let a=x.value.usage.modelMetrics[e].usage;a&&(t+=(a.inputTokens||0)+(a.outputTokens||0))}return t}),yt=v(()=>{if(!x.value.usage||!x.value.usage.modelMetrics)return 0;let t=0;for(let e in x.value.usage.modelMetrics)t+=x.value.usage.modelMetrics[e].requests?.count||0;return t}),xt=v(()=>!x.value.usage||!x.value.usage.modelMetrics?0:Object.keys(x.value.usage.modelMetrics).length),kt=t=>{let e=x.value.usage?.modelMetrics[t];return!e||!e.usage?null:y(e.usage)},Ct=t=>{let e=x.value.usage?.modelMetrics[t];return!e||!e.usage?0:f(e.usage)},wt=v(()=>{let t=new Map;for(let e of k.value)if(e.data?.tools&&Array.isArray(e.data.tools))for(let a of e.data.tools)a&&a.name&&t.set(a.name,(t.get(a.name)||0)+1);return Array.from(t,([e,a])=>({name:e,count:a})).sort((e,a)=>a.count-e.count)}),St=t=>t==null?"":t+" premium",Ee=["#3b82f6","#10b981","#f59e0b","#ef4444","#8b5cf6","#ec4899","#06b6d4","#f97316"],It=t=>{let e=0;for(let a=0;a<t.length;a++)e=t.charCodeAt(a)+((e<<5)-e);return Ee[Math.abs(e)%Ee.length]},Et=async()=>{try{let t=await fetch(`/api/sessions/${p.value}/tags`);if(t.ok){let e=await t.json();K.value=e.tags||[]}}catch(t){console.error("Error loading tags:",t)}},Me=async()=>{try{let t=await fetch("/api/tags");if(t.ok){let e=await t.json();de.value=e.tags||[]}}catch(t){console.error("Error loading all tags:",t)}},Mt=async t=>{try{window.trackClick&&t.filter(s=>!K.value.includes(s)).forEach(s=>{window.trackClick("TagAdded",{sessionId:p.value,tag:s})});let e=await fetch(`/api/sessions/${p.value}/tags`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({tags:t})});if(e.ok){let a=await e.json();return K.value=a.tags||[],E.value="",!0}else{let a=await e.json();return E.value=a.error||"Failed to save tags",!1}}catch(e){return console.error("Error saving tags:",e),E.value="Network error",!1}},Rt=()=>{I.value=[...K.value],J.value=!0,E.value="",setTimeout(()=>{ue.value&&ue.value.focus()},10)},Nt=()=>{J.value=!1,I.value=[],_.value="",U.value=!1,E.value=""},Re=()=>{let t=_.value.trim().toLowerCase();if(t){if(t.length>30){E.value="Tag must be 30 characters or less";return}if(I.value.length>=10){E.value="Maximum 10 tags per session";return}if(I.value.includes(t)){E.value="Tag already added",_.value="";return}I.value.push(t),_.value="",U.value=!1,E.value=""}},At=t=>{I.value=I.value.filter(e=>e!==t),E.value=""},Lt=()=>{let t=_.value.trim().toLowerCase();if(!t){U.value=!1,ee.value=[];return}let e=de.value.filter(a=>a.toLowerCase().includes(t)&&!I.value.includes(a)).slice(0,5);e.length>0?(U.value=!0,ee.value=e,Ie.value=0):(U.value=!1,ee.value=[])},Dt=t=>{_.value=t,Re()},Ot=async()=>{setTimeout(async()=>{if(!J.value)return;await Mt(I.value)&&(J.value=!1,I.value=[],_.value="",U.value=!1,await Me())},200)};return o(async()=>{await Et(),await Me()}),{sessionId:p,metadata:x,exporting:re,sidebarCollapsed:X,expandedTools:L,expandedContent:D,expansionCount:ze,currentFilter:N,searchText:H,currentTurnIndex:pe,scrollerRef:h,visibleRange:ve,loadedEvents:O,eventsLoading:fe,eventsError:be,flatEvents:k,filteredEvents:W,eventCounts:Fe,filters:Te,turns:G,userReqs:Ve,truncateText:Be,formatTime:je,formatToolTime:We,formatDateTime:vt,renderMarkdown:Ge,highlightSearchText:Je,toggleTool:Ke,toggleContent:Xe,isContentTooLong:Ye,truncateContent:Ze,getBadgeInfo:Qe,getToolStatus:et,getToolErrorMessage:tt,getToolDuration:at,getToolCommand:st,hasTools:nt,getToolGroups:ot,getSubagentInfo:ye,getSubagentColor:rt,setFilter:lt,selectSubagent:ct,selectedSubagent:A,subagentList:Pe,subagentTokenUsage:He,SUBAGENT_COLORS:ce,typeFilterOpen:le,activeFilterCount:_e,clearAllFilters:$e,scrollToTurn:xe,scrollToTop:dt,scrollToBottom:ut,jumpToTurn:ke,getTurnNumber:gt,getTurnDuration:pt,repoBasename:mt,escapeHtml:Ce,exportSession:ft,searchResultCount:Ue,sessionTags:K,allTags:de,tagsEditing:J,editingTags:I,tagInputValue:_,tagInputRef:ue,tagsError:E,showAutocomplete:U,autocompleteOptions:ee,autocompleteSelectedIndex:Ie,getTagColor:It,startEditTags:Rt,cancelEditTags:Nt,addTag:Re,removeTagFromEdit:At,updateAutocomplete:Lt,selectAutocompleteOption:Dt,saveTagsOnBlur:Ot,formatTokens:bt,formatDuration:Tt,formatCost:St,totalTokens:ht,totalRequests:yt,totalModels:xt,getDisplayUsageInputTokens:Ct,getModelCacheHitRatio:kt,toolCallingSummary:wt}},template:`
|
|
10
11
|
<div class="container">
|
|
11
12
|
<div class="header">
|
|
12
13
|
<a href="/" class="home-btn">\u2190 Back to Home</a>
|
|
@@ -32,7 +33,6 @@
|
|
|
32
33
|
<div class="sidebar-section">
|
|
33
34
|
<div class="sidebar-section-title">Session Info</div>
|
|
34
35
|
<div class="session-info">
|
|
35
|
-
<div v-if="metadata.summary" class="session-summary-block">{{ metadata.summary }}</div>
|
|
36
36
|
<table class="session-info-table">
|
|
37
37
|
<tbody>
|
|
38
38
|
<tr v-if="metadata.source">
|
|
@@ -44,7 +44,15 @@
|
|
|
44
44
|
</span>
|
|
45
45
|
</td>
|
|
46
46
|
</tr>
|
|
47
|
-
<tr v-if="metadata.
|
|
47
|
+
<tr v-if="metadata.modernizeVersion">
|
|
48
|
+
<td>Version</td>
|
|
49
|
+
<td>{{ metadata.modernizeVersion }}</td>
|
|
50
|
+
</tr>
|
|
51
|
+
<tr v-if="metadata.source === 'modernize' && metadata.copilotVersion">
|
|
52
|
+
<td>Copilot SDK</td>
|
|
53
|
+
<td>{{ metadata.copilotVersion }}</td>
|
|
54
|
+
</tr>
|
|
55
|
+
<tr v-if="metadata.copilotVersion && metadata.source !== 'modernize'">
|
|
48
56
|
<td>Version</td>
|
|
49
57
|
<td>{{ metadata.copilotVersion }}</td>
|
|
50
58
|
</tr>
|
|
@@ -77,18 +85,131 @@
|
|
|
77
85
|
</div>
|
|
78
86
|
</div>
|
|
79
87
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
<div class="
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
88
|
+
<!-- Usage Section -->
|
|
89
|
+
<div v-if="metadata.usage" class="sidebar-section">
|
|
90
|
+
<div class="sidebar-section-title">Token Usage</div>
|
|
91
|
+
<div class="usage-container">
|
|
92
|
+
<div class="usage-summary">
|
|
93
|
+
<div class="usage-summary-eyebrow">Overview</div>
|
|
94
|
+
<div class="usage-summary-total">
|
|
95
|
+
{{ formatTokens(totalTokens) }} <span class="usage-summary-total-unit">tokens</span>
|
|
96
|
+
</div>
|
|
97
|
+
<div class="usage-summary-caption">
|
|
98
|
+
Usage captured across {{ totalModels }} model{{ totalModels === 1 ? '' : 's' }}
|
|
99
|
+
</div>
|
|
100
|
+
<div class="usage-summary-metrics">
|
|
101
|
+
<div class="usage-metric-card usage-metric-card-summary">
|
|
102
|
+
<span class="usage-metric-label">Requests</span>
|
|
103
|
+
<span class="usage-metric-value">{{ totalRequests }} reqs</span>
|
|
104
|
+
</div>
|
|
105
|
+
<div class="usage-metric-card usage-metric-card-summary">
|
|
106
|
+
<span class="usage-metric-label">Models</span>
|
|
107
|
+
<span class="usage-metric-value">{{ totalModels }}</span>
|
|
108
|
+
</div>
|
|
109
|
+
<div class="usage-metric-card usage-metric-card-summary">
|
|
110
|
+
<span class="usage-metric-label">API Time</span>
|
|
111
|
+
<span class="usage-metric-value">{{ formatDuration(metadata.usage.totalApiDurationMs) }}</span>
|
|
112
|
+
</div>
|
|
113
|
+
</div>
|
|
114
|
+
</div>
|
|
115
|
+
|
|
116
|
+
<div class="usage-expanded">
|
|
117
|
+
<!-- Model breakdown -->
|
|
118
|
+
<div v-if="Object.keys(metadata.usage.modelMetrics).length > 0" class="usage-section">
|
|
119
|
+
<div class="usage-section-header">
|
|
120
|
+
<div class="usage-section-title">Models</div>
|
|
121
|
+
<div class="usage-section-badge">{{ totalModels }}</div>
|
|
122
|
+
</div>
|
|
123
|
+
<div class="usage-model-list">
|
|
124
|
+
<div v-for="(metrics, model) in metadata.usage.modelMetrics" :key="model" class="usage-model">
|
|
125
|
+
<div class="usage-model-header">
|
|
126
|
+
<div class="usage-model-name" :title="model">{{ model }}</div>
|
|
127
|
+
<div class="usage-model-meta">
|
|
128
|
+
<span class="usage-meta-pill">{{ metrics.requests?.count || 0 }} reqs</span>
|
|
129
|
+
<span v-if="metrics.requests?.cost" class="usage-meta-pill usage-meta-pill-premium">{{ formatCost(metrics.requests.cost) }}</span>
|
|
130
|
+
<span v-if="getModelCacheHitRatio(model) !== null" class="usage-meta-pill usage-meta-pill-cache">{{ getModelCacheHitRatio(model) }}% cache</span>
|
|
131
|
+
</div>
|
|
132
|
+
</div>
|
|
133
|
+
<div v-if="metrics.usage" class="usage-metric-grid">
|
|
134
|
+
<div class="usage-metric-card">
|
|
135
|
+
<span class="usage-metric-label">Input</span>
|
|
136
|
+
<span class="usage-metric-value">{{ formatTokens(getDisplayUsageInputTokens(model)) }}</span>
|
|
137
|
+
</div>
|
|
138
|
+
<div class="usage-metric-card">
|
|
139
|
+
<span class="usage-metric-label">Output</span>
|
|
140
|
+
<span class="usage-metric-value">{{ formatTokens(metrics.usage.outputTokens || 0) }}</span>
|
|
141
|
+
</div>
|
|
142
|
+
<div v-if="metrics.usage?.cacheReadTokens" class="usage-metric-card">
|
|
143
|
+
<span class="usage-metric-label">Cache Read</span>
|
|
144
|
+
<span class="usage-metric-value">{{ formatTokens(metrics.usage.cacheReadTokens) }}</span>
|
|
145
|
+
</div>
|
|
146
|
+
<div v-if="metrics.usage?.cacheWriteTokens" class="usage-metric-card">
|
|
147
|
+
<span class="usage-metric-label">Cache Write</span>
|
|
148
|
+
<span class="usage-metric-value">{{ formatTokens(metrics.usage.cacheWriteTokens) }}</span>
|
|
149
|
+
</div>
|
|
150
|
+
</div>
|
|
151
|
+
</div>
|
|
152
|
+
</div>
|
|
153
|
+
</div>
|
|
154
|
+
|
|
155
|
+
<!-- Context window breakdown -->
|
|
156
|
+
<div v-if="metadata.usage.currentTokens || metadata.usage.systemTokens || metadata.usage.conversationTokens || metadata.usage.toolDefinitionsTokens" class="usage-section">
|
|
157
|
+
<div class="usage-section-header">
|
|
158
|
+
<div class="usage-section-title">Context Window</div>
|
|
159
|
+
</div>
|
|
160
|
+
<div class="usage-metric-grid">
|
|
161
|
+
<div v-if="metadata.usage.currentTokens" class="usage-metric-card">
|
|
162
|
+
<span class="usage-metric-label">Current</span>
|
|
163
|
+
<span class="usage-metric-value">{{ formatTokens(metadata.usage.currentTokens) }}</span>
|
|
164
|
+
</div>
|
|
165
|
+
<div v-if="metadata.usage.systemTokens" class="usage-metric-card">
|
|
166
|
+
<span class="usage-metric-label">System</span>
|
|
167
|
+
<span class="usage-metric-value">{{ formatTokens(metadata.usage.systemTokens) }}</span>
|
|
168
|
+
</div>
|
|
169
|
+
<div v-if="metadata.usage.conversationTokens" class="usage-metric-card">
|
|
170
|
+
<span class="usage-metric-label">Conversation</span>
|
|
171
|
+
<span class="usage-metric-value">{{ formatTokens(metadata.usage.conversationTokens) }}</span>
|
|
172
|
+
</div>
|
|
173
|
+
<div v-if="metadata.usage.toolDefinitionsTokens" class="usage-metric-card">
|
|
174
|
+
<span class="usage-metric-label">Tools</span>
|
|
175
|
+
<span class="usage-metric-value">{{ formatTokens(metadata.usage.toolDefinitionsTokens) }}</span>
|
|
176
|
+
</div>
|
|
177
|
+
</div>
|
|
178
|
+
</div>
|
|
179
|
+
|
|
180
|
+
<!-- Code changes -->
|
|
181
|
+
<div v-if="metadata.usage.codeChanges && (metadata.usage.codeChanges.linesAdded > 0 || metadata.usage.codeChanges.linesRemoved > 0)" class="usage-section">
|
|
182
|
+
<div class="usage-section-header">
|
|
183
|
+
<div class="usage-section-title">Code Changes</div>
|
|
184
|
+
</div>
|
|
185
|
+
<div class="usage-metric-grid usage-metric-grid-compact">
|
|
186
|
+
<div class="usage-metric-card">
|
|
187
|
+
<span class="usage-metric-label">Added</span>
|
|
188
|
+
<span class="usage-metric-value usage-metric-value-added">+{{ metadata.usage.codeChanges.linesAdded }}</span>
|
|
189
|
+
</div>
|
|
190
|
+
<div class="usage-metric-card">
|
|
191
|
+
<span class="usage-metric-label">Removed</span>
|
|
192
|
+
<span class="usage-metric-value usage-metric-value-removed">-{{ metadata.usage.codeChanges.linesRemoved }}</span>
|
|
193
|
+
</div>
|
|
194
|
+
<div class="usage-metric-card">
|
|
195
|
+
<span class="usage-metric-label">Files</span>
|
|
196
|
+
<span class="usage-metric-value">{{ metadata.usage.codeChanges.filesModified?.length || 0 }}</span>
|
|
197
|
+
</div>
|
|
198
|
+
</div>
|
|
199
|
+
</div>
|
|
200
|
+
</div>
|
|
201
|
+
</div>
|
|
202
|
+
</div>
|
|
203
|
+
|
|
204
|
+
<!-- Tool Calling Summary -->
|
|
205
|
+
<div v-if="toolCallingSummary.length" class="sidebar-section">
|
|
206
|
+
<div class="sidebar-section-title">Tool Calls</div>
|
|
207
|
+
<div class="tool-summary-list">
|
|
208
|
+
<div v-for="item in toolCallingSummary" :key="item.name" class="tool-summary-item">
|
|
209
|
+
<div class="tool-summary-bar" :style="{ width: (item.count / toolCallingSummary[0].count * 100) + '%' }"></div>
|
|
210
|
+
<span class="tool-summary-name" :title="item.name">{{ item.name }}</span>
|
|
211
|
+
<span class="tool-summary-count">{{ item.count }}</span>
|
|
212
|
+
</div>
|
|
92
213
|
</div>
|
|
93
214
|
</div>
|
|
94
215
|
|
|
@@ -148,8 +269,8 @@
|
|
|
148
269
|
</div>
|
|
149
270
|
|
|
150
271
|
<div class="content">
|
|
151
|
-
<div class="
|
|
152
|
-
<div class="
|
|
272
|
+
<div class="unified-filter-bar">
|
|
273
|
+
<div class="filter-bar-row">
|
|
153
274
|
<button
|
|
154
275
|
class="sidebar-toggle"
|
|
155
276
|
@click="() => { sidebarCollapsed = !sidebarCollapsed; trackClick && trackClick('SidebarToggled', { state: sidebarCollapsed ? 'open' : 'collapsed', sessionId: sessionId }); }"
|
|
@@ -158,6 +279,20 @@
|
|
|
158
279
|
\u2630
|
|
159
280
|
</button>
|
|
160
281
|
|
|
282
|
+
<div class="filter-bar-search">
|
|
283
|
+
<input
|
|
284
|
+
v-model="searchText"
|
|
285
|
+
type="text"
|
|
286
|
+
placeholder="\u{1F50D} Search events..."
|
|
287
|
+
class="search-input"
|
|
288
|
+
/>
|
|
289
|
+
<span v-if="searchResultCount" class="search-result-count">
|
|
290
|
+
{{ searchResultCount }}
|
|
291
|
+
</span>
|
|
292
|
+
</div>
|
|
293
|
+
|
|
294
|
+
<div class="filter-bar-divider"></div>
|
|
295
|
+
|
|
161
296
|
<!-- Turn dropdown with optgroup -->
|
|
162
297
|
<select
|
|
163
298
|
v-if="turns.length > 0"
|
|
@@ -175,19 +310,69 @@
|
|
|
175
310
|
</option>
|
|
176
311
|
</optgroup>
|
|
177
312
|
</select>
|
|
313
|
+
|
|
314
|
+
<div class="filter-bar-divider"></div>
|
|
315
|
+
|
|
316
|
+
<!-- Subagent selector -->
|
|
317
|
+
<div v-if="subagentList.length > 0" class="subagent-selector">
|
|
318
|
+
<select
|
|
319
|
+
:value="selectedSubagent || ''"
|
|
320
|
+
@change="selectSubagent($event.target.value || null)"
|
|
321
|
+
class="subagent-dropdown"
|
|
322
|
+
>
|
|
323
|
+
<option value="">\u{1F916} All Agents</option>
|
|
324
|
+
<option v-for="sa in subagentList" :key="sa.toolCallId" :value="sa.toolCallId">
|
|
325
|
+
\u{1F916} {{ sa.name }}
|
|
326
|
+
</option>
|
|
327
|
+
</select>
|
|
328
|
+
<span v-if="subagentTokenUsage" class="subagent-usage-badge">
|
|
329
|
+
{{ subagentTokenUsage.eventCount }} events \xB7 {{ formatDuration(subagentTokenUsage.durationMs) }}
|
|
330
|
+
</span>
|
|
331
|
+
</div>
|
|
332
|
+
|
|
333
|
+
<div class="filter-bar-divider"></div>
|
|
334
|
+
|
|
335
|
+
<!-- Event type dropdown -->
|
|
336
|
+
<div class="filter-type-wrapper">
|
|
337
|
+
<button
|
|
338
|
+
class="filter-type-toggle"
|
|
339
|
+
:class="{ active: currentFilter !== 'all' }"
|
|
340
|
+
@click.stop="typeFilterOpen = !typeFilterOpen"
|
|
341
|
+
>
|
|
342
|
+
\u26A1 {{ currentFilter === 'all' ? 'All Types' : currentFilter }} \u25BE
|
|
343
|
+
</button>
|
|
344
|
+
<div v-if="typeFilterOpen" class="filter-type-menu">
|
|
345
|
+
<div class="filter-type-menu-header">Event Types</div>
|
|
346
|
+
<div class="filter-type-menu-options">
|
|
347
|
+
<div
|
|
348
|
+
v-for="filter in filters"
|
|
349
|
+
:key="filter.type"
|
|
350
|
+
:class="['filter-type-menu-item', { active: currentFilter === filter.type }]"
|
|
351
|
+
@click="setFilter(filter.type); typeFilterOpen = false"
|
|
352
|
+
>
|
|
353
|
+
<span class="filter-type-menu-label">{{ filter.type === 'all' ? 'All' : filter.type }}</span>
|
|
354
|
+
<span class="filter-type-menu-count">{{ filter.count }}</span>
|
|
355
|
+
</div>
|
|
356
|
+
</div>
|
|
357
|
+
</div>
|
|
358
|
+
</div>
|
|
178
359
|
</div>
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
<div class="
|
|
182
|
-
<
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
360
|
+
|
|
361
|
+
<!-- Active filter chips -->
|
|
362
|
+
<div v-if="activeFilterCount > 0" class="active-filters-bar">
|
|
363
|
+
<span v-if="currentFilter !== 'all'" class="filter-chip">
|
|
364
|
+
Type: {{ currentFilter }}
|
|
365
|
+
<button @click="setFilter('all')" class="filter-chip-remove" title="Remove filter">\xD7</button>
|
|
366
|
+
</span>
|
|
367
|
+
<span v-if="selectedSubagent" class="filter-chip">
|
|
368
|
+
Agent: {{ subagentList.find(s => s.toolCallId === selectedSubagent)?.name || selectedSubagent }}
|
|
369
|
+
<button @click="selectSubagent(null)" class="filter-chip-remove" title="Remove filter">\xD7</button>
|
|
370
|
+
</span>
|
|
371
|
+
<span v-if="searchText.trim()" class="filter-chip">
|
|
372
|
+
Search: "{{ searchText.length > 20 ? searchText.substring(0, 20) + '\u2026' : searchText }}"
|
|
373
|
+
<button @click="searchText = ''" class="filter-chip-remove" title="Remove filter">\xD7</button>
|
|
190
374
|
</span>
|
|
375
|
+
<button class="clear-all-filters-btn" @click="clearAllFilters">Clear all</button>
|
|
191
376
|
</div>
|
|
192
377
|
</div>
|
|
193
378
|
|
|
@@ -211,6 +396,7 @@
|
|
|
211
396
|
ref="scrollerRef"
|
|
212
397
|
:items="filteredEvents"
|
|
213
398
|
:min-item-size="80"
|
|
399
|
+
:prerender="10"
|
|
214
400
|
key-field="stableId"
|
|
215
401
|
class="scroller"
|
|
216
402
|
>
|
|
@@ -275,8 +461,10 @@
|
|
|
275
461
|
<span
|
|
276
462
|
v-if="getSubagentInfo(item)"
|
|
277
463
|
class="subagent-owner-tag"
|
|
278
|
-
:style="{ color: getSubagentColor(item),
|
|
279
|
-
|
|
464
|
+
:style="{ '--subagent-color': getSubagentColor(item) || '#58a6ff', '--subagent-hover-bg': ((getSubagentColor(item) || '#58a6ff') + '26') }"
|
|
465
|
+
:title="'Filter to ' + getSubagentInfo(item).name"
|
|
466
|
+
@click.stop="selectSubagent(getSubagentInfo(item).toolCallId)"
|
|
467
|
+
>\u{1F916} {{ getSubagentInfo(item).name }}</span>
|
|
280
468
|
<span v-if="metadata.source !== 'vscode'" class="event-timestamp">{{ formatTime(item.timestamp) }}</span>
|
|
281
469
|
</div>
|
|
282
470
|
|
|
@@ -487,4 +675,4 @@
|
|
|
487
675
|
</div>
|
|
488
676
|
|
|
489
677
|
</div>
|
|
490
|
-
`});console.log("Mounting Vue app to #app..."),console.log("App config:",
|
|
678
|
+
`});console.log("Mounting Vue app to #app..."),console.log("App config:",P.config),console.log("Target element:",document.getElementById("app"));try{let p=P.mount("#app");console.log("Vue app mounted successfully!",p?"Instance created":"No instance"),console.log("VM type:",typeof p,"Has exportSession:",typeof p?.exportSession),console.log("VM keys:",p?Object.keys(p).slice(0,10):"NO_VM"),console.log("#app innerHTML length:",document.getElementById("app").innerHTML.length),console.log("#app first 100 chars:",document.getElementById("app").innerHTML.substring(0,100))}catch(p){console.error("Mount failed:",p),console.error("Error stack:",p.stack)}})();})();
|