@qiaolei81/copilot-session-viewer 0.3.8 → 0.4.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/README.md +4 -4
- package/dist/client/assets/index-DQ7OTJ0v.css +2 -0
- package/dist/client/assets/index-maVEluks.js +77 -0
- package/dist/client/assets/sessionStore-CW_Vbbsn.js +1 -0
- package/dist/client/index.html +16 -0
- package/dist/server.min.js +95 -71
- package/dist/server.min.js.map +7 -0
- package/package.json +22 -18
- package/public/js/homepage.min.js +0 -35
- package/public/js/session-detail.min.js +0 -678
- package/public/js/telemetry-browser.min.js +0 -1
- package/public/js/time-analyze.min.js +0 -518
- package/public/vendor/marked.umd.min.js +0 -8
- package/public/vendor/purify.min.js +0 -3
- package/public/vendor/vue-virtual-scroller.css +0 -1
- package/public/vendor/vue-virtual-scroller.min.js +0 -2
- package/public/vendor/vue.global.prod.min.js +0 -19
- package/views/index.ejs +0 -820
- package/views/session-vue.ejs +0 -1570
- package/views/telemetry-snippet.ejs +0 -26
- package/views/time-analyze.ejs +0 -783
|
@@ -1 +0,0 @@
|
|
|
1
|
-
(()=>{(function(){"use strict";let n=window.__DISABLE_TELEMETRY===!0;window.trackClick=function(e,t={}){if(!n)try{if(typeof window.appInsights>"u"||!window.appInsights){console.debug("Application Insights not loaded, skipping telemetry event:",e);return}window.appInsights.trackEvent({name:e,properties:{...t,timestamp:new Date().toISOString(),userAgent:navigator.userAgent,viewportWidth:window.innerWidth,viewportHeight:window.innerHeight}}),console.debug("Telemetry event tracked:",e,t)}catch(i){console.error("Failed to track telemetry event:",e,i)}},window.trackPageView=function(e,t={}){if(!n)try{if(typeof window.appInsights>"u"||!window.appInsights){console.debug("Application Insights not loaded, skipping page view:",e);return}window.appInsights.trackPageView({name:e,properties:{...t,timestamp:new Date().toISOString()}}),console.debug("Page view tracked:",e,t)}catch(i){console.error("Failed to track page view:",e,i)}},window.trackMetric=function(e,t,i={}){if(!n)try{if(typeof window.appInsights>"u"||!window.appInsights){console.debug("Application Insights not loaded, skipping metric:",e);return}window.appInsights.trackMetric({name:e,average:t,properties:{...i,timestamp:new Date().toISOString()}}),console.debug("Metric tracked:",e,t,i)}catch(o){console.error("Failed to track metric:",e,o)}},console.log(n?"\u{1F4CA} Browser telemetry disabled (window.__DISABLE_TELEMETRY = true)":"\u{1F4CA} Browser telemetry wrapper initialized")})();})();
|
|
@@ -1,518 +0,0 @@
|
|
|
1
|
-
(()=>{var{createApp:st,ref:h,computed:v,onMounted:Ce,onUnmounted:at}=Vue,Me=st({setup(){let C=h(window.__PAGE_DATA.sessionId),fe=h(window.__PAGE_DATA.metadata),b=h([]),E=h(!0),Z=h(null),Ae=h("timeline"),I=h("timestamp"),M=h("asc"),L=h(null),P=h(null),X=h(!1),K=h(null),J=h(null),Ee=h(!1),$=h("\u{1F4CA} Copy as Mermaid Gantt"),ve=e=>e?typeof e=="string"?e:Array.isArray(e)?e.map(t=>t.text||t.content||"").join(" "):typeof e=="object"&&e.text?e.text:String(e):"",U=h(null),he=h(""),Ie=e=>{let t=e.currentTarget,a=t.querySelector(".gantt-bar-area");if(!a)return;let s=a.getBoundingClientRect(),n=t.getBoundingClientRect(),o=s.left-n.left,i=o+s.width,d=e.clientX-n.left;if(d>=o&&d<=i){U.value=d;let l=(d-o)/s.width,r=A.value+l*_.value,c=new Date(r),m=String(c.getHours()).padStart(2,"0"),p=String(c.getMinutes()).padStart(2,"0"),u=String(c.getSeconds()).padStart(2,"0");he.value=m+":"+p+":"+u}else U.value=null},De=()=>{U.value=null},_e=async()=>{let e=ie.value;if(!e.length)return;let t=l=>l?new Date(l).getTime():0,a=l=>(l||"").replace(/[`\n\r]/g,"").replace(/[:;#]/g,"-").replace(/\s+/g," ").trim().substring(0,100),s=l=>l?typeof l=="string"?l:Array.isArray(l)?l.map(r=>r.text||r.content||"").join(" "):typeof l=="object"&&l.text?l.text:String(l):"",n={},o=l=>{let r=l.replace(/[^a-zA-Z0-9]/g,"_").substring(0,30);return n[r]?(n[r]++,r+"_"+n[r]):(n[r]=1,r)},i=[];i.push("```mermaid"),i.push("gantt"),i.push(" title Session Timeline \u2013 "+a(C.value)),i.push(" dateFormat x"),i.push(" axisFormat %H:%M:%S"),i.push("");for(let l of e)if(l.rowType==="user-req"){let r=a(s(l.message)||"No message").substring(0,40),c="UserReq "+l.userReqNumber+" \u2013 "+r+" ("+W(l.duration)+")",m=o("userreq_"+l.userReqNumber),p=t(l.startTime),u=t(l.endTime);i.push(" "+c+" :milestone, "+m+", "+p+", "+u)}else if(l.rowType==="subagent"){let r=t(l.startTime),c=t(l.endTime),m=(l.toolCalls??0)+" tools",p=a(l.name)+" \u2013 "+W(l.duration)+" ("+m+")",u=o(l.name),g=l.status==="failed"?"crit, ":l.status==="incomplete"?"active, ":"";i.push(" "+p+" :"+g+u+", "+r+", "+c)}else if(l.rowType==="main-agent"){let r=t(l.startTime),c=t(l.endTime),m=a(l.summary||"idle"),p="Main Agent \u2013 "+W(l.duration)+" ("+m+")",u=o("main_agent");i.push(" "+p+" :"+u+", "+r+", "+c)}i.push("```"),i.push("");let d=i.join(`
|
|
2
|
-
`);try{await navigator.clipboard.writeText(d),$.value="\u2705 Copied!"}catch{let r=document.createElement("textarea");r.value=d,r.style.position="fixed",r.style.opacity="0",document.body.appendChild(r),r.select(),document.execCommand("copy"),document.body.removeChild(r),$.value="\u2705 Copied!"}setTimeout(()=>{$.value="\u{1F4CA} Copy as Mermaid Gantt"},2e3)},D={"tool.execution_start":{color:"#d29922",shape:"diamond",label:"Tool Start"},"tool.execution_complete":{color:"#e3b341",shape:"diamond",label:"Tool Complete"},"assistant.message":{color:"#8b949e",shape:"circle",label:"Message"},"user.message":{color:"#79c0ff",shape:"square",label:"User Message"},"session.start":{color:"#56d364",shape:"square",label:"Session Start"},"session.resume":{color:"#56d364",shape:"square",label:"Session Resume"},"session.error":{color:"#f85149",shape:"triangle",label:"Error"},"session.truncation":{color:"#f0883e",shape:"triangle",label:"Truncation"},"session.compaction_start":{color:"#a371f7",shape:"square",label:"Compaction Start"},"session.compaction_complete":{color:"#bc8cff",shape:"square",label:"Compaction End"},"session.model_change":{color:"#f778ba",shape:"square",label:"Model Change"},abort:{color:"#ff7b72",shape:"triangle",label:"Abort"}},V=new Set(Object.keys(D)),W=e=>{if(e==null||e<0)return"\u2014";if(e<1e3)return Math.round(e)+"ms";let t=e/1e3;if(t<60){let o=Math.round(t*10)/10;return(o%1===0?Math.round(o):o.toFixed(1))+"s"}let a=Math.floor(t/60),s=Math.floor(t%60);return a<60?a+"m "+s+"s":Math.floor(a/60)+"h "+a%60+"m"},qe=e=>{if(!e)return"";let t=new Date(e);return String(t.getHours()).padStart(2,"0")+":"+String(t.getMinutes()).padStart(2,"0")+":"+String(t.getSeconds()).padStart(2,"0")},Ne=e=>e?new Date(e).toLocaleString():"",A=v(()=>{if(!b.value.length)return null;for(let e of b.value){let t=e.timestamp||e.snapshot?.timestamp;if(t)return new Date(t).getTime()}return null}),G=v(()=>{if(!b.value.length)return null;for(let e=b.value.length-1;e>=0;e--){let t=b.value[e],a=t.timestamp||t.snapshot?.timestamp;if(a)return new Date(a).getTime()}return null}),_=v(()=>!A.value||!G.value?0:G.value-A.value),S=v(()=>[...b.value].sort((e,t)=>{let a=e.timestamp?new Date(e.timestamp).getTime():0,s=t.timestamp?new Date(t.timestamp).getTime():0;return a!==s?a-s:(e._fileIndex??0)-(t._fileIndex??0)})),z=v(()=>{let e=S.value,t=new Set;for(let o of e)if(o.type==="subagent.started"){let i=o.data?.toolCallId;i&&t.add(i)}let a=new Map;for(let o of e)o.id&&a.set(o.id,o);let s=new Map,n=new Map;for(let o of e){if(o.type!=="tool.execution_start")continue;let i=o.parentId,d=0;for(;i&&d<10;){let l=a.get(i);if(!l)break;if(l.type==="assistant.message"){let r=l.data?.parentToolCallId;if(r&&t.has(r)){s.set(o.id,r);let c=o.data?.toolCallId;c&&n.set(c,r)}break}i=l.parentId,d++}}for(let o of e){if(o.type!=="tool.execution_complete")continue;let i=o.data?.toolCallId;i&&n.has(i)&&s.set(o.id,n.get(i))}return s}),B=v(()=>{let e=S.value,t=[],a=[];for(let n of e)if(n.type==="subagent.started")a.push(n);else if(n.type==="subagent.completed"||n.type==="subagent.failed"){let o=n.data?.toolCallId,i=-1;if(o){for(let f=a.length-1;f>=0;f--)if(a[f].data?.toolCallId===o){i=f;break}}i<0&&a.length>0&&(i=a.length-1);let d=i>=0?a.splice(i,1)[0]:null,l=d?.data?.agentDisplayName||d?.data?.agentName||"SubAgent",r=d?new Date(d.timestamp).getTime():null,c=new Date(n.timestamp).getTime(),m=r?c-r:null,p=d?.data?.toolCallId,u=0,g=[];if(d)for(let f of e){f.type==="tool.execution_start"&&p&&z.value.get(f.id)===p&&u++;let w=new Date(f.timestamp).getTime();w>=r&&w<=c&&V.has(f.type)&&(f.type!=="tool.execution_start"&&f.type!=="tool.execution_complete"?g.push({type:f.type,timestamp:w,data:f.data}):p&&z.value.get(f.id)===p&&g.push({type:f.type,timestamp:w,data:f.data}))}let T=Q(g,r,m);t.push({name:l,status:n.type==="subagent.completed"?"completed":"failed",startTime:d?.timestamp||null,endTime:n.timestamp,duration:m,toolCalls:u,innerEventMarkers:T})}let s=e.length>0?new Date(e[e.length-1].timestamp).getTime():Date.now();for(let n of a){let o=n.data?.agentDisplayName||n.data?.agentName||"SubAgent",i=new Date(n.timestamp).getTime(),d=s-i,l=n.data?.toolCallId,r=0,c=[];for(let p of e){p.type==="tool.execution_start"&&l&&z.value.get(p.id)===l&&r++;let u=new Date(p.timestamp).getTime();u>=i&&u<=s&&V.has(p.type)&&(p.type!=="tool.execution_start"&&p.type!=="tool.execution_complete"?c.push({type:p.type,timestamp:u,data:p.data}):l&&z.value.get(p.id)===l&&c.push({type:p.type,timestamp:u,data:p.data}))}let m=Q(c,i,d);t.push({name:o,status:"incomplete",startTime:n.timestamp,endTime:e[e.length-1]?.timestamp||n.timestamp,duration:d,toolCalls:r,innerEventMarkers:m})}return t.sort((n,o)=>{let i=n.startTime?new Date(n.startTime).getTime():0,d=o.startTime?new Date(o.startTime).getTime():0;return i-d})}),Re=v(()=>Math.max(...B.value.map(e=>e.duration||0),1)),Le=v(()=>{let e=B.value,t=e.filter(r=>r.status==="completed").length,a=e.filter(r=>r.status==="failed").length,s=e.filter(r=>r.status==="incomplete").length,n=e.length?(t/e.length*100).toFixed(0):100,o=e.filter(r=>r.startTime&&r.endTime).map(r=>[new Date(r.startTime).getTime(),new Date(r.endTime).getTime()]).sort((r,c)=>r[0]-c[0]),i=[];for(let[r,c]of o)!i.length||r>=i[i.length-1].e?i.push({s:r,e:c}):c>i[i.length-1].e&&(i[i.length-1].e=c);let d=i.reduce((r,c)=>r+(c.e-c.s),0),l=e.reduce((r,c)=>r+(c.toolCalls||0),0);return{completed:t,failed:a,incomplete:s,totalTime:d,totalTools:l,successRate:n}}),Q=(e,t,a)=>{if(!e.length||!a)return[];let s=new Set(["session.start","session.resume","session.error","session.truncation","session.compaction_start","session.compaction_complete","session.model_change","abort","user.message"]),n=[],o=[];for(let u of e)s.has(u.type)?n.push(u):o.push(u);let i=n.map(u=>{let g=(u.timestamp-t)/a*100,T=D[u.type]||{};return{type:u.type,position:Math.max(0,Math.min(100,g)),color:T.color||"#8b949e",shape:T.shape||"circle",label:T.label||u.type,timestamp:u.timestamp,toolName:u.data?.toolName||null}}),d=300*1e3,l=Math.max(d,a/20),r=new Map;for(let u of o){let g=Math.floor((u.timestamp-t)/l);r.has(g)||r.set(g,[]),r.get(g).push(u)}let c=u=>{let g=Math.round(210+38*u),T=Math.round(153+-72*u),f=Math.round(34+39*u);return"rgb("+g+","+T+","+f+")"},m=u=>u.type==="tool.execution_complete"&&(u.data?.isError||!!u.data?.error),p=[];for(let[u,g]of r){let f=(t+(u+.5)*l-t)/a*100,w=Math.max(0,Math.min(100,f)),q=g.filter(m).length,pe=g.filter(y=>y.type==="tool.execution_complete").length,k=pe>0?q/pe:0;if(g.length===1){let y=g[0],N=D[y.type]||{},ge=m(y)?"#f85149":N.color||"#8b949e";p.push({type:y.type,position:w,color:ge,shape:N.shape||"circle",label:m(y)?(N.label||y.type)+" (error)":N.label||y.type,timestamp:y.timestamp,toolName:y.data?.toolName||null})}else{let y={};g.forEach(R=>{let j=(D[R.type]||{}).label||R.type;y[j]=(y[j]||0)+1}),q>0&&(y.Errors=q);let N=Object.entries(y).map(([R,O])=>O+" "+R),ge=k>0?c(k):(()=>{let R=g.reduce((O,j)=>{let Se=g.filter(tt=>tt.type===j.type).length;return Se>O.cnt?{type:j.type,cnt:Se}:O},{type:g[0].type,cnt:0}).type;return(D[R]||{}).color||"#8b949e"})();p.push({type:"cluster",position:w,color:ge,shape:"cluster",label:N.join(", "),count:g.length,items:g})}}return[...i,...p].sort((u,g)=>u.position-g.position)},F=(e,t,a)=>{let s=a-t,n=[],o={},i=0;for(let c of e){let m=new Date(c.timestamp).getTime();if(m>=t&&m<=a)if(c.type.startsWith("tool."))c.id&&z.value.has(c.id)||(V.has(c.type)&&n.push({type:c.type,timestamp:m,data:c.data}),c.type==="tool.execution_start"&&i++,o.tool=(o.tool||0)+1);else{V.has(c.type)&&n.push({type:c.type,timestamp:m,data:c.data});let p="other";c.type.startsWith("assistant.")?p="message":c.type.startsWith("user.")?p="user":c.type.startsWith("session.")&&(p="session"),o[p]=(o[p]||0)+1}}let d=[];i&&d.push(i+" tool"+(i>1?"s":"")),o.message&&d.push(o.message+" message"+(o.message>1?"s":"")),o.user&&d.push(o.user+" user msg"),o.session&&d.push(o.session+" session event"+(o.session>1?"s":"")),o.other&&d.push(o.other+" other");let l=d.length?d.join(", "):"idle",r=Q(n,t,s);return{itemType:"agent-op",name:"Main Agent",summary:l,toolCalls:i,startTime:new Date(t).toISOString(),endTime:new Date(a).toISOString(),duration:s,eventCounts:o,innerEventMarkers:r}},ee=v(()=>{let e=B.value;if(!e.length)return[];let t=S.value,a=[];for(let s=0;s<e.length;s++){let n=e[s];if(s===0&&n.startTime){let l=A.value,r=new Date(n.startTime).getTime();r-l>500&&a.push(F(t,l,r))}a.push({...n,itemType:"subagent"});let o=e[s+1],i=new Date(n.endTime).getTime(),d=o?new Date(o.startTime).getTime():G.value;d-i>500&&a.push(F(t,i,d))}return a}),te=v(()=>{try{let e=S.value,t=e.filter(s=>s.type==="assistant.message"),a=e.filter(s=>s.type==="user.message");return t.map((s,n)=>{let o=s.timestamp;if(!o)return console.warn("[turnAnalysis] Message without timestamp:",s),null;let i=new Date(o).getTime();if(isNaN(i))return console.warn("[turnAnalysis] Invalid timestamp:",o,s),null;let d=t[n+1],r=(d?new Date(d.timestamp).getTime():G.value||i)-i,c=e.indexOf(s),m=e.slice(0,c).reverse().find(f=>f.type==="user.message"),p=m?a.indexOf(m)+1:0,u="",g=s.data?.message&&s.data.message.trim()!=="";g?u=ve(s.data.message):s.data?.tools&&s.data.tools.length>0?u=`Tool calls: ${s.data.tools.map(w=>w.name||"unknown").join(", ")}`:u="(empty assistant message)";let T=s.data?.tools?.length||0;return{turnId:s.id??`msg-${n}`,userReqNumber:p,message:ve(m?.data?.message||m?.data?.content||m?.data?.transformedContent||""),displayText:u,hasText:g,startTime:s.timestamp,endTime:d?.timestamp||b.value[b.value.length-1]?.timestamp,duration:r,toolCalls:T}}).filter(s=>s!==null)}catch(e){return console.error("[turnAnalysis] Error:",e),Z.value="Error analyzing turns: "+e.message,[]}}),Pe=v(()=>Math.max(...te.value.map(e=>e.duration||0),1)),ye=v(()=>{let e=new Map;for(let t of te.value){let a=t.userReqNumber||0;e.has(a)||e.set(a,{userReqNumber:a,message:t.message,turns:[]}),e.get(a).turns.push(t)}return Array.from(e.values()).sort((t,a)=>t.userReqNumber-a.userReqNumber)}),x=v(()=>{let e=S.value,t=new Map;for(let s of e)if(s.type==="tool.execution_start"){let n=s.data?.toolCallId;n&&t.set(n,{start:s})}else if(s.type==="tool.execution_complete"){let n=s.data?.toolCallId;n&&t.has(n)&&(t.get(n).complete=s)}let a=[];return t.forEach((s,n)=>{let o=new Date(s.start.timestamp).getTime(),i=s.complete?new Date(s.complete.timestamp).getTime():null,d=i?i-o:null,l=s.start.data?.toolName||s.start.data?.tool||"unknown",r=s.start.data?.arguments||{},c=s.complete?.data?.isError||!!s.complete?.data?.error,m="";l==="Bash"||l==="bash"||l==="exec"?m=r.command||r.description||"":["Read","read","Write","write","Edit","edit"].includes(l)?m=r.file_path||r.path||"":["Glob","glob"].includes(l)||["Grep","grep"].includes(l)?m=r.pattern||"":["Task","task"].includes(l)?m=r.description||r.prompt?.substring(0,80)||"":m=r.description||r.command||r.file_path||r.path||r.query||r.url||"",m.length>120&&(m=m.substring(0,120)+"..."),a.push({toolId:n,toolName:l,description:m,startTime:s.start.timestamp,endTime:s.complete?.timestamp||null,duration:d,isError:c,isRunning:!s.complete})}),a}),Ge=v(()=>{let e=[...x.value];return e.sort((t,a)=>{if(I.value==="duration")return M.value==="asc"?(t.duration||0)-(a.duration||0):(a.duration||0)-(t.duration||0);if(I.value==="toolName"){let o=(t.toolName||"").localeCompare(a.toolName||"");return M.value==="asc"?o:-o}let s=new Date(t.startTime).getTime(),n=new Date(a.startTime).getTime();return M.value==="asc"?s-n:n-s}),e}),ze=v(()=>Math.max(...x.value.map(e=>e.duration||0),1)),be=v(()=>{let e=["view","read","write","edit","create","glob","grep","notebookedit"],t={copilot_readfile:"read",copilot_createfile:"write",copilot_createdirectory:"write",copilot_findfiles:"search",copilot_findtextinfiles:"search",copilot_listdirectory:"read",textedit:"edit",copilot_replacestring:"edit",copilot_multireplacestring:"edit"},a=[];for(let s of b.value)if(s.type==="tool.execution_start"){let n=s.data?.toolName?.toLowerCase()||"",o=s.data?.arguments||{},i=o.path||o.file||o.directory||o.pattern||"";if(e.includes(n)){if(i){let d="other";n==="view"||n==="read"?d="read":n==="write"||n==="notebookedit"||n==="create"?d="write":n==="edit"?d="edit":(n==="glob"||n==="grep")&&(d="search"),a.push({toolName:s.data?.toolName||n,opType:d,filePath:i,timestamp:s.timestamp,startTime:s.timestamp})}}else if(t[n]){let d=t[n];a.push({toolName:s.data?.toolName||n,opType:d,filePath:i||"(implicit)",timestamp:s.timestamp,startTime:s.timestamp})}}return a.sort((s,n)=>new Date(s.startTime)-new Date(n.startTime))}),Be=v(()=>{let e=be.value;return{uniqueCount:new Set(e.map(a=>a.filePath)).size,totalOps:e.length,reads:e.filter(a=>a.opType==="read").length,writes:e.filter(a=>a.opType==="write").length,edits:e.filter(a=>a.opType==="edit").length,searches:e.filter(a=>a.opType==="search").length}}),Te=v(()=>{let e={};return x.value.forEach(t=>{let a=(t.toolName||"unknown").toLowerCase(),s;["bash","exec"].includes(a)?s="Bash/Exec":["read"].includes(a)?s="Read":["write"].includes(a)?s="Write":["edit"].includes(a)?s="Edit":["glob"].includes(a)?s="Glob":["grep"].includes(a)?s="Grep":["task"].includes(a)?s="Task (SubAgent)":["web_search","websearch"].includes(a)?s="Web Search":["web_fetch","webfetch"].includes(a)?s="Web Fetch":s=t.toolName||"Other",e[s]||(e[s]={category:s,totalTime:0,count:0,errors:0}),e[s].totalTime+=t.duration||0,e[s].count++,t.isError&&e[s].errors++}),Object.values(e).sort((t,a)=>a.count-t.count)}),Fe=v(()=>Math.max(...Te.value.map(e=>e.totalTime),1)),se=v(()=>{let e=x.value.filter(n=>n.duration&&n.startTime&&n.endTime).map(n=>({start:new Date(n.startTime).getTime(),end:new Date(n.endTime).getTime()})).sort((n,o)=>n.start-o.start);if(!e.length)return 0;let t=0,a=e[0].start,s=e[0].end;for(let n=1;n<e.length;n++)e[n].start<=s?s=Math.max(s,e[n].end):(t+=s-a,a=e[n].start,s=e[n].end);return t+=s-a,t}),nt=v(()=>{let e=0,t={};for(let a of b.value)if(a.type==="tool.execution_complete"&&a.data?.toolTelemetry?.metrics){let s=a.data.toolTelemetry.metrics.resultForLlmLength||0;e+=s;let n=a.data.toolName||"unknown";t[n]||(t[n]=0),t[n]+=s}return{total:e,byCategory:t}}),ae=v(()=>{let e=S.value,t=[];for(let a=0;a<e.length-1;a++){let s=e[a],n=e[a+1],o=new Date(s.timestamp).getTime(),d=new Date(n.timestamp).getTime()-o;if(d<100)continue;let l=null,r="";s.type==="user.message"&&n.type==="assistant.turn_start"?(l="input-consumption",r=`LLM reading user input (${(s.data?.message||"").length} chars)`):s.type==="assistant.turn_start"&&n.type==="assistant.message"?(l="llm-generation",r=`LLM generating response (${(n.data?.content||"").length} chars output)`):s.type==="assistant.turn_start"&&n.type==="tool.execution_start"?(l="llm-generation",r=`LLM deciding to call ${n.data?.toolName||"unknown"}`):s.type==="assistant.message"&&n.type==="assistant.turn_start"?(l="turn-gap",r="Gap between assistant response and next turn"):s.type==="tool.execution_complete"&&d>500?(l="post-tool",r=`Processing ${s.data?.toolName||"unknown"} result`):d>5e3&&(l="idle",r=`${s.type} \u2192 ${n.type}`),l&&t.push({type:l,description:r,startTime:s.timestamp,endTime:n.timestamp,duration:d,fromEvent:s.type,toEvent:n.type,fromData:s.data,toData:n.data})}return t.sort((a,s)=>(s.duration||0)-(a.duration||0))}),Oe=v(()=>Math.max(...ae.value.map(e=>e.duration||0),1)),je=v(()=>{let e={"input-consumption":{count:0,total:0,avg:0},"llm-generation":{count:0,total:0,avg:0},"post-tool":{count:0,total:0,avg:0},"turn-gap":{count:0,total:0,avg:0},idle:{count:0,total:0,avg:0}};return ae.value.forEach(t=>{e[t.type]&&(e[t.type].count++,e[t.type].total+=t.duration)}),Object.keys(e).forEach(t=>{e[t].count>0&&(e[t].avg=e[t].total/e[t].count)}),e}),$e=v(()=>{let e=x.value.length;if(e===0)return 100;let t=x.value.filter(a=>a.isError).length;return((e-t)/e*100).toFixed(1)}),Ue=v(()=>x.value.filter(e=>e.isError).length),Ve=v(()=>{let e=S.value,t=0;for(let o=0;o<e.length-1;o++){let i=e[o],d=e[o+1];if(d.type==="user.message"&&i.type!=="user.message"){let l=new Date(d.timestamp).getTime()-new Date(i.timestamp).getTime();l>1e3&&(t+=l)}}let a=_.value||0,s=Math.max(a-t,0),n=Math.max(s-se.value,0);return{userThinkingTime:t,agentWorkingTime:s,llmTime:n,userThinkingPct:a>0?(t/a*100).toFixed(0):0,agentWorkingPct:a>0?(s/a*100).toFixed(0):0,llmPct:a>0?(n/a*100).toFixed(0):0,toolPct:a>0?(se.value/a*100).toFixed(0):0}}),ne=v(()=>x.value.length),We=v(()=>ne.value?x.value.reduce((t,a)=>t+(a.duration||0),0)/ne.value:0),He=v(()=>x.value.length?x.value.reduce((e,t)=>(t.duration||0)>(e.duration||0)?t:e):null),oe=v(()=>{let e=S.value;if(e.some(n=>n.data?.source==="vscode"))return!0;let a=e.some(n=>n.type==="assistant.message"&&n.data?.subAgentName),s=e.some(n=>n.type==="subagent.started"||n.type==="subagent.completed"||n.type==="subagent.failed");return a&&!s}),we=v(()=>{if(!oe.value)return[];let e=S.value,t=new Map;for(let a=0;a<e.length;a++){let s=e[a];if(s.type==="assistant.message"&&s.data?.subAgentName){let n=s.data.subAgentId||s.data.subAgentName;t.has(n)||t.set(n,{name:s.data.subAgentName,events:[],toolCount:0,firstIndex:a,status:"completed",subAgentId:s.data.subAgentId});let o=t.get(n);o.events.push(s),s.data.tools&&Array.isArray(s.data.tools)&&(o.toolCount+=s.data.tools.length),(s.data.error||s.data.status==="error")&&(o.status="failed")}}return Array.from(t.values()).sort((a,s)=>a.firstIndex-s.firstIndex)}),ie=v(()=>{let e=[],t=ye.value,a=B.value,s=S.value;if(oe.value){let n=we.value,o=[];for(let i=0;i<s.length;i++)s[i].type==="user.message"&&o.push({event:s[i],sortedIndex:i});if(o.length>0&&n.length>0)for(let i=0;i<o.length;i++){let{event:d,sortedIndex:l}=o[i],r=o[i+1]?o[i+1].sortedIndex:1/0,c=n.filter(u=>u.firstIndex>=l&&u.firstIndex<r),m=c.reduce((u,g)=>u+g.toolCount,0),p=d.data?.message||d.data?.content||"";e.push({rowType:"user-req",userReqNumber:i+1,message:typeof p=="string"?p.substring(0,120):String(p).substring(0,120),toolCount:m,sequenceIndex:l,isSequenceEstimated:!0,duration:m});for(let u of c)e.push({rowType:"subagent",itemType:"subagent",name:u.name,status:u.status,toolCount:u.toolCount,sequenceIndex:u.firstIndex,isSequenceEstimated:!0,duration:u.toolCount,indented:!0})}else if(n.length>0)for(let i of n)e.push({rowType:"subagent",itemType:"subagent",name:i.name,status:i.status,toolCount:i.toolCount,sequenceIndex:i.firstIndex,isSequenceEstimated:!0,duration:i.toolCount});return e}if(t.length)for(let n=0;n<t.length;n++){let o=t[n],i=o.turns;if(!i.length)continue;let d=new Date(i[0].startTime).getTime(),l=new Date(i[i.length-1].endTime).getTime();e.push({rowType:"user-req",userReqNumber:o.userReqNumber,message:o.message,startTime:i[0].startTime,endTime:i[i.length-1].endTime,duration:l-d});let r=a.filter(c=>{if(!c.startTime)return!1;let m=new Date(c.startTime).getTime();return m>=d&&m<=l});if(r.length)for(let c=0;c<r.length;c++){let m=r[c],p=c===0?d:new Date(r[c-1].endTime).getTime(),u=new Date(m.startTime).getTime();if(u-p>500){let g=F(s,p,u);g.rowType="main-agent",e.push(g)}if(e.push({...m,rowType:"subagent",itemType:"subagent"}),c===r.length-1){let g=new Date(m.endTime).getTime(),T=l;if(T-g>500){let f=F(s,g,T);f.rowType="main-agent",e.push(f)}}}else if(l-d>0){let c=F(s,d,l);c.rowType="main-agent",e.push(c)}}else if(ee.value.length)for(let n of ee.value)e.push({...n,rowType:n.itemType==="agent-op"?"main-agent":"subagent"});return e}),Ye=(e,t)=>{if(!A.value||!_.value||!e)return{left:"0%",width:"0%"};let a=new Date(e).getTime(),s=t?new Date(t).getTime():a+1e3,n=(a-A.value)/_.value*100,o=Math.max((s-a)/_.value*100,.5);return{left:n+"%",width:Math.min(o,100-n)+"%"}},re=e=>{let t=ie.value;if(t.length===0)return{left:"0%",width:"0%"};if(e.rowType==="user-req"){let c=t.findIndex(f=>f===e);if(c===-1)return{left:"0%",width:"0%"};let m=[];for(let f=c+1;f<t.length&&t[f].rowType!=="user-req";f++)t[f].rowType==="subagent"&&m.push(t[f]);if(m.length===0){let w=t.filter(k=>k.rowType!=="user-req").reduce((k,y)=>k+(y.toolCount||0),0);if(w===0)return{left:"0%",width:"0%"};let q=0;for(let k=0;k<c;k++)t[k].rowType==="subagent"&&(q+=t[k].toolCount||0);return{left:q/w*100+"%",width:Math.max(1,1/w*100)+"%"}}let p=re(m[0]),u=re(m[m.length-1]),g=parseFloat(p.left),T=parseFloat(u.left)+parseFloat(u.width);return{left:g+"%",width:T-g+"%"}}if(t.findIndex(c=>c===e)===-1)return{left:"0%",width:"0%"};let s=t.filter(c=>c.rowType!=="user-req"),n=s.reduce((c,m)=>c+(m.toolCount||0),0);if(n===0)return{left:"0%",width:"0%"};let o=s.findIndex(c=>c===e);if(o===-1)return{left:"0%",width:"0%"};let i=0;for(let c=0;c<o;c++)i+=s[c].toolCount||0;let d=i/n*100,l=e.toolCount||0,r=Math.max(l/n*100,2);return{left:d+"%",width:Math.min(r,100-d)+"%"}},Ze=e=>{I.value===e?M.value=M.value==="asc"?"desc":"asc":(I.value=e,M.value=e==="duration"?"desc":"asc")},Xe=e=>I.value!==e?"\u2195":M.value==="asc"?"\u2191":"\u2193",Ke=e=>{let t=(e||"").toLowerCase();return["bash","exec"].includes(t)?"badge-bash":t==="read"?"badge-read":t==="write"||t==="notebookedit"?"badge-write":t==="edit"?"badge-edit":t==="glob"||t==="grep"?"badge-search":t==="task"?"badge-subagent":"badge-other"},Je=e=>({read:"badge-read",write:"badge-write",edit:"badge-edit",create:"badge-create",search:"badge-search"})[e]||"badge-other";Ce(async()=>{try{let e=await fetch("/api/sessions/"+C.value+"/events");if(!e.ok)throw new Error("Failed to load events: "+e.statusText);let t=await e.json();console.log("[TIME-ANALYZE] Loaded events:",t.length),console.log("[TIME-ANALYZE] Event types:",[...new Set(t.map(a=>a.type))]),console.log("[TIME-ANALYZE] Turn starts:",t.filter(a=>a.type==="assistant.turn_start").length),console.log("[TIME-ANALYZE] User messages:",t.filter(a=>a.type==="user.message").length),b.value=t.sort((a,s)=>{let n=a.timestamp?new Date(a.timestamp).getTime():0,o=s.timestamp?new Date(s.timestamp).getTime():0;return n!==o?n-o:(a._fileIndex??0)-(s._fileIndex??0)}),console.log("[TIME-ANALYZE] Events set, length:",b.value.length)}catch(e){console.error("[TIME-ANALYZE] Error loading events:",e),Z.value=e.message}finally{E.value=!1}});let le=h("not_started"),ce=h(null),H=h(null),de=h(0),Y=null,Qe=v(()=>L.value?marked.parse(L.value):""),xe=async()=>{try{let t=await(await fetch(`/session/${C.value}/insight`)).json();le.value=t.status,t.status==="completed"?(L.value=t.report,P.value=null,J.value=t.generatedAt,me()):t.status==="generating"?(P.value=t.log||null,H.value=t.startedAt,ce.value=t.lastUpdate,de.value=t.ageMs,ue(),Vue.nextTick(()=>{let a=document.getElementById("insight-log");a&&(a.scrollTop=a.scrollHeight)})):t.status==="timeout"&&(P.value=t.log||null,H.value=t.startedAt,ce.value=t.lastUpdate,de.value=t.ageMs,ue())}catch(e){console.error("Failed to check insight:",e)}},ue=()=>{me(),Y=setInterval(xe,2e3)},me=()=>{Y&&(clearInterval(Y),Y=null)},ke=async(e=!1)=>{X.value=!0,K.value=null,P.value=null;try{let t=await fetch(`/session/${C.value}/insight`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({force:e})});if(!t.ok){let s=await t.json();throw new Error(s.error||"Failed to generate insight")}let a=await t.json();le.value=a.status,a.status==="generating"?(H.value=a.startedAt,ue()):a.status==="completed"&&(L.value=a.report,J.value=a.generatedAt)}catch(t){K.value=t.message}finally{X.value=!1}},et=async()=>{await ke(!0)};return Ce(async()=>{await xe()}),at(()=>{me()}),{sessionId:C,metadata:fe,events:b,loading:E,error:Z,activeTab:Ae,sortField:I,sortDir:M,insightReport:L,insightLog:P,insightLoading:X,insightError:K,insightGeneratedAt:J,insightStatus:le,insightLastUpdate:ce,insightStartedAt:H,insightAgeMs:de,renderedInsight:Qe,generateInsight:ke,regenerateInsight:et,formatDuration:W,formatTime:qe,formatDateTime:Ne,sessionStart:A,sessionEnd:G,totalDuration:_,subagentAnalysis:B,maxSubagentDuration:Re,subagentTimelineItems:ee,subagentStats:Le,EVENT_MARKER_CATEGORIES:D,showMarkerLegend:Ee,copyLabel:$,copyTimelineMarkdown:_e,ganttCrosshairX:U,ganttCrosshairTime:he,onGanttMouseMove:Ie,onGanttMouseLeave:De,turnAnalysis:te,maxTurnDuration:Pe,groupedTurns:ye,unifiedTimelineItems:ie,toolAnalysis:x,sortedToolAnalysis:Ge,maxToolDuration:ze,fileOperations:be,fileStats:Be,toolTimeByCategory:Te,maxCategoryTime:Fe,totalToolTime:se,totalToolCount:ne,avgToolDuration:We,longestTool:He,successRate:$e,errorCount:Ue,timeBreakdown:Ve,gapAnalysis:ae,maxGapDuration:Oe,gapStats:je,ganttPosition:Ye,ganttSequencePosition:re,toggleSort:Ze,sortIcon:Xe,getToolBadgeClass:Ke,getOpBadgeClass:Je,isVSCodeSession:oe,vsCodeSubagents:we}},template:`
|
|
3
|
-
<div v-if="loading" class="empty-state" style="padding: 60px;">
|
|
4
|
-
\u23F3 Loading events...
|
|
5
|
-
</div>
|
|
6
|
-
|
|
7
|
-
<div v-else-if="error" class="empty-state" style="padding: 60px; color: #f85149;">
|
|
8
|
-
\u274C {{ error }}
|
|
9
|
-
</div>
|
|
10
|
-
|
|
11
|
-
<div v-else>
|
|
12
|
-
<!-- Summary Cards -->
|
|
13
|
-
<div class="summary-grid">
|
|
14
|
-
<div class="summary-card" title="Wall-clock time from first event to last event in this session.">
|
|
15
|
-
<div class="summary-card-label">Total Duration</div>
|
|
16
|
-
<div class="summary-card-value">{{ formatDuration(totalDuration) }}</div>
|
|
17
|
-
<div class="summary-card-sub" v-if="sessionStart" style="margin-top: 2px; font-size: 10px; opacity: 0.7;">
|
|
18
|
-
{{ formatDateTime(sessionStart) }} \u2192 {{ formatDateTime(sessionEnd) }}
|
|
19
|
-
</div>
|
|
20
|
-
</div>
|
|
21
|
-
<div class="summary-card" title="Number of user messages that triggered agent work. Each request may involve multiple LLM turns.">
|
|
22
|
-
<div class="summary-card-label">User Requests</div>
|
|
23
|
-
<div class="summary-card-value">{{ groupedTurns.length }}</div>
|
|
24
|
-
<div class="summary-card-sub">{{ turnAnalysis.length }} turn{{ turnAnalysis.length !== 1 ? 's' : '' }}</div>
|
|
25
|
-
</div>
|
|
26
|
-
<div class="summary-card" title="Total tool executions (Read, Write, Edit, Bash, Grep, etc.) across the entire session, including subagent tools.">
|
|
27
|
-
<div class="summary-card-label">Tool Calls</div>
|
|
28
|
-
<div class="summary-card-value">{{ totalToolCount }}</div>
|
|
29
|
-
<div class="summary-card-sub">
|
|
30
|
-
<span :style="{ color: successRate >= 95 ? '#3fb950' : successRate >= 80 ? '#d29922' : '#f85149' }">{{ successRate }}%</span> success
|
|
31
|
-
<span v-if="errorCount > 0" style="color: #f85149;"> \xB7 {{ errorCount }} error{{ errorCount !== 1 ? 's' : '' }}</span>
|
|
32
|
-
</div>
|
|
33
|
-
</div>
|
|
34
|
-
<div class="summary-card" title="Spawned subagents (via Task tool). Shows completed/failed/incomplete counts, total wall-clock time, and tool calls attributed to subagents.">
|
|
35
|
-
<div class="summary-card-label">Sub-Agents</div>
|
|
36
|
-
<div class="summary-card-value">{{ subagentAnalysis.length || vsCodeSubagents.length }}</div>
|
|
37
|
-
<div class="summary-card-sub" v-if="subagentAnalysis.length > 0">
|
|
38
|
-
<span :style="{ color: subagentStats.successRate >= 95 ? '#3fb950' : subagentStats.successRate >= 80 ? '#d29922' : '#f85149' }">{{ subagentStats.completed }}\u2713</span>
|
|
39
|
-
<span v-if="subagentStats.failed > 0" style="color: #f85149;"> \xB7 {{ subagentStats.failed }}\u2717</span>
|
|
40
|
-
<span v-if="subagentStats.incomplete > 0" style="color: #d29922;"> \xB7 {{ subagentStats.incomplete }}\u23F3</span>
|
|
41
|
-
\xB7 {{ formatDuration(subagentStats.totalTime) }}
|
|
42
|
-
\xB7 {{ subagentStats.totalTools }} tools
|
|
43
|
-
</div>
|
|
44
|
-
<div class="summary-card-sub" v-else-if="vsCodeSubagents.length > 0">
|
|
45
|
-
<span style="color: #3fb950;">{{ vsCodeSubagents.length }}\u2713</span>
|
|
46
|
-
\xB7 {{ vsCodeSubagents.reduce((s, a) => s + a.toolCount, 0) }} tools
|
|
47
|
-
</div>
|
|
48
|
-
</div>
|
|
49
|
-
<div class="summary-card" title="Estimated LLM reasoning time (total duration minus tool execution and user thinking time). Breakdown shows LLM percentage, tool wall-clock time, and user idle time.">
|
|
50
|
-
<div class="summary-card-label">Time Breakdown</div>
|
|
51
|
-
<div class="summary-card-value">{{ formatDuration(timeBreakdown.llmTime) }} <span style="font-size: 14px; opacity: 0.6;">({{ timeBreakdown.llmPct }}% LLM Reasoning)</span></div>
|
|
52
|
-
<div class="summary-card-sub">
|
|
53
|
-
Tools {{ formatDuration(totalToolTime) }} ({{ timeBreakdown.toolPct }}%)
|
|
54
|
-
<span v-if="timeBreakdown.userThinkingTime > 1000"> \xB7 User {{ formatDuration(timeBreakdown.userThinkingTime) }} ({{ timeBreakdown.userThinkingPct }}%)</span>
|
|
55
|
-
</div>
|
|
56
|
-
</div>
|
|
57
|
-
<div class="summary-card" title="File system operations: reads (Read/Glob), edits (Edit), writes (Write), and searches (Grep).">
|
|
58
|
-
<div class="summary-card-label">File Operations</div>
|
|
59
|
-
<div class="summary-card-value">{{ fileStats.totalOps }}</div>
|
|
60
|
-
<div class="summary-card-sub">
|
|
61
|
-
{{ fileStats.reads }} reads \xB7 {{ fileStats.edits }} edits \xB7 {{ fileStats.writes }} writes \xB7 {{ fileStats.searches }} searches
|
|
62
|
-
</div>
|
|
63
|
-
</div>
|
|
64
|
-
</div>
|
|
65
|
-
|
|
66
|
-
<!-- Tabs -->
|
|
67
|
-
<div class="tabs">
|
|
68
|
-
<button :class="['tab', { active: activeTab === 'timeline' }]" @click="activeTab = 'timeline'; trackClick && trackClick('TimeAnalysisInteraction', { interactionType: 'tab-timeline', sessionId: window.__PAGE_DATA.sessionId })">
|
|
69
|
-
\u{1F4CA} Timeline
|
|
70
|
-
</button>
|
|
71
|
-
<button :class="['tab', { active: activeTab === 'insight' }]" @click="activeTab = 'insight'; trackClick && trackClick('TimeAnalysisInteraction', { interactionType: 'tab-insight', sessionId: window.__PAGE_DATA.sessionId })">
|
|
72
|
-
\u{1F4A1} Agent Review
|
|
73
|
-
</button>
|
|
74
|
-
</div>
|
|
75
|
-
|
|
76
|
-
<!-- \u2550\u2550\u2550 Unified Timeline Tab \u2550\u2550\u2550 -->
|
|
77
|
-
<div v-if="activeTab === 'timeline'" class="section">
|
|
78
|
-
<div v-if="error" class="empty-state" style="color: #f85149;">
|
|
79
|
-
Error loading timeline: {{ error }}
|
|
80
|
-
</div>
|
|
81
|
-
<div v-else-if="!unifiedTimelineItems.length" class="empty-state">
|
|
82
|
-
No timeline data found in this session.
|
|
83
|
-
</div>
|
|
84
|
-
<div v-else>
|
|
85
|
-
<!-- Section A: Gantt Chart -->
|
|
86
|
-
<div class="section-title" style="display: flex; align-items: center;">
|
|
87
|
-
Timeline
|
|
88
|
-
<button class="legend-toggle-btn" @click="showMarkerLegend = !showMarkerLegend; trackClick && trackClick('TimeAnalysisInteraction', { interactionType: 'toggle-legend', sessionId: window.__PAGE_DATA.sessionId })">
|
|
89
|
-
{{ showMarkerLegend ? 'Hide Legend' : 'Show Legend' }}
|
|
90
|
-
</button>
|
|
91
|
-
<button class="legend-toggle-btn" @click="copyTimelineMarkdown; trackClick && trackClick('TimeAnalysisInteraction', { interactionType: 'copy-timeline', sessionId: window.__PAGE_DATA.sessionId })">
|
|
92
|
-
{{ copyLabel }}
|
|
93
|
-
</button>
|
|
94
|
-
</div>
|
|
95
|
-
|
|
96
|
-
<!-- Event Legend -->
|
|
97
|
-
<div v-show="showMarkerLegend" class="event-legend">
|
|
98
|
-
<div class="event-legend-item">
|
|
99
|
-
<span class="event-legend-swatch" style="background: rgba(88, 166, 255, 0.5);"></span>
|
|
100
|
-
<span>User Request</span>
|
|
101
|
-
</div>
|
|
102
|
-
<div class="event-legend-item">
|
|
103
|
-
<span class="event-legend-swatch" style="background: rgba(63, 185, 80, 0.8);"></span>
|
|
104
|
-
<span>Sub-Agent</span>
|
|
105
|
-
</div>
|
|
106
|
-
<div class="event-legend-item">
|
|
107
|
-
<span class="event-legend-swatch" style="background: rgba(139, 148, 158, 0.3); border: 1px dashed rgba(139, 148, 158, 0.5);"></span>
|
|
108
|
-
<span>Main Agent</span>
|
|
109
|
-
</div>
|
|
110
|
-
<div class="event-legend-item">
|
|
111
|
-
<span class="event-legend-swatch" style="background: #d29922;"></span>
|
|
112
|
-
<span>Tool (no errors)</span>
|
|
113
|
-
</div>
|
|
114
|
-
<div class="event-legend-item">
|
|
115
|
-
<span class="event-legend-swatch" style="background: linear-gradient(to right, #d29922, #f85149);"></span>
|
|
116
|
-
<span>Tool (error gradient)</span>
|
|
117
|
-
</div>
|
|
118
|
-
<div class="event-legend-item">
|
|
119
|
-
<span class="event-legend-swatch" style="background: #f85149;"></span>
|
|
120
|
-
<span>Tool Error (100%)</span>
|
|
121
|
-
</div>
|
|
122
|
-
<template v-for="(cat, type) in EVENT_MARKER_CATEGORIES" :key="type">
|
|
123
|
-
<div v-if="type && !type.startsWith('tool.')" class="event-legend-item">
|
|
124
|
-
<span class="event-legend-swatch" :style="{ background: cat.color, borderRadius: cat.shape === 'circle' ? '50%' : cat.shape === 'diamond' ? '1px' : '2px', transform: cat.shape === 'diamond' ? 'rotate(45deg)' : 'none' }"></span>
|
|
125
|
-
<span>{{ cat.label }}</span>
|
|
126
|
-
</div>
|
|
127
|
-
</template>
|
|
128
|
-
</div>
|
|
129
|
-
|
|
130
|
-
<!-- VS Code Session Banner -->
|
|
131
|
-
<div v-if="isVSCodeSession" style="
|
|
132
|
-
background: rgba(88, 166, 255, 0.1);
|
|
133
|
-
border: 1px solid rgba(88, 166, 255, 0.3);
|
|
134
|
-
border-radius: 6px;
|
|
135
|
-
padding: 12px 16px;
|
|
136
|
-
margin-bottom: 16px;
|
|
137
|
-
display: flex;
|
|
138
|
-
align-items: center;
|
|
139
|
-
gap: 8px;
|
|
140
|
-
color: #58a6ff;
|
|
141
|
-
font-size: 13px;
|
|
142
|
-
">
|
|
143
|
-
<span style="font-size: 16px;">\u24D8</span>
|
|
144
|
-
<span>Sequence layout \u2014 bar widths represent tool count, not elapsed time</span>
|
|
145
|
-
</div>
|
|
146
|
-
|
|
147
|
-
<div class="gantt-container" @mousemove="onGanttMouseMove" @mouseleave="onGanttMouseLeave">
|
|
148
|
-
<!-- Crosshair -->
|
|
149
|
-
<div v-if="ganttCrosshairX !== null" class="gantt-crosshair" :style="{ left: ganttCrosshairX + 'px' }">
|
|
150
|
-
<div class="gantt-crosshair-label">{{ ganttCrosshairTime }}</div>
|
|
151
|
-
</div>
|
|
152
|
-
<template v-for="(item, idx) in unifiedTimelineItems" :key="'utl-' + idx">
|
|
153
|
-
|
|
154
|
-
<!-- Divider row -->
|
|
155
|
-
<div v-if="item.rowType === 'divider'" class="gantt-divider">
|
|
156
|
-
Tool Summary
|
|
157
|
-
</div>
|
|
158
|
-
|
|
159
|
-
<!-- User Request row -->
|
|
160
|
-
<div v-else-if="item.rowType === 'user-req'" class="gantt-row">
|
|
161
|
-
<div class="gantt-label user-req" :title="item.message || 'No message'">
|
|
162
|
-
<span class="user-req-badge">UserReq {{ item.userReqNumber }}</span>
|
|
163
|
-
<span class="user-req-msg">{{ (item.message || '').substring(0, 40) }}{{ (item.message || '').length > 40 ? '...' : '' }}</span>
|
|
164
|
-
</div>
|
|
165
|
-
<div class="gantt-bar-area">
|
|
166
|
-
<div
|
|
167
|
-
class="gantt-bar user-req"
|
|
168
|
-
:style="item.isSequenceEstimated ? ganttSequencePosition(item) : ganttPosition(item.startTime, item.endTime)"
|
|
169
|
-
:title="item.isSequenceEstimated ? ('UserReq ' + item.userReqNumber + ' \u2014 ' + item.toolCount + ' tools') : ('UserReq ' + item.userReqNumber + ' \u2014 ' + formatDuration(item.duration))"
|
|
170
|
-
>
|
|
171
|
-
{{ item.isSequenceEstimated ? (item.toolCount + ' tools') : formatDuration(item.duration) }}
|
|
172
|
-
</div>
|
|
173
|
-
</div>
|
|
174
|
-
</div>
|
|
175
|
-
|
|
176
|
-
<!-- Sub-Agent row (indented for CLI, not indented for VS Code) -->
|
|
177
|
-
<div v-else-if="item.rowType === 'subagent'" :class="['gantt-row', item.indented ? 'indented' : (item.isSequenceEstimated ? '' : 'indented')]">
|
|
178
|
-
<div class="gantt-label" :title="item.name">
|
|
179
|
-
<a
|
|
180
|
-
:href="'/session/' + sessionId + '?eventType=subagent.started&eventName=' + encodeURIComponent(item.name) + '&eventTimestamp=' + encodeURIComponent(item.startTime || '')"
|
|
181
|
-
class="subagent-link"
|
|
182
|
-
:title="'View events from here'"
|
|
183
|
-
>
|
|
184
|
-
<span :style="{ color: item.status === 'completed' ? '#3fb950' : item.status === 'failed' ? '#f85149' : '#d29922' }">
|
|
185
|
-
{{ item.status === 'completed' ? '\u2713' : item.status === 'failed' ? '\u2717' : '\u23F3' }}
|
|
186
|
-
</span>
|
|
187
|
-
{{ item.name }}
|
|
188
|
-
</a>
|
|
189
|
-
</div>
|
|
190
|
-
<div class="gantt-bar-area">
|
|
191
|
-
<div
|
|
192
|
-
:class="[
|
|
193
|
-
'gantt-bar',
|
|
194
|
-
item.isSequenceEstimated ? 'sequence-estimated' : '',
|
|
195
|
-
item.status === 'completed' ? 'subagent' : item.status === 'failed' ? 'subagent-failed' : 'subagent-incomplete'
|
|
196
|
-
]"
|
|
197
|
-
:style="item.isSequenceEstimated ? ganttSequencePosition(item) : ganttPosition(item.startTime, item.endTime)"
|
|
198
|
-
:title="item.isSequenceEstimated ? (item.name + ' \u2014 ' + item.toolCount + ' tools') : (item.name + ' \u2014 ' + formatDuration(item.duration))"
|
|
199
|
-
>
|
|
200
|
-
{{ item.isSequenceEstimated ? (item.toolCount + ' tools') : formatDuration(item.duration) }}
|
|
201
|
-
|
|
202
|
-
<!-- Event markers (only for non-sequence bars) -->
|
|
203
|
-
<template v-if="!item.isSequenceEstimated && item.innerEventMarkers && item.innerEventMarkers.length">
|
|
204
|
-
<span
|
|
205
|
-
v-for="(marker, midx) in item.innerEventMarkers"
|
|
206
|
-
:key="'m-' + midx"
|
|
207
|
-
class="event-marker"
|
|
208
|
-
:style="{ left: marker.position + '%' }"
|
|
209
|
-
>
|
|
210
|
-
<template v-if="marker.shape === 'cluster'">
|
|
211
|
-
<span class="event-marker--cluster" :style="{ background: marker.color }">{{ marker.count }}</span>
|
|
212
|
-
</template>
|
|
213
|
-
<template v-else-if="marker.shape === 'circle'">
|
|
214
|
-
<span class="event-marker--circle" :style="{ background: marker.color }"></span>
|
|
215
|
-
</template>
|
|
216
|
-
<template v-else-if="marker.shape === 'diamond'">
|
|
217
|
-
<span class="event-marker--diamond" :style="{ background: marker.color }"></span>
|
|
218
|
-
</template>
|
|
219
|
-
<template v-else-if="marker.shape === 'square'">
|
|
220
|
-
<span class="event-marker--square" :style="{ background: marker.color }"></span>
|
|
221
|
-
</template>
|
|
222
|
-
<template v-else-if="marker.shape === 'triangle'">
|
|
223
|
-
<span class="event-marker--triangle" :style="{ color: marker.color }"></span>
|
|
224
|
-
</template>
|
|
225
|
-
<span class="event-marker-tooltip">
|
|
226
|
-
<template v-if="marker.shape === 'cluster'">{{ marker.count }} events: {{ marker.label }}</template>
|
|
227
|
-
<template v-else>{{ marker.label }}<span v-if="marker.toolName"> ({{ marker.toolName }})</span></template>
|
|
228
|
-
</span>
|
|
229
|
-
</span>
|
|
230
|
-
</template>
|
|
231
|
-
</div>
|
|
232
|
-
</div>
|
|
233
|
-
</div>
|
|
234
|
-
|
|
235
|
-
<!-- Main Agent gap row (indented) -->
|
|
236
|
-
<div v-else-if="item.rowType === 'main-agent'" class="gantt-row indented">
|
|
237
|
-
<div class="gantt-label agent-op" :title="item.summary">
|
|
238
|
-
<span class="agent-op-icon">\u2699</span>
|
|
239
|
-
<span>Main Agent</span>
|
|
240
|
-
<span class="agent-op-summary">{{ item.summary }}</span>
|
|
241
|
-
</div>
|
|
242
|
-
<div class="gantt-bar-area">
|
|
243
|
-
<div
|
|
244
|
-
class="gantt-bar agent-op"
|
|
245
|
-
:style="ganttPosition(item.startTime, item.endTime)"
|
|
246
|
-
:title="'Main Agent \u2014 ' + formatDuration(item.duration)"
|
|
247
|
-
>
|
|
248
|
-
{{ formatDuration(item.duration) }}
|
|
249
|
-
|
|
250
|
-
<!-- Event markers -->
|
|
251
|
-
<template v-if="item.innerEventMarkers && item.innerEventMarkers.length">
|
|
252
|
-
<span
|
|
253
|
-
v-for="(marker, midx) in item.innerEventMarkers"
|
|
254
|
-
:key="'m-' + midx"
|
|
255
|
-
class="event-marker"
|
|
256
|
-
:style="{ left: marker.position + '%' }"
|
|
257
|
-
>
|
|
258
|
-
<template v-if="marker.shape === 'cluster'">
|
|
259
|
-
<span class="event-marker--cluster" :style="{ background: marker.color }">{{ marker.count }}</span>
|
|
260
|
-
</template>
|
|
261
|
-
<template v-else-if="marker.shape === 'circle'">
|
|
262
|
-
<span class="event-marker--circle" :style="{ background: marker.color }"></span>
|
|
263
|
-
</template>
|
|
264
|
-
<template v-else-if="marker.shape === 'diamond'">
|
|
265
|
-
<span class="event-marker--diamond" :style="{ background: marker.color }"></span>
|
|
266
|
-
</template>
|
|
267
|
-
<template v-else-if="marker.shape === 'square'">
|
|
268
|
-
<span class="event-marker--square" :style="{ background: marker.color }"></span>
|
|
269
|
-
</template>
|
|
270
|
-
<template v-else-if="marker.shape === 'triangle'">
|
|
271
|
-
<span class="event-marker--triangle" :style="{ color: marker.color }"></span>
|
|
272
|
-
</template>
|
|
273
|
-
<span class="event-marker-tooltip">
|
|
274
|
-
<template v-if="marker.shape === 'cluster'">{{ marker.count }} events: {{ marker.label }}</template>
|
|
275
|
-
<template v-else>{{ marker.label }}<span v-if="marker.toolName"> ({{ marker.toolName }})</span></template>
|
|
276
|
-
</span>
|
|
277
|
-
</span>
|
|
278
|
-
</template>
|
|
279
|
-
</div>
|
|
280
|
-
</div>
|
|
281
|
-
</div>
|
|
282
|
-
|
|
283
|
-
</template>
|
|
284
|
-
|
|
285
|
-
<div class="gantt-time-axis">
|
|
286
|
-
<span>{{ formatTime(events[0]?.timestamp) }}</span>
|
|
287
|
-
<span>{{ formatTime(events[events.length-1]?.timestamp) }}</span>
|
|
288
|
-
</div>
|
|
289
|
-
</div>
|
|
290
|
-
|
|
291
|
-
<!-- Tool Summary -->
|
|
292
|
-
<div v-if="toolTimeByCategory.length" style="margin-top: 24px;">
|
|
293
|
-
<h3 style="color: #e6edf3; font-size: 14px; margin-bottom: 12px;">\u{1F527} Tool Summary</h3>
|
|
294
|
-
<div style="display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 8px;">
|
|
295
|
-
<div
|
|
296
|
-
v-for="cat in toolTimeByCategory"
|
|
297
|
-
:key="cat.category"
|
|
298
|
-
style="background: #161b22; border: 1px solid #30363d; border-radius: 6px; padding: 10px 12px; display: flex; align-items: center; gap: 10px;"
|
|
299
|
-
>
|
|
300
|
-
<div style="flex: 1; min-width: 0;">
|
|
301
|
-
<div style="display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 4px;">
|
|
302
|
-
<span style="color: #d29922; font-weight: 500; font-size: 13px;">{{ cat.category }}</span>
|
|
303
|
-
<span style="color: #7d8590; font-size: 11px;">{{ cat.count }} call{{ cat.count !== 1 ? 's' : '' }}<span v-if="cat.errors" style="color: #f85149;"> \xB7 {{ cat.errors }} err</span></span>
|
|
304
|
-
</div>
|
|
305
|
-
<div style="background: #21262d; border-radius: 3px; height: 6px; overflow: hidden;">
|
|
306
|
-
<div :style="{ width: (cat.totalTime / maxCategoryTime * 100) + '%', height: '100%', background: 'rgba(158, 106, 3, 0.7)', borderRadius: '3px' }"></div>
|
|
307
|
-
</div>
|
|
308
|
-
<div style="color: #7d8590; font-size: 11px; margin-top: 3px;">{{ formatDuration(cat.totalTime) }}</div>
|
|
309
|
-
</div>
|
|
310
|
-
</div>
|
|
311
|
-
</div>
|
|
312
|
-
</div>
|
|
313
|
-
|
|
314
|
-
</div>
|
|
315
|
-
</div>
|
|
316
|
-
|
|
317
|
-
<!-- \u2550\u2550\u2550 Copilot Insight Tab \u2550\u2550\u2550 -->
|
|
318
|
-
<div v-if="activeTab === 'insight'" class="section">
|
|
319
|
-
<!-- Error State -->
|
|
320
|
-
<div v-if="insightError" class="empty-state" style="padding: 60px; color: #f85149;">
|
|
321
|
-
\u274C {{ insightError }}
|
|
322
|
-
</div>
|
|
323
|
-
|
|
324
|
-
<!-- Generating State -->
|
|
325
|
-
<div v-else-if="insightStatus === 'generating'" style="padding: 20px;">
|
|
326
|
-
<div :style="{
|
|
327
|
-
background: '#0d1117',
|
|
328
|
-
border: '1px solid ' + (insightAgeMs > 300000 ? '#d29922' : '#30363d'),
|
|
329
|
-
borderRadius: '6px',
|
|
330
|
-
padding: '20px',
|
|
331
|
-
marginBottom: '20px',
|
|
332
|
-
}">
|
|
333
|
-
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px;">
|
|
334
|
-
<div style="display: flex; align-items: center;">
|
|
335
|
-
<span style="font-size: 24px; margin-right: 10px;">\u23F3</span>
|
|
336
|
-
<div>
|
|
337
|
-
<div style="font-weight: 600; color: #58a6ff; margin-bottom: 5px;">
|
|
338
|
-
Generating Agent Review...
|
|
339
|
-
</div>
|
|
340
|
-
<div style="font-size: 13px; color: #7d8590;">
|
|
341
|
-
Started: {{ formatDateTime(insightStartedAt) }} \u2022
|
|
342
|
-
Age: {{ Math.floor(insightAgeMs / 1000) }}s
|
|
343
|
-
</div>
|
|
344
|
-
</div>
|
|
345
|
-
</div>
|
|
346
|
-
<button
|
|
347
|
-
v-if="insightAgeMs > 300000"
|
|
348
|
-
@click="regenerateInsight(); trackClick && trackClick('InsightRequested', { sessionId: window.__PAGE_DATA.sessionId, action: 'regenerate' })"
|
|
349
|
-
style="
|
|
350
|
-
background: #d29922;
|
|
351
|
-
color: #fff;
|
|
352
|
-
border: none;
|
|
353
|
-
padding: 8px 16px;
|
|
354
|
-
border-radius: 6px;
|
|
355
|
-
font-size: 13px;
|
|
356
|
-
cursor: pointer;
|
|
357
|
-
font-weight: 500;
|
|
358
|
-
white-space: nowrap;
|
|
359
|
-
"
|
|
360
|
-
@mouseover="$event.target.style.background='#e3b341'"
|
|
361
|
-
@mouseleave="$event.target.style.background='#d29922'"
|
|
362
|
-
>
|
|
363
|
-
\u{1F504} Stop & Retry
|
|
364
|
-
</button>
|
|
365
|
-
</div>
|
|
366
|
-
<!-- Slow generation warning -->
|
|
367
|
-
<div v-if="insightAgeMs > 300000" style="
|
|
368
|
-
background: rgba(210, 153, 34, 0.1);
|
|
369
|
-
border: 1px solid rgba(210, 153, 34, 0.3);
|
|
370
|
-
border-radius: 6px;
|
|
371
|
-
padding: 10px 14px;
|
|
372
|
-
margin-bottom: 12px;
|
|
373
|
-
font-size: 13px;
|
|
374
|
-
color: #d29922;
|
|
375
|
-
">
|
|
376
|
-
\u26A0\uFE0F Generation is taking longer than 5 minutes. For large sessions this is normal \u2014 the agent needs to read and analyze all events. If it appears stuck, you can click <strong>Stop & Retry</strong> to cancel and start fresh.
|
|
377
|
-
</div>
|
|
378
|
-
<div v-if="insightLog" id="insight-log" style="
|
|
379
|
-
background: #161b22;
|
|
380
|
-
border: 1px solid #30363d;
|
|
381
|
-
border-radius: 6px;
|
|
382
|
-
padding: 14px 16px;
|
|
383
|
-
font-family: 'SF Mono', 'Monaco', 'Consolas', monospace;
|
|
384
|
-
font-size: 12px;
|
|
385
|
-
line-height: 1.6;
|
|
386
|
-
color: #8b949e;
|
|
387
|
-
white-space: pre-wrap;
|
|
388
|
-
word-break: break-word;
|
|
389
|
-
max-height: 400px;
|
|
390
|
-
overflow-y: auto;
|
|
391
|
-
">{{ insightLog }}</div>
|
|
392
|
-
</div>
|
|
393
|
-
</div>
|
|
394
|
-
|
|
395
|
-
<!-- Timeout State -->
|
|
396
|
-
<div v-else-if="insightStatus === 'timeout'" style="padding: 20px;">
|
|
397
|
-
<div style="
|
|
398
|
-
background: #0d1117;
|
|
399
|
-
border: 1px solid #d29922;
|
|
400
|
-
border-radius: 6px;
|
|
401
|
-
padding: 20px;
|
|
402
|
-
margin-bottom: 20px;
|
|
403
|
-
">
|
|
404
|
-
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px;">
|
|
405
|
-
<div style="display: flex; align-items: center;">
|
|
406
|
-
<span style="font-size: 24px; margin-right: 10px;">\u23F3</span>
|
|
407
|
-
<div>
|
|
408
|
-
<div style="font-weight: 600; color: #d29922; margin-bottom: 5px;">
|
|
409
|
-
Still generating... ({{ Math.floor(insightAgeMs / 1000 / 60) }}m elapsed)
|
|
410
|
-
</div>
|
|
411
|
-
<div style="font-size: 13px; color: #7d8590;">
|
|
412
|
-
Large sessions with sub-agents may take 10\u201315 minutes. Still polling for completion.
|
|
413
|
-
</div>
|
|
414
|
-
</div>
|
|
415
|
-
</div>
|
|
416
|
-
<button
|
|
417
|
-
@click="regenerateInsight"
|
|
418
|
-
style="
|
|
419
|
-
background: #d29922;
|
|
420
|
-
color: #fff;
|
|
421
|
-
border: none;
|
|
422
|
-
padding: 8px 16px;
|
|
423
|
-
border-radius: 6px;
|
|
424
|
-
font-size: 13px;
|
|
425
|
-
cursor: pointer;
|
|
426
|
-
font-weight: 500;
|
|
427
|
-
white-space: nowrap;
|
|
428
|
-
"
|
|
429
|
-
@mouseover="$event.target.style.background='#e3b341'"
|
|
430
|
-
@mouseleave="$event.target.style.background='#d29922'"
|
|
431
|
-
>
|
|
432
|
-
\u{1F504} Stop & Retry
|
|
433
|
-
</button>
|
|
434
|
-
</div>
|
|
435
|
-
<div v-if="insightLog" style="
|
|
436
|
-
background: #161b22;
|
|
437
|
-
border: 1px solid #30363d;
|
|
438
|
-
border-radius: 6px;
|
|
439
|
-
padding: 14px 16px;
|
|
440
|
-
font-family: 'SF Mono', 'Monaco', 'Consolas', monospace;
|
|
441
|
-
font-size: 12px;
|
|
442
|
-
line-height: 1.6;
|
|
443
|
-
color: #8b949e;
|
|
444
|
-
white-space: pre-wrap;
|
|
445
|
-
word-break: break-word;
|
|
446
|
-
max-height: 400px;
|
|
447
|
-
overflow-y: auto;
|
|
448
|
-
">{{ insightLog }}</div>
|
|
449
|
-
</div>
|
|
450
|
-
</div>
|
|
451
|
-
|
|
452
|
-
<!-- Not Started State -->
|
|
453
|
-
<div v-else-if="insightStatus === 'not_started'" style="padding: 40px; text-align: center;">
|
|
454
|
-
<p style="margin-bottom: 20px; color: #7d8590;">
|
|
455
|
-
Generate an AI-powered quality & performance review of how the agent used its tools, prompts, and workflow in this session
|
|
456
|
-
</p>
|
|
457
|
-
<button
|
|
458
|
-
@click="generateInsight(false); trackClick && trackClick('InsightRequested', { sessionId: window.__PAGE_DATA.sessionId })"
|
|
459
|
-
:disabled="insightLoading"
|
|
460
|
-
style="
|
|
461
|
-
background: #238636;
|
|
462
|
-
color: #fff;
|
|
463
|
-
border: none;
|
|
464
|
-
padding: 10px 20px;
|
|
465
|
-
border-radius: 6px;
|
|
466
|
-
font-size: 14px;
|
|
467
|
-
cursor: pointer;
|
|
468
|
-
font-weight: 500;
|
|
469
|
-
"
|
|
470
|
-
@mouseover="$event.target.style.background='#2ea043'"
|
|
471
|
-
@mouseleave="$event.target.style.background='#238636'"
|
|
472
|
-
>
|
|
473
|
-
\u{1F4A1} Generate Agent Review
|
|
474
|
-
</button>
|
|
475
|
-
<p style="margin-top: 12px; font-size: 12px; color: #6e7681;">
|
|
476
|
-
For large sessions this may take several minutes \u2014 you'll see a live progress log while the review is being generated.
|
|
477
|
-
</p>
|
|
478
|
-
</div>
|
|
479
|
-
|
|
480
|
-
<!-- Completed State -->
|
|
481
|
-
<div v-else-if="insightStatus === 'completed'" style="padding: 20px;">
|
|
482
|
-
<div style="
|
|
483
|
-
background: #161b22;
|
|
484
|
-
border: 1px solid #30363d;
|
|
485
|
-
border-radius: 6px;
|
|
486
|
-
padding: 20px;
|
|
487
|
-
margin-bottom: 20px;
|
|
488
|
-
">
|
|
489
|
-
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px;">
|
|
490
|
-
<span style="color: #7d8590; font-size: 13px;">
|
|
491
|
-
Generated: {{ formatDateTime(insightGeneratedAt) }}
|
|
492
|
-
</span>
|
|
493
|
-
<button
|
|
494
|
-
@click="regenerateInsight"
|
|
495
|
-
style="
|
|
496
|
-
background: transparent;
|
|
497
|
-
color: #58a6ff;
|
|
498
|
-
border: 1px solid #58a6ff;
|
|
499
|
-
padding: 5px 12px;
|
|
500
|
-
border-radius: 6px;
|
|
501
|
-
font-size: 12px;
|
|
502
|
-
cursor: pointer;
|
|
503
|
-
"
|
|
504
|
-
@mouseover="$event.target.style.background='rgba(88, 166, 255, 0.1)'"
|
|
505
|
-
@mouseleave="$event.target.style.background='transparent'"
|
|
506
|
-
>
|
|
507
|
-
\u{1F504} Regenerate
|
|
508
|
-
</button>
|
|
509
|
-
</div>
|
|
510
|
-
<div v-html="renderedInsight" style="
|
|
511
|
-
color: #c9d1d9;
|
|
512
|
-
line-height: 1.6;
|
|
513
|
-
"></div>
|
|
514
|
-
</div>
|
|
515
|
-
</div>
|
|
516
|
-
</div>
|
|
517
|
-
</div>
|
|
518
|
-
`});Me.config.errorHandler=(C,fe,b)=>{console.error("[Vue Error]",C,b);let E=document.createElement("div");E.style.cssText="position: fixed; top: 20px; left: 50%; transform: translateX(-50%); background: #f85149; color: white; padding: 20px; border-radius: 6px; z-index: 9999; max-width: 80%; font-family: monospace; font-size: 14px;",E.innerHTML=`<strong>Vue Error:</strong><br>${C.message}<br><br><small>${b}</small>`,document.body.appendChild(E)};Me.mount("#app");})();
|