|
152 | 152 | .agentstatus { font-size: 12px; margin: 5px 2px; opacity: .9; display: flex; gap: 7px; align-items: center; font-style: italic; } |
153 | 153 | .agentstatus .spin { display: inline-block; font-style: normal; animation: spin .8s linear infinite; } |
154 | 154 | @keyframes spin { to { transform: rotate(360deg); } } |
| 155 | + /* persistent working indicator above the composer — visible the whole time the agent runs */ |
| 156 | + #workbar { display: flex; align-items: center; gap: 8px; padding: 5px 4px 7px; } |
| 157 | + #workbar[hidden] { display: none; } |
| 158 | + #workbar .wspin { display: inline-flex; flex: 0 0 auto; } |
| 159 | + #workbar .wspin .lc-spin { width: 18px; height: 18px; } |
| 160 | + #workbar .wtext { font-size: 12px; } |
155 | 161 | /* shimmering "glance" text while the agent is working (Cursor/Claude-Code style sweep) */ |
156 | 162 | .shimmer { |
157 | 163 | background: linear-gradient(90deg, var(--muted) 0%, var(--muted) 38%, var(--vscode-foreground) 50%, var(--muted) 62%, var(--muted) 100%); |
|
891 | 897 | <button class="undoall">Undo all</button> |
892 | 898 | </div> |
893 | 899 |
|
| 900 | + <!-- persistent "agent is working" indicator: shown for the WHOLE run (tied to streaming), so there's |
| 901 | + never a gap between actions where nothing shows. Its label tracks the current activity. --> |
| 902 | + <div id="workbar" hidden role="status" aria-live="polite"><span class="wspin" aria-hidden="true"></span><span class="wtext shimmer">Working…</span></div> |
| 903 | + |
894 | 904 | <div id="composer"> |
895 | 905 | <div id="chips"></div> |
896 | 906 | <textarea id="input" rows="2" placeholder="Give the agent a goal…"></textarea> |
|
1060 | 1070 | + '<path class="c1" d="M29,92 L60,70 L91,92"/>' |
1061 | 1071 | + '<path class="c2" d="M29,71 L60,49 L91,71"/>' |
1062 | 1072 | + '<path class="c3" d="M29,50 L60,28 L91,50"/></svg>'; |
| 1073 | + // The persistent working bar (above the composer) reuses the same spinner. Its visibility is tied to |
| 1074 | + // `streaming` (setStreaming), so it can never fall into a gap between actions; setWork() updates its label. |
| 1075 | + const workbarEl = document.getElementById('workbar'); |
| 1076 | + const workTextEl = workbarEl && workbarEl.querySelector('.wtext'); |
| 1077 | + { const s = workbarEl && workbarEl.querySelector('.wspin'); if (s) { s.innerHTML = LC_SPIN_SVG; } } |
| 1078 | + function setWork(text){ if (workTextEl) { workTextEl.textContent = text || 'Working…'; } } |
1063 | 1079 |
|
1064 | 1080 | function esc(s){ return String(s == null ? '' : s).replace(/[&<>]/g, c => ({'&':'&','<':'<','>':'>'}[c])); } |
1065 | 1081 | // Inline VS Code codicons (MIT, @vscode/codicons) — replaces UI emojis, CSP/font-free. |
|
1381 | 1397 | + '<div class="tl-body"><span class="toolt">' + esc(text || '') + '</span></div>'; |
1382 | 1398 | log.appendChild(d); scrollIfStuck(); |
1383 | 1399 | } |
1384 | | - function setAgentStatus(text){ |
1385 | | - clearStatus(); |
1386 | | - const d = document.createElement('div'); d.className = 'agentstatus'; |
1387 | | - const t = (!text || text === 'thinking…') ? 'Thinking…' : text; // shimmering label while the agent works |
1388 | | - d.innerHTML = LC_SPIN_SVG + '<span class="shimmer">' + esc(t) + '</span>'; |
1389 | | - log.appendChild(d); agentStatusEl = d; scrollIfStuck(); |
1390 | | - } |
| 1400 | + // Drives the persistent working bar (not an inline log element), so the indicator can't be cleared into |
| 1401 | + // a gap by an intervening tool card or bubble. The bar's visibility is owned by setStreaming. |
| 1402 | + function setAgentStatus(text){ setWork((!text || text === 'thinking…') ? 'Thinking…' : text); } |
1391 | 1403 | function applyMode(agent){ |
1392 | 1404 | agentMode = agent; |
1393 | 1405 | document.getElementById('modeLabel').textContent = agent ? 'Agent' : 'Chat'; |
|
2094 | 2106 | sendBtn.classList.toggle('stop', on); |
2095 | 2107 | sendBtn.innerHTML = on ? '■' : '↵'; // ■ while streaming, ↵ otherwise |
2096 | 2108 | sendBtn.title = on ? 'Stop' : 'Send (Enter)'; |
| 2109 | + if (workbarEl){ workbarEl.hidden = !on; } // the working bar is visible for the ENTIRE run |
| 2110 | + if (on){ setWork('Working…'); } |
2097 | 2111 | } |
2098 | 2112 | // Shell-style history recall in the composer: ↑ = previous sent message, ↓ = newer (back to your draft). |
2099 | 2113 | // Session-only. In multiline text, ↑/↓ recall only when the caret is on the first/last line (arrows still move otherwise). |
|
2456 | 2470 | else if (m.type === 'autopilot'){ applyAutopilot(!!m.on); } |
2457 | 2471 | else if (m.type === 'agentStart'){ setStreaming(true); agentBubble = null; agentRaw = ''; renderPlan([]); setAgentStatus('thinking…'); } |
2458 | 2472 | else if (m.type === 'agentStatus'){ finishAgentBubble(); setAgentStatus(m.text || 'working…'); } |
2459 | | - else if (m.type === 'agentDelta'){ clearStatus(); if (!agentBubble){ agentBubble = makeStream(add('assistant', '')); agentRaw = ''; } agentRaw += m.text; streamFeed(agentBubble, agentRaw); } |
| 2473 | + else if (m.type === 'agentDelta'){ clearStatus(); setWork('Responding…'); if (!agentBubble){ agentBubble = makeStream(add('assistant', '')); agentRaw = ''; } agentRaw += m.text; streamFeed(agentBubble, agentRaw); } |
2460 | 2474 | else if (m.type === 'agentTurnEnd'){ finishAgentBubble(); } |
2461 | 2475 | else if (m.type === 'agentTool'){ finishAgentBubble(); addAgentLine(m.icon, m.text); } |
2462 | 2476 | else if (m.type === 'agentApproval'){ addApproval(m); } |
|
0 commit comments