Skip to content

Commit 9a4bea0

Browse files
committed
fix: replace pending-dots with CSS spinner on model-icon
- Loading state now shows a spinning circle on the AI icon instead of a separate blinking '…' span, so there is only one message bubble (no duplicate avatar icon) - Spinner uses @Keyframes ai-spin (CSS-driven), unaffected by Android animator duration scale (Window.ANIMATOR_DURATION_SCALE) - On message arrival the spinner icon is swapped in-place to the real avatar (👤), so the bubble updates rather than a second one appearing
1 parent 13c7f45 commit 9a4bea0

1 file changed

Lines changed: 12 additions & 12 deletions

File tree

index.html

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -218,8 +218,11 @@
218218
.model-text{flex:1;white-space:pre-wrap}
219219
.bubble.error{background:var(--bg-error-container);color:var(--text-on-error-container);width:100%;max-width:100%}
220220
.bubble-wrap.error-wrap{display:block}
221-
.pending-dots{display:inline-block;margin-left:6px;font-size:16px;line-height:1;animation:blink 1.2s infinite}
222-
@keyframes blink{0%,100%{opacity:.2}50%{opacity:1}}
221+
/* pending-dots removed – replaced by spinner on model-icon */
222+
.model-icon.loading{background:transparent;font-size:0}
223+
.model-icon.loading::after{content:'';display:block;width:18px;height:18px;border:2.5px solid var(--outline);border-top-color:var(--primary);border-radius:50%;animation:ai-spin 1.4s linear infinite}
224+
/* Use CSS-driven keyframe – unaffected by Android animator duration scale */
225+
@keyframes ai-spin{to{transform:rotate(360deg)}}
223226

224227
/* ── Command status card ─────────────────────────────────── */
225228
.cmd-status-card{background:var(--bg-secondary-container);border-radius:12px;padding:12px 14px;margin:6px 0;font-size:13px;color:var(--text-on-secondary-container)}
@@ -1038,16 +1041,13 @@
10381041
const allModelBubbles = [...msgs.querySelectorAll('.bubble.model')];
10391042
const last = allModelBubbles.length ? allModelBubbles[allModelBubbles.length - 1] : null;
10401043
const parentWrap = last && last.closest('.bubble-wrap');
1041-
if (parentWrap && parentWrap.querySelector('.pending-dots')) {
1044+
if (parentWrap && last.querySelector('.model-icon.loading')) {
10421045
const textSpan = last.querySelector('.model-text') || last;
1043-
// Preserve the icon span if present
1044-
const icon = last.querySelector('.model-icon');
1045-
if (icon) {
1046-
textSpan.textContent = text;
1047-
if (!isPending) parentWrap.querySelector('.pending-dots')?.remove();
1048-
} else {
1049-
last.textContent = text;
1050-
if (!isPending) parentWrap.querySelector('.pending-dots')?.remove();
1046+
textSpan.textContent = text;
1047+
if (!isPending) {
1048+
// Swap spinner icon → real avatar icon
1049+
const icon = last.querySelector('.model-icon');
1050+
if (icon) { icon.classList.remove('loading'); icon.innerHTML = '👤'; }
10511051
}
10521052
} else {
10531053
addModelBubble(text, isPending);
@@ -3442,7 +3442,7 @@
34423442
function addModelBubble(text, isPending) {
34433443
const wrap = document.createElement('div');
34443444
wrap.className = 'bubble-wrap';
3445-
wrap.innerHTML = `<div class="bubble model"><span class="model-icon">&#128100;</span><span class="model-text">${escHtml(text)}${isPending?'<span class="pending-dots">…</span>':''}</span></div>`;
3445+
wrap.innerHTML = `<div class="bubble model"><span class="model-icon${isPending?' loading':''}">&#${isPending?'0':128100};</span><span class="model-text">${escHtml(text)}</span></div>`;
34463446
document.getElementById('chat-messages').appendChild(wrap);
34473447
scrollToBottom();
34483448
}

0 commit comments

Comments
 (0)