@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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qiaolei81/copilot-session-viewer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Web UI for viewing GitHub Copilot CLI session logs",
|
|
5
5
|
"author": "Lei Qiao <qiaolei81@gmail.com>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -31,16 +31,16 @@
|
|
|
31
31
|
"copilot-session-viewer": "./bin/copilot-session-viewer"
|
|
32
32
|
},
|
|
33
33
|
"scripts": {
|
|
34
|
-
"build": "node scripts/build.mjs",
|
|
34
|
+
"build": "vite build && node scripts/build.mjs",
|
|
35
35
|
"build:watch": "node scripts/build.mjs --watch",
|
|
36
36
|
"start": "cross-env DISABLE_TELEMETRY=true node server.js",
|
|
37
37
|
"dev": "cross-env DISABLE_TELEMETRY=true nodemon server.js",
|
|
38
38
|
"test": "jest",
|
|
39
39
|
"test:watch": "jest --watch",
|
|
40
40
|
"test:coverage": "jest --coverage",
|
|
41
|
-
"test:e2e": "cross-env DISABLE_TELEMETRY=true playwright test",
|
|
42
|
-
"test:e2e:headed": "cross-env DISABLE_TELEMETRY=true playwright test --headed",
|
|
43
|
-
"test:e2e:debug": "cross-env DISABLE_TELEMETRY=true playwright test --debug",
|
|
41
|
+
"test:e2e": "cross-env DISABLE_TELEMETRY=true E2E_USE_FIXTURES=1 playwright test",
|
|
42
|
+
"test:e2e:headed": "cross-env DISABLE_TELEMETRY=true E2E_USE_FIXTURES=1 playwright test --headed",
|
|
43
|
+
"test:e2e:debug": "cross-env DISABLE_TELEMETRY=true E2E_USE_FIXTURES=1 playwright test --debug",
|
|
44
44
|
"test:all": "npm test && npm run test:e2e",
|
|
45
45
|
"test:coverage:all": "npm run test:coverage && npm run test:e2e",
|
|
46
46
|
"lint": "eslint .",
|
|
@@ -51,6 +51,9 @@
|
|
|
51
51
|
"release:minor": "./scripts/release.sh minor",
|
|
52
52
|
"release:major": "./scripts/release.sh major",
|
|
53
53
|
"build:prod": "node scripts/build.mjs --prod",
|
|
54
|
+
"dev:client": "vite",
|
|
55
|
+
"build:client": "vite build",
|
|
56
|
+
"generate:api": "openapi-typescript openapi.yaml -o src/client/api/schema.d.ts",
|
|
54
57
|
"prepublishOnly": "npm run build:prod"
|
|
55
58
|
},
|
|
56
59
|
"dependencies": {
|
|
@@ -58,12 +61,15 @@
|
|
|
58
61
|
"applicationinsights": "^2.9.8",
|
|
59
62
|
"compression": "^1.8.1",
|
|
60
63
|
"dompurify": "^3.3.1",
|
|
61
|
-
"ejs": "^4.0.1",
|
|
62
64
|
"express": "^4.18.2",
|
|
63
|
-
"express-rate-limit": "^8.2.1",
|
|
64
65
|
"helmet": "^8.1.0",
|
|
65
66
|
"js-yaml": "^4.1.1",
|
|
67
|
+
"marked": "^18.0.4",
|
|
66
68
|
"multer": "^2.0.2",
|
|
69
|
+
"pinia": "^3.0.4",
|
|
70
|
+
"vue": "^3.5.34",
|
|
71
|
+
"vue-router": "^5.0.7",
|
|
72
|
+
"vue-virtual-scroller": "^2.0.0-beta.8",
|
|
67
73
|
"zod": "^4.3.6"
|
|
68
74
|
},
|
|
69
75
|
"devDependencies": {
|
|
@@ -71,31 +77,29 @@
|
|
|
71
77
|
"@eslint/js": "^10.0.1",
|
|
72
78
|
"@eslint/json": "^1.0.1",
|
|
73
79
|
"@eslint/markdown": "^7.5.1",
|
|
74
|
-
"@istanbuljs/nyc-config-typescript": "^1.0.2",
|
|
75
80
|
"@playwright/test": "^1.58.2",
|
|
81
|
+
"@tailwindcss/vite": "^4.3.0",
|
|
76
82
|
"@types/jest": "^30.0.0",
|
|
83
|
+
"@vitejs/plugin-vue": "^6.0.7",
|
|
84
|
+
"c8": "^11.0.0",
|
|
77
85
|
"cross-env": "^10.1.0",
|
|
78
86
|
"esbuild": "^0.27.3",
|
|
79
87
|
"eslint": "^10.0.0",
|
|
80
88
|
"eslint-plugin-vue": "^10.8.0",
|
|
81
89
|
"globals": "^17.3.0",
|
|
82
|
-
"istanbul-lib-coverage": "^3.2.2",
|
|
83
90
|
"jest": "^30.2.0",
|
|
84
91
|
"jsdom": "^28.1.0",
|
|
85
92
|
"nodemon": "^3.0.1",
|
|
86
|
-
"
|
|
93
|
+
"openapi-fetch": "^0.17.0",
|
|
94
|
+
"openapi-typescript": "^7.13.0",
|
|
87
95
|
"supertest": "^7.2.2",
|
|
88
|
-
"
|
|
96
|
+
"tailwindcss": "^4.3.0",
|
|
97
|
+
"vite": "^8.0.14"
|
|
89
98
|
},
|
|
90
99
|
"files": [
|
|
91
100
|
"bin/",
|
|
92
|
-
"dist/
|
|
93
|
-
"views/",
|
|
94
|
-
"public/css/",
|
|
95
|
-
"public/js/*.min.js",
|
|
96
|
-
"public/img/",
|
|
101
|
+
"dist/",
|
|
97
102
|
"LICENSE",
|
|
98
|
-
"README.md"
|
|
99
|
-
"public/vendor/"
|
|
103
|
+
"README.md"
|
|
100
104
|
]
|
|
101
105
|
}
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
(()=>{var y=window.__PAGE_DATA||{},A=y.sessions||[],G=y.totalSessions||0,D=y.hasMore||!1,k=y.sourceHints||{},m=[...A],u={};u.copilot={offset:A.length,hasMore:D};var f=!1,S="sessionViewer.sourceFilter",$;try{$=localStorage.getItem(S)}catch{$=null}var i=$||"copilot";function g(){return u[i]||(u[i]={offset:0,hasMore:!0}),u[i]}async function B(){let e=g();if(f||!e.hasMore)return;f=!0;let t=document.getElementById("loading-indicator");t.style.display="block",window.trackClick("LoadMoreClicked",{currentPage:Math.floor(e.offset/20)+1,offset:e.offset,source:i});try{let s=await fetch(`/api/sessions/load-more?offset=${g().offset}&limit=20&source=${encodeURIComponent(i)}`);if(!s.ok)throw new Error("Failed to load more sessions");let o=await s.json(),n=new Set(m.map(r=>r.id)),a=[];for(let r of o.sessions)n.has(r.id)||(m.push(r),a.push(r));g().offset+=o.sessions.length,g().hasMore=o.hasMore,await C(a),E()}catch(s){console.error("Error loading more sessions:",s)}finally{f=!1,t.style.display="none"}}function F(){return m.filter(e=>e.source===i)}function E(){let e=document.getElementById("sessions-container");e.innerHTML="";let t=F();if(t.length===0){e.innerHTML='<div style="text-align: center; color: #6e7681; padding: 40px; font-size: 14px;">No sessions found for this filter.</div>';return}let s=O(t);Object.keys(s).sort((n,a)=>a.localeCompare(n)).forEach(n=>{let a=document.createElement("div");a.className="date-group-header",a.textContent=N(s[n][0].createdAt),e.appendChild(a);let r=document.createElement("div");r.className="recent-list",s[n].forEach(h=>{r.innerHTML+=R(h)}),e.appendChild(r)})}function z(){let e=window.pageYOffset||document.documentElement.scrollTop,t=window.innerHeight,s=document.documentElement.scrollHeight;e+t>=s-500&&g().hasMore&&!f&&B()}var w;function P(){w||(w=setTimeout(()=>{z(),w=null},100))}function Z(e){e.preventDefault();let t=document.getElementById("sessionInput").value.trim();t&&(window.location.href=`/session/${t}`)}document.getElementById("sessionForm").addEventListener("submit",Z);var M=document.getElementById("fileInput"),d=document.getElementById("importLink"),L=document.getElementById("importStatus");d.addEventListener("click",e=>{e.preventDefault(),M.click()});M.addEventListener("change",async e=>{let t=e.target.files[0];if(t){if(!t.name.endsWith(".zip")){p("error","\u274C Please select a .zip file");return}d.style.pointerEvents="none",d.style.opacity="0.5",d.textContent="Importing...",p("loading","Uploading and extracting session...");try{let s=new FormData;s.append("sessionZip",t);let o=await fetch("/session/import",{method:"POST",body:s}),n=await o.json();o.ok?(p("success",`\u2705 Session ${n.sessionId} imported successfully!`),setTimeout(()=>{window.location.reload()},1500)):(p("error",`\u274C Import failed: ${n.error}`),d.style.pointerEvents="auto",d.style.opacity="1",d.textContent="Import session from zip")}catch(s){p("error",`\u274C Import failed: ${s.message}`),d.style.pointerEvents="auto",d.style.opacity="1",d.textContent="Import session from zip"}finally{M.value=""}}});function p(e,t){L.className=`import-status ${e}`,L.textContent=t}function _(e){if(!e||e<0)return"\u2014";let t=Math.floor(e/1e3),s=Math.floor(t/60),o=Math.floor(s/60);if(o>0){let n=s%60;return`${o}h ${n}m`}else if(s>0){let n=t%60;return`${s}m ${n}s`}else return`${t}s`}function N(e){let t=new Date(e),s=t.getFullYear(),o=String(t.getMonth()+1).padStart(2,"0"),n=String(t.getDate()).padStart(2,"0");return`${s}/${o}/${n}`}function j(e){if(!e)return"Unknown";let t=new Date(e),s=t.getFullYear(),o=String(t.getMonth()+1).padStart(2,"0"),n=String(t.getDate()).padStart(2,"0");return`${s}-${o}-${n}`}function O(e){let t={};return e.forEach(s=>{let o=j(s.createdAt);t[o]||(t[o]=[]),t[o].push(s)}),t}function R(e){let t="",s=e.sourceBadgeClass||"source-copilot",o=e.sourceName||"Copilot";if(t+=`<span class="status-badge ${s}" title="${o}">${o}</span>`,e.sessionStatus==="wip"&&(t+='<span class="status-badge wip" title="Session in progress">\u{1F504} WIP</span>'),e.isImported&&(t+='<span class="status-badge imported" title="Imported session">\u{1F4E5}</span>'),e.hasInsight&&(t+='<span class="status-badge insight" title="Has Agent Review">\u{1F4A1}</span>'),e.selectedModel){let v=e.selectedModel.replace("claude-","").replace("gpt-","").replace("gemini-",""),l="model-other";e.selectedModel.includes("claude")?l="model-claude":e.selectedModel.includes("gpt")?l="model-gpt":e.selectedModel.includes("gemini")&&(l="model-gemini"),t+=`<span class="status-badge model ${l}" title="Model: ${c(e.selectedModel)}">${c(v)}</span>`}e.source==="modernize"&&e.modernizeVersion?t+=`<span class="status-badge version" title="Modernize version">${c(e.modernizeVersion)}</span>`:e.copilotVersion&&(t+=`<span class="status-badge version" title="CLI version">${c(e.copilotVersion)}</span>`);let n="";if(e.summary&&e.summary!=="No summary"&&e.summary!=="Legacy session"){let v=e.summary.replace(/"/g,"""),l=c(e.summary).replace(/\n+/g," ");n=`<div class="session-summary" title="${v}">${l}</div>`}else n='<div class="session-summary" style="color: #6e7681; font-style: italic;">No summary available</div>';let a="";e.workspace&&e.workspace.cwd&&(a=`
|
|
2
|
-
<div class="session-info-item workspace" title="${c(e.workspace.cwd)}">
|
|
3
|
-
<svg viewBox="0 0 16 16" fill="currentColor"><path d="M1.75 1A1.75 1.75 0 000 2.75v10.5C0 14.216.784 15 1.75 15h12.5A1.75 1.75 0 0016 13.25v-8.5A1.75 1.75 0 0014.25 3H7.5a.25.25 0 01-.2-.1l-.9-1.2C6.07 1.26 5.55 1 5 1H1.75z"></path></svg>
|
|
4
|
-
<span class="session-info-value">${c(e.workspace.cwd)}</span>
|
|
5
|
-
</div>
|
|
6
|
-
`);let r=e.createdAt?new Date(e.createdAt).toLocaleString("en-US",{month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",hour12:!1}):"unknown",h="";e.duration&&(h=`
|
|
7
|
-
<div class="session-info-item">
|
|
8
|
-
<svg viewBox="0 0 16 16" fill="currentColor"><path d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8Zm8-6.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13ZM7.25 12.5v-5A.75.75 0 0 1 8 6.75h2.5a.75.75 0 0 1 0 1.5H8.75v4.25a.75.75 0 0 1-1.5 0Z"></path></svg>
|
|
9
|
-
<span class="session-info-value">${_(e.duration)}</span>
|
|
10
|
-
</div>
|
|
11
|
-
`);let T=e.sessionStatus==="wip"?" recent-item-wip":"",I="";return e.tags&&e.tags.length>0&&(I=`<div class="session-tags">${e.tags.map(l=>`<span class="session-tag" style="background-color: ${V(l)}" title="${c(l)}">${c(l)}</span>`).join("")}</div>`),`
|
|
12
|
-
<a href="/session/${e.id}" class="recent-item${T}" onclick="trackClick('SessionCardClicked', { sessionId: '${c(e.id)}', source: '${c(e.source||"unknown")}' })">
|
|
13
|
-
<div class="session-id">
|
|
14
|
-
<span class="session-id-text" title="${c(e.id)}">${c(e.id)}</span>
|
|
15
|
-
</div>
|
|
16
|
-
<div class="session-badges-tags">
|
|
17
|
-
<div class="session-badges">${t}</div>
|
|
18
|
-
${I}
|
|
19
|
-
</div>
|
|
20
|
-
${n}
|
|
21
|
-
<div class="session-divider"></div>
|
|
22
|
-
<div class="session-info">
|
|
23
|
-
${a}
|
|
24
|
-
<div class="session-info-item">
|
|
25
|
-
<svg viewBox="0 0 16 16" fill="currentColor"><path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Zm7-3.25v2.992l2.028.812a.75.75 0 0 1-.557 1.392l-2.5-1A.751.751 0 0 1 7 8.25v-3.5a.75.75 0 0 1 1.5 0Z"></path></svg>
|
|
26
|
-
<span class="session-info-value">${r}</span>
|
|
27
|
-
</div>
|
|
28
|
-
${h}
|
|
29
|
-
<div class="session-info-item">
|
|
30
|
-
<svg viewBox="0 0 16 16" fill="currentColor"><path d="M7.72.72a.75.75 0 0 1 1.06 0l1.5 1.5a.75.75 0 0 1-1.06 1.06l-.22-.22v1.69a.75.75 0 0 1-1.5 0V3.06l-.22.22a.75.75 0 0 1-1.06-1.06ZM2 7a.75.75 0 0 0 0 1.5h3.69l-.22.22a.75.75 0 1 0 1.06 1.06l1.5-1.5a.75.75 0 0 0 0-1.06l-1.5-1.5a.75.75 0 0 0-1.06 1.06l.22.22Zm8.53-.28a.75.75 0 0 0 0 1.06l1.5 1.5a.75.75 0 1 0 1.06-1.06l-.22-.22H16a.75.75 0 0 0 0-1.5h-3.13l.22-.22a.75.75 0 0 0-1.06-1.06ZM7.72 12.22a.75.75 0 0 1 1.06 0l1.5 1.5a.75.75 0 1 1-1.06 1.06l-.22-.22v1.69a.75.75 0 0 1-1.5 0v-1.69l-.22.22a.75.75 0 0 1-1.06-1.06Z"></path></svg>
|
|
31
|
-
<span class="session-info-value">${e.eventCount||0} events</span>
|
|
32
|
-
</div>
|
|
33
|
-
</div>
|
|
34
|
-
</a>
|
|
35
|
-
`}function c(e){let t=document.createElement("div");return t.textContent=e,t.innerHTML}var b=["#3b82f6","#10b981","#f59e0b","#ef4444","#8b5cf6","#ec4899","#06b6d4","#f97316"];function V(e){let t=0;for(let s=0;s<e.length;s++)t=e.charCodeAt(s)+((t<<5)-t);return b[Math.abs(t)%b.length]}async function U(e){try{let t=e.map(n=>fetch(`/api/sessions/${n}/tags`).then(a=>a.ok?a.json():{tags:[]}).then(a=>({id:n,tags:a.tags||[]})).catch(()=>({id:n,tags:[]}))),s=await Promise.all(t),o={};return s.forEach(({id:n,tags:a})=>{o[n]=a}),o}catch(t){return console.error("Error loading session tags:",t),{}}}async function C(e){let t=e.map(o=>o.id),s=await U(t);e.forEach(o=>{o.tags=s[o.id]||[]})}function H(e){let t=document.getElementById("sourceHint");t&&k[e]?t.innerHTML='Sessions from <span class="hint-code">'+k[e]+"</span>":t&&(t.textContent="")}async function x(e){if(u[e]||(u[e]={offset:0,hasMore:!0}),u[e].offset===0&&!f){f=!0;let t=document.getElementById("sessions-container");t.innerHTML='<div style="text-align: center; color: #6e7681; padding: 40px; font-size: 14px;">\u23F3 Loading...</div>',document.getElementById("loading-indicator").style.display="none";try{let s=await fetch(`/api/sessions/load-more?offset=0&limit=20&source=${encodeURIComponent(e)}`);if(s.ok){let o=await s.json(),n=new Set(m.map(r=>r.id)),a=[];for(let r of o.sessions||[])n.has(r.id)||(m.push(r),a.push(r));u[e].offset=(o.sessions||[]).length,u[e].hasMore=o.hasMore,await C(a)}}catch(s){console.error("Failed to load sessions for source:",e,s)}finally{f=!1}}E()}function Y(){let e=document.querySelectorAll(".filter-pill");if(!new Set([...e].map(s=>s.getAttribute("data-source"))).has(i)){i="copilot";try{localStorage.setItem(S,i)}catch{}}e.forEach(s=>{s.classList.toggle("active",s.getAttribute("data-source")===i)}),H(i),e.forEach(s=>{s.addEventListener("click",async()=>{e.forEach(o=>o.classList.remove("active")),s.classList.add("active"),i=s.getAttribute("data-source");try{localStorage.setItem(S,i)}catch{}H(i),window.trackClick("FilterPillClicked",{pillName:s.textContent.trim(),dataSource:i}),await x(i)})})}document.addEventListener("DOMContentLoaded",async function(){await C(m),window.addEventListener("scroll",P),Y(),i==="copilot"?E():await x(i)});})();
|