Updates
Rolling release / release (push) Failing after 1m24s

This commit is contained in:
2026-09-11 19:36:12 -04:00
parent 7604cded2c
commit a78708f8ff
26 changed files with 298 additions and 59 deletions
+30 -5
View File
@@ -18,13 +18,17 @@ class Store:
self.data[key] = value; CONFIG.parent.mkdir(parents=True, exist_ok=True); CONFIG.write_text(json.dumps(self.data, indent=2) + '\n')
class ControlCenter(Adw.Application):
def __init__(self): super().__init__(application_id='io.qvac.Jarvis.Control', flags=Gio.ApplicationFlags.DEFAULT); self.store = Store(); self.rows = {}; self.proxy = None
def __init__(self): super().__init__(application_id='io.qvac.Jarvis.Control', flags=Gio.ApplicationFlags.DEFAULT_FLAGS); self.store = Store(); self.rows = {}; self.proxy = None
def do_activate(self):
if getattr(self, 'window', None): self.window.present(); return
self.window = Adw.ApplicationWindow(application=self, title='Jarvis Control Center', default_width=1080, default_height=700)
self.window.set_content(self.build_shell()); self.connect_daemon(); self.window.present()
def build_shell(self):
split = Adw.NavigationSplitView(); sidebar = Adw.NavigationPage(title='Jarvis', child=self.build_sidebar(split)); content = Adw.NavigationPage(title='General', child=self.build_page('general')); split.set_sidebar(sidebar); split.set_content(content); self.content = content; self.split = split; return split
split = Adw.NavigationSplitView(); sidebar = Adw.NavigationPage(title='Jarvis', child=self.build_sidebar(split)); content = Adw.NavigationPage(title='General', child=self.build_page('general')); split.set_sidebar(sidebar); split.set_content(content); self.content = content; self.split = split
shell = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
header = Adw.HeaderBar()
header.set_title_widget(Adw.WindowTitle(title='Jarvis', subtitle='Local voice assistant'))
shell.append(header); split.set_vexpand(True); shell.append(split); return shell
def build_sidebar(self, split):
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=12); box.set_margin_top(18); box.set_margin_bottom(18); box.set_margin_start(12); box.set_margin_end(12)
heading = Gtk.Label(label='JARVIS', xalign=0); heading.add_css_class('title-2'); box.append(heading)
@@ -34,7 +38,9 @@ class ControlCenter(Adw.Application):
row = Gtk.ListBoxRow(); row.set_child(Gtk.Label(label=label, xalign=0, margin_top=10, margin_bottom=10, margin_start=12, margin_end=12)); row.set_name(key); listbox.append(row)
listbox.connect('row-selected', lambda _list, row: self.select_page(split, row.get_name() if row else 'general')); listbox.select_row(listbox.get_row_at_index(0)); box.append(listbox)
self.runtime = Gtk.Label(label='Connecting to jarvisd…', xalign=0, wrap=True); self.runtime.add_css_class('dim-label'); box.append(self.runtime); return box
def select_page(self, split, key): self.content.set_child(self.build_page(key)); self.content.set_title(dict((p[0], p[1]) for p in PAGES)[key])
def select_page(self, split, key):
if not hasattr(self, 'content'): return
self.content.set_child(self.build_page(key)); self.content.set_title(dict((p[0], p[1]) for p in PAGES)[key])
def build_page(self, key):
page = Gtk.ScrolledWindow(); page.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC); root = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=18); root.set_margin_top(28); root.set_margin_bottom(28); root.set_margin_start(32); root.set_margin_end(32); page.set_child(root)
title = next(p for p in PAGES if p[0] == key); header = Gtk.Label(label=title[1], xalign=0); header.add_css_class('title-1'); root.append(header); sub = Gtk.Label(label=title[2], xalign=0); sub.add_css_class('dim-label'); root.append(sub)
@@ -50,7 +56,15 @@ class ControlCenter(Adw.Application):
def general(self, root):
group = self.group(root, 'General'); self.entry(group, 'wake_phrase', 'Wake phrase', 'hey jarvis'); self.entry(group, 'language', 'Language', 'en-US'); self.entry(group, 'hotkey', 'Hotkey', '<Super><Space>'); self.switch(group, 'startup', 'Start Jarvis when I log in', True)
def voice(self, root):
group = self.group(root, 'Voice'); self.entry(group, 'voice_id', 'Voice ID'); self.entry(group, 'voice_reference', 'Reference WAV path'); self.entry(group, 'wake_command', 'Local wake bridge command'); self.switch(group, 'tts_enabled', 'Enable spoken replies', True); self.button(group, 'Voice enrollment', 'Enroll reference', lambda _b: self.call('IngestPath', '(s)', [self.store.data.get('voice_reference', '')])); self.button(group, 'Preview voice', 'Speak preview', lambda _b: self.call('Say', '(s)', ['Jarvis voice preview.'])); self.button(group, 'Wake model', 'Refresh runtime', lambda _b: self.call('GetRuntimeStatus'))
group = self.group(root, 'Speech and microphone', 'Hold Talk in the overlay to speak. Wake detection is optional. Changes apply after restarting Jarvis.')
self.voice_status = Gtk.Label(label='Checking voice readiness…', xalign=0, wrap=True)
root.append(self.voice_status)
self.entry(group, 'wake_command', 'Local wake bridge command')
self.switch(group, 'tts_enabled', 'Enable spoken replies', True)
self.button(group, 'Test your speakers', 'Speak preview', lambda _b: self.call('Say', '(s)', ['Jarvis voice preview.']))
self.button(group, 'Voice readiness', 'Refresh', lambda _b: self.call('GetRuntimeStatus'))
self.call('GetRuntimeStatus')
def models(self, root):
group = self.group(root, 'Single QVAC master'); self.model_status = Gtk.Label(label='Loading master status…', xalign=0, wrap=True); group.add(Adw.ActionRow(title='Runtime status', child=self.model_status)); self.entry(group, 'model', 'Active model', 'qwen3.5-4b'); self.button(group, 'Assess model fit', 'Assess', lambda _b: self.assess()); self.button(group, 'Model lifecycle', 'Download / resume', lambda _b: self.download()); self.button(group, 'Model lifecycle', 'Pause / cancel', lambda _b: self.call('CancelModel', '(s)', [self.store.data.get('model', 'qwen3.5-4b')]))
capabilities = self.group(root, 'Capabilities', 'Every capability remains visible when its model does not fit the GPU profile.')
@@ -81,7 +95,18 @@ class ControlCenter(Adw.Application):
try: self.update_status(proxy.call_finish(result))
except Exception as exc: self.runtime.set_text(f'{method}: {exc}')
def update_status(self, result):
value = result.unpack()[0] if result else ''; self.runtime.set_text('Connected · local QVAC master');
values = result.unpack() if result else (); value = values[0] if values else ''; self.runtime.set_text('Connected · local QVAC master');
try:
status = json.loads(value) if isinstance(value, str) else {}
voice = status.get('voice') or {}
if hasattr(self, 'voice_status') and 'voice' in status:
self.voice_status.set_text('\n'.join([
'Speech output: ' + ('ready' if voice.get('tts') else 'unavailable or disabled'),
'Microphone: ' + ('ready' if voice.get('asr') and voice.get('capture') else 'unavailable'),
'Wake phrase: ' + ('enabled' if voice.get('wake') else 'off — use Hold Talk'),
*[f'{key}: {message}' for key, message in voice.get('errors', {}).items()],
]))
except (ValueError, AttributeError): pass
if hasattr(self, 'model_status'): self.model_status.set_text(value)
def assess(self): self.call('AssessModelFit', '(s)', [self.store.data.get('model', 'qwen3.5-4b')])
def download(self): self.call('DownloadModel', '(s)', [self.store.data.get('model', 'qwen3.5-4b')])