@@ -741,7 +741,7 @@
|
||||
"group": "Chat model",
|
||||
"default": "laptop-16gb",
|
||||
"type": "choice",
|
||||
"description": "Requires a daemon restart. GPU inference remains required.",
|
||||
"description": "Local model for files, shell, desktop, camera, and every tool except web-page thinking. Requires a daemon restart. GPU inference remains required.",
|
||||
"options": [
|
||||
{
|
||||
"value": "laptop-4gb-mm",
|
||||
@@ -775,12 +775,91 @@
|
||||
"group": "Chat model",
|
||||
"default": "",
|
||||
"type": "string",
|
||||
"description": "Advanced: overrides the profile. Empty uses the profile model. Requires restart.",
|
||||
"description": "Advanced: overrides the profile for local tools. Empty uses the profile model. Requires restart.",
|
||||
"aliases": [
|
||||
"model"
|
||||
],
|
||||
"restart": true
|
||||
},
|
||||
{
|
||||
"key": "agentInference",
|
||||
"title": "Agent inference",
|
||||
"group": "Chat model",
|
||||
"default": "local",
|
||||
"type": "choice",
|
||||
"description": "Local QVAC runs every tool on this GPU. Groq thinks only about pages the local Chrome window has crawled. The crawl, and every other tool, stay on this computer. Apply is enough. Default stays local.",
|
||||
"options": [
|
||||
{
|
||||
"value": "local",
|
||||
"label": "Local QVAC"
|
||||
},
|
||||
{
|
||||
"value": "groq",
|
||||
"label": "Groq (web browsing)"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "groqApiKey",
|
||||
"title": "Groq API key",
|
||||
"group": "Chat model",
|
||||
"default": "",
|
||||
"type": "password",
|
||||
"description": "Stored in config.json. GROQ_API_KEY or JARVIS_GROQ_API_KEY overrides this field. Create a key at console.groq.com. Do not use groq/compound; Jarvis runs tools locally.",
|
||||
"maxLength": 256,
|
||||
"when": {
|
||||
"agentInference": [
|
||||
"groq"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "groqModel",
|
||||
"title": "Groq chat model",
|
||||
"group": "Chat model",
|
||||
"default": "openai/gpt-oss-20b",
|
||||
"type": "choice",
|
||||
"description": "Dropdown of Groq models that can think about a local Chrome crawl. Fast default is GPT-OSS 20B. Compound, Whisper, and speech models are not listed. Other tools never go to Groq.",
|
||||
"options": [
|
||||
{
|
||||
"value": "openai/gpt-oss-20b",
|
||||
"label": "Fast \u00b7 openai/gpt-oss-20b"
|
||||
},
|
||||
{
|
||||
"value": "openai/gpt-oss-120b",
|
||||
"label": "Large \u00b7 openai/gpt-oss-120b"
|
||||
},
|
||||
{
|
||||
"value": "llama-3.1-8b-instant",
|
||||
"label": "Instant \u00b7 llama-3.1-8b-instant"
|
||||
},
|
||||
{
|
||||
"value": "llama-3.3-70b-versatile",
|
||||
"label": "Versatile \u00b7 llama-3.3-70b-versatile"
|
||||
},
|
||||
{
|
||||
"value": "qwen/qwen3.6-27b",
|
||||
"label": "Vision \u00b7 qwen/qwen3.6-27b"
|
||||
},
|
||||
{
|
||||
"value": "qwen/qwen3.8-27b",
|
||||
"label": "Vision \u00b7 qwen/qwen3.8-27b"
|
||||
},
|
||||
{
|
||||
"value": "minimaxai/minimax-m2.7",
|
||||
"label": "MiniMax M2.7 \u00b7 minimaxai/minimax-m2.7"
|
||||
},
|
||||
{
|
||||
"value": "openai/gpt-oss-safeguard-20b",
|
||||
"label": "Safety \u00b7 openai/gpt-oss-safeguard-20b"
|
||||
}
|
||||
],
|
||||
"when": {
|
||||
"agentInference": [
|
||||
"groq"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "maxTurns",
|
||||
"title": "Maximum reasoning turns",
|
||||
|
||||
@@ -155,6 +155,10 @@ export class SettingsEditor {
|
||||
if (buffer.get_text(buffer.get_start_iter(), buffer.get_end_iter(), false) !== next) buffer.set_text(next, -1);
|
||||
};
|
||||
row = box;
|
||||
} else if (field.type === 'password') {
|
||||
row = new Adw.PasswordEntryRow({ title: field.title, tooltip_text: field.description, text: this.values[field.key] || '' });
|
||||
row.connect('changed', () => changed(row.text));
|
||||
row._setValue = value => { row.text = value || ''; };
|
||||
} else {
|
||||
row = new Adw.EntryRow({ title: field.title, tooltip_text: field.description, text: field.type === 'list' ? this.values[field.key].join(', ') : this.values[field.key] });
|
||||
row.connect('changed', () => changed(field.type === 'list' ? row.text.split(',').map(v => v.trim()).filter(Boolean) : row.text));
|
||||
@@ -191,7 +195,7 @@ export class SettingsEditor {
|
||||
for (const field of fields.filter(item => item.group === name)) {
|
||||
group.add(this.row(field, parent));
|
||||
// EntryRow has no subtitle. Show advanced guidance underneath it.
|
||||
if (['string', 'list'].includes(field.type)) {
|
||||
if (['string', 'list', 'password'].includes(field.type)) {
|
||||
const help = new Gtk.Label({ label: field.description, wrap: true, xalign: 0, margin_start: 12, margin_end: 12, margin_bottom: 8 });
|
||||
help.add_css_class('dim-label');
|
||||
this.rows.get(field.key).bind_property('visible', help, 'visible', 2);
|
||||
|
||||
@@ -16,7 +16,7 @@ export function normalizeSettings(source, fields, { strict = false } = {}) {
|
||||
if (field.type === 'number' && typeof value === 'string' && value.trim()) value = Number(value);
|
||||
if (field.type === 'list' && typeof value === 'string') value = value.split(',').map(v => v.trim()).filter(Boolean);
|
||||
if (field.key === 'asrLanguage' && typeof value === 'string') value = value.toLowerCase().split(/[-_]/)[0];
|
||||
if (['string', 'file'].includes(field.type) && typeof value === 'string') value = value.trim();
|
||||
if (['string', 'file', 'password'].includes(field.type) && typeof value === 'string') value = value.trim();
|
||||
if (field.key === 'assistantName' && typeof value === 'string') {
|
||||
value = value.replace(/[\r\n\t]/g, ' ').replace(/\s+/g, ' ').slice(0, field.maxLength || 32);
|
||||
if (!value || /[<>&]/.test(value)) value = field.default;
|
||||
@@ -29,6 +29,7 @@ export function normalizeSettings(source, fields, { strict = false } = {}) {
|
||||
: field.type === 'choice' ? field.options.some(option => option.value === value)
|
||||
: field.type === 'list' ? Array.isArray(value) && value.every(item => typeof item === 'string')
|
||||
: field.type === 'text' ? typeof value === 'string' && value.length <= (field.maxLength || 4000)
|
||||
: field.type === 'password' ? typeof value === 'string' && value.length <= (field.maxLength || 256)
|
||||
: typeof value === 'string' && value.length <= (field.maxLength || 4096);
|
||||
if (!valid && strict) throw new Error(`Invalid value for ${field.title}`);
|
||||
result[field.key] = valid ? value : field.default;
|
||||
@@ -49,3 +50,11 @@ export function mergeSettings(source, changes, fields) {
|
||||
export function settingVisible(field, values) {
|
||||
return !field.when || Object.entries(field.when).every(([key, choices]) => choices.includes(values[key]));
|
||||
}
|
||||
|
||||
export function publicSettings(values, fields) {
|
||||
const out = { ...(values || {}) };
|
||||
for (const field of fields || []) {
|
||||
if (field.type === 'password') out[field.key] = out[field.key] ? 'set' : '';
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user