@qiaolei81/copilot-session-viewer 0.3.8 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/dist/client/assets/index-DQ7OTJ0v.css +2 -0
- package/dist/client/assets/index-maVEluks.js +77 -0
- package/dist/client/assets/sessionStore-CW_Vbbsn.js +1 -0
- package/dist/client/index.html +16 -0
- package/dist/server.min.js +95 -71
- package/dist/server.min.js.map +7 -0
- package/package.json +22 -18
- package/public/js/homepage.min.js +0 -35
- package/public/js/session-detail.min.js +0 -678
- package/public/js/telemetry-browser.min.js +0 -1
- package/public/js/time-analyze.min.js +0 -518
- package/public/vendor/marked.umd.min.js +0 -8
- package/public/vendor/purify.min.js +0 -3
- package/public/vendor/vue-virtual-scroller.css +0 -1
- package/public/vendor/vue-virtual-scroller.min.js +0 -2
- package/public/vendor/vue.global.prod.min.js +0 -19
- package/views/index.ejs +0 -820
- package/views/session-vue.ejs +0 -1570
- package/views/telemetry-snippet.ejs +0 -26
- package/views/time-analyze.ejs +0 -783
|
@@ -1,678 +0,0 @@
|
|
|
1
|
-
(()=>{var Ae=(d,g)=>()=>(g||d((g={exports:{}}).exports,g),g.exports);var Le=Ae((Bt,oe)=>{function qt(d){let g=new Map,f=new Map,y=0;for(let o of d)if(o.type==="subagent.started"){let u=o.data?.toolCallId;u&&f.set(u,{name:o.data?.agentDisplayName||o.data?.agentName||"SubAgent",colorIndex:y++})}for(let o of d)if(o.type==="assistant.message"&&o.data?.subAgentName&&o.data?.subAgentId){let u=o.data.subAgentId;f.has(u)||f.set(u,{name:o.data.subAgentName,colorIndex:y++}),g.set(o.stableId,u)}for(let o of d)if(o._subagent?.id){let u=o._subagent.id;f.has(u)||f.set(u,{name:o._subagent.name||"SubAgent",colorIndex:y++}),g.set(o.stableId,u)}if(f.size===0)return{ownerMap:g,subagentInfo:f};let B=new Map;for(let o of d)o.id&&B.set(o.id,o);for(let o of d)if(o.type==="assistant.message"){let u=o.data?.parentToolCallId;u&&f.has(u)&&g.set(o.stableId,u)}for(let o of d){if(o.type!=="reasoning")continue;let u=o.parentId,M=0;for(;u&&M<10;){let w=B.get(u);if(!w)break;if(w.type==="assistant.message"){let R=w.data?.parentToolCallId;R&&f.has(R)&&g.set(o.stableId,R);break}u=w.parentId,M++}}let m=new Map;for(let o of d){if(o.type!=="tool.execution_start")continue;let u=o.parentId,M=0;for(;u&&M<10;){let w=B.get(u);if(!w)break;if(w.type==="assistant.message"){let R=w.data?.parentToolCallId;if(R&&f.has(R)){g.set(o.stableId,R);let P=o.data?.toolCallId;P&&m.set(P,R)}break}u=w.parentId,M++}}for(let o of d){if(o.type!=="tool.execution_complete")continue;let u=o.data?.toolCallId;u&&m.has(u)&&g.set(o.stableId,m.get(u))}for(let o of d){if(o.type!=="tool.invocation")continue;let u=o.data?.parentToolCallId;u&&f.has(u)&&g.set(o.stableId,u)}let v=null;for(let o of d)o.type==="subagent.started"&&o.data?.toolCallId&&f.has(o.data.toolCallId)?v=o.data.toolCallId:(o.type==="subagent.completed"||o.type==="subagent.failed")&&o.data?.toolCallId===v?v=null:v&&!g.has(o.stableId)&&g.set(o.stableId,v);return{ownerMap:g,subagentInfo:f}}function Ft(d,g,f){return g?d.filter(y=>(y.type==="subagent.started"||y.type==="subagent.completed"||y.type==="subagent.failed")&&y.data?.toolCallId===g||f.get(y.stableId)===g||y._subagent?.id===g||y.data?.subAgentId===g):d}typeof oe<"u"&&oe.exports&&(oe.exports={computeSubagentOwnership:qt,filterBySubagent:Ft})});var Oe=Ae((Pt,ie)=>{function De(d){if(!d||typeof d!="object")return 0;let g=Number.isFinite(d.inputTokens)?d.inputTokens:0,f=Number.isFinite(d.cacheReadTokens)?d.cacheReadTokens:0,y=Number.isFinite(d.cacheWriteTokens)?d.cacheWriteTokens:0;return Math.max(g-f-y,0)}function Ut(d){if(!d||typeof d!="object")return null;let g=Number.isFinite(d.cacheReadTokens)?d.cacheReadTokens:0,f=De(d)+g;return g===0||f===0?null:Math.round(g/f*100)}typeof ie<"u"&&ie.exports&&(ie.exports={getDisplayInputTokens:De,getCacheHitRatio:Ut})});(function(){let{computeSubagentOwnership:d,filterBySubagent:g}=Le(),{getDisplayInputTokens:f,getCacheHitRatio:y}=Oe();if(typeof Vue>"u"){console.error("Vue is not loaded");return}if(typeof window.VueVirtualScroller>"u"){console.error("VueVirtualScroller is not loaded");return}console.log("Initializing Vue app...");let{createApp:B,ref:m,computed:v,onMounted:o,onBeforeUnmount:u,watch:M}=Vue,{DynamicScroller:w,DynamicScrollerItem:R}=window.VueVirtualScroller,P=B({components:{DynamicScroller:w,DynamicScrollerItem:R},setup(){let p=m(window.__PAGE_DATA.sessionId),x=m(window.__PAGE_DATA.metadata),re=m(!1),me=()=>window.innerWidth<=640,X=m(me()?!0:localStorage.getItem("sidebarCollapsed")==="true");M(X,t=>{me()||localStorage.setItem("sidebarCollapsed",t.toString())});let L=m({}),D=m({}),Y=50,ge=()=>{let t=Object.keys(L.value);t.length>Y&&t.slice(0,t.length-Y).forEach(s=>delete L.value[s]);let e=Object.keys(D.value);e.length>Y&&e.slice(0,e.length-Y).forEach(s=>delete D.value[s])},N=m("all"),H=m(""),F=m(""),pe=m(0),h=m(null),ve=m({start:0,end:0}),A=m(null),le=m(!1),_e=v(()=>{let t=0;return N.value!=="all"&&t++,A.value&&t++,H.value.trim()&&t++,t}),$e=()=>{N.value="all",A.value=null,H.value="",F.value="",le.value=!1},j=null,Z=null;M(H,t=>{clearTimeout(j),j=setTimeout(()=>{F.value=t,t.trim()&&window.trackClick&&window.trackClick("SearchUsed",{query:t.substring(0,50),resultCount:z.value.length,sessionId:p.value})},300)}),M(N,()=>{ge()}),M(F,()=>{ge()});let O=m([]),fe=m(!0),be=m(null),k=v(()=>O.value.filter(e=>e.type!=="assistant.turn_end"&&e.type!=="assistant.turn_complete").sort((e,a)=>{let s=e.timestamp?new Date(e.timestamp).getTime():0,n=a.timestamp?new Date(a.timestamp).getTime():0;return s!==n?s-n:(e._fileIndex??0)-(a._fileIndex??0)}).map((e,a)=>({...e,virtualIndex:a,stableId:e.id||`${e.timestamp}-${e.type}-${a}`}))),qe=t=>{if(!F.value.trim())return!0;let e=F.value.toLowerCase();return[t.data?.message,t.data?.text,t.data?.content,t.data?.reason,t.data?.reasoningText,t.data?.errorType,t.data?.previousModel,t.data?.newModel].filter(Boolean).join(" ").toLowerCase().includes(e)},z=v(()=>{let t=a=>{let s=a.type||"";return s!=="tool.execution_start"&&s!=="tool.execution_complete"},e=k.value.filter(t);return F.value.trim()&&(e=e.filter(qe)),e}),W=v(()=>{let t=z.value;if(A.value){let{ownerMap:s}=Q.value;t=g(t,A.value,s)}N.value!=="all"&&(t=t.filter(s=>s.type===N.value));let e=["assistant.turn_start","subagent.started","subagent.completed","subagent.failed"],a=t.length;return t.map((s,n)=>{let i=t[n+1],r=n===a-1,l=i&&e.includes(i.type);return{...s,filteredIndex:n,filteredTotal:a,isLastEvent:r||l}})}),Fe=v(()=>{let t={};return z.value.forEach(e=>{e.type&&(t[e.type]=(t[e.type]||0)+1)}),t}),Ue=v(()=>{if(!F.value.trim())return null;let t=z.value.length;return t>0?`${t} result${t!==1?"s":""}`:"No matches"}),ze=v(()=>{let t=Object.keys(L.value).filter(a=>L.value[a]).length,e=Object.keys(D.value).filter(a=>D.value[a]).length;return t+e}),Te=v(()=>{let t=z.value.length,e=[{type:"all",label:`All (${t})`,count:t}],a={};z.value.forEach(n=>{n.type&&(a[n.type]=(a[n.type]||0)+1)});let s=Object.entries(a).sort((n,i)=>i[1]-n[1]).map(([n,i])=>({type:n,label:`${n} (${i})`,count:i,disabled:!1}));return[...e,...s]}),G=v(()=>{let t=k.value.filter(a=>a.type==="assistant.turn_start"),e=k.value.filter(a=>a.type==="user.message");return t.map((a,s)=>{let n=s,i=new Date(a.timestamp).getTime(),r,l=t.indexOf(a)+1;l<t.length?r=new Date(t[l].timestamp).getTime():r=Date.now();let c=r-i,b=Math.floor(c/1e3),T=Math.floor(b/60),$=b%60,q=T>0?`${T}m ${$}s`:`${$}s`,V=k.value.slice(0,k.value.indexOf(a)).reverse().find(C=>C.type==="user.message"),te=V?e.indexOf(V)+1:0;return{id:n,index:a.virtualIndex,originalTurnId:a.data?.turnId,timestamp:a.timestamp,duration:q,message:V?.data?.content||V?.data?.transformedContent||"",userReqNumber:te}})}),Ve=v(()=>{let t=[],e=new Map;return G.value.forEach(a=>{let s=a.userReqNumber||0;if(!e.has(s)){let n={reqNumber:s,message:a.message,turns:[]};e.set(s,n),t.push(n)}e.get(s).turns.push(a)}),t}),Be=(t,e)=>t?t.length<=e?t:t.substring(0,e)+"\u2026":"",Q=v(()=>d(k.value)),Pe=v(()=>{let{subagentInfo:t}=Q.value;if(t.size===0)return[];let e=[];for(let[a,s]of t)e.push({toolCallId:a,name:s.name,colorIndex:s.colorIndex});return e}),He=v(()=>{if(!A.value)return null;let{ownerMap:t,subagentInfo:e}=Q.value,a=A.value;if(!e.has(a))return null;let s=0,n=null,i=null;for(let l of k.value){let c=(l.type==="subagent.started"||l.type==="subagent.completed"||l.type==="subagent.failed")&&l.data?.toolCallId===a,b=t.get(l.stableId)===a,T=l._subagent?.id===a,$=l.data?.subAgentId===a;if((c||b||T||$)&&(s++,l.timestamp!==null&&l.timestamp!==void 0)){let q=new Date(l.timestamp).getTime();(n===null||q<n)&&(n=q),(i===null||q>i)&&(i=q)}}let r=n===null||i===null?0:i-n;return{eventCount:s,durationMs:r}}),je=t=>{if(!t)return"";let e=new Date(t),a=String(e.getHours()).padStart(2,"0"),s=String(e.getMinutes()).padStart(2,"0"),n=String(e.getSeconds()).padStart(2,"0");return`${a}:${s}:${n}`},We=t=>{if(!t)return"";let e=new Date(t),a=String(e.getHours()).padStart(2,"0"),s=String(e.getMinutes()).padStart(2,"0"),n=String(e.getSeconds()).padStart(2,"0"),i=String(e.getMilliseconds()).padStart(3,"0");return`${a}:${s}:${n}.${i}`},S=new Map,he=200,Ge=t=>{if(!t)return"";if(S.has(t))return S.get(t);try{let e=t.replace(/\\r\\n/g,`
|
|
2
|
-
`).replace(/\\n/g,`
|
|
3
|
-
`).replace(/\\t/g," ").replace(/\\"/g,'"').replace(/\\\\/g,"\\"),a={ALLOWED_TAGS:["p","br","strong","em","code","pre","a","ul","ol","li","h1","h2","h3","h4","h5","h6","blockquote","table","thead","tbody","tr","th","td","hr","del","span","div","mark"],ALLOWED_ATTR:["href","style","class"],ALLOW_DATA_ATTR:!1,ALLOWED_URI_REGEXP:/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp):|[^a-z]|[a-z+.-]+(?:[^a-z+.\-:]|$))/i},s=e.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);if(s){let r=s[1],l=s[2],c=r.split(`
|
|
4
|
-
`),b=[],T=0;for(;T<c.length;){let C=c[T];if(!C.trim()||!C.includes(":")){T++;continue}let ae=C.indexOf(":"),se=C.substring(0,ae).trim(),ne=C.substring(ae+1).trim();if(ne==="|"||ne===">"){let Ne=[];for(T++;T<c.length&&(c[T].startsWith(" ")||c[T].startsWith(" ")||c[T].trim()==="");)Ne.push(c[T].trim()),T++;let _t=ne===">"?" ":`
|
|
5
|
-
`;b.push({key:se,value:Ne.filter($t=>$t).join(_t)})}else b.push({key:se,value:ne}),T++}let $='<table style="margin-bottom: 16px; border-collapse: collapse; width: 100%;"><tbody>';b.forEach(C=>{let ae=DOMPurify.sanitize(C.key,{ALLOWED_TAGS:[]}),se=DOMPurify.sanitize(C.value,{ALLOWED_TAGS:[]});$+=`<tr><td style="padding: 4px 12px; border: 1px solid #30363d; font-weight: 600; color: #7d8590;">${ae}</td><td style="padding: 4px 12px; border: 1px solid #30363d;">${se}</td></tr>`}),$+="</tbody></table>";let q=marked.parse(l),V=DOMPurify.sanitize(q,a),te=$+V;if(S.size>=he){let C=S.keys().next().value;S.delete(C)}return S.set(t,te),te}let n=marked.parse(e),i=DOMPurify.sanitize(n,a);if(S.size>=he){let r=S.keys().next().value;S.delete(r)}return S.set(t,i),i}catch{return t}},Ke=t=>{let e={...L.value},a=!!e[t];e[t]?delete e[t]:e[t]=!0,L.value=e,window.trackClick&&window.trackClick("EventExpanded",{eventType:"tool",action:a?"collapse":"expand",sessionId:p.value})},Je=(t,e)=>{if(!e||!e.trim()||!t)return t;let a=e.trim(),s=Ce(a).replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),n=document.createElement("div");n.innerHTML=t;let i=r=>{if(r.nodeType===Node.TEXT_NODE){let l=r.textContent,c=new RegExp(`(${s})`,"gi");if(c.test(l)){let b=l.replace(c,'<mark class="search-highlight">$1</mark>'),T=document.createElement("span");T.innerHTML=b,r.parentNode.replaceChild(T,r)}}else r.nodeType===Node.ELEMENT_NODE&&r.tagName!=="SCRIPT"&&r.tagName!=="STYLE"&&Array.from(r.childNodes).forEach(i)};return Array.from(n.childNodes).forEach(i),n.innerHTML},Xe=t=>{let e={...D.value},a=!!e[t];e[t]?delete e[t]:e[t]=!0,D.value=e,window.trackClick&&window.trackClick("EventExpanded",{eventType:"content",action:a?"collapse":"expand",sessionId:p.value})},Ye=t=>t?t.split(`
|
|
6
|
-
`).length>20||t.length>2e3:!1,Ze=t=>{let e=t.split(`
|
|
7
|
-
`);return e.length<=20?t:e.slice(0,20).join(`
|
|
8
|
-
`)+`
|
|
9
|
-
|
|
10
|
-
...`},Qe=(t,e)=>{if(e?.data?.badgeLabel&&e?.data?.badgeClass)return{label:e.data.badgeLabel,class:e.data.badgeClass};if(t==="message"&&e?.data?.role==="toolResult")return{label:"TOOL RESULT",class:"badge-tool"};if(t==="session.model_change")return{label:"MODEL CHANGE",class:"badge-session"};if(t==="session.truncation")return{label:"TRUNCATION",class:"badge-truncation"};if(t==="session.compaction_start"||t==="session.compaction_complete")return{label:"COMPACTION",class:"badge-compaction"};if(t==="system.notification")return{label:"SYSTEM",class:"badge-system"};let s=(t||"").split(".")[0]||"unknown";return{user:{label:"USER",class:"badge-user"},assistant:{label:"ASSISTANT",class:"badge-assistant"},reasoning:{label:"REASONING",class:"badge-reasoning"},turn:{label:"TURN",class:"badge-turn"},tool:{label:"TOOL",class:"badge-tool"},subagent:{label:"SUBAGENT",class:"badge-subagent"},skill:{label:"SKILL",class:"badge-skill"},session:{label:"SESSION",class:"badge-session"},error:{label:"ERROR",class:"badge-error"},abort:{label:"ABORT",class:"badge-error"}}[s]||{label:s.toUpperCase(),class:"badge-info"}},et=t=>{if(!t.complete)return{icon:"\u23F3",color:"tool-status-running",text:""};let e=t.complete.data||{};return e.error||e.isError?{icon:"\u274C",color:"tool-status-error",text:""}:{icon:"\u2713",color:"tool-status-success",text:""}},tt=t=>{if(!t.complete?.data?.error)return"";let e=t.complete.data.error;if(typeof e=="object"&&e.message)return e.message;if(typeof e=="string"){try{let a=JSON.parse(e);if(a.message)return a.message}catch{}return e}return String(e)},at=t=>{if(!t.complete)return"";let e=new Date(t.start.timestamp).getTime(),s=new Date(t.complete.timestamp).getTime()-e;return s>=100?`${parseFloat((s/1e3).toPrecision(3))}s`:""},zt=t=>{let e={};if(t.start?.timestamp&&(e.startTime=t.start.timestamp),t.complete?.timestamp&&(e.endTime=t.complete.timestamp),e.startTime&&e.endTime){let a=new Date(e.endTime).getTime()-new Date(e.startTime).getTime();a>=0&&(e.duration=`${parseFloat((a/1e3).toPrecision(3))}s (${a}ms)`)}return e},st=t=>{if(!t.start)return"";let e=t.start.data?.arguments||{},a=t.start.data?.toolName||t.tool||"",s="";if(a==="bash"||a==="exec")s=e.command||e.description||"";else if(a==="ask_user")s=e.question||e.message||"";else if(a==="read"||a==="write"||a==="edit")s=e.file_path||e.path||"";else if(a==="view")s=e.path||e.file||"";else if(a==="create")s=e.path||e.name||"";else if(a==="report_intent")s=e.intent||e.message||"";else if(a==="web_search")s=e.query||"";else if(a==="web_fetch")s=e.url||"";else if(a==="browser"){let n=e.action||"",i=e.targetUrl||e.url||"";s=i?`${n} ${i}`:n}else s=e.description||e.command||e.message||e.path||e.file_path||e.query||"";return s&&s.length>200&&(s=s.substring(0,200)+"..."),s},nt=t=>t.data?.tools&&t.data.tools.length>0,ot=t=>t.data?.tools&&Array.isArray(t.data.tools)?t.data.tools.filter(e=>e&&typeof e=="object"&&e.name).map(e=>{let a=e.result!==void 0||e.status==="completed"||e.status==="error",s={};if(e.startTime&&(s.startTime=e.startTime),e.endTime&&(s.endTime=e.endTime),s.startTime&&s.endTime){let n=new Date(s.endTime).getTime()-new Date(s.startTime).getTime();n>=0&&(s.duration=`${parseFloat((n/1e3).toPrecision(3))}s (${n}ms)`)}return{tool:e.name,timing:s,start:{timestamp:e.startTime,data:{toolName:e.name,arguments:e.input||e.arguments||{}}},complete:a?{timestamp:e.endTime,data:{result:e.result,error:e.status==="error"?e.error:null}}:null}}):[],ce=["#58a6ff","#f0883e","#a371f7","#3fb950","#f778ba","#79c0ff","#d29922","#56d4dd"],it=t=>{let e=0;for(let a=0;a<t.length;a++){let s=t.charCodeAt(a);e=(e<<5)-e+s,e=e&e}return e},ye=t=>{let{ownerMap:e,subagentInfo:a}=Q.value;if(t.type==="subagent.started"||t.type==="subagent.completed"||t.type==="subagent.failed"){let i=t.data?.toolCallId;if(i&&a.has(i)){let r=a.get(i);return{name:r.name,toolCallId:i,colorIndex:r.colorIndex}}return null}if(t._subagent){let i=t._subagent.id,r=t._subagent.name;if(a.has(i)){let l=a.get(i);return{name:l.name,toolCallId:i,colorIndex:l.colorIndex}}return{name:r,toolCallId:i,colorIndex:Math.abs(it(i))}}if(t.data?.subAgentId){let i=t.data.subAgentId,r=a.get(i);if(r)return{name:r.name,toolCallId:i,colorIndex:r.colorIndex}}let s=e.get(t.stableId);if(!s)return null;let n=a.get(s);return n?{name:n.name,toolCallId:s,colorIndex:n.colorIndex}:null},rt=t=>{let e=ye(t);return e?ce[e.colorIndex%ce.length]:null},lt=t=>{if(window.trackClick){let e=Te.value.find(a=>a.type===t);window.trackClick("EventFilterClicked",{filterType:t,filterLabel:e?e.label:t,sessionId:p.value})}N.value=t},ct=t=>{A.value=t,t&&(N.value="all"),window.trackClick&&window.trackClick("SubagentSelected",{subagent:t,sessionId:p.value})},xe=t=>{H.value="",N.value="all",A.value=null,pe.value=t.id,Vue.nextTick(()=>{if(h.value){let e=W.value.findIndex(a=>a.virtualIndex===t.index);if(e>=0){let a=s=>{s<=0||!h.value||(h.value.scrollToItem(e),setTimeout(()=>a(s-1),100))};setTimeout(()=>a(3),50)}}})},dt=()=>{if(!h.value)return;let t=e=>{e<=0||!h.value||(h.value.scrollToItem(0),setTimeout(()=>t(e-1),100))};t(3)},ut=()=>{if(!h.value)return;let t=W.value.length-1,e=a=>{a<=0||!h.value||(h.value.scrollToItem(t),setTimeout(()=>e(a-1),100))};e(5)},ke=t=>{window.trackClick&&window.trackClick("TurnClicked",{turnNumber:t,sessionId:p.value});let e=G.value.find(a=>a.id===t);if(e){let a=`UserReq${e.userReqNumber}_Turn${e.id}`,s=`${window.location.pathname}?eventType=assistant.turn_start&eventName=${a}`;window.history.pushState({},"",s),xe(e)}},mt=t=>{if(!t)return"";let e=t.replace(/\/$/,"").split("/");return e[e.length-1]||t},gt=t=>{let e=G.value.find(s=>s.index===t);if(!e)return"?";let a=e.originalTurnId??e.id;return e.userReqNumber>0?`${e.userReqNumber} - Turn ${a}`:`Turn ${a}`},pt=t=>G.value.find(a=>a.index===t)?.duration||null,Ce=t=>{let e=document.createElement("div");return e.textContent=t,e.innerHTML},vt=t=>t?new Date(t).toLocaleString():"N/A",ft=async()=>{console.log("[Export] exportSession called"),window.trackClick&&window.trackClick("ExportClicked",{sessionId:p.value}),re.value=!0;try{console.log("[Export] Fetching:",`/session/${p.value}/export`);let t=await fetch(`/session/${p.value}/export`);if(console.log("[Export] Response received:",t.status,t.ok),console.log("[Export] Response received:",t.status,t.ok),!t.ok)throw new Error("Share failed");console.log("[Export] Creating blob...");let e=await t.blob();console.log("[Export] Blob size:",e.size,"type:",e.type);let a=window.URL.createObjectURL(e);console.log("[Export] Creating download link...");let s=document.createElement("a");s.href=a,s.download=`session-${p.value}.zip`,document.body.appendChild(s),s.click(),console.log("[Export] Download triggered"),window.URL.revokeObjectURL(a),document.body.removeChild(s),console.log("[Export] Showing success feedback...");let n="\u{1F4E4} Share Session",i="\u2713 Downloaded!",r=document.querySelector(".export-btn");r&&(r.textContent=i,r.style.background="#238636",console.log("[Export] Button text updated to:",r.textContent),setTimeout(()=>{r.textContent=n,r.style.background="",console.log("[Export] Button text restored")},2e3))}catch(t){console.error("[Export] Share session error:",t),alert("Failed to share session: "+t.message)}finally{re.value=!1,console.log("[Export] Export complete")}},we=t=>{let e=document.querySelector(".filter-type-wrapper");e&&!e.contains(t.target)&&(le.value=!1)},Se=t=>{t.ctrlKey&&t.key==="b"&&(t.preventDefault(),X.value=!X.value)};u(()=>{document.removeEventListener("click",we),window.removeEventListener("keydown",Se),j&&(clearTimeout(j),j=null),Z&&(Z(),Z=null),L.value={},D.value={},S.clear()}),o(async()=>{document.addEventListener("click",we);try{console.log("[Navigation] Starting event loading...");let e=await fetch(`/api/sessions/${p.value}/events`);if(!e.ok)throw new Error(`Failed to load events: ${e.statusText}`);let a=await e.json();if(Array.isArray(a))O.value=a;else if(a.events&&Array.isArray(a.events))O.value=a.events,console.log("[Navigation] Pagination:",a.pagination);else throw new Error("Invalid response format");if(console.log("[Navigation] Events loaded:",O.value.length),O.value.length>0){let l=O.value[O.value.length-1],c=l.timestamp||l.time||l.data?.timestamp;c&&(x.value.updated=new Date(c))}let s=new URLSearchParams(window.location.search),n=s.get("eventType"),i=s.get("eventName"),r=s.get("eventTimestamp");console.log("[Navigation] URL params:",n,i,r),n&&i&&(console.log("[Navigation] Waiting for Vue to render..."),Vue.nextTick(()=>{console.log("[Navigation] nextTick - flatEvents count:",k.value?.length);let l=null;if(n==="assistant.turn_start"){let c=i.match(/UserReq(\d+)_Turn(\d+)/);if(c){let b=parseInt(c[2],10);if(!isNaN(b)){console.log("[Navigation] Jumping to turn:",b),ke(b);return}}}else n==="subagent.started"?(console.log("[Navigation] Searching for subagent:",i,"timestamp:",r),r&&(l=k.value.find(c=>c.type==="subagent.started"&&c.timestamp===r)),l||(l=k.value.find(c=>c.type==="subagent.started"&&(c.data?.agentDisplayName===i||c.data?.agentName===i||c.data?.label===i))),console.log("[Navigation] Target event found:",l?"YES":"NO","virtualIndex:",l?.virtualIndex)):l=k.value.find(c=>c.type===n);if(l){let c=W.value.findIndex(b=>b.virtualIndex===l.virtualIndex);if(console.log("[Navigation] Target in filteredEvents at index:",c),c>=0&&h.value){console.log("[Navigation] Scrolling to index:",c);let b=T=>{T<=0||!h.value||(h.value.scrollToItem(c),setTimeout(()=>b(T-1),100))};setTimeout(()=>b(3),50)}else console.log("[Navigation] Failed - targetIndex:",c,"scrollerRef:",!!h.value)}else console.log("[Navigation] Target event not found")}))}catch(e){console.error("Error loading events:",e),be.value=e.message}finally{fe.value=!1}window.addEventListener("keydown",Se),window.marked&&marked.setOptions({breaks:!0,gfm:!0});let t=()=>{if(!h.value)return;let e=null;if(h.value.$el&&typeof h.value.$el.querySelector=="function"?e=h.value.$el.querySelector(".vue-recycle-scroller"):h.value.querySelector&&typeof h.value.querySelector=="function"&&(e=h.value.querySelector(".vue-recycle-scroller")),e||(e=document.querySelector(".vue-recycle-scroller")),e){let a=e.scrollTop,s=e.clientHeight,n=80,i=Math.floor(a/n),r=Math.ceil(s/n),l=Math.min(i+r,W.value.length),c=Math.max(1,i+1),b=Math.max(1,l);ve.value={start:Math.min(c,b),end:b}}};setTimeout(()=>{t();let e=document.querySelector(".vue-recycle-scroller");e&&(e.addEventListener("scroll",t),Z=()=>{e.removeEventListener("scroll",t)})},500)});let K=m([]),de=m([]),J=m(!1),I=m([]),_=m(""),ue=m(null),E=m(""),U=m(!1),ee=m([]),Ie=m(0),bt=t=>!t||t===0?"0":t<1e3?t.toString():Math.floor(t/1e3)+"K",Tt=t=>{if(!t||t===0)return"0s";let e=Math.floor(t/1e3);if(e<60)return(t/1e3).toFixed(1)+"s";let a=Math.floor(e/60),s=e%60;return`${a}m ${s}s`},ht=v(()=>{if(!x.value.usage||!x.value.usage.modelMetrics)return 0;let t=0;for(let e in x.value.usage.modelMetrics){let a=x.value.usage.modelMetrics[e].usage;a&&(t+=(a.inputTokens||0)+(a.outputTokens||0))}return t}),yt=v(()=>{if(!x.value.usage||!x.value.usage.modelMetrics)return 0;let t=0;for(let e in x.value.usage.modelMetrics)t+=x.value.usage.modelMetrics[e].requests?.count||0;return t}),xt=v(()=>!x.value.usage||!x.value.usage.modelMetrics?0:Object.keys(x.value.usage.modelMetrics).length),kt=t=>{let e=x.value.usage?.modelMetrics[t];return!e||!e.usage?null:y(e.usage)},Ct=t=>{let e=x.value.usage?.modelMetrics[t];return!e||!e.usage?0:f(e.usage)},wt=v(()=>{let t=new Map;for(let e of k.value)if(e.data?.tools&&Array.isArray(e.data.tools))for(let a of e.data.tools)a&&a.name&&t.set(a.name,(t.get(a.name)||0)+1);return Array.from(t,([e,a])=>({name:e,count:a})).sort((e,a)=>a.count-e.count)}),St=t=>t==null?"":t+" premium",Ee=["#3b82f6","#10b981","#f59e0b","#ef4444","#8b5cf6","#ec4899","#06b6d4","#f97316"],It=t=>{let e=0;for(let a=0;a<t.length;a++)e=t.charCodeAt(a)+((e<<5)-e);return Ee[Math.abs(e)%Ee.length]},Et=async()=>{try{let t=await fetch(`/api/sessions/${p.value}/tags`);if(t.ok){let e=await t.json();K.value=e.tags||[]}}catch(t){console.error("Error loading tags:",t)}},Me=async()=>{try{let t=await fetch("/api/tags");if(t.ok){let e=await t.json();de.value=e.tags||[]}}catch(t){console.error("Error loading all tags:",t)}},Mt=async t=>{try{window.trackClick&&t.filter(s=>!K.value.includes(s)).forEach(s=>{window.trackClick("TagAdded",{sessionId:p.value,tag:s})});let e=await fetch(`/api/sessions/${p.value}/tags`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({tags:t})});if(e.ok){let a=await e.json();return K.value=a.tags||[],E.value="",!0}else{let a=await e.json();return E.value=a.error||"Failed to save tags",!1}}catch(e){return console.error("Error saving tags:",e),E.value="Network error",!1}},Rt=()=>{I.value=[...K.value],J.value=!0,E.value="",setTimeout(()=>{ue.value&&ue.value.focus()},10)},Nt=()=>{J.value=!1,I.value=[],_.value="",U.value=!1,E.value=""},Re=()=>{let t=_.value.trim().toLowerCase();if(t){if(t.length>30){E.value="Tag must be 30 characters or less";return}if(I.value.length>=10){E.value="Maximum 10 tags per session";return}if(I.value.includes(t)){E.value="Tag already added",_.value="";return}I.value.push(t),_.value="",U.value=!1,E.value=""}},At=t=>{I.value=I.value.filter(e=>e!==t),E.value=""},Lt=()=>{let t=_.value.trim().toLowerCase();if(!t){U.value=!1,ee.value=[];return}let e=de.value.filter(a=>a.toLowerCase().includes(t)&&!I.value.includes(a)).slice(0,5);e.length>0?(U.value=!0,ee.value=e,Ie.value=0):(U.value=!1,ee.value=[])},Dt=t=>{_.value=t,Re()},Ot=async()=>{setTimeout(async()=>{if(!J.value)return;await Mt(I.value)&&(J.value=!1,I.value=[],_.value="",U.value=!1,await Me())},200)};return o(async()=>{await Et(),await Me()}),{sessionId:p,metadata:x,exporting:re,sidebarCollapsed:X,expandedTools:L,expandedContent:D,expansionCount:ze,currentFilter:N,searchText:H,currentTurnIndex:pe,scrollerRef:h,visibleRange:ve,loadedEvents:O,eventsLoading:fe,eventsError:be,flatEvents:k,filteredEvents:W,eventCounts:Fe,filters:Te,turns:G,userReqs:Ve,truncateText:Be,formatTime:je,formatToolTime:We,formatDateTime:vt,renderMarkdown:Ge,highlightSearchText:Je,toggleTool:Ke,toggleContent:Xe,isContentTooLong:Ye,truncateContent:Ze,getBadgeInfo:Qe,getToolStatus:et,getToolErrorMessage:tt,getToolDuration:at,getToolCommand:st,hasTools:nt,getToolGroups:ot,getSubagentInfo:ye,getSubagentColor:rt,setFilter:lt,selectSubagent:ct,selectedSubagent:A,subagentList:Pe,subagentTokenUsage:He,SUBAGENT_COLORS:ce,typeFilterOpen:le,activeFilterCount:_e,clearAllFilters:$e,scrollToTurn:xe,scrollToTop:dt,scrollToBottom:ut,jumpToTurn:ke,getTurnNumber:gt,getTurnDuration:pt,repoBasename:mt,escapeHtml:Ce,exportSession:ft,searchResultCount:Ue,sessionTags:K,allTags:de,tagsEditing:J,editingTags:I,tagInputValue:_,tagInputRef:ue,tagsError:E,showAutocomplete:U,autocompleteOptions:ee,autocompleteSelectedIndex:Ie,getTagColor:It,startEditTags:Rt,cancelEditTags:Nt,addTag:Re,removeTagFromEdit:At,updateAutocomplete:Lt,selectAutocompleteOption:Dt,saveTagsOnBlur:Ot,formatTokens:bt,formatDuration:Tt,formatCost:St,totalTokens:ht,totalRequests:yt,totalModels:xt,getDisplayUsageInputTokens:Ct,getModelCacheHitRatio:kt,toolCallingSummary:wt}},template:`
|
|
11
|
-
<div class="container">
|
|
12
|
-
<div class="header">
|
|
13
|
-
<a href="/" class="home-btn">\u2190 Back to Home</a>
|
|
14
|
-
<h1>\u{1F4CB} Session: {{ sessionId }}
|
|
15
|
-
<span v-if="metadata.sessionStatus === 'wip'" style="font-size: 12px; padding: 2px 8px; border-radius: 3px; background: rgba(210, 153, 34, 0.2); color: #d29922; border: 1px solid rgba(210, 153, 34, 0.4); vertical-align: middle; margin-left: 8px;">\u{1F504} WIP</span>
|
|
16
|
-
</h1>
|
|
17
|
-
<div style="display: flex; gap: 10px;">
|
|
18
|
-
<a :href="'/session/' + sessionId + '/time-analyze'" class="time-analyze-btn" @click="trackClick && trackClick('TimeAnalyzeClicked', { sessionId: sessionId })">\u23F1 Analysis</a>
|
|
19
|
-
<button @click="exportSession" class="export-btn" :disabled="exporting">
|
|
20
|
-
{{ exporting ? '\u23F3 Sharing...' : '\u{1F4E4} Share Session' }}
|
|
21
|
-
</button>
|
|
22
|
-
</div>
|
|
23
|
-
</div>
|
|
24
|
-
|
|
25
|
-
<div class="main-layout">
|
|
26
|
-
<!-- Mobile overlay backdrop -->
|
|
27
|
-
<div
|
|
28
|
-
v-if="!sidebarCollapsed"
|
|
29
|
-
@click="sidebarCollapsed = true"
|
|
30
|
-
class="sidebar-backdrop"
|
|
31
|
-
></div>
|
|
32
|
-
<div :class="['sidebar', { collapsed: sidebarCollapsed }]">
|
|
33
|
-
<div class="sidebar-section">
|
|
34
|
-
<div class="sidebar-section-title">Session Info</div>
|
|
35
|
-
<div class="session-info">
|
|
36
|
-
<table class="session-info-table">
|
|
37
|
-
<tbody>
|
|
38
|
-
<tr v-if="metadata.source">
|
|
39
|
-
<td>Source</td>
|
|
40
|
-
<td>
|
|
41
|
-
<!-- Use backend-provided source metadata (Violation #3 fix) -->
|
|
42
|
-
<span :class="['source-badge', metadata.sourceBadgeClass || 'source-copilot']">
|
|
43
|
-
{{ metadata.sourceName || 'GitHub Copilot' }}
|
|
44
|
-
</span>
|
|
45
|
-
</td>
|
|
46
|
-
</tr>
|
|
47
|
-
<tr v-if="metadata.modernizeVersion">
|
|
48
|
-
<td>Version</td>
|
|
49
|
-
<td>{{ metadata.modernizeVersion }}</td>
|
|
50
|
-
</tr>
|
|
51
|
-
<tr v-if="metadata.source === 'modernize' && metadata.copilotVersion">
|
|
52
|
-
<td>Copilot SDK</td>
|
|
53
|
-
<td>{{ metadata.copilotVersion }}</td>
|
|
54
|
-
</tr>
|
|
55
|
-
<tr v-if="metadata.copilotVersion && metadata.source !== 'modernize'">
|
|
56
|
-
<td>Version</td>
|
|
57
|
-
<td>{{ metadata.copilotVersion }}</td>
|
|
58
|
-
</tr>
|
|
59
|
-
<tr v-if="metadata.model">
|
|
60
|
-
<td>Model</td>
|
|
61
|
-
<td>{{ metadata.model }}</td>
|
|
62
|
-
</tr>
|
|
63
|
-
<tr v-if="metadata.repo">
|
|
64
|
-
<td>Repo</td>
|
|
65
|
-
<td>{{ metadata.repo }}</td>
|
|
66
|
-
</tr>
|
|
67
|
-
<tr v-if="metadata.branch">
|
|
68
|
-
<td>Branch</td>
|
|
69
|
-
<td>{{ metadata.branch }}</td>
|
|
70
|
-
</tr>
|
|
71
|
-
<tr v-if="metadata.cwd && !metadata.repo">
|
|
72
|
-
<td>Repo</td>
|
|
73
|
-
<td>{{ metadata.cwd }}</td>
|
|
74
|
-
</tr>
|
|
75
|
-
<tr v-if="metadata.created">
|
|
76
|
-
<td>Created</td>
|
|
77
|
-
<td>{{ formatDateTime(metadata.created) }}</td>
|
|
78
|
-
</tr>
|
|
79
|
-
<tr v-if="metadata.updated">
|
|
80
|
-
<td>Updated</td>
|
|
81
|
-
<td>{{ formatDateTime(metadata.updated) }}</td>
|
|
82
|
-
</tr>
|
|
83
|
-
</tbody>
|
|
84
|
-
</table>
|
|
85
|
-
</div>
|
|
86
|
-
</div>
|
|
87
|
-
|
|
88
|
-
<!-- Usage Section -->
|
|
89
|
-
<div v-if="metadata.usage" class="sidebar-section">
|
|
90
|
-
<div class="sidebar-section-title">Token Usage</div>
|
|
91
|
-
<div class="usage-container">
|
|
92
|
-
<div class="usage-summary">
|
|
93
|
-
<div class="usage-summary-eyebrow">Overview</div>
|
|
94
|
-
<div class="usage-summary-total">
|
|
95
|
-
{{ formatTokens(totalTokens) }} <span class="usage-summary-total-unit">tokens</span>
|
|
96
|
-
</div>
|
|
97
|
-
<div class="usage-summary-caption">
|
|
98
|
-
Usage captured across {{ totalModels }} model{{ totalModels === 1 ? '' : 's' }}
|
|
99
|
-
</div>
|
|
100
|
-
<div class="usage-summary-metrics">
|
|
101
|
-
<div class="usage-metric-card usage-metric-card-summary">
|
|
102
|
-
<span class="usage-metric-label">Requests</span>
|
|
103
|
-
<span class="usage-metric-value">{{ totalRequests }} reqs</span>
|
|
104
|
-
</div>
|
|
105
|
-
<div class="usage-metric-card usage-metric-card-summary">
|
|
106
|
-
<span class="usage-metric-label">Models</span>
|
|
107
|
-
<span class="usage-metric-value">{{ totalModels }}</span>
|
|
108
|
-
</div>
|
|
109
|
-
<div class="usage-metric-card usage-metric-card-summary">
|
|
110
|
-
<span class="usage-metric-label">API Time</span>
|
|
111
|
-
<span class="usage-metric-value">{{ formatDuration(metadata.usage.totalApiDurationMs) }}</span>
|
|
112
|
-
</div>
|
|
113
|
-
</div>
|
|
114
|
-
</div>
|
|
115
|
-
|
|
116
|
-
<div class="usage-expanded">
|
|
117
|
-
<!-- Model breakdown -->
|
|
118
|
-
<div v-if="Object.keys(metadata.usage.modelMetrics).length > 0" class="usage-section">
|
|
119
|
-
<div class="usage-section-header">
|
|
120
|
-
<div class="usage-section-title">Models</div>
|
|
121
|
-
<div class="usage-section-badge">{{ totalModels }}</div>
|
|
122
|
-
</div>
|
|
123
|
-
<div class="usage-model-list">
|
|
124
|
-
<div v-for="(metrics, model) in metadata.usage.modelMetrics" :key="model" class="usage-model">
|
|
125
|
-
<div class="usage-model-header">
|
|
126
|
-
<div class="usage-model-name" :title="model">{{ model }}</div>
|
|
127
|
-
<div class="usage-model-meta">
|
|
128
|
-
<span class="usage-meta-pill">{{ metrics.requests?.count || 0 }} reqs</span>
|
|
129
|
-
<span v-if="metrics.requests?.cost" class="usage-meta-pill usage-meta-pill-premium">{{ formatCost(metrics.requests.cost) }}</span>
|
|
130
|
-
<span v-if="getModelCacheHitRatio(model) !== null" class="usage-meta-pill usage-meta-pill-cache">{{ getModelCacheHitRatio(model) }}% cache</span>
|
|
131
|
-
</div>
|
|
132
|
-
</div>
|
|
133
|
-
<div v-if="metrics.usage" class="usage-metric-grid">
|
|
134
|
-
<div class="usage-metric-card">
|
|
135
|
-
<span class="usage-metric-label">Input</span>
|
|
136
|
-
<span class="usage-metric-value">{{ formatTokens(getDisplayUsageInputTokens(model)) }}</span>
|
|
137
|
-
</div>
|
|
138
|
-
<div class="usage-metric-card">
|
|
139
|
-
<span class="usage-metric-label">Output</span>
|
|
140
|
-
<span class="usage-metric-value">{{ formatTokens(metrics.usage.outputTokens || 0) }}</span>
|
|
141
|
-
</div>
|
|
142
|
-
<div v-if="metrics.usage?.cacheReadTokens" class="usage-metric-card">
|
|
143
|
-
<span class="usage-metric-label">Cache Read</span>
|
|
144
|
-
<span class="usage-metric-value">{{ formatTokens(metrics.usage.cacheReadTokens) }}</span>
|
|
145
|
-
</div>
|
|
146
|
-
<div v-if="metrics.usage?.cacheWriteTokens" class="usage-metric-card">
|
|
147
|
-
<span class="usage-metric-label">Cache Write</span>
|
|
148
|
-
<span class="usage-metric-value">{{ formatTokens(metrics.usage.cacheWriteTokens) }}</span>
|
|
149
|
-
</div>
|
|
150
|
-
</div>
|
|
151
|
-
</div>
|
|
152
|
-
</div>
|
|
153
|
-
</div>
|
|
154
|
-
|
|
155
|
-
<!-- Context window breakdown -->
|
|
156
|
-
<div v-if="metadata.usage.currentTokens || metadata.usage.systemTokens || metadata.usage.conversationTokens || metadata.usage.toolDefinitionsTokens" class="usage-section">
|
|
157
|
-
<div class="usage-section-header">
|
|
158
|
-
<div class="usage-section-title">Context Window</div>
|
|
159
|
-
</div>
|
|
160
|
-
<div class="usage-metric-grid">
|
|
161
|
-
<div v-if="metadata.usage.currentTokens" class="usage-metric-card">
|
|
162
|
-
<span class="usage-metric-label">Current</span>
|
|
163
|
-
<span class="usage-metric-value">{{ formatTokens(metadata.usage.currentTokens) }}</span>
|
|
164
|
-
</div>
|
|
165
|
-
<div v-if="metadata.usage.systemTokens" class="usage-metric-card">
|
|
166
|
-
<span class="usage-metric-label">System</span>
|
|
167
|
-
<span class="usage-metric-value">{{ formatTokens(metadata.usage.systemTokens) }}</span>
|
|
168
|
-
</div>
|
|
169
|
-
<div v-if="metadata.usage.conversationTokens" class="usage-metric-card">
|
|
170
|
-
<span class="usage-metric-label">Conversation</span>
|
|
171
|
-
<span class="usage-metric-value">{{ formatTokens(metadata.usage.conversationTokens) }}</span>
|
|
172
|
-
</div>
|
|
173
|
-
<div v-if="metadata.usage.toolDefinitionsTokens" class="usage-metric-card">
|
|
174
|
-
<span class="usage-metric-label">Tools</span>
|
|
175
|
-
<span class="usage-metric-value">{{ formatTokens(metadata.usage.toolDefinitionsTokens) }}</span>
|
|
176
|
-
</div>
|
|
177
|
-
</div>
|
|
178
|
-
</div>
|
|
179
|
-
|
|
180
|
-
<!-- Code changes -->
|
|
181
|
-
<div v-if="metadata.usage.codeChanges && (metadata.usage.codeChanges.linesAdded > 0 || metadata.usage.codeChanges.linesRemoved > 0)" class="usage-section">
|
|
182
|
-
<div class="usage-section-header">
|
|
183
|
-
<div class="usage-section-title">Code Changes</div>
|
|
184
|
-
</div>
|
|
185
|
-
<div class="usage-metric-grid usage-metric-grid-compact">
|
|
186
|
-
<div class="usage-metric-card">
|
|
187
|
-
<span class="usage-metric-label">Added</span>
|
|
188
|
-
<span class="usage-metric-value usage-metric-value-added">+{{ metadata.usage.codeChanges.linesAdded }}</span>
|
|
189
|
-
</div>
|
|
190
|
-
<div class="usage-metric-card">
|
|
191
|
-
<span class="usage-metric-label">Removed</span>
|
|
192
|
-
<span class="usage-metric-value usage-metric-value-removed">-{{ metadata.usage.codeChanges.linesRemoved }}</span>
|
|
193
|
-
</div>
|
|
194
|
-
<div class="usage-metric-card">
|
|
195
|
-
<span class="usage-metric-label">Files</span>
|
|
196
|
-
<span class="usage-metric-value">{{ metadata.usage.codeChanges.filesModified?.length || 0 }}</span>
|
|
197
|
-
</div>
|
|
198
|
-
</div>
|
|
199
|
-
</div>
|
|
200
|
-
</div>
|
|
201
|
-
</div>
|
|
202
|
-
</div>
|
|
203
|
-
|
|
204
|
-
<!-- Tool Calling Summary -->
|
|
205
|
-
<div v-if="toolCallingSummary.length" class="sidebar-section">
|
|
206
|
-
<div class="sidebar-section-title">Tool Calls</div>
|
|
207
|
-
<div class="tool-summary-list">
|
|
208
|
-
<div v-for="item in toolCallingSummary" :key="item.name" class="tool-summary-item">
|
|
209
|
-
<div class="tool-summary-bar" :style="{ width: (item.count / toolCallingSummary[0].count * 100) + '%' }"></div>
|
|
210
|
-
<span class="tool-summary-name" :title="item.name">{{ item.name }}</span>
|
|
211
|
-
<span class="tool-summary-count">{{ item.count }}</span>
|
|
212
|
-
</div>
|
|
213
|
-
</div>
|
|
214
|
-
</div>
|
|
215
|
-
|
|
216
|
-
<!-- Session Tags -->
|
|
217
|
-
<div class="sidebar-section session-tags-container">
|
|
218
|
-
<div class="sidebar-section-title">Tags</div>
|
|
219
|
-
<div v-if="!tagsEditing" class="tags-display">
|
|
220
|
-
<span
|
|
221
|
-
v-for="tag in sessionTags"
|
|
222
|
-
:key="tag"
|
|
223
|
-
class="tag-label"
|
|
224
|
-
:style="{ backgroundColor: getTagColor(tag) }"
|
|
225
|
-
>
|
|
226
|
-
{{ tag }}
|
|
227
|
-
</span>
|
|
228
|
-
<button class="tags-edit-btn" @click="startEditTags" title="Edit tags">
|
|
229
|
-
\u270F\uFE0F
|
|
230
|
-
</button>
|
|
231
|
-
</div>
|
|
232
|
-
<div v-else class="tags-dropdown">
|
|
233
|
-
<div class="tags-input-container">
|
|
234
|
-
<span
|
|
235
|
-
v-for="tag in editingTags"
|
|
236
|
-
:key="tag"
|
|
237
|
-
class="tag-input-chip"
|
|
238
|
-
:style="{ backgroundColor: getTagColor(tag) }"
|
|
239
|
-
>
|
|
240
|
-
{{ tag }}
|
|
241
|
-
<button @click="removeTagFromEdit(tag)" title="Remove tag">\xD7</button>
|
|
242
|
-
</span>
|
|
243
|
-
<input
|
|
244
|
-
ref="tagInputRef"
|
|
245
|
-
v-model="tagInputValue"
|
|
246
|
-
@keydown.enter.prevent="addTag"
|
|
247
|
-
@keydown.escape="cancelEditTags"
|
|
248
|
-
@blur="saveTagsOnBlur"
|
|
249
|
-
@input="updateAutocomplete"
|
|
250
|
-
class="tags-text-input"
|
|
251
|
-
placeholder="Type tag name..."
|
|
252
|
-
maxlength="30"
|
|
253
|
-
/>
|
|
254
|
-
</div>
|
|
255
|
-
<div v-if="showAutocomplete && autocompleteOptions.length > 0" class="tags-autocomplete">
|
|
256
|
-
<div
|
|
257
|
-
v-for="(option, index) in autocompleteOptions"
|
|
258
|
-
:key="option"
|
|
259
|
-
:class="['tags-autocomplete-item', { selected: index === autocompleteSelectedIndex }]"
|
|
260
|
-
@click="selectAutocompleteOption(option)"
|
|
261
|
-
@mouseenter="autocompleteSelectedIndex = index"
|
|
262
|
-
>
|
|
263
|
-
{{ option }}
|
|
264
|
-
</div>
|
|
265
|
-
</div>
|
|
266
|
-
<div v-if="tagsError" class="tags-error">{{ tagsError }}</div>
|
|
267
|
-
</div>
|
|
268
|
-
</div>
|
|
269
|
-
</div>
|
|
270
|
-
|
|
271
|
-
<div class="content">
|
|
272
|
-
<div class="unified-filter-bar">
|
|
273
|
-
<div class="filter-bar-row">
|
|
274
|
-
<button
|
|
275
|
-
class="sidebar-toggle"
|
|
276
|
-
@click="() => { sidebarCollapsed = !sidebarCollapsed; trackClick && trackClick('SidebarToggled', { state: sidebarCollapsed ? 'open' : 'collapsed', sessionId: sessionId }); }"
|
|
277
|
-
:title="sidebarCollapsed ? 'Expand sidebar' : 'Collapse sidebar'"
|
|
278
|
-
>
|
|
279
|
-
\u2630
|
|
280
|
-
</button>
|
|
281
|
-
|
|
282
|
-
<div class="filter-bar-search">
|
|
283
|
-
<input
|
|
284
|
-
v-model="searchText"
|
|
285
|
-
type="text"
|
|
286
|
-
placeholder="\u{1F50D} Search events..."
|
|
287
|
-
class="search-input"
|
|
288
|
-
/>
|
|
289
|
-
<span v-if="searchResultCount" class="search-result-count">
|
|
290
|
-
{{ searchResultCount }}
|
|
291
|
-
</span>
|
|
292
|
-
</div>
|
|
293
|
-
|
|
294
|
-
<div class="filter-bar-divider"></div>
|
|
295
|
-
|
|
296
|
-
<!-- Turn dropdown with optgroup -->
|
|
297
|
-
<select
|
|
298
|
-
v-if="turns.length > 0"
|
|
299
|
-
v-model="currentTurnIndex"
|
|
300
|
-
@change="jumpToTurn(currentTurnIndex)"
|
|
301
|
-
class="turn-dropdown"
|
|
302
|
-
>
|
|
303
|
-
<optgroup
|
|
304
|
-
v-for="req in userReqs"
|
|
305
|
-
:key="req.reqNumber"
|
|
306
|
-
:label="req.reqNumber > 0 ? 'UserReq ' + req.reqNumber + ': ' + truncateText(req.message, 40) : 'Setup'"
|
|
307
|
-
>
|
|
308
|
-
<option v-for="turn in req.turns" :key="turn.id" :value="turn.id">
|
|
309
|
-
Turn {{ turn.originalTurnId ?? turn.id }} ({{ turn.duration }})
|
|
310
|
-
</option>
|
|
311
|
-
</optgroup>
|
|
312
|
-
</select>
|
|
313
|
-
|
|
314
|
-
<div class="filter-bar-divider"></div>
|
|
315
|
-
|
|
316
|
-
<!-- Subagent selector -->
|
|
317
|
-
<div v-if="subagentList.length > 0" class="subagent-selector">
|
|
318
|
-
<select
|
|
319
|
-
:value="selectedSubagent || ''"
|
|
320
|
-
@change="selectSubagent($event.target.value || null)"
|
|
321
|
-
class="subagent-dropdown"
|
|
322
|
-
>
|
|
323
|
-
<option value="">\u{1F916} All Agents</option>
|
|
324
|
-
<option v-for="sa in subagentList" :key="sa.toolCallId" :value="sa.toolCallId">
|
|
325
|
-
\u{1F916} {{ sa.name }}
|
|
326
|
-
</option>
|
|
327
|
-
</select>
|
|
328
|
-
<span v-if="subagentTokenUsage" class="subagent-usage-badge">
|
|
329
|
-
{{ subagentTokenUsage.eventCount }} events \xB7 {{ formatDuration(subagentTokenUsage.durationMs) }}
|
|
330
|
-
</span>
|
|
331
|
-
</div>
|
|
332
|
-
|
|
333
|
-
<div class="filter-bar-divider"></div>
|
|
334
|
-
|
|
335
|
-
<!-- Event type dropdown -->
|
|
336
|
-
<div class="filter-type-wrapper">
|
|
337
|
-
<button
|
|
338
|
-
class="filter-type-toggle"
|
|
339
|
-
:class="{ active: currentFilter !== 'all' }"
|
|
340
|
-
@click.stop="typeFilterOpen = !typeFilterOpen"
|
|
341
|
-
>
|
|
342
|
-
\u26A1 {{ currentFilter === 'all' ? 'All Types' : currentFilter }} \u25BE
|
|
343
|
-
</button>
|
|
344
|
-
<div v-if="typeFilterOpen" class="filter-type-menu">
|
|
345
|
-
<div class="filter-type-menu-header">Event Types</div>
|
|
346
|
-
<div class="filter-type-menu-options">
|
|
347
|
-
<div
|
|
348
|
-
v-for="filter in filters"
|
|
349
|
-
:key="filter.type"
|
|
350
|
-
:class="['filter-type-menu-item', { active: currentFilter === filter.type }]"
|
|
351
|
-
@click="setFilter(filter.type); typeFilterOpen = false"
|
|
352
|
-
>
|
|
353
|
-
<span class="filter-type-menu-label">{{ filter.type === 'all' ? 'All' : filter.type }}</span>
|
|
354
|
-
<span class="filter-type-menu-count">{{ filter.count }}</span>
|
|
355
|
-
</div>
|
|
356
|
-
</div>
|
|
357
|
-
</div>
|
|
358
|
-
</div>
|
|
359
|
-
</div>
|
|
360
|
-
|
|
361
|
-
<!-- Active filter chips -->
|
|
362
|
-
<div v-if="activeFilterCount > 0" class="active-filters-bar">
|
|
363
|
-
<span v-if="currentFilter !== 'all'" class="filter-chip">
|
|
364
|
-
Type: {{ currentFilter }}
|
|
365
|
-
<button @click="setFilter('all')" class="filter-chip-remove" title="Remove filter">\xD7</button>
|
|
366
|
-
</span>
|
|
367
|
-
<span v-if="selectedSubagent" class="filter-chip">
|
|
368
|
-
Agent: {{ subagentList.find(s => s.toolCallId === selectedSubagent)?.name || selectedSubagent }}
|
|
369
|
-
<button @click="selectSubagent(null)" class="filter-chip-remove" title="Remove filter">\xD7</button>
|
|
370
|
-
</span>
|
|
371
|
-
<span v-if="searchText.trim()" class="filter-chip">
|
|
372
|
-
Search: "{{ searchText.length > 20 ? searchText.substring(0, 20) + '\u2026' : searchText }}"
|
|
373
|
-
<button @click="searchText = ''" class="filter-chip-remove" title="Remove filter">\xD7</button>
|
|
374
|
-
</span>
|
|
375
|
-
<button class="clear-all-filters-btn" @click="clearAllFilters">Clear all</button>
|
|
376
|
-
</div>
|
|
377
|
-
</div>
|
|
378
|
-
|
|
379
|
-
<!-- Loading state -->
|
|
380
|
-
<div v-if="eventsLoading" class="loading-message">
|
|
381
|
-
<div style="text-align: center; padding: 40px; color: #c9d1d9;">
|
|
382
|
-
\u23F3 Loading events...
|
|
383
|
-
</div>
|
|
384
|
-
</div>
|
|
385
|
-
|
|
386
|
-
<!-- Error state -->
|
|
387
|
-
<div v-else-if="eventsError" class="error-message">
|
|
388
|
-
<div style="text-align: center; padding: 40px; color: #f85149;">
|
|
389
|
-
\u274C Error loading events: {{ eventsError }}
|
|
390
|
-
</div>
|
|
391
|
-
</div>
|
|
392
|
-
|
|
393
|
-
<!-- Events list -->
|
|
394
|
-
<DynamicScroller
|
|
395
|
-
v-else
|
|
396
|
-
ref="scrollerRef"
|
|
397
|
-
:items="filteredEvents"
|
|
398
|
-
:min-item-size="80"
|
|
399
|
-
:prerender="10"
|
|
400
|
-
key-field="stableId"
|
|
401
|
-
class="scroller"
|
|
402
|
-
>
|
|
403
|
-
<template #default="{ item, index, active }">
|
|
404
|
-
<DynamicScrollerItem
|
|
405
|
-
:item="item"
|
|
406
|
-
:active="active"
|
|
407
|
-
:size-dependencies="[expansionCount]"
|
|
408
|
-
:data-index="index"
|
|
409
|
-
>
|
|
410
|
-
<!-- Turn Start Divider -->
|
|
411
|
-
<div
|
|
412
|
-
v-if="item.type === 'assistant.turn_start'"
|
|
413
|
-
:data-type="item.type"
|
|
414
|
-
:data-index="item.virtualIndex"
|
|
415
|
-
class="turn-divider"
|
|
416
|
-
>
|
|
417
|
-
<div class="turn-divider-line-left"></div>
|
|
418
|
-
<span class="turn-divider-text">
|
|
419
|
-
UserReq {{ getTurnNumber(item.virtualIndex) }}
|
|
420
|
-
<template v-if="metadata.source === 'vscode'">
|
|
421
|
-
<span class="turn-time">{{ formatTime(item.timestamp) }}</span>
|
|
422
|
-
<span v-if="getTurnDuration(item.virtualIndex)" class="turn-duration">{{ getTurnDuration(item.virtualIndex) }}</span>
|
|
423
|
-
</template>
|
|
424
|
-
<template v-else>Start</template>
|
|
425
|
-
</span>
|
|
426
|
-
<div class="turn-divider-line-right"></div>
|
|
427
|
-
<div class="divider-separator"></div>
|
|
428
|
-
</div>
|
|
429
|
-
|
|
430
|
-
<!-- Subagent Divider -->
|
|
431
|
-
<div
|
|
432
|
-
v-else-if="item.type === 'subagent.started' || item.type === 'subagent.completed' || item.type === 'subagent.failed'"
|
|
433
|
-
:data-type="item.type"
|
|
434
|
-
:data-index="item.virtualIndex"
|
|
435
|
-
:class="['subagent-divider', item.type.split('.')[1]]"
|
|
436
|
-
:style="{
|
|
437
|
-
'--sa-color': getSubagentColor(item) || '#58a6ff'
|
|
438
|
-
}"
|
|
439
|
-
>
|
|
440
|
-
<div class="subagent-divider-line-left" :style="{ background: getSubagentColor(item) || '#58a6ff' }"></div>
|
|
441
|
-
<span class="subagent-divider-text" :style="{ color: getSubagentColor(item) || '#58a6ff', borderColor: getSubagentColor(item) || '#58a6ff', background: (getSubagentColor(item) || '#58a6ff') + '1a' }">
|
|
442
|
-
\u{1F916} {{ item.data?.agentDisplayName || item.data?.agentName || 'SubAgent' }}
|
|
443
|
-
{{ item.type === 'subagent.started' ? 'Start \u25B6' : item.type === 'subagent.completed' ? 'Complete \u2713' : 'Failed \u2717' }}
|
|
444
|
-
</span>
|
|
445
|
-
<div class="subagent-divider-line-right" :style="{ background: getSubagentColor(item) || '#58a6ff' }"></div>
|
|
446
|
-
<div class="divider-separator"></div>
|
|
447
|
-
</div>
|
|
448
|
-
|
|
449
|
-
<!-- Regular Event -->
|
|
450
|
-
<div
|
|
451
|
-
v-else
|
|
452
|
-
:class="['event', getSubagentInfo(item) ? 'event-in-subagent' : '']"
|
|
453
|
-
:data-type="item.type"
|
|
454
|
-
:data-index="item.virtualIndex"
|
|
455
|
-
:style="getSubagentColor(item) ? { '--subagent-border-color': getSubagentColor(item) } : {}"
|
|
456
|
-
>
|
|
457
|
-
<div class="event-header">
|
|
458
|
-
<span :class="['event-badge', getBadgeInfo(item.type, item).class]">
|
|
459
|
-
{{ getBadgeInfo(item.type, item).label }}
|
|
460
|
-
</span>
|
|
461
|
-
<span
|
|
462
|
-
v-if="getSubagentInfo(item)"
|
|
463
|
-
class="subagent-owner-tag"
|
|
464
|
-
:style="{ '--subagent-color': getSubagentColor(item) || '#58a6ff', '--subagent-hover-bg': ((getSubagentColor(item) || '#58a6ff') + '26') }"
|
|
465
|
-
:title="'Filter to ' + getSubagentInfo(item).name"
|
|
466
|
-
@click.stop="selectSubagent(getSubagentInfo(item).toolCallId)"
|
|
467
|
-
>\u{1F916} {{ getSubagentInfo(item).name }}</span>
|
|
468
|
-
<span v-if="metadata.source !== 'vscode'" class="event-timestamp">{{ formatTime(item.timestamp) }}</span>
|
|
469
|
-
</div>
|
|
470
|
-
|
|
471
|
-
<!-- Abort event: show reason -->
|
|
472
|
-
<div v-if="item.type === 'abort' && item.data?.reason" class="event-content">
|
|
473
|
-
<strong>Reason:</strong> {{ item.data.reason }}
|
|
474
|
-
</div>
|
|
475
|
-
|
|
476
|
-
<!-- Session start: show type and selectedModel -->
|
|
477
|
-
<div v-else-if="item.type === 'session.start'" class="event-content">
|
|
478
|
-
<div v-if="item.data?.type"><strong>Type:</strong> {{ item.data.type }}</div>
|
|
479
|
-
<div v-if="item.data?.selectedModel"><strong>Model:</strong> {{ item.data.selectedModel }}</div>
|
|
480
|
-
<div v-if="item.data?.producer"><strong>Producer:</strong> {{ item.data.producer }}</div>
|
|
481
|
-
</div>
|
|
482
|
-
|
|
483
|
-
<!-- Session resume: show resumeTime, eventCount, context -->
|
|
484
|
-
<div v-else-if="item.type === 'session.resume'" class="event-content">
|
|
485
|
-
<div v-if="item.data?.resumeTime"><strong>Resume Time:</strong> {{ formatDateTime(item.data.resumeTime) }}</div>
|
|
486
|
-
<div v-if="item.data?.eventCount"><strong>Event Count:</strong> {{ item.data.eventCount }}</div>
|
|
487
|
-
<div v-if="item.data?.context?.branch"><strong>Branch:</strong> {{ item.data.context.branch }}</div>
|
|
488
|
-
<div v-if="item.data?.context?.repository"><strong>Repository:</strong> {{ item.data.context.repository }}</div>
|
|
489
|
-
<div v-if="item.data?.context?.cwd"><strong>Working Directory:</strong> {{ item.data.context.cwd }}</div>
|
|
490
|
-
</div>
|
|
491
|
-
|
|
492
|
-
<!-- Session error: show errorType + message -->
|
|
493
|
-
<div v-else-if="item.type === 'session.error' && (item.data?.errorType || item.data?.message)" class="event-content">
|
|
494
|
-
<div v-if="item.data?.errorType"><strong>Error Type:</strong> {{ item.data.errorType }}</div>
|
|
495
|
-
<div v-if="item.data?.message"><strong>Message:</strong> {{ item.data.message }}</div>
|
|
496
|
-
</div>
|
|
497
|
-
|
|
498
|
-
<!-- Model change: show previousModel \u2192 newModel -->
|
|
499
|
-
<div v-else-if="item.type === 'session.model_change'" class="event-content model-change-content">
|
|
500
|
-
<div v-if="item.data?.previousModel && item.data?.newModel" class="model-change-text">
|
|
501
|
-
<span class="model-name">{{ item.data.previousModel }}</span>
|
|
502
|
-
<span class="model-arrow">\u2192</span>
|
|
503
|
-
<span class="model-name">{{ item.data.newModel }}</span>
|
|
504
|
-
</div>
|
|
505
|
-
<div v-else-if="item.data?.newModel" class="model-change-text">
|
|
506
|
-
Switched to <span class="model-name">{{ item.data.newModel }}</span>
|
|
507
|
-
</div>
|
|
508
|
-
<div v-else-if="item.data?.model" class="model-change-text">
|
|
509
|
-
Switched to <span class="model-name">{{ item.data.model }}</span>
|
|
510
|
-
</div>
|
|
511
|
-
<div v-else class="model-change-text">
|
|
512
|
-
Model changed
|
|
513
|
-
</div>
|
|
514
|
-
</div>
|
|
515
|
-
|
|
516
|
-
<!-- Session truncation: show token/message removal info -->
|
|
517
|
-
<div v-else-if="item.type === 'system.notification'" class="event-content" style="opacity:0.7">
|
|
518
|
-
<span>{{ item.data?.message }}</span>
|
|
519
|
-
</div>
|
|
520
|
-
|
|
521
|
-
<div v-else-if="item.type === 'session.truncation'" class="event-content">
|
|
522
|
-
<div v-if="item.data?.messagesRemovedDuringTruncation"><strong>Messages removed:</strong> {{ item.data.messagesRemovedDuringTruncation }}</div>
|
|
523
|
-
<div v-if="item.data?.tokensRemovedDuringTruncation"><strong>Tokens removed:</strong> {{ item.data.tokensRemovedDuringTruncation.toLocaleString() }}</div>
|
|
524
|
-
<div v-if="item.data?.preTruncationTokensInMessages"><strong>Pre-truncation tokens:</strong> {{ item.data.preTruncationTokensInMessages.toLocaleString() }}</div>
|
|
525
|
-
<div v-if="item.data?.postTruncationMessagesLength"><strong>Post-truncation messages:</strong> {{ item.data.postTruncationMessagesLength }}</div>
|
|
526
|
-
<div v-if="item.data?.performedBy"><strong>Performed by:</strong> {{ item.data.performedBy }}</div>
|
|
527
|
-
</div>
|
|
528
|
-
|
|
529
|
-
<!-- Session compaction start -->
|
|
530
|
-
<div v-else-if="item.type === 'session.compaction_start'" class="event-content">
|
|
531
|
-
Context compaction started
|
|
532
|
-
</div>
|
|
533
|
-
|
|
534
|
-
<!-- Session compaction complete: show results -->
|
|
535
|
-
<div v-else-if="item.type === 'session.compaction_complete'" class="event-content">
|
|
536
|
-
<div v-if="item.data?.success != null"><strong>Success:</strong> {{ item.data.success ? '\u2713' : '\u2717' }}</div>
|
|
537
|
-
<div v-if="item.data?.compactionTokensUsed">
|
|
538
|
-
<strong>Tokens used:</strong>
|
|
539
|
-
input {{ item.data.compactionTokensUsed.input?.toLocaleString() || 0 }},
|
|
540
|
-
output {{ item.data.compactionTokensUsed.output?.toLocaleString() || 0 }}
|
|
541
|
-
<span v-if="item.data.compactionTokensUsed.cachedInput">, cached {{ item.data.compactionTokensUsed.cachedInput.toLocaleString() }}</span>
|
|
542
|
-
</div>
|
|
543
|
-
<div v-if="item.data?.preCompactionMessagesLength"><strong>Pre-compaction messages:</strong> {{ item.data.preCompactionMessagesLength }}</div>
|
|
544
|
-
<div v-if="item.data?.preCompactionTokens"><strong>Pre-compaction tokens:</strong> {{ item.data.preCompactionTokens.toLocaleString() }}</div>
|
|
545
|
-
<div v-if="item.data?.summaryContent" style="margin-top: 8px;">
|
|
546
|
-
<button
|
|
547
|
-
@click="toggleContent('compaction-' + item.stableId)"
|
|
548
|
-
style="background: none; border: 1px solid #30363d; color: #58a6ff; padding: 4px 12px; border-radius: 4px; cursor: pointer; font-size: 13px;"
|
|
549
|
-
>
|
|
550
|
-
{{ expandedContent['compaction-' + item.stableId] ? 'Hide summary \u25B2' : 'Show summary \u25BC' }}
|
|
551
|
-
</button>
|
|
552
|
-
<div v-if="expandedContent['compaction-' + item.stableId]" class="event-content" style="margin-top: 8px;" v-html="renderMarkdown(item.data.summaryContent)"></div>
|
|
553
|
-
</div>
|
|
554
|
-
</div>
|
|
555
|
-
|
|
556
|
-
<!-- Regular content (unified format from server) -->
|
|
557
|
-
<div v-else-if="item.data?.message || item.data?.text || item.data?.content || item.data?.transformedContent">
|
|
558
|
-
<div
|
|
559
|
-
class="event-content"
|
|
560
|
-
v-html="highlightSearchText(
|
|
561
|
-
renderMarkdown(
|
|
562
|
-
(expandedContent[item.stableId] || !isContentTooLong(item.data?.message || item.data?.text || item.data?.content || item.data?.transformedContent))
|
|
563
|
-
? (item.data?.message || item.data?.text || item.data?.content || item.data?.transformedContent)
|
|
564
|
-
: truncateContent(item.data?.message || item.data?.text || item.data?.content || item.data?.transformedContent)
|
|
565
|
-
),
|
|
566
|
-
searchText
|
|
567
|
-
)"
|
|
568
|
-
></div>
|
|
569
|
-
<div
|
|
570
|
-
v-if="isContentTooLong(item.data?.message || item.data?.text || item.data?.content || item.data?.transformedContent)"
|
|
571
|
-
style="margin-top: 8px;"
|
|
572
|
-
>
|
|
573
|
-
<button
|
|
574
|
-
@click="toggleContent(item.stableId)"
|
|
575
|
-
:data-content-id="item.stableId"
|
|
576
|
-
style="background: none; border: 1px solid #30363d; color: #58a6ff; padding: 4px 12px; border-radius: 4px; cursor: pointer; font-size: 13px;"
|
|
577
|
-
>
|
|
578
|
-
{{ expandedContent[item.stableId] ? 'Show less \u25B2' : 'Show more \u25BC' }}
|
|
579
|
-
</button>
|
|
580
|
-
</div>
|
|
581
|
-
</div>
|
|
582
|
-
|
|
583
|
-
<!-- No content at all (no message and no tools) -->
|
|
584
|
-
<div v-else-if="!hasTools(item) && !item.data?.reasoningText" class="event-content" style="color: #7d8590; font-style: italic;">
|
|
585
|
-
No available message
|
|
586
|
-
</div>
|
|
587
|
-
|
|
588
|
-
<!-- Reasoning text (shown after main content, before tool calls) -->
|
|
589
|
-
<div v-if="item.data?.reasoningText" class="event-content reasoning-text-content">
|
|
590
|
-
<div
|
|
591
|
-
v-html="highlightSearchText(
|
|
592
|
-
renderMarkdown(
|
|
593
|
-
(expandedContent[item.stableId + '-reasoning'] || !isContentTooLong(item.data.reasoningText))
|
|
594
|
-
? item.data.reasoningText
|
|
595
|
-
: truncateContent(item.data.reasoningText)
|
|
596
|
-
),
|
|
597
|
-
searchText
|
|
598
|
-
)"
|
|
599
|
-
></div>
|
|
600
|
-
<div v-if="isContentTooLong(item.data.reasoningText)" style="margin-top: 8px;">
|
|
601
|
-
<button
|
|
602
|
-
@click="toggleContent(item.stableId + '-reasoning')"
|
|
603
|
-
style="background: none; border: 1px solid #30363d; color: #58a6ff; padding: 4px 12px; border-radius: 4px; cursor: pointer; font-size: 13px;"
|
|
604
|
-
>
|
|
605
|
-
{{ expandedContent[item.stableId + '-reasoning'] ? 'Show less \u25B2' : 'Show more \u25BC' }}
|
|
606
|
-
</button>
|
|
607
|
-
</div>
|
|
608
|
-
</div>
|
|
609
|
-
|
|
610
|
-
<!-- Tool calls section (independent of message content, but don't need "No available message" if tools exist) -->
|
|
611
|
-
<div v-if="hasTools(item)" class="tool-list">
|
|
612
|
-
<div
|
|
613
|
-
v-for="(group, idx) in getToolGroups(item)"
|
|
614
|
-
:key="idx"
|
|
615
|
-
class="tool-item"
|
|
616
|
-
>
|
|
617
|
-
<div
|
|
618
|
-
class="tool-header-line"
|
|
619
|
-
@click="toggleTool(item.stableId + '-' + idx)"
|
|
620
|
-
>
|
|
621
|
-
<span class="tool-connector">{{ idx === getToolGroups(item).length - 1 ? '\u2514\u2500' : '\u251C\u2500' }}</span>
|
|
622
|
-
<span class="tool-expand-icon">{{ expandedTools[item.stableId + '-' + idx] ? '\u25BC' : '\u25B6' }}</span>
|
|
623
|
-
<span class="tool-name">\u{1F527} {{ group.start?.data?.toolName || group.tool || 'Tool' }}</span>
|
|
624
|
-
<span :class="getToolStatus(group).color" style="margin-left: 4px;">({{ getToolStatus(group).icon }}{{ getToolDuration(group) ? ' ' + getToolDuration(group) : '' }})</span>
|
|
625
|
-
<span v-if="getToolCommand(group)" style="color: #7d8590; margin-left: 8px;">{{ getToolCommand(group) }}</span>
|
|
626
|
-
<span v-if="getToolErrorMessage(group)" style="color: #ff7b72; margin-left: 8px;">{{ getToolErrorMessage(group).length > 80 ? getToolErrorMessage(group).substring(0, 80) + '...' : getToolErrorMessage(group) }}</span>
|
|
627
|
-
</div>
|
|
628
|
-
|
|
629
|
-
<div v-if="expandedTools[item.stableId + '-' + idx]" class="tool-detail">
|
|
630
|
-
<div v-if="group.timing.startTime || group.timing.endTime || group.timing.duration" class="tool-detail-section">
|
|
631
|
-
<div class="tool-detail-content tool-timing-line">
|
|
632
|
-
<span v-if="group.timing.startTime"><span class="tool-timing-label">Start</span> {{ formatToolTime(group.timing.startTime) }}</span>
|
|
633
|
-
<span v-if="group.timing.endTime"><span class="tool-timing-label">Complete</span> {{ formatToolTime(group.timing.endTime) }}</span>
|
|
634
|
-
<span v-if="group.timing.duration"><span class="tool-timing-label">Duration</span> {{ group.timing.duration }}</span>
|
|
635
|
-
</div>
|
|
636
|
-
</div>
|
|
637
|
-
<div v-if="group.start?.data?.arguments" class="tool-detail-section">
|
|
638
|
-
<div class="tool-detail-title">Arguments:</div>
|
|
639
|
-
<div class="tool-detail-content">
|
|
640
|
-
<pre>{{ JSON.stringify(group.start.data.arguments, null, 2) }}</pre>
|
|
641
|
-
</div>
|
|
642
|
-
</div>
|
|
643
|
-
<div v-if="group.complete?.data?.result" class="tool-detail-section">
|
|
644
|
-
<div class="tool-detail-title">Result:</div>
|
|
645
|
-
<div class="tool-detail-content">
|
|
646
|
-
<pre>{{ JSON.stringify(group.complete.data.result, null, 2) }}</pre>
|
|
647
|
-
</div>
|
|
648
|
-
</div>
|
|
649
|
-
<div v-if="getToolErrorMessage(group)" class="tool-detail-section">
|
|
650
|
-
<div class="tool-detail-title">Error:</div>
|
|
651
|
-
<div class="tool-detail-content" style="color: #ff7b72;">
|
|
652
|
-
{{ getToolErrorMessage(group) }}
|
|
653
|
-
</div>
|
|
654
|
-
</div>
|
|
655
|
-
</div>
|
|
656
|
-
</div>
|
|
657
|
-
</div>
|
|
658
|
-
|
|
659
|
-
<!-- Separator (inside event for proper height calculation) -->
|
|
660
|
-
<div v-if="!item.isLastEvent" class="event-separator"></div>
|
|
661
|
-
</div>
|
|
662
|
-
</DynamicScrollerItem>
|
|
663
|
-
</template>
|
|
664
|
-
</DynamicScroller>
|
|
665
|
-
|
|
666
|
-
<!-- Bottom spacer: ensures last item clears mobile browser nav bar -->
|
|
667
|
-
<div class="scroller-bottom-spacer"></div>
|
|
668
|
-
|
|
669
|
-
<!-- Floating scroll buttons -->
|
|
670
|
-
<div class="scroll-float-btns">
|
|
671
|
-
<button @click="scrollToTop" title="Scroll to top" class="scroll-edge-btn">\u25B2</button>
|
|
672
|
-
<button @click="scrollToBottom" title="Scroll to bottom" class="scroll-edge-btn">\u25BC</button>
|
|
673
|
-
</div>
|
|
674
|
-
</div>
|
|
675
|
-
</div>
|
|
676
|
-
|
|
677
|
-
</div>
|
|
678
|
-
`});console.log("Mounting Vue app to #app..."),console.log("App config:",P.config),console.log("Target element:",document.getElementById("app"));try{let p=P.mount("#app");console.log("Vue app mounted successfully!",p?"Instance created":"No instance"),console.log("VM type:",typeof p,"Has exportSession:",typeof p?.exportSession),console.log("VM keys:",p?Object.keys(p).slice(0,10):"NO_VM"),console.log("#app innerHTML length:",document.getElementById("app").innerHTML.length),console.log("#app first 100 chars:",document.getElementById("app").innerHTML.substring(0,100))}catch(p){console.error("Mount failed:",p),console.error("Error stack:",p.stack)}})();})();
|