Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 29 additions & 10 deletions extensions/levelcode-ai/media/chat.html
Original file line number Diff line number Diff line change
Expand Up @@ -555,6 +555,13 @@
.plancard .phtitle { flex: 1 1 auto; min-width: 0; }
.plancard .pcount { flex: 0 0 auto; opacity: .75; font-weight: 500; font-variant-numeric: tabular-nums; font-family: var(--vscode-editor-font-family, ui-monospace, monospace); font-size: 11px; padding: 1px 8px; border-radius: 999px; background: rgba(127,127,127,.14); }
.plancard.alldone .pcount { opacity: 1; color: var(--vscode-gitDecoration-addedResourceForeground, #4ec97a); background: color-mix(in srgb, var(--vscode-gitDecoration-addedResourceForeground, #4ec97a) 16%, transparent); }
/* Dismiss (×): reveals on header hover, and stays visible once the plan is done (its primary use). */
.plancard .pdismiss { flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center; width: 18px; height: 18px; color: var(--muted); font-size: 14px; line-height: 1; opacity: 0; cursor: pointer; border-radius: 6px; transition: opacity .12s, background .12s; }
.plancard .phead:hover .pdismiss, .plancard.alldone .pdismiss { opacity: .55; }
/* :focus-visible so a keyboard-only user can see AND ring the control even on a not-yet-done plan
— it is opacity:0 by default (revealed on hover/done), so focus must reveal it too. */
.plancard .pdismiss:hover, .plancard .pdismiss:focus-visible { opacity: 1; background: rgba(127,127,127,.16); }
.plancard .pdismiss:focus-visible { outline: 1px solid var(--accent); outline-offset: 1px; }
.plancard .pprog { height: 3px; margin: 0 12px 2px; border-radius: 3px; background: rgba(127,127,127,.16); overflow: hidden; }
.plancard .pprog .pfill { height: 100%; border-radius: 3px; background: var(--accent); transition: width .4s cubic-bezier(.4,0,.2,1); }
.plancard.alldone .pprog .pfill { background: var(--vscode-gitDecoration-addedResourceForeground, #4ec97a); }
Expand Down Expand Up @@ -700,7 +707,7 @@

/* Group the plan/mode chip (st-key, e.g. "Gateway · Pro+") beside the account avatar on the
RIGHT — a Claude-Code-style cluster. Pure flex order, so no DOM move of the big inline SVGs:
st-local + context ring stay left; grow pushes; st-key then the avatar sit together right. */
the context ring stays left; grow pushes; st-key then the avatar sit together right. */
#status .grow { order: 1; }
#status #st-key { order: 2; }
#status .acct { order: 3; }
Expand Down Expand Up @@ -737,7 +744,7 @@
.lc-spin .c1 { opacity: .5; } .lc-spin .c2 { opacity: .78; } .lc-spin .c3 { opacity: 1; }
}
#status .item { display: inline-flex; align-items: center; gap: 4px; }
/* Approvals dropdown, seated in the status bar between LevelCode Cloud (st-local) and the context ring.
/* Approvals dropdown, seated in the status bar just before the context ring.
Its menu reuses .modemenu and opens UPWARD (the status bar is at the very bottom). */
#status .st-approvals { position: relative; display: inline-flex; }
#status .st-approvals[hidden] { display: none; }
Expand Down Expand Up @@ -1005,7 +1012,6 @@
<path d="M29,71 L60,49 L91,71" opacity=".78"/>
<path d="M29,50 L60,28 L91,50"/>
</svg>
<span class="item" id="st-local" title="Requests go directly from your machine to the provider — no LevelCode backend."><svg class="ci" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true"><path d="M7.31299 1.26164C7.69849 0.897163 8.30151 0.897163 8.68701 1.26164L13.5305 5.84098C13.8302 6.12431 14 6.51853 14 6.93094V12.5002C14 13.3286 13.3284 14.0002 12.5 14.0002H10.5C9.67157 14.0002 9 13.3286 9 12.5002V10.0002C9 9.72407 8.77614 9.50021 8.5 9.50021H7.5C7.22386 9.50021 7 9.72407 7 10.0002V12.5002C7 13.3286 6.32843 14.0002 5.5 14.0002H3.5C2.67157 14.0002 2 13.3286 2 12.5002V6.93094C2 6.51853 2.1698 6.12431 2.46948 5.84098L7.31299 1.26164ZM8 1.98828L3.15649 6.56762C3.0566 6.66207 3 6.79347 3 6.93094V12.5002C3 12.7763 3.22386 13.0002 3.5 13.0002H5.5C5.77614 13.0002 6 12.7763 6 12.5002V10.0002C6 9.17179 6.67157 8.50022 7.5 8.50022H8.5C9.32843 8.50022 10 9.17179 10 10.0002V12.5002C10 12.7763 10.2239 13.0002 10.5 13.0002H12.5C12.7761 13.0002 13 12.7763 13 12.5002V6.93094C13 6.79347 12.9434 6.66207 12.8435 6.56762L8 1.98828Z"/></svg> Local</span>
<span class="item" id="st-key" title="Bring your own API key, stored in your OS keychain."><svg class="ci" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true"><path d="M9.25 7.25C9.25 7.76258 8.94148 8.2031 8.5 8.39599V9.50358C8.5 9.77973 8.27614 10.0036 8 10.0036C7.72386 10.0036 7.5 9.77973 7.5 9.50358V8.39599C7.05852 8.2031 6.75 7.76258 6.75 7.25C6.75 6.55964 7.30964 6 8 6C8.69036 6 9.25 6.55964 9.25 7.25ZM7.14309 2.04175C6.78097 2.2883 6.21583 2.61563 5.42482 2.91681C4.6399 3.21566 3.90375 3.36204 3.36353 3.43333C3.09405 3.46889 2.87509 3.48554 2.72547 3.49331C2.6507 3.49719 2.5934 3.49885 2.55593 3.49954L2.50489 3.50003C2.37157 3.49872 2.24323 3.55072 2.14842 3.64449C2.05344 3.73841 2 3.86643 2 4V6.75508C2 9.40779 3.4013 11.8632 5.68525 13.2124L7.74707 14.4305C7.90399 14.5232 8.09891 14.5232 8.2558 14.4304L10.3162 13.2126C12.5993 11.8632 14 9.40823 14 6.7561V4C14 3.86598 13.9462 3.73757 13.8507 3.64358C13.7552 3.54964 13.626 3.49794 13.4921 3.50007L13.4421 3.49981C13.4048 3.49928 13.3478 3.49787 13.2735 3.49426C13.1246 3.48705 12.9066 3.47109 12.6384 3.43602C12.1006 3.36573 11.3679 3.21959 10.5869 2.91771C9.79733 2.61248 9.22913 2.28442 8.86335 2.03774C8.68039 1.91435 8.54795 1.81124 8.46371 1.74141C8.4256 1.70981 8.38768 1.6777 8.35191 1.64343C8.2576 1.55073 8.13037 1.49913 7.99807 1.50001C7.86585 1.50089 7.73916 1.55434 7.64611 1.64819L7.53744 1.74536C7.45475 1.81517 7.32423 1.91842 7.14309 2.04175ZM3 6.75508V4.47725C3.14066 4.46608 3.30705 4.44945 3.49436 4.42473C4.09055 4.34605 4.90577 4.18447 5.78065 3.85136C6.64943 3.52057 7.28362 3.15585 7.70588 2.86835C7.82102 2.78996 7.92034 2.71735 8.00434 2.65277C8.08878 2.71677 8.18861 2.78886 8.30421 2.86682C8.72757 3.15233 9.362 3.51631 10.2264 3.85045C11.0994 4.18794 11.9134 4.34976 12.5088 4.42759C12.6947 4.45189 12.86 4.46809 13 4.47888V6.7561C13 9.05461 11.7861 11.1822 9.80736 12.3518L8.00133 13.4192L6.19388 12.3515C4.21446 11.1821 3 9.0541 3 6.75508Z"/></svg> Direct · BYO key</span>
<span class="st-approvals" id="approvalsWrap">
<span class="item stap" id="approvals" role="button" tabindex="0" aria-haspopup="listbox" aria-expanded="false" title="How the agent handles command approvals">
Expand Down Expand Up @@ -2377,24 +2383,40 @@
renderBgTasks();
}
const planBar = document.getElementById('planBar');
let planCollapsed = false; // persists across the agent's repeated update_plan re-renders
let planCollapsed = false; // persists across the agent's repeated update_plan re-renders (within a run)
let planWasDone = false; // rising-edge latch → auto-collapse a finished plan exactly once
function renderPlan(todos){
if (!todos || !todos.length){ planBar.style.display = 'none'; planBar.innerHTML = ''; return; }
// Empty list = clear (agentStart / newChat / dismiss). Reset the collapse state so the NEXT plan
// starts fresh and expanded rather than inheriting a previous run's collapsed/done latches.
if (!todos || !todos.length){ planBar.style.display = 'none'; planBar.innerHTML = ''; planCollapsed = false; planWasDone = false; return; }
const total = todos.length, done = todos.filter(t => t && t.status === 'done').length;
const pct = Math.round(done / total * 100), allDone = done === total;
// A finished checklist shouldn't keep hogging up to 38vh: auto-collapse it to the (green) header the
// moment it first completes. Rising-edge only, so a manual re-expand of a done plan sticks afterward.
if (allDone && !planWasDone){ planCollapsed = true; }
planWasDone = allDone;
let items = '';
for (const t of todos){
const st = t.status === 'done' ? 'done' : t.status === 'in_progress' ? 'doing' : 'todo';
const ic = st === 'done' ? codicon('check-circle') : st === 'doing' ? codicon('sync') : codicon('circle');
items += '<div class="pitem ' + st + '"><span class="pic">' + ic + '</span><span class="ptitle">' + esc(t.title) + '</span></div>';
}
const head = '<div class="phead"><span class="ptoggle">' + codicon('chevron-down') + '</span>'
+ '<span class="phtitle">Plan</span><span class="pcount">' + done + '/' + total + '</span></div>'
+ '<span class="phtitle">Plan</span><span class="pcount">' + done + '/' + total + '</span>'
+ '<span class="pdismiss" title="Dismiss plan" role="button" tabindex="0" aria-label="Dismiss plan">&times;</span></div>'
+ '<div class="pprog"><div class="pfill" style="width:' + pct + '%"></div></div>';
Comment thread
ndemianc marked this conversation as resolved.
planBar.innerHTML = '<div class="plancard' + (allDone ? ' alldone' : '') + (planCollapsed ? ' collapsed' : '') + '">'
+ head + '<div class="pitems">' + items + '</div></div>';
const phead = planBar.querySelector('.phead');
if (phead){ phead.onclick = () => { planCollapsed = !planCollapsed; const c = planBar.querySelector('.plancard'); if (c){ c.classList.toggle('collapsed', planCollapsed); } }; }
// Dismiss (×): close the finished — or abandoned — plan outright. stopPropagation so the header's
// collapse-toggle doesn't also fire. Clearing resets the latches via the empty-todos branch above.
const pdismiss = planBar.querySelector('.pdismiss');
if (pdismiss){
const dismiss = (e) => { e.preventDefault(); e.stopPropagation(); renderPlan([]); };
pdismiss.onclick = dismiss;
pdismiss.onkeydown = (e) => { if (e.key === 'Enter' || e.key === ' '){ dismiss(e); } }; // keyboard-activate, like ctxStat / approvals
}
planBar.style.display = 'block';
scrollIfStuck(); // log shrank as the sticky plan appeared — keep pinned if we were at bottom
}
Expand Down Expand Up @@ -2731,7 +2753,7 @@
renderRouting();
}

// Footer routing indicator: st-local = WHERE requests go, st-key = the key/plan.
// Footer routing indicator: st-key = the mode + plan (e.g. "Gateway · Pro+", "BYO key", "Direct · no key").
// Driven by the latest 'config' (provider) + 'account' (signedIn, mode, plan).
function setStatusItem(id, label, title){
const el = document.getElementById(id);
Expand All @@ -2742,13 +2764,10 @@
function renderRouting(){
const gateway = account.signedIn && account.mode === 'gateway';
if (gateway){
setStatusItem('st-local', 'LevelCode Cloud', 'Requests run through your metered LevelCode Cloud plan (gateway).');
setStatusItem('st-key', 'Gateway' + (account.plan ? ' · ' + account.plan : ''), 'Metered gateway — no provider key needed.');
} else if (lastProvider === 'ollama'){
setStatusItem('st-local', 'Local', 'Requests go to a local model on your machine (Ollama) — no key, no backend.');
setStatusItem('st-key', 'Direct · no key', 'Local model — no API key needed.');
} else {
setStatusItem('st-local', 'Direct', 'Requests go directly from your machine to your provider — no LevelCode backend.');
setStatusItem('st-key', 'BYO key', 'Bring your own API key, stored in your OS keychain.');
}
}
Expand Down