feat(desktop): conductor token stats summary row in token page
- conductor.py: install cost_tracker + expose /token-stats endpoint (5 lines) - app.js: fetch conductor token totals and display as a single summary row at top of token table (not expandable, with tooltip noting it's excluded from cumulative totals) - styles.css: conductor row styling
This commit is contained in:
1 parent
db7dd70ab3
commit
90a37236aa
3 files changed
+33
-1
No files matched your search
@@ -343,8 +343,14 @@ def conductor_loop():
|
||||
async def on_startup():
|
||||
global main_loop
|
||||
main_loop = asyncio.get_running_loop()
|
||||
import cost_tracker; cost_tracker.install()
|
||||
threading.Thread(target=conductor_loop, name="conductor-loop", daemon=True).start()
|
||||
|
||||
@app.get("/token-stats")
|
||||
def conductor_token_stats():
|
||||
import cost_tracker
|
||||
return {"records": [{"thread": k, "input": v.input, "output": v.output, "cacheCreate": v.cache_create, "cacheRead": v.cache_read} for k, v in cost_tracker.all_trackers().items()]}
|
||||
|
||||
@app.get("/")
|
||||
def index():
|
||||
return FileResponse(HTML_PATH)
|
||||
|
||||
@@ -2953,7 +2953,30 @@ function tokRenderTable(records) {
|
||||
if(tokPager){tokPager.innerHTML='';if(totalPages>1)for(let i=0;i<totalPages;i++){const b=document.createElement('button');b.textContent=i+1;if(i===_tokPage)b.className='active';b.addEventListener('click',()=>{_tokPage=i;tokRenderTable(records);});tokPager.appendChild(b);}}
|
||||
}
|
||||
|
||||
async function loadTokenPage(){await tokPollBridge();const f=tokGetFiltered();const all=tokLoadHistory();tokRenderStats(f,all);tokRenderTable(f);}
|
||||
async function loadTokenPage(){await tokPollBridge();const f=tokGetFiltered();const all=tokLoadHistory();tokRenderStats(f,all);tokRenderTable(f);await tokRenderConductorRow();}
|
||||
|
||||
let _conductorRowGen = 0;
|
||||
async function tokRenderConductorRow() {
|
||||
if (!tokTbody) return;
|
||||
const gen = ++_conductorRowGen;
|
||||
tokTbody.querySelectorAll('.tok-row-conductor').forEach(el => el.remove());
|
||||
try {
|
||||
const data = await (await fetch(`http://${location.hostname}:8900/token-stats`)).json();
|
||||
if (gen !== _conductorRowGen) return;
|
||||
tokTbody.querySelectorAll('.tok-row-conductor').forEach(el => el.remove());
|
||||
const recs = (data.records || []).filter(r => r.thread === 'conductor-agent' || r.thread.startsWith('subagent-'));
|
||||
if (!recs.length) return;
|
||||
let totalIn = 0, totalOut = 0, totalCc = 0, totalCr = 0, cost = 0;
|
||||
for (const r of recs) {
|
||||
totalIn += r.input || 0; totalOut += r.output || 0; totalCc += r.cacheCreate || 0; totalCr += r.cacheRead || 0;
|
||||
cost += parseFloat(estCost(r.input || 0, r.output || 0, r.model || '', r.cacheRead || 0, r.cacheCreate || 0));
|
||||
}
|
||||
const tr = document.createElement('tr'); tr.className = 'tok-row-session tok-row-conductor';
|
||||
tr.title = 'Conductor 消耗的 token 不计入累计 token 中';
|
||||
tr.innerHTML = `<td>🎛 Conductor (${recs.length} threads)</td><td>${fmtTok(totalIn)}</td><td>${fmtTok(totalOut)}</td><td>${fmtTok(totalCc)}</td><td>${fmtTok(totalCr)}</td><td>¥${cost.toFixed(2)}</td>`;
|
||||
tokTbody.insertBefore(tr, tokTbody.firstChild);
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
/* ─── Conductor token tab (disabled — pending time tracking) ─── */
|
||||
/*
|
||||
|
||||
@@ -844,6 +844,9 @@ span.flatpickr-weekday{ color:var(--muted) !important; font-size:11px; }
|
||||
.tok-row-session:hover td{ background:var(--hover-dim); }
|
||||
.tok-row-session td:first-child::before{ content:'▶'; margin-right:6px; font-size:10px; color:var(--muted2); transition:transform .15s; display:inline-block; }
|
||||
.tok-row-session.open td:first-child::before{ transform:rotate(90deg); }
|
||||
.tok-row-conductor{ cursor:default; background:var(--blue-soft); }
|
||||
.tok-row-conductor td:first-child::before{ content:''; margin:0; }
|
||||
.tok-row-conductor:hover td{ background:var(--blue-soft); }
|
||||
.tok-detail td{ padding:5px 12px 5px 32px; font-size:11px; color:var(--muted); border-bottom:1px solid var(--line-soft); background:var(--field-bg); }
|
||||
.tok-detail td:not(:first-child){ text-align:right; }
|
||||
.tok-detail[hidden]{ display:none; }
|
||||
|
||||
Reference in new issue
Block a user