This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
<!doctype html><html lang="ja"><head><meta charset="utf-8" />
|
||||
<style>
|
||||
body{font:14px system-ui;margin:0;padding:16px}
|
||||
.row{display:flex;gap:8px;align-items:center;margin-bottom:12px}
|
||||
#summary{font-size:16px;font-weight:bold;margin-bottom:16px;min-height:24px}
|
||||
.bar-wrap{margin-bottom:16px}
|
||||
.bar-label{font-size:12px;margin-bottom:2px;color:#444}
|
||||
svg.bar{display:block;width:100%;max-width:400px;height:20px;border-radius:4px;overflow:hidden;background:#eee}
|
||||
rect.bar-fill{fill:#2563eb}
|
||||
span[data-testid="status"]{font-size:12px;color:#555}
|
||||
button{padding:6px 16px;border-radius:4px;border:none;background:#2563eb;color:#fff;cursor:pointer}
|
||||
button:hover{background:#1d4ed8}
|
||||
</style></head>
|
||||
<body>
|
||||
<div class="row">
|
||||
<button id="refreshBtn" data-testid="refresh">更新</button>
|
||||
<span id="status" data-testid="status"></span>
|
||||
</div>
|
||||
<div id="summary" data-testid="summary"></div>
|
||||
<div id="bars"></div>
|
||||
<script>
|
||||
let seq=0;const pending=new Map();
|
||||
function call(req){return new Promise((res,rej)=>{const id=++seq;pending.set(id,{res,rej});
|
||||
window.parent.postMessage({...req,id},'*');});}
|
||||
window.addEventListener('message',e=>{const r=e.data;const p=pending.get(r&&r.id);if(!p)return;
|
||||
pending.delete(r.id);r.ok?p.res(r.data):p.rej(new Error(r.error));});
|
||||
const $=id=>document.getElementById(id);
|
||||
|
||||
const TEXT_EXTS=['.txt','.md','.csv','.json','.jsonl','.log','.yaml','.yml'];
|
||||
function isText(name){return TEXT_EXTS.some(e=>name.endsWith(e));}
|
||||
|
||||
async function refresh(){
|
||||
$('status').textContent='集計中…';
|
||||
$('summary').textContent='';
|
||||
$('bars').innerHTML='';
|
||||
try{
|
||||
const d=await call({type:'listFiles',dir:'output'});
|
||||
const entries=d.entries||[];
|
||||
const count=entries.length;
|
||||
const lineCounts=[];
|
||||
for(const name of entries){
|
||||
if(!isText(name)){lineCounts.push({name,lines:null});continue;}
|
||||
try{
|
||||
const fd=await call({type:'readFile',path:'output/'+name});
|
||||
const lines=(fd.content||'').split('\n').filter(l=>l.length>0).length;
|
||||
lineCounts.push({name,lines});
|
||||
}catch(_){lineCounts.push({name,lines:null});}
|
||||
}
|
||||
$('summary').textContent='ファイル数: '+count;
|
||||
const withLines=lineCounts.filter(x=>x.lines!==null);
|
||||
const maxLines=Math.max(...withLines.map(x=>x.lines),1);
|
||||
withLines.forEach(({name,lines})=>{
|
||||
const wrap=document.createElement('div');wrap.className='bar-wrap';
|
||||
const lbl=document.createElement('div');lbl.className='bar-label';
|
||||
lbl.textContent=name+' ('+lines+'行)';
|
||||
const pct=Math.round((lines/maxLines)*100);
|
||||
const svg=document.createElementNS('http://www.w3.org/2000/svg','svg');
|
||||
svg.setAttribute('class','bar');svg.setAttribute('viewBox','0 0 100 20');
|
||||
svg.setAttribute('preserveAspectRatio','none');
|
||||
const rect=document.createElementNS('http://www.w3.org/2000/svg','rect');
|
||||
rect.setAttribute('class','bar-fill');rect.setAttribute('x','0');rect.setAttribute('y','0');
|
||||
rect.setAttribute('width',String(pct));rect.setAttribute('height','20');
|
||||
svg.appendChild(rect);wrap.appendChild(lbl);wrap.appendChild(svg);
|
||||
$('bars').appendChild(wrap);
|
||||
});
|
||||
$('status').textContent='更新OK';
|
||||
}catch(err){$('status').textContent='エラー:'+err.message;}
|
||||
}
|
||||
|
||||
$('refreshBtn').onclick=refresh;
|
||||
refresh();
|
||||
</script></body></html>
|
||||
Reference in New Issue
Block a user