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