@qiaolei81/copilot-session-viewer 0.3.6 → 0.3.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/server.min.js +39 -34
- package/package.json +10 -8
- package/public/js/homepage.min.js +5 -5
- package/public/js/session-detail.min.js +223 -35
- package/public/js/time-analyze.min.js +1 -1
- package/views/index.ejs +253 -0
- package/views/session-vue.ejs +487 -182
|
@@ -1,5 +1,5 @@
|
|
|
1
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.totalTime-t.totalTime)}),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:`
|
|
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
3
|
<div v-if="loading" class="empty-state" style="padding: 60px;">
|
|
4
4
|
\u23F3 Loading events...
|
|
5
5
|
</div>
|
package/views/index.ejs
CHANGED
|
@@ -175,6 +175,7 @@
|
|
|
175
175
|
-webkit-line-clamp: 3;
|
|
176
176
|
-webkit-box-orient: vertical;
|
|
177
177
|
overflow: hidden;
|
|
178
|
+
cursor: help;
|
|
178
179
|
}
|
|
179
180
|
.session-divider {
|
|
180
181
|
height: 1px;
|
|
@@ -355,6 +356,15 @@
|
|
|
355
356
|
font-weight: 600;
|
|
356
357
|
font-family: 'SF Mono', 'Monaco', 'Consolas', monospace;
|
|
357
358
|
}
|
|
359
|
+
.status-badge.source-modernize {
|
|
360
|
+
padding: 2px 8px;
|
|
361
|
+
background: rgba(76, 175, 80, 0.15);
|
|
362
|
+
color: #66bb6a;
|
|
363
|
+
border-radius: 12px;
|
|
364
|
+
font-size: 11px;
|
|
365
|
+
font-weight: 600;
|
|
366
|
+
font-family: 'SF Mono', 'Monaco', 'Consolas', monospace;
|
|
367
|
+
}
|
|
358
368
|
|
|
359
369
|
/* Filter pills */
|
|
360
370
|
.filter-pills {
|
|
@@ -519,6 +529,7 @@
|
|
|
519
529
|
<button class="filter-pill active" data-source="copilot">Copilot CLI</button>
|
|
520
530
|
<button class="filter-pill" data-source="vscode">Copilot Chat</button>
|
|
521
531
|
<button class="filter-pill" data-source="claude">Claude</button>
|
|
532
|
+
<button class="filter-pill" data-source="modernize">Modernize CLI</button>
|
|
522
533
|
<button class="filter-pill" data-source="pi-mono">Pi</button>
|
|
523
534
|
</div>
|
|
524
535
|
<p class="hint source-hint" id="sourceHint"></p>
|
|
@@ -562,6 +573,248 @@
|
|
|
562
573
|
.date-group-header:first-child {
|
|
563
574
|
margin-top: 0;
|
|
564
575
|
}
|
|
576
|
+
|
|
577
|
+
/* Custom summary tooltip */
|
|
578
|
+
.summary-tooltip {
|
|
579
|
+
display: none;
|
|
580
|
+
position: fixed;
|
|
581
|
+
z-index: 9999;
|
|
582
|
+
background: #1c2128;
|
|
583
|
+
border: 1px solid #30363d;
|
|
584
|
+
border-radius: 8px;
|
|
585
|
+
padding: 12px 16px;
|
|
586
|
+
max-width: 600px;
|
|
587
|
+
width: max-content;
|
|
588
|
+
max-height: 400px;
|
|
589
|
+
overflow-y: auto;
|
|
590
|
+
font-size: 13px;
|
|
591
|
+
line-height: 1.6;
|
|
592
|
+
color: #c9d1d9;
|
|
593
|
+
white-space: normal;
|
|
594
|
+
word-break: break-word;
|
|
595
|
+
box-shadow: 0 8px 24px rgba(0,0,0,0.5);
|
|
596
|
+
pointer-events: none;
|
|
597
|
+
}
|
|
598
|
+
.summary-tooltip.visible {
|
|
599
|
+
display: block;
|
|
600
|
+
pointer-events: auto;
|
|
601
|
+
}
|
|
602
|
+
/* Markdown prose styles inside tooltip */
|
|
603
|
+
.summary-tooltip h1, .summary-tooltip h2, .summary-tooltip h3,
|
|
604
|
+
.summary-tooltip h4, .summary-tooltip h5, .summary-tooltip h6 {
|
|
605
|
+
color: #e6edf3; margin: 8px 0 4px; font-weight: 600;
|
|
606
|
+
}
|
|
607
|
+
.summary-tooltip h1 { font-size: 15px; }
|
|
608
|
+
.summary-tooltip h2 { font-size: 14px; }
|
|
609
|
+
.summary-tooltip h3, .summary-tooltip h4 { font-size: 13px; }
|
|
610
|
+
.summary-tooltip p { margin: 4px 0; }
|
|
611
|
+
.summary-tooltip ul, .summary-tooltip ol { margin: 4px 0; padding-left: 18px; }
|
|
612
|
+
.summary-tooltip li { margin: 2px 0; }
|
|
613
|
+
.summary-tooltip code {
|
|
614
|
+
background: #2d333b; border-radius: 3px;
|
|
615
|
+
padding: 1px 4px; font-size: 12px; font-family: monospace;
|
|
616
|
+
}
|
|
617
|
+
.summary-tooltip pre {
|
|
618
|
+
background: #2d333b; border-radius: 6px;
|
|
619
|
+
padding: 8px 10px; overflow-x: auto; margin: 6px 0;
|
|
620
|
+
}
|
|
621
|
+
.summary-tooltip pre code { background: none; padding: 0; }
|
|
622
|
+
.summary-tooltip strong { color: #e6edf3; }
|
|
623
|
+
.summary-tooltip hr { border-color: #30363d; margin: 8px 0; }
|
|
624
|
+
|
|
625
|
+
/* Hide hover tooltip on touch devices */
|
|
626
|
+
@media (hover: none) {
|
|
627
|
+
.summary-tooltip { display: none !important; }
|
|
628
|
+
}
|
|
629
|
+
/* Bottom sheet for mobile long-press */
|
|
630
|
+
.bottom-sheet-overlay {
|
|
631
|
+
display: none;
|
|
632
|
+
position: fixed;
|
|
633
|
+
inset: 0;
|
|
634
|
+
background: rgba(0,0,0,0.5);
|
|
635
|
+
z-index: 1000;
|
|
636
|
+
touch-action: none;
|
|
637
|
+
}
|
|
638
|
+
.bottom-sheet-overlay.visible { display: block; }
|
|
639
|
+
.bottom-sheet {
|
|
640
|
+
position: fixed;
|
|
641
|
+
bottom: 0;
|
|
642
|
+
left: 0;
|
|
643
|
+
right: 0;
|
|
644
|
+
background: #161b22;
|
|
645
|
+
border-top: 1px solid #30363d;
|
|
646
|
+
border-radius: 16px 16px 0 0;
|
|
647
|
+
padding: 0 16px max(env(safe-area-inset-bottom, 0px), 24px);
|
|
648
|
+
max-height: 70vh;
|
|
649
|
+
overflow-y: auto;
|
|
650
|
+
z-index: 1001;
|
|
651
|
+
transform: translateY(100%);
|
|
652
|
+
transition: transform 0.28s cubic-bezier(0.32, 0.72, 0, 1);
|
|
653
|
+
}
|
|
654
|
+
.bottom-sheet.visible { transform: translateY(0); }
|
|
655
|
+
.bottom-sheet-handle {
|
|
656
|
+
width: 36px;
|
|
657
|
+
height: 4px;
|
|
658
|
+
background: #444c56;
|
|
659
|
+
border-radius: 2px;
|
|
660
|
+
margin: 12px auto 16px;
|
|
661
|
+
}
|
|
662
|
+
.bottom-sheet-content {
|
|
663
|
+
font-size: 14px;
|
|
664
|
+
line-height: 1.6;
|
|
665
|
+
color: #c9d1d9;
|
|
666
|
+
}
|
|
667
|
+
.bottom-sheet-content h1, .bottom-sheet-content h2, .bottom-sheet-content h3 { color: #e6edf3; margin: 12px 0 6px; }
|
|
668
|
+
.bottom-sheet-content h1 { font-size: 16px; }
|
|
669
|
+
.bottom-sheet-content h2 { font-size: 15px; }
|
|
670
|
+
.bottom-sheet-content h3 { font-size: 14px; }
|
|
671
|
+
.bottom-sheet-content p { margin: 6px 0; }
|
|
672
|
+
.bottom-sheet-content ul, .bottom-sheet-content ol { margin: 6px 0; padding-left: 20px; }
|
|
673
|
+
.bottom-sheet-content li { margin: 3px 0; }
|
|
674
|
+
.bottom-sheet-content code { background: #0d1117; padding: 1px 5px; border-radius: 4px; font-size: 13px; }
|
|
675
|
+
.bottom-sheet-content pre { background: #0d1117; padding: 10px; border-radius: 6px; overflow-x: auto; }
|
|
676
|
+
.bottom-sheet-content pre code { background: none; padding: 0; }
|
|
677
|
+
.bottom-sheet-content strong { color: #e6edf3; }
|
|
678
|
+
.bottom-sheet-content hr { border-color: #30363d; margin: 10px 0; }
|
|
565
679
|
</style>
|
|
680
|
+
|
|
681
|
+
<script src="https://cdn.jsdelivr.net/npm/marked@9/marked.min.js"></script>
|
|
682
|
+
<div id="summary-tooltip" class="summary-tooltip"></div>
|
|
683
|
+
|
|
684
|
+
<script>
|
|
685
|
+
const tooltip = document.getElementById('summary-tooltip');
|
|
686
|
+
let tooltipTarget = null;
|
|
687
|
+
let hideTimer = null;
|
|
688
|
+
let showTimer = null;
|
|
689
|
+
let pendingEl = null;
|
|
690
|
+
let pendingEvent = null;
|
|
691
|
+
|
|
692
|
+
function showTooltip(el, e) {
|
|
693
|
+
clearTimeout(hideTimer);
|
|
694
|
+
tooltipTarget = el;
|
|
695
|
+
if (!el.dataset.tooltipText) {
|
|
696
|
+
el.dataset.tooltipText = el.getAttribute('title');
|
|
697
|
+
el.removeAttribute('title');
|
|
698
|
+
}
|
|
699
|
+
const md = el.dataset.tooltipText || '';
|
|
700
|
+
tooltip.innerHTML = (typeof marked !== 'undefined')
|
|
701
|
+
? marked.parse(md, { breaks: true })
|
|
702
|
+
: md.replace(/</g, '<').replace(/\n/g, '<br>');
|
|
703
|
+
tooltip.classList.add('visible');
|
|
704
|
+
positionTooltip(e);
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
function scheduleShow(el, e) {
|
|
708
|
+
clearTimeout(showTimer);
|
|
709
|
+
pendingEl = el;
|
|
710
|
+
pendingEvent = e;
|
|
711
|
+
showTimer = setTimeout(() => {
|
|
712
|
+
if (pendingEl) showTooltip(pendingEl, pendingEvent);
|
|
713
|
+
}, 500);
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
function cancelShow() {
|
|
717
|
+
clearTimeout(showTimer);
|
|
718
|
+
pendingEl = null;
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
function scheduleHide() {
|
|
722
|
+
cancelShow();
|
|
723
|
+
hideTimer = setTimeout(() => {
|
|
724
|
+
tooltipTarget = null;
|
|
725
|
+
tooltip.classList.remove('visible');
|
|
726
|
+
}, 120);
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
document.addEventListener('mouseover', e => {
|
|
730
|
+
const el = e.target.closest('.session-summary');
|
|
731
|
+
if (el) { clearTimeout(hideTimer); scheduleShow(el, e); return; }
|
|
732
|
+
if (e.target.closest('#summary-tooltip')) { clearTimeout(hideTimer); cancelShow(); return; }
|
|
733
|
+
});
|
|
734
|
+
|
|
735
|
+
document.addEventListener('mousemove', e => {
|
|
736
|
+
if (pendingEl) { pendingEvent = e; }
|
|
737
|
+
if (!tooltipTarget) return;
|
|
738
|
+
if (e.target.closest('#summary-tooltip')) return;
|
|
739
|
+
positionTooltip(e);
|
|
740
|
+
});
|
|
741
|
+
|
|
742
|
+
document.addEventListener('mouseout', e => {
|
|
743
|
+
const toEl = e.relatedTarget;
|
|
744
|
+
if (toEl && (toEl.closest('.session-summary') || toEl.closest('#summary-tooltip'))) return;
|
|
745
|
+
scheduleHide();
|
|
746
|
+
});
|
|
747
|
+
|
|
748
|
+
tooltip.addEventListener('mouseleave', () => scheduleHide());
|
|
749
|
+
|
|
750
|
+
function positionTooltip(e) {
|
|
751
|
+
const pad = 14;
|
|
752
|
+
const tw = tooltip.offsetWidth;
|
|
753
|
+
const th = tooltip.offsetHeight;
|
|
754
|
+
let x = e.clientX + pad;
|
|
755
|
+
let y = e.clientY + pad;
|
|
756
|
+
if (x + tw > window.innerWidth - 8) x = e.clientX - tw - pad;
|
|
757
|
+
if (y + th > window.innerHeight - 8) y = e.clientY - th - pad;
|
|
758
|
+
tooltip.style.left = x + 'px';
|
|
759
|
+
tooltip.style.top = y + 'px';
|
|
760
|
+
}
|
|
761
|
+
</script>
|
|
762
|
+
|
|
763
|
+
<!-- Bottom sheet for mobile long-press summary -->
|
|
764
|
+
<div id="sheet-overlay" class="bottom-sheet-overlay">
|
|
765
|
+
<div id="bottom-sheet" class="bottom-sheet">
|
|
766
|
+
<div class="bottom-sheet-handle"></div>
|
|
767
|
+
<div id="sheet-content" class="bottom-sheet-content"></div>
|
|
768
|
+
</div>
|
|
769
|
+
</div>
|
|
770
|
+
|
|
771
|
+
<script>
|
|
772
|
+
const sheetOverlay = document.getElementById('sheet-overlay');
|
|
773
|
+
const bottomSheet = document.getElementById('bottom-sheet');
|
|
774
|
+
const sheetContent = document.getElementById('sheet-content');
|
|
775
|
+
|
|
776
|
+
function openSheet(md) {
|
|
777
|
+
sheetContent.innerHTML = (typeof marked !== 'undefined')
|
|
778
|
+
? marked.parse(md, { breaks: true })
|
|
779
|
+
: md.replace(/</g, '<').replace(/\n/g, '<br>');
|
|
780
|
+
sheetOverlay.classList.add('visible');
|
|
781
|
+
requestAnimationFrame(() => bottomSheet.classList.add('visible'));
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
function closeSheet() {
|
|
785
|
+
bottomSheet.classList.remove('visible');
|
|
786
|
+
setTimeout(() => sheetOverlay.classList.remove('visible'), 280);
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
sheetOverlay.addEventListener('click', e => {
|
|
790
|
+
if (!bottomSheet.contains(e.target)) closeSheet();
|
|
791
|
+
});
|
|
792
|
+
|
|
793
|
+
// Long-press detection (500ms)
|
|
794
|
+
let lpTimer = null;
|
|
795
|
+
let lpMoved = false;
|
|
796
|
+
|
|
797
|
+
document.addEventListener('touchstart', e => {
|
|
798
|
+
const el = e.target.closest('.session-summary');
|
|
799
|
+
if (!el) return;
|
|
800
|
+
lpMoved = false;
|
|
801
|
+
const md = el.dataset.tooltipText || el.getAttribute('title') || '';
|
|
802
|
+
if (!md) return;
|
|
803
|
+
lpTimer = setTimeout(() => {
|
|
804
|
+
if (!lpMoved) {
|
|
805
|
+
e.preventDefault();
|
|
806
|
+
openSheet(md);
|
|
807
|
+
}
|
|
808
|
+
}, 500);
|
|
809
|
+
}, { passive: false });
|
|
810
|
+
|
|
811
|
+
document.addEventListener('touchmove', () => {
|
|
812
|
+
lpMoved = true;
|
|
813
|
+
clearTimeout(lpTimer);
|
|
814
|
+
}, { passive: true });
|
|
815
|
+
|
|
816
|
+
document.addEventListener('touchend', () => clearTimeout(lpTimer), { passive: true });
|
|
817
|
+
document.addEventListener('touchcancel', () => clearTimeout(lpTimer), { passive: true });
|
|
818
|
+
</script>
|
|
566
819
|
</body>
|
|
567
820
|
</html>
|