@@ -0,0 +1,33 @@
|
||||
export const BRAND = {
|
||||
name: 'Jarvis',
|
||||
product: 'Jarvis QVAC',
|
||||
tagline: 'Local voice for your desktop',
|
||||
applicationId: 'io.qvac.Jarvis.Control',
|
||||
uuid: '[email protected]',
|
||||
colors: {
|
||||
gold: '#F4B942',
|
||||
goldBright: '#FFE09A',
|
||||
goldDeep: '#C48A1A',
|
||||
signal: '#4FD2FF',
|
||||
ink: '#0B0E14',
|
||||
slate: '#16191F',
|
||||
paper: '#F6F7FB',
|
||||
mute: '#AEB6C8',
|
||||
dim: '#9AA8C0',
|
||||
},
|
||||
accents: [
|
||||
{ id: 'gold', label: 'Gold', value: '#F4B942' },
|
||||
{ id: 'signal', label: 'Signal', value: '#4FD2FF' },
|
||||
{ id: 'copper', label: 'Copper', value: '#E07A3D' },
|
||||
{ id: 'jade', label: 'Jade', value: '#3DDC97' },
|
||||
{ id: 'rose', label: 'Rose', value: '#F071A8' },
|
||||
],
|
||||
};
|
||||
|
||||
export const DEFAULT_ACCENT = BRAND.colors.gold;
|
||||
export const SIGNAL = BRAND.colors.signal;
|
||||
|
||||
export function normalizeAccent(value, fallback = DEFAULT_ACCENT) {
|
||||
const color = String(value || '').trim();
|
||||
return /^#[0-9A-Fa-f]{6}$/.test(color) ? color : fallback;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
@define-color jarvis_gold #F4B942;
|
||||
@define-color jarvis_gold_bright #FFE09A;
|
||||
@define-color jarvis_signal #4FD2FF;
|
||||
@define-color jarvis_ink #0B0E14;
|
||||
@define-color jarvis_slate #16191F;
|
||||
@define-color accent_color @jarvis_gold;
|
||||
@define-color accent_bg_color @jarvis_gold;
|
||||
@define-color accent_fg_color @jarvis_slate;
|
||||
|
||||
.jarvis-brand-name {
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.6px;
|
||||
}
|
||||
|
||||
.jarvis-brand-tagline {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
button.jarvis-swatch {
|
||||
min-width: 24px;
|
||||
min-height: 24px;
|
||||
padding: 0;
|
||||
border-radius: 999px;
|
||||
border: 1px solid alpha(#ffffff, 0.22);
|
||||
}
|
||||
|
||||
button.jarvis-swatch-gold { background: #F4B942; }
|
||||
button.jarvis-swatch-signal { background: #4FD2FF; }
|
||||
button.jarvis-swatch-copper { background: #E07A3D; }
|
||||
button.jarvis-swatch-jade { background: #3DDC97; }
|
||||
button.jarvis-swatch-rose { background: #F071A8; }
|
||||
|
||||
button.jarvis-swatch:hover {
|
||||
border-color: alpha(#ffffff, 0.55);
|
||||
}
|
||||
|
||||
button.jarvis-swatch:checked,
|
||||
button.jarvis-swatch:active {
|
||||
box-shadow: 0 0 0 2px @jarvis_ink, 0 0 0 4px @jarvis_gold;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128" role="img" aria-label="Jarvis">
|
||||
<title>Jarvis</title>
|
||||
<defs>
|
||||
<radialGradient id="glow" cx="50%" cy="42%" r="48%">
|
||||
<stop offset="0%" stop-color="#4FD2FF" stop-opacity=".22"/>
|
||||
<stop offset="100%" stop-color="#0B0E14" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
</defs>
|
||||
<rect width="128" height="128" rx="28" fill="#0B0E14"/>
|
||||
<rect x="5" y="5" width="118" height="118" rx="24" fill="none" stroke="#F4B942" stroke-opacity=".2" stroke-width="1.5"/>
|
||||
<circle cx="64" cy="58" r="42" fill="url(#glow)"/>
|
||||
<g transform="translate(32 22) scale(1)">
|
||||
<path d="M14.5 30.5c0-10.77 8.73-19.5 19.5-19.5 8.05 0 14.95 4.88 17.92 11.82" fill="none" stroke="#F4B942" stroke-width="6" stroke-linecap="round"/>
|
||||
<path d="M34 11v29.5c0 6.35-5.15 11.5-11.5 11.5" fill="none" stroke="#F4B942" stroke-width="6" stroke-linecap="round"/>
|
||||
<circle cx="34" cy="33" r="5.5" fill="#4FD2FF"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 971 B |
@@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="none" role="img" aria-label="Jarvis">
|
||||
<path d="M3.2 7.4A5.3 5.3 0 0 1 8.5 2.2c2.2 0 4.1 1.33 4.9 3.25" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/>
|
||||
<path d="M8.5 2.2v7.7A3.1 3.1 0 0 1 5.4 13" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/>
|
||||
<circle cx="8.5" cy="8.1" r="1.25" fill="currentColor"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 415 B |
@@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" fill="none" role="img" aria-label="Jarvis">
|
||||
<title>Jarvis</title>
|
||||
<path d="M14.5 30.5c0-10.77 8.73-19.5 19.5-19.5 8.05 0 14.95 4.88 17.92 11.82" stroke="#F4B942" stroke-width="5.5" stroke-linecap="round"/>
|
||||
<path d="M34 11v29.5c0 6.35-5.15 11.5-11.5 11.5" stroke="#F4B942" stroke-width="5.5" stroke-linecap="round"/>
|
||||
<circle cx="34" cy="33" r="5" fill="#4FD2FF"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 438 B |
@@ -0,0 +1,9 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 300 64" role="img" aria-label="Jarvis">
|
||||
<title>Jarvis</title>
|
||||
<g transform="translate(8 10) scale(.68)">
|
||||
<path d="M14.5 30.5c0-10.77 8.73-19.5 19.5-19.5 8.05 0 14.95 4.88 17.92 11.82" fill="none" stroke="#F4B942" stroke-width="5.5" stroke-linecap="round"/>
|
||||
<path d="M34 11v29.5c0 6.35-5.15 11.5-11.5 11.5" fill="none" stroke="#F4B942" stroke-width="5.5" stroke-linecap="round"/>
|
||||
<circle cx="34" cy="33" r="5" fill="#4FD2FF"/>
|
||||
</g>
|
||||
<text x="64" y="42" fill="#F6F7FB" font-family="Adwaita Sans, Cantarell, sans-serif" font-size="26" font-weight="700" letter-spacing="3.2">JARVIS</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 664 B |
+17
@@ -0,0 +1,17 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128" role="img" aria-label="Jarvis">
|
||||
<title>Jarvis</title>
|
||||
<defs>
|
||||
<radialGradient id="glow" cx="50%" cy="42%" r="48%">
|
||||
<stop offset="0%" stop-color="#4FD2FF" stop-opacity=".22"/>
|
||||
<stop offset="100%" stop-color="#0B0E14" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
</defs>
|
||||
<rect width="128" height="128" rx="28" fill="#0B0E14"/>
|
||||
<rect x="5" y="5" width="118" height="118" rx="24" fill="none" stroke="#F4B942" stroke-opacity=".2" stroke-width="1.5"/>
|
||||
<circle cx="64" cy="58" r="42" fill="url(#glow)"/>
|
||||
<g transform="translate(32 22) scale(1)">
|
||||
<path d="M14.5 30.5c0-10.77 8.73-19.5 19.5-19.5 8.05 0 14.95 4.88 17.92 11.82" fill="none" stroke="#F4B942" stroke-width="6" stroke-linecap="round"/>
|
||||
<path d="M34 11v29.5c0 6.35-5.15 11.5-11.5 11.5" fill="none" stroke="#F4B942" stroke-width="6" stroke-linecap="round"/>
|
||||
<circle cx="34" cy="33" r="5.5" fill="#4FD2FF"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 971 B |
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"name": "Jarvis",
|
||||
"product": "Jarvis QVAC",
|
||||
"tagline": "Local voice for your desktop",
|
||||
"voice": "Precise, warm, local-first. Gold is presence. Cyan is thought.",
|
||||
"applicationId": "io.qvac.Jarvis.Control",
|
||||
"uuid": "[email protected]",
|
||||
"colors": {
|
||||
"gold": "#F4B942",
|
||||
"goldBright": "#FFE09A",
|
||||
"goldDeep": "#C48A1A",
|
||||
"signal": "#4FD2FF",
|
||||
"ink": "#0B0E14",
|
||||
"slate": "#16191F",
|
||||
"paper": "#F6F7FB",
|
||||
"mute": "#AEB6C8",
|
||||
"dim": "#9AA8C0"
|
||||
},
|
||||
"accents": [
|
||||
{ "id": "gold", "label": "Gold", "value": "#F4B942" },
|
||||
{ "id": "signal", "label": "Signal", "value": "#4FD2FF" },
|
||||
{ "id": "copper", "label": "Copper", "value": "#E07A3D" },
|
||||
{ "id": "jade", "label": "Jade", "value": "#3DDC97" },
|
||||
{ "id": "rose", "label": "Rose", "value": "#F071A8" }
|
||||
],
|
||||
"type": {
|
||||
"ui": "Adwaita Sans / Cantarell",
|
||||
"wordmarkTracking": "0.8px",
|
||||
"wordmarkWeight": 700
|
||||
},
|
||||
"radius": {
|
||||
"hud": 16,
|
||||
"chip": 999,
|
||||
"row": 8
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,8 @@ import { ConversationView, JarvisOsd, ComputerUseChrome, SessionPanel, coerceTex
|
||||
const BUS = 'io.qvac.Jarvis';
|
||||
const PATH = '/io/qvac/Jarvis';
|
||||
const IFACE = 'io.qvac.Jarvis.Session';
|
||||
const GLYPHS = { ARMED: '◯', LISTENING: '◌', THINKING: '◉', SPEAKING: '◎', SLEEPING: '◐' };
|
||||
const DEFAULT_ACCENT = '#F4B942';
|
||||
const SIGNAL = '#4FD2FF';
|
||||
|
||||
class JarvisProxy {
|
||||
async connect() {
|
||||
@@ -55,17 +56,23 @@ export default class JarvisExtension extends Extension {
|
||||
this._theme = St.ThemeContext.get_for_stage(global.stage).get_theme(); this._stylesheet = Gio.File.new_for_path(`${this.path}/stylesheet.css`); try { this._theme.load_stylesheet(this._stylesheet); } catch (error) { log(`Jarvis stylesheet unavailable: ${error.message}`); }
|
||||
this.settings = this.getSettings();
|
||||
this.proxy = new JarvisProxy();
|
||||
this.popup = new ConversationView({ compact: true });
|
||||
this.popup = new ConversationView({ compact: true, brandDir: this.path });
|
||||
this.osd = new JarvisOsd();
|
||||
this.cu = new ComputerUseChrome(); this.cu.attach();
|
||||
this.session = new SessionPanel(); this.session.attach();
|
||||
this.session = new SessionPanel({ brandDir: this.path }); this.session.attach();
|
||||
this._bindSurface(this.popup);
|
||||
this._bindSurface(this.session.view);
|
||||
this._applyAccessibility();
|
||||
this._removeShellService = installShellService();
|
||||
this._indicator = new PanelMenu.Button(0.0, 'Jarvis QVAC', false); this._indicator.accessible_name = 'Jarvis voice assistant';
|
||||
this._panelBox = new St.BoxLayout({ style_class: 'jarvis-panel-box', y_align: Clutter.ActorAlign.CENTER });
|
||||
this._glyph = new St.Label({ text: '◯ Jarvis', style_class: 'jarvis-panel-glyph', y_align: Clutter.ActorAlign.CENTER }); this._glyph.accessible_name = 'Jarvis idle';
|
||||
this._mark = new St.Icon({ icon_name: 'audio-input-microphone-symbolic', icon_size: 16, style_class: 'jarvis-panel-mark', y_align: Clutter.ActorAlign.CENTER });
|
||||
try {
|
||||
const file = Gio.File.new_for_path(`${this.path}/brand/icons/jarvis-mark.svg`);
|
||||
if (file.query_exists(null)) this._mark.gicon = new Gio.FileIcon({ file });
|
||||
} catch (error) { log(`Jarvis mark unavailable: ${error.message}`); }
|
||||
this._glyph = new St.Label({ text: 'Jarvis', style_class: 'jarvis-panel-glyph', y_align: Clutter.ActorAlign.CENTER }); this._glyph.accessible_name = 'Jarvis idle';
|
||||
this._panelBox.add_child(this._mark);
|
||||
this._panelBox.add_child(this._glyph);
|
||||
this.osd.attach(this._panelBox);
|
||||
this._indicator.add_child(this._panelBox);
|
||||
@@ -264,17 +271,27 @@ export default class JarvisExtension extends Extension {
|
||||
}
|
||||
_setState(state) {
|
||||
const value = safeText(state);
|
||||
this._glyph.text = `${GLYPHS[value] || '◯'} Jarvis`;
|
||||
this._glyph.text = 'Jarvis';
|
||||
this._glyph.accessible_name = `Jarvis ${value.toLowerCase()}`;
|
||||
this._indicator.accessible_name = `Jarvis ${value.toLowerCase()}`;
|
||||
if (this._panelBox) {
|
||||
for (const name of ['armed', 'listening', 'speaking', 'thinking', 'sleeping']) {
|
||||
this._panelBox.remove_style_class_name(`jarvis-state-${name}`);
|
||||
}
|
||||
this._panelBox.add_style_class_name(`jarvis-state-${value.toLowerCase()}`);
|
||||
}
|
||||
this._eachView((view) => view.setState(value));
|
||||
this.osd.setState(value);
|
||||
}
|
||||
_applyAccent() {
|
||||
const style = `--jarvis-accent: ${this.settings.get_string('accent-color')};`;
|
||||
const color = this.settings.get_string('accent-color');
|
||||
const accent = /^#[0-9A-Fa-f]{6}$/.test(String(color || '')) ? color : DEFAULT_ACCENT;
|
||||
const style = `--jarvis-accent: ${accent}; --jarvis-signal: ${SIGNAL};`;
|
||||
this.popup.root.set_style(style);
|
||||
this.session.root.set_style(style);
|
||||
this.osd.root.set_style(style);
|
||||
this.cu.root.set_style(style);
|
||||
this._panelBox?.set_style(style);
|
||||
}
|
||||
_applyAccessibility() {
|
||||
try {
|
||||
@@ -300,7 +317,7 @@ export default class JarvisExtension extends Extension {
|
||||
this.osd?.destroy(); this.cu?.destroy(); this.session?.destroy(); this.popup?.destroy();
|
||||
this._indicator?.destroy();
|
||||
if (this._theme && this._stylesheet) { try { this._theme.unload_stylesheet(this._stylesheet); } catch {} }
|
||||
this.popup = this.session = this.osd = this.cu = this._indicator = this._glyph = this.proxy = null;
|
||||
this.popup = this.session = this.osd = this.cu = this._indicator = this._glyph = this._mark = this.proxy = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"uuid": "[email protected]",
|
||||
"name": "Jarvis QVAC",
|
||||
"description": "Local-first QVAC voice assistant in the GNOME top bar",
|
||||
"name": "Jarvis",
|
||||
"description": "Local voice for your desktop. Jarvis QVAC lives in the GNOME top bar.",
|
||||
"shell-version": ["46", "47", "48", "49", "50"],
|
||||
"version": 2,
|
||||
"session-modes": ["user"],
|
||||
|
||||
@@ -149,8 +149,9 @@ export class SettingsEditor {
|
||||
button.connect('clicked', () => { for (const field of fields) { this.setValue(field, field.default); this.rows.get(field.key)?._setValue(field.default); } });
|
||||
reset.add_suffix(button); group.add(reset); page.add(group);
|
||||
}
|
||||
page(parent, title, groups, iconName, preview = false) {
|
||||
page(parent, title, groups, iconName, preview = false, intro = null) {
|
||||
const page = new Adw.PreferencesPage({ title, icon_name: iconName });
|
||||
if (intro) page.add(intro);
|
||||
const fields = this.fields.filter(field => groups.includes(field.group));
|
||||
this.controls(page, fields, { preview });
|
||||
for (const name of groups) {
|
||||
|
||||
@@ -2,17 +2,61 @@ import Adw from 'gi://Adw';
|
||||
import Gio from 'gi://Gio';
|
||||
import GLib from 'gi://GLib';
|
||||
import Gtk from 'gi://Gtk';
|
||||
import Gdk from 'gi://Gdk';
|
||||
import { SettingsEditor } from './settings-editor.js';
|
||||
import { BRAND, normalizeAccent } from './brand.js';
|
||||
|
||||
const BUS = 'io.qvac.Jarvis';
|
||||
const PATH = '/io/qvac/Jarvis';
|
||||
const IFACE = 'io.qvac.Jarvis.Session';
|
||||
const OVERLAY_STYLES = ['tray', 'expanded'];
|
||||
|
||||
function entryRow(settings, title, key) {
|
||||
const row = new Adw.EntryRow({ title });
|
||||
row.set_text(settings.get_string(key));
|
||||
row.connect('changed', () => settings.set_string(key, row.get_text()));
|
||||
function applyBrandCss(directory) {
|
||||
try {
|
||||
const css = new Gtk.CssProvider();
|
||||
css.load_from_path(`${directory}/brand/gtk.css`);
|
||||
Gtk.StyleContext.add_provider_for_display(Gdk.Display.get_default(), css, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
|
||||
} catch {}
|
||||
}
|
||||
|
||||
function brandBanner(directory) {
|
||||
const row = new Adw.ActionRow({ title: BRAND.product, subtitle: BRAND.tagline });
|
||||
try {
|
||||
const image = Gtk.Image.new_from_file(`${directory}/brand/icons/jarvis-mark.svg`);
|
||||
image.set_pixel_size(36);
|
||||
row.add_prefix(image);
|
||||
} catch {}
|
||||
row.activatable = false;
|
||||
return row;
|
||||
}
|
||||
|
||||
function accentRow(settings) {
|
||||
const current = normalizeAccent(settings.get_string('accent-color'));
|
||||
const row = new Adw.ActionRow({
|
||||
title: 'Accent color',
|
||||
subtitle: 'Gold is the Jarvis default. This tints the HUD immediately.',
|
||||
});
|
||||
const box = new Gtk.Box({ spacing: 6, valign: Gtk.Align.CENTER });
|
||||
for (const accent of BRAND.accents) {
|
||||
const button = new Gtk.Button({ tooltip_text: accent.label, valign: Gtk.Align.CENTER });
|
||||
button.add_css_class('circular');
|
||||
button.add_css_class('jarvis-swatch');
|
||||
button.add_css_class(`jarvis-swatch-${accent.id}`);
|
||||
button.set_size_request(24, 24);
|
||||
button.connect('clicked', () => settings.set_string('accent-color', accent.value));
|
||||
box.append(button);
|
||||
}
|
||||
const entry = new Gtk.Entry({ text: current, width_chars: 8, maxlength: 7, valign: Gtk.Align.CENTER });
|
||||
entry.connect('changed', () => {
|
||||
const value = normalizeAccent(entry.get_text(), '');
|
||||
if (value) settings.set_string('accent-color', value);
|
||||
});
|
||||
settings.connect('changed::accent-color', () => {
|
||||
const value = normalizeAccent(settings.get_string('accent-color'));
|
||||
if (entry.get_text() !== value) entry.set_text(value);
|
||||
});
|
||||
row.add_suffix(box);
|
||||
row.add_suffix(entry);
|
||||
return row;
|
||||
}
|
||||
|
||||
@@ -107,11 +151,14 @@ function grantRow() {
|
||||
}
|
||||
|
||||
export function fillSettingsWindow(window, settings, directory) {
|
||||
window.set_title('Jarvis QVAC');
|
||||
window.set_title(BRAND.product);
|
||||
window.set_default_size(840, 780);
|
||||
window.search_enabled = true;
|
||||
applyBrandCss(directory);
|
||||
const editor = new SettingsEditor(directory, settings);
|
||||
const voice = editor.page(window, 'Voice', ['Speech', 'Voice design'], 'audio-speakers-symbolic', true);
|
||||
const intro = new Adw.PreferencesGroup();
|
||||
intro.add(brandBanner(directory));
|
||||
const voice = editor.page(window, 'Voice', ['Speech', 'Voice design'], 'audio-speakers-symbolic', true, intro);
|
||||
window.add(voice);
|
||||
const listening = editor.page(window, 'Listening', ['Listening', 'Wake and privacy', 'Detection tuning', 'Audio routing'], 'audio-input-microphone-symbolic');
|
||||
window.add(listening);
|
||||
@@ -119,7 +166,7 @@ export function fillSettingsWindow(window, settings, directory) {
|
||||
const desktop = editor.page(window, 'Desktop', ['Desktop access', 'Desktop images'], 'preferences-desktop-display-symbolic');
|
||||
const desktopGroup = new Adw.PreferencesGroup({ title: 'Overlay and shortcuts', description: 'These appearance settings take effect immediately.' });
|
||||
desktopGroup.add(strvRow(settings, 'Hotkey', 'hotkey'));
|
||||
desktopGroup.add(entryRow(settings, 'Accent color', 'accent-color'));
|
||||
desktopGroup.add(accentRow(settings));
|
||||
desktopGroup.add(comboRow(settings, 'Desktop layout', 'Tray keeps Jarvis in the top bar; expanded opens a conversation panel', 'overlay-style', OVERLAY_STYLES));
|
||||
desktop.add(desktopGroup);
|
||||
const grantGroup = new Adw.PreferencesGroup({ title: 'Temporary desktop access' });
|
||||
|
||||
@@ -1,21 +1,61 @@
|
||||
.jarvis-panel-box { spacing: 6px; }
|
||||
.jarvis-panel-glyph { color: var(--jarvis-accent, #F4B942); font-size: 14px; }
|
||||
.jarvis-panel-state { padding: 1px 8px; border-radius: 999px; background-color: rgba(244, 185, 66, .22); }
|
||||
.jarvis-panel-state-label { color: var(--jarvis-accent, #F4B942); font-size: 12px; font-weight: bold; }
|
||||
/* Jarvis brand tokens. Gold is presence; cyan is thought. Accent is overridden live via --jarvis-accent. */
|
||||
.jarvis-panel-box {
|
||||
spacing: 7px;
|
||||
--jarvis-signal: #4FD2FF;
|
||||
--jarvis-ink: #0B0E14;
|
||||
--jarvis-slate: #16191F;
|
||||
--jarvis-paper: #F6F7FB;
|
||||
--jarvis-mute: #AEB6C8;
|
||||
--jarvis-dim: #9AA8C0;
|
||||
}
|
||||
.jarvis-panel-mark { color: var(--jarvis-accent, #F4B942); icon-size: 16px; }
|
||||
.jarvis-panel-glyph {
|
||||
color: var(--jarvis-accent, #F4B942);
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.8px;
|
||||
}
|
||||
.jarvis-panel-state { padding: 2px 9px; border-radius: 999px; background-color: alpha(var(--jarvis-accent, #F4B942), .22); }
|
||||
.jarvis-panel-state-label { color: var(--jarvis-accent, #F4B942); font-size: 11px; font-weight: 700; letter-spacing: 0.3px; }
|
||||
.jarvis-panel-box.jarvis-state-thinking .jarvis-panel-state,
|
||||
.jarvis-panel-state.jarvis-state-thinking { background-color: alpha(#4FD2FF, .22); }
|
||||
.jarvis-panel-box.jarvis-state-thinking .jarvis-panel-state-label,
|
||||
.jarvis-panel-state.jarvis-state-thinking .jarvis-panel-state-label { color: #4FD2FF; }
|
||||
.jarvis-panel-box.jarvis-state-sleeping .jarvis-panel-state,
|
||||
.jarvis-panel-state.jarvis-state-sleeping { background-color: rgba(174, 182, 200, .18); }
|
||||
.jarvis-panel-box.jarvis-state-sleeping .jarvis-panel-state-label,
|
||||
.jarvis-panel-state.jarvis-state-sleeping .jarvis-panel-state-label { color: #AEB6C8; }
|
||||
.jarvis-menu { max-width: 360px; }
|
||||
.jarvis-menu-item { padding: 0; }
|
||||
.jarvis-popup { width: 320px; padding: 12px 14px; spacing: 8px; color: #f6f7fb; }
|
||||
.jarvis-session { width: 420px; padding: 14px 16px; spacing: 8px; border-radius: 16px; background-color: rgba(11, 14, 20, .94); border: 1px solid rgba(244, 185, 66, .42); color: #f6f7fb; box-shadow: 0 12px 40px rgba(0, 0, 0, .45); }
|
||||
.jarvis-popup {
|
||||
width: 320px;
|
||||
padding: 12px 14px;
|
||||
spacing: 8px;
|
||||
color: #f6f7fb;
|
||||
--jarvis-signal: #4FD2FF;
|
||||
}
|
||||
.jarvis-session {
|
||||
width: 420px;
|
||||
padding: 14px 16px;
|
||||
spacing: 8px;
|
||||
border-radius: 16px;
|
||||
background-color: rgba(11, 14, 20, .94);
|
||||
border: 1px solid alpha(var(--jarvis-accent, #F4B942), .42);
|
||||
color: #f6f7fb;
|
||||
box-shadow: 0 12px 40px rgba(0, 0, 0, .45);
|
||||
--jarvis-signal: #4FD2FF;
|
||||
}
|
||||
.jarvis-popup-header { spacing: 8px; }
|
||||
.jarvis-title { font-weight: bold; letter-spacing: 0.4px; font-size: 14px; }
|
||||
.jarvis-local { color: var(--jarvis-accent, #F4B942); font-size: 11px; }
|
||||
.jarvis-mark { icon-size: 18px; color: var(--jarvis-accent, #F4B942); }
|
||||
.jarvis-title { font-weight: 700; letter-spacing: 0.6px; font-size: 15px; color: #f6f7fb; }
|
||||
.jarvis-local { color: var(--jarvis-accent, #F4B942); font-size: 11px; font-weight: 700; letter-spacing: 0.4px; }
|
||||
.jarvis-status-line { color: #aeb6c8; font-size: 12px; }
|
||||
.jarvis-tabs { spacing: 6px; }
|
||||
.jarvis-tab { padding: 4px 10px; border-radius: 999px; background-color: transparent; color: #9aa8c0; font-size: 12px; }
|
||||
.jarvis-tab:hover, .jarvis-tab:focus { color: #f6f7fb; background-color: rgba(255, 255, 255, .08); }
|
||||
.jarvis-tab-active { background-color: var(--jarvis-accent, #F4B942); color: #16191f; font-weight: bold; }
|
||||
.jarvis-tab-active { background-color: var(--jarvis-accent, #F4B942); color: #16191f; font-weight: 700; }
|
||||
.jarvis-thinking { color: #aeb6c8; font-size: 12px; padding: 8px 10px; border-left: 2px solid #4FD2FF; background-color: rgba(79, 210, 255, .06); border-radius: 8px; }
|
||||
.jarvis-notice { color: #f4b942; font-size: 12px; }
|
||||
.jarvis-notice { color: var(--jarvis-accent, #F4B942); font-size: 12px; }
|
||||
.jarvis-confirm { spacing: 8px; padding: 4px 0; }
|
||||
.jarvis-confirm-actions { spacing: 6px; }
|
||||
.jarvis-confirm-label { color: #f6f7fb; font-size: 13px; }
|
||||
@@ -26,22 +66,26 @@
|
||||
.jarvis-session-thinking { height: 200px; }
|
||||
.jarvis-transcript { spacing: 6px; }
|
||||
.jarvis-row { padding: 6px 10px; border-radius: 8px; font-size: 13px; }
|
||||
.jarvis-row-user { border-left: 2px solid var(--jarvis-accent, #F4B942); background-color: rgba(244, 185, 66, .08); }
|
||||
.jarvis-row-user { border-left: 2px solid var(--jarvis-accent, #F4B942); background-color: alpha(var(--jarvis-accent, #F4B942), .08); }
|
||||
.jarvis-row-jarvis { border-right: 2px solid #4FD2FF; background-color: rgba(79, 210, 255, .05); }
|
||||
.jarvis-row-tool { background-color: transparent; color: #9aa8c0; font-size: 12px; padding: 2px 6px; }
|
||||
.jarvis-chip-scroll { height: 32px; }
|
||||
.jarvis-chips { spacing: 6px; }
|
||||
.jarvis-chip { padding: 5px 9px; border-radius: 999px; background-color: rgba(255, 255, 255, .08); }
|
||||
.jarvis-chip:hover, .jarvis-chip:focus { background-color: rgba(244, 185, 66, .25); }
|
||||
.jarvis-chip:hover, .jarvis-chip:focus { background-color: alpha(var(--jarvis-accent, #F4B942), .25); }
|
||||
.jarvis-chip-quiet { background-color: transparent; color: #aeb6c8; padding: 5px 8px; }
|
||||
.jarvis-chip-quiet:hover, .jarvis-chip-quiet:focus { color: #f6f7fb; background-color: rgba(255, 255, 255, .08); }
|
||||
.jarvis-talk { background-color: #F4B942; color: #16191f; font-weight: bold; padding: 8px 14px; }
|
||||
.jarvis-talk { background-color: var(--jarvis-accent, #F4B942); color: #16191f; font-weight: 700; padding: 8px 14px; }
|
||||
.jarvis-talk:hover, .jarvis-talk:focus { background-color: #ffe09a; }
|
||||
.jarvis-talk:active { background-color: #ffe09a; }
|
||||
.jarvis-controls { spacing: 6px; }
|
||||
.jarvis-settings { font-size: 12px; padding: 4px 8px; }
|
||||
.jarvis-popup StEntry, .jarvis-session StEntry { border-radius: 10px; padding: 8px 10px; background-color: rgba(255, 255, 255, .06); color: #f6f7fb; border: 1px solid rgba(255, 255, 255, .15); }
|
||||
.jarvis-popup StEntry:focus, .jarvis-session StEntry:focus { border-color: #F4B942; }
|
||||
.jarvis-popup StEntry:focus, .jarvis-session StEntry:focus { border-color: var(--jarvis-accent, #F4B942); }
|
||||
.jarvis-cu { padding: 8px 12px; spacing: 4px; border-radius: 12px; background-color: rgba(11, 14, 20, .82); border: 1px solid rgba(79, 210, 255, .4); }
|
||||
.jarvis-job, .jarvis-target, .jarvis-cu-step { color: #4FD2FF; font-size: 12px; }
|
||||
.jarvis-agent-cursor { color: var(--jarvis-accent, #F4B942); font-size: 22px; }
|
||||
.jarvis-high-contrast { border-width: 2px; background-color: #000; }
|
||||
.jarvis-high-contrast .jarvis-title,
|
||||
.jarvis-high-contrast .jarvis-panel-glyph,
|
||||
.jarvis-high-contrast .jarvis-status-line { color: #fff; }
|
||||
|
||||
@@ -1,9 +1,26 @@
|
||||
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
|
||||
import Gio from 'gi://Gio';
|
||||
import GLib from 'gi://GLib';
|
||||
import St from 'gi://St';
|
||||
import Clutter from 'gi://Clutter';
|
||||
import Pango from 'gi://Pango';
|
||||
|
||||
function brandMark(size = 16, directory = '') {
|
||||
if (typeof Gio === 'undefined' || typeof St.Icon !== 'function' || !directory) return null;
|
||||
try {
|
||||
const file = Gio.File.new_for_path(`${directory}/brand/icons/jarvis-mark.svg`);
|
||||
if (!file.query_exists(null)) return null;
|
||||
return new St.Icon({
|
||||
gicon: new Gio.FileIcon({ file }),
|
||||
icon_size: size,
|
||||
style_class: 'jarvis-mark',
|
||||
y_align: Clutter.ActorAlign.CENTER,
|
||||
});
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export const STATES = new Set(['ARMED', 'LISTENING', 'THINKING', 'SPEAKING', 'SLEEPING']);
|
||||
export const POPUP_ROWS = 8;
|
||||
export const SESSION_WIDTH = 420;
|
||||
@@ -56,17 +73,19 @@ function wrapLabel(label) {
|
||||
}
|
||||
|
||||
export class ConversationView {
|
||||
constructor({ compact = true, maxRows = compact ? POPUP_ROWS : 40 } = {}) {
|
||||
constructor({ compact = true, maxRows = compact ? POPUP_ROWS : 40, brandDir = '' } = {}) {
|
||||
this.compact = compact;
|
||||
this.maxRows = maxRows;
|
||||
this.root = new St.BoxLayout({ style_class: compact ? 'jarvis-popup' : 'jarvis-session', vertical: true, reactive: true, can_focus: true, x_expand: true });
|
||||
this.root.accessible_name = compact ? 'Jarvis voice assistant' : 'Jarvis conversation';
|
||||
this.header = new St.BoxLayout({ style_class: 'jarvis-popup-header', x_expand: true });
|
||||
this.mark = brandMark(compact ? 16 : 20, brandDir);
|
||||
this.title = new St.Label({ text: 'Jarvis', style_class: 'jarvis-title', x_align: Clutter.ActorAlign.START });
|
||||
this.title.accessible_name = 'Jarvis status';
|
||||
this.status = new St.Label({ text: 'LOCAL', style_class: 'jarvis-local' });
|
||||
this.status.accessible_name = 'Local model status';
|
||||
if (this.status.clutter_text) this.status.clutter_text.ellipsize = Pango.EllipsizeMode.END;
|
||||
if (this.mark) this.header.add_child(this.mark);
|
||||
this.header.add_child(this.title);
|
||||
this.header.add_child(new St.Widget({ x_expand: true }));
|
||||
this.header.add_child(this.status);
|
||||
@@ -418,6 +437,10 @@ export class JarvisOsd {
|
||||
this.label.text = text;
|
||||
this.root.visible = Boolean(text);
|
||||
this.root.accessible_name = text ? `Jarvis ${text}` : 'Jarvis status';
|
||||
for (const name of ['armed', 'listening', 'speaking', 'thinking', 'sleeping']) {
|
||||
this.root.remove_style_class_name(`jarvis-state-${name}`);
|
||||
}
|
||||
if (this._state) this.root.add_style_class_name(`jarvis-state-${this._state.toLowerCase()}`);
|
||||
}
|
||||
_clearTimer() { if (this._timeout) { GLib.Source.remove(this._timeout); this._timeout = 0; } }
|
||||
hide() { this._wake = false; this._clearTimer(); this.root.visible = false; }
|
||||
@@ -463,8 +486,8 @@ export class ComputerUseChrome {
|
||||
}
|
||||
|
||||
export class SessionPanel {
|
||||
constructor() {
|
||||
this.view = new ConversationView({ compact: false });
|
||||
constructor({ brandDir = '' } = {}) {
|
||||
this.view = new ConversationView({ compact: false, brandDir });
|
||||
this.root = this.view.root;
|
||||
this.minimized = true;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user