Fix Hud
Rolling release / release (push) Successful in 7m27s

This commit is contained in:
2026-09-12 16:09:52 -04:00
parent 2f6e4b3af9
commit 04a9306594
3 changed files with 108 additions and 13 deletions
@@ -137,7 +137,10 @@ export default class JarvisExtension extends Extension {
settingsItem.connect('activate', () => this._openSettings());
menu.addMenuItem(settingsItem);
} catch {}
this._menuState = menu.connect('open-state-changed', (_menu, open) => { if (!open) this.popup.endTalk(); });
this._menuState = menu.connect('open-state-changed', (_menu, open) => {
if (!open) this.popup.endTalk();
else this.popup.followActive();
});
}
_openPopup() { try { this._indicator.menu.open(); } catch {} }
_openSettings() {
+89 -12
View File
@@ -177,6 +177,10 @@ export class ConversationView {
this._userPickedTab = false;
this._activity = '';
this.reducedMotion = false;
this._chatLayoutTimers = [];
this._thinkLayoutTimers = [];
this._bindFollow(this.scroll);
this._bindFollow(this.thinkingScroll);
}
_bindChip(button, action) {
button.connect('clicked', () => action?.());
@@ -204,16 +208,71 @@ export class ConversationView {
this.showTab('chat');
}
setNotice(text) { const body = shortError(text); this.notice.text = body; this.notice.visible = Boolean(body); }
_followConversation() { this._followScroll(this.scroll); }
_followThinking() { this._followScroll(this.thinkingScroll); }
_followScroll(scroll) {
_bindFollow(scroll) {
const adjustment = scroll?.get_vadjustment?.() || scroll?.vadjustment;
if (!adjustment || typeof adjustment.connect !== 'function') return;
scroll._jarvisFollow = true;
adjustment.connect('notify::value', () => {
if (this._pinning) return;
scroll._jarvisFollow = this._nearBottom(adjustment);
});
adjustment.connect('notify::upper', () => {
if (scroll.visible === false) return;
if (scroll._jarvisFollow !== false) this._pinScroll(scroll, { force: true });
});
}
_nearBottom(adjustment) {
const upper = Number(adjustment?.upper) || 0;
const page = Number(adjustment?.page_size) || 0;
const value = Number(adjustment?.value) || 0;
return value >= Math.max(0, upper - page) - 32;
}
_clearLayoutTimers(slot) {
for (const id of this[slot] || []) {
try { GLib.Source.remove(id); } catch {}
}
this[slot] = [];
}
_relayoutPane(scroll) {
try { scroll?.queue_relayout?.(); } catch {}
const child = scroll?.get_child?.() || scroll?.get_first_child?.();
try { child?.queue_relayout?.(); } catch {}
try { child?.get_first_child?.()?.queue_relayout?.(); } catch {}
}
_pinScroll(scroll, { force = false } = {}) {
const adjustment = scroll?.get_vadjustment?.() || scroll?.vadjustment;
if (!adjustment) return;
if (force) scroll._jarvisFollow = true;
if (!force && scroll._jarvisFollow === false) return;
const upper = Number(adjustment.upper) || 0;
const page = Number(adjustment.page_size) || 0;
this._pinning = true;
try { adjustment.value = Math.max(0, upper - page); } catch {}
this._pinning = false;
}
_followScroll(scroll, { force = false } = {}) {
this._pinScroll(scroll, { force });
GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE || GLib.PRIORITY_DEFAULT, () => {
try { adjustment.value = Math.max(0, adjustment.upper - adjustment.page_size); } catch {}
this._pinScroll(scroll, { force });
return GLib.SOURCE_REMOVE;
});
}
_followAfterLayout(scroll, { force = true } = {}) {
const slot = scroll === this.thinkingScroll ? '_thinkLayoutTimers' : '_chatLayoutTimers';
this._relayoutPane(scroll);
this._followScroll(scroll, { force });
this._clearLayoutTimers(slot);
this[slot] = [0, 50].map((delay) => GLib.timeout_add(GLib.PRIORITY_DEFAULT, delay, () => {
this._pinScroll(scroll, { force });
return GLib.SOURCE_REMOVE;
}));
}
_followConversation() { this._followScroll(this.scroll); }
_followThinking() { this._followScroll(this.thinkingScroll); }
followActive() {
const scroll = this._tab === 'thinking' ? this.thinkingScroll : this.scroll;
this._followAfterLayout(scroll, { force: true });
}
showTab(name, { user = false } = {}) {
if (user) this._userPickedTab = true;
this._tab = name === 'thinking' ? 'thinking' : 'chat';
@@ -228,6 +287,7 @@ export class ConversationView {
this.chipScroll.visible = !thinking && this.chips.get_n_children() > 0;
this.entry.visible = !thinking;
this.talk.visible = !thinking;
this.followActive();
}
addRow(who, text) {
const body = safeText(text);
@@ -240,7 +300,8 @@ export class ConversationView {
if (typeof this.transcript.remove_child === 'function') this.transcript.remove_child(first);
else first.destroy();
}
this._followConversation();
if (this._tab === 'chat') this._followAfterLayout(this.scroll, { force: false });
else this._followConversation();
return row;
}
token(text) {
@@ -255,14 +316,16 @@ export class ConversationView {
}
row.text = `${row.text}${chunk}`;
row.accessible_name = `Jarvis: ${row.text}`;
this._followConversation();
if (this._tab === 'chat') this._followAfterLayout(this.scroll, { force: false });
else this._followConversation();
}
updateThinking(text) {
const chunk = safeText(text);
if (!chunk) return;
this.thinking.text = `${this.thinking.text || ''}${chunk}`;
if (this._state === 'THINKING' && !this._userPickedTab) this.showTab('thinking');
this._followThinking();
if (this._tab === 'thinking') this._followAfterLayout(this.thinkingScroll, { force: false });
else this._followThinking();
}
toggleThinking() { this.showTab(this._tab === 'thinking' ? 'chat' : 'thinking', { user: true }); }
finishThinking() { if (this._state !== 'THINKING') this.showTab('chat'); }
@@ -318,7 +381,8 @@ export class ConversationView {
row.text = body;
}
row.accessible_name = `Activity: ${body}`;
this._followConversation();
if (this._tab === 'chat') this._followAfterLayout(this.scroll, { force: false });
else this._followConversation();
return row;
}
_addToolRow(text) { return this._setToolActivity(text); }
@@ -341,15 +405,22 @@ export class ConversationView {
finalizeReply(text) {
this.finishThinking();
const spoken = safeText(text);
if (this.replyFinalized) return;
if (this.replyFinalized) {
this.followActive();
return;
}
let row = this.transcript.get_last_child?.();
if (this.streamingReply && row && String(row.style_class || '').includes('jarvis-row-jarvis') && !String(row.style_class || '').includes('jarvis-row-tool')) {
const body = String(row.text || '').replace(/^J\s+/, '');
if (spoken && !body.trim()) { row.text = `J ${spoken}`; row.accessible_name = `Jarvis: ${spoken}`; }
this.streamingReply = false; this.replyFinalized = true; return;
this.streamingReply = false;
this.replyFinalized = true;
this.followActive();
return;
}
if (spoken) this.addRow('J', spoken);
this.replyFinalized = true;
this.followActive();
}
setConnectionStatus(kind) {
this._connection = kind;
@@ -382,7 +453,8 @@ export class ConversationView {
this.showTab('chat');
}
this._refreshStatusLine();
this.title.text = value === 'SLEEPING' ? 'Jarvis · privacy' : 'Jarvis';
const name = this._assistantName || 'Jarvis';
this.title.text = value === 'SLEEPING' ? `${name} · privacy` : name;
}
_refreshStatusLine() {
const value = this._state;
@@ -416,7 +488,11 @@ export class ConversationView {
} catch { this.addRow('J', json); }
}
endTalk() { this.onTalk?.(false); }
destroy() { this.root.destroy(); }
destroy() {
this._clearLayoutTimers('_chatLayoutTimers');
this._clearLayoutTimers('_thinkLayoutTimers');
this.root.destroy();
}
}
const PANEL_STATES = {
@@ -538,6 +614,7 @@ export class SessionPanel {
this.view.thinkingScroll.set_height(Math.max(120, Math.min(240, monitor.height - 400)));
}
this.root.visible = true;
this.view.followActive();
}
hide() { this.view.endTalk(); this.root.visible = false; this.minimized = true; }
toggle() { this.root.visible ? this.hide() : this.show(true); }