mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-08-05 02:45:28 +00:00
feat(chat): add model control defaults
This commit is contained in:
parent
380e5305a6
commit
201d466adb
8 changed files with 209 additions and 12 deletions
|
|
@ -656,6 +656,10 @@ def setup_auth_routes(auth_manager: AuthManager) -> APIRouter:
|
|||
"agent_max_rounds": (1, 200),
|
||||
"agent_max_tool_calls": (0, 1000), # 0 = unlimited
|
||||
}
|
||||
_CHOICE_VALUES = {
|
||||
"default_reasoning_effort": {"", "auto", "off", "on", "none", "minimal", "low", "medium", "high", "xhigh"},
|
||||
"default_verbosity": {"", "auto", "low", "medium", "high"},
|
||||
}
|
||||
for key in DEFAULT_SETTINGS:
|
||||
if key not in body:
|
||||
continue
|
||||
|
|
@ -667,6 +671,14 @@ def setup_auth_routes(auth_manager: AuthManager) -> APIRouter:
|
|||
except (TypeError, ValueError):
|
||||
raise HTTPException(400, f"{key} must be an integer")
|
||||
val = max(lo, min(val, hi))
|
||||
if key in _CHOICE_VALUES:
|
||||
val = str(val or "").strip().lower().replace("-", "_")
|
||||
if val == "x_high":
|
||||
val = "xhigh"
|
||||
if val in ("auto", "default"):
|
||||
val = ""
|
||||
if val not in _CHOICE_VALUES[key]:
|
||||
raise HTTPException(400, f"Unsupported value for {key}")
|
||||
current[key] = val
|
||||
_save_settings(current)
|
||||
return current
|
||||
|
|
|
|||
|
|
@ -2438,6 +2438,8 @@ def setup_model_routes(model_discovery):
|
|||
ep_id = (_user_prefs.get("default_endpoint_id") or "").strip()
|
||||
model = (_user_prefs.get("default_model") or "").strip()
|
||||
_fallbacks = _user_prefs.get("default_model_fallbacks") or []
|
||||
default_reasoning_effort = (_user_prefs.get("default_reasoning_effort") or "").strip()
|
||||
default_verbosity = (_user_prefs.get("default_verbosity") or "").strip()
|
||||
# If user has no personal default, fall back to global default
|
||||
# But only based on the "share_defaults_with_users" flag
|
||||
# (only if share_defaults_with_users is enabled)
|
||||
|
|
@ -2448,10 +2450,35 @@ def setup_model_routes(model_discovery):
|
|||
model = settings.get("default_model", "")
|
||||
if not _fallbacks:
|
||||
_fallbacks = settings.get("default_model_fallbacks") or []
|
||||
if not default_reasoning_effort:
|
||||
default_reasoning_effort = (settings.get("default_reasoning_effort") or "").strip()
|
||||
if not default_verbosity:
|
||||
default_verbosity = (settings.get("default_verbosity") or "").strip()
|
||||
else:
|
||||
ep_id = settings.get("default_endpoint_id", "")
|
||||
model = settings.get("default_model", "")
|
||||
_fallbacks = settings.get("default_model_fallbacks") or []
|
||||
default_reasoning_effort = (settings.get("default_reasoning_effort") or "").strip()
|
||||
default_verbosity = (settings.get("default_verbosity") or "").strip()
|
||||
|
||||
def _clean_default_reasoning(value: str) -> str:
|
||||
cleaned = (value or "").strip().lower().replace("-", "_")
|
||||
if cleaned in {"", "auto", "default"}:
|
||||
return ""
|
||||
if cleaned == "none":
|
||||
return "off"
|
||||
return cleaned if cleaned in {"off", "on", "minimal", "low", "medium", "high", "xhigh"} else ""
|
||||
|
||||
def _clean_default_verbosity(value: str) -> str:
|
||||
cleaned = (value or "").strip().lower()
|
||||
if cleaned in {"", "auto", "default"}:
|
||||
return ""
|
||||
return cleaned if cleaned in {"low", "medium", "high"} else ""
|
||||
|
||||
default_controls = {
|
||||
"default_reasoning_effort": _clean_default_reasoning(default_reasoning_effort),
|
||||
"default_verbosity": _clean_default_verbosity(default_verbosity),
|
||||
}
|
||||
db = SessionLocal()
|
||||
try:
|
||||
ep = None
|
||||
|
|
@ -2504,7 +2531,7 @@ def setup_model_routes(model_discovery):
|
|||
_last_q = owner_filter(_last_q, ModelEndpoint, _user, include_shared=False)
|
||||
ep = _last_q.first()
|
||||
if not ep:
|
||||
return {"endpoint_id": "", "endpoint_url": "", "model": ""}
|
||||
return {"endpoint_id": "", "endpoint_url": "", "model": "", **default_controls}
|
||||
base = _normalize_base(ep.base_url)
|
||||
chat_url = build_chat_url(base)
|
||||
if not model and (getattr(ep, "cached_models", None) or getattr(ep, "pinned_models", None)):
|
||||
|
|
@ -2514,7 +2541,7 @@ def setup_model_routes(model_discovery):
|
|||
model = visible[0]
|
||||
except Exception:
|
||||
pass
|
||||
return {"endpoint_id": ep.id, "endpoint_url": chat_url, "model": model}
|
||||
return {"endpoint_id": ep.id, "endpoint_url": chat_url, "model": model, **default_controls}
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
|
|
|||
|
|
@ -134,6 +134,8 @@ DEFAULT_SETTINGS = {
|
|||
"task_model": "",
|
||||
"default_endpoint_id": "",
|
||||
"default_model": "",
|
||||
"default_reasoning_effort": "",
|
||||
"default_verbosity": "",
|
||||
# Optional prose style used only for normal document writing/editing.
|
||||
# Email replies use email_writing_style instead because greetings,
|
||||
# signatures, and mailbox identity rules are medium-specific.
|
||||
|
|
@ -271,6 +273,7 @@ _PER_USER_KEYS = {
|
|||
# account inherited whatever the most-recent admin picked, which then
|
||||
# got injected into the chat composer on first open.
|
||||
"default_endpoint_id", "default_model", "default_model_fallbacks",
|
||||
"default_reasoning_effort", "default_verbosity",
|
||||
"utility_endpoint_id", "utility_model", "utility_model_fallbacks",
|
||||
"research_endpoint_id", "research_model",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -209,6 +209,10 @@ try {
|
|||
if (cachedDefaultChat && cachedDefaultChat.endpoint_url && cachedDefaultChat.model) {
|
||||
_defaultChat = cachedDefaultChat;
|
||||
window.__odysseusDefaultChat = cachedDefaultChat;
|
||||
window.__odysseusModelControlDefaults = {
|
||||
reasoning_effort: cachedDefaultChat.default_reasoning_effort || 'auto',
|
||||
verbosity: cachedDefaultChat.default_verbosity || 'auto',
|
||||
};
|
||||
}
|
||||
} catch (_) {}
|
||||
async function _refreshDefaultChat() {
|
||||
|
|
@ -218,6 +222,10 @@ async function _refreshDefaultChat() {
|
|||
_defaultChat = d;
|
||||
try {
|
||||
window.__odysseusDefaultChat = d;
|
||||
window.__odysseusModelControlDefaults = {
|
||||
reasoning_effort: d.default_reasoning_effort || 'auto',
|
||||
verbosity: d.default_verbosity || 'auto',
|
||||
};
|
||||
localStorage.setItem('odysseus-default-chat-cache', JSON.stringify(d));
|
||||
} catch (_) {}
|
||||
return d;
|
||||
|
|
@ -248,7 +256,11 @@ async function _createDirectChatFromPreferredModel() {
|
|||
|
||||
const dc = await _refreshDefaultChat();
|
||||
if (dc) {
|
||||
sessionModule.createDirectChat(dc.endpoint_url, dc.model, dc.endpoint_id, { source: 'default' });
|
||||
sessionModule.createDirectChat(dc.endpoint_url, dc.model, dc.endpoint_id, {
|
||||
source: 'default',
|
||||
reasoning_effort: dc.default_reasoning_effort || '',
|
||||
verbosity: dc.default_verbosity || '',
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -1991,6 +2003,29 @@ function initializeEventListeners() {
|
|||
const v = String(value || 'auto').toLowerCase();
|
||||
return Object.prototype.hasOwnProperty.call(labels, v) ? v : 'auto';
|
||||
};
|
||||
const normalizeDefaultControl = (key, value) => {
|
||||
let normalized = String(value || 'auto').toLowerCase().replace(/-/g, '_');
|
||||
if (normalized === 'none') normalized = 'off';
|
||||
const config = controls.find(c => c.key === key);
|
||||
return config ? normalizeValue(normalized, config.labels) : 'auto';
|
||||
};
|
||||
let modelControlDefaults = {
|
||||
reasoning_effort: 'auto',
|
||||
verbosity: 'auto',
|
||||
};
|
||||
function setModelControlDefaults(values = {}) {
|
||||
modelControlDefaults = {
|
||||
reasoning_effort: normalizeDefaultControl(
|
||||
'reasoning_effort',
|
||||
values.reasoning_effort || values.default_reasoning_effort || 'auto',
|
||||
),
|
||||
verbosity: normalizeDefaultControl(
|
||||
'verbosity',
|
||||
values.verbosity || values.default_verbosity || 'auto',
|
||||
),
|
||||
};
|
||||
try { window.__odysseusModelControlDefaults = { ...modelControlDefaults }; } catch (_) {}
|
||||
}
|
||||
|
||||
const isThinkingModel = model => {
|
||||
const m = String(model || '').toLowerCase();
|
||||
|
|
@ -2216,6 +2251,7 @@ function initializeEventListeners() {
|
|||
});
|
||||
|
||||
refreshCapabilities();
|
||||
setModelControlDefaults(window.__odysseusModelControlDefaults || window.__odysseusDefaultChat || {});
|
||||
|
||||
window.odysseusModelControls = {
|
||||
applySession(meta = {}) {
|
||||
|
|
@ -2227,9 +2263,22 @@ function initializeEventListeners() {
|
|||
}
|
||||
refreshCapabilities();
|
||||
},
|
||||
getDefaults() {
|
||||
return { ...modelControlDefaults };
|
||||
},
|
||||
setDefaults(values = {}) {
|
||||
setModelControlDefaults(values);
|
||||
},
|
||||
refreshCapabilities,
|
||||
};
|
||||
|
||||
fetch('/api/auth/settings', { credentials: 'same-origin' })
|
||||
.then(res => res.ok ? res.json() : null)
|
||||
.then(settings => {
|
||||
if (settings) setModelControlDefaults(settings);
|
||||
})
|
||||
.catch(() => {});
|
||||
|
||||
document.addEventListener('odysseus:model-picked', e => {
|
||||
const detail = (e && e.detail) || {};
|
||||
refreshCapabilities({ model: detail.mid, endpointUrl: detail.url });
|
||||
|
|
|
|||
|
|
@ -1523,6 +1523,34 @@
|
|||
<div id="set-defaultChatMsg" style="font-size:11px;color:color-mix(in srgb, var(--fg) 45%, transparent);"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="admin-card">
|
||||
<h2><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:5px;opacity:0.6"><path d="M9 18h6"/><path d="M10 22h4"/><path d="M12 2a7 7 0 0 0-4 12c.6.5 1 1.2 1 2h6c0-.8.4-1.5 1-2a7 7 0 0 0-4-12z"/></svg>Model Response Defaults</h2>
|
||||
<div class="admin-toggle-sub" style="margin-bottom:8px">Default controls for newly created chat sessions. Existing chats keep their saved settings.</div>
|
||||
<div class="settings-col">
|
||||
<div class="settings-row">
|
||||
<label class="settings-label">Reasoning</label>
|
||||
<select id="set-defaultReasoningSelect" class="settings-select">
|
||||
<option value="">Auto</option>
|
||||
<option value="off">Off</option>
|
||||
<option value="on">On</option>
|
||||
<option value="minimal">Minimal</option>
|
||||
<option value="low">Low</option>
|
||||
<option value="medium">Medium</option>
|
||||
<option value="high">High</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="settings-row">
|
||||
<label class="settings-label">Verbosity</label>
|
||||
<select id="set-defaultVerbositySelect" class="settings-select">
|
||||
<option value="">Auto</option>
|
||||
<option value="low">Low</option>
|
||||
<option value="medium">Medium</option>
|
||||
<option value="high">High</option>
|
||||
</select>
|
||||
</div>
|
||||
<div id="set-defaultControlsMsg" style="font-size:11px;color:color-mix(in srgb, var(--fg) 45%, transparent);"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="admin-card">
|
||||
<h2 style="display:flex;align-items:center;gap:6px;"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="margin-right:1px;opacity:0.6;flex-shrink:0"><path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"/></svg>Utility Model <span style="font-size:0.72em;opacity:0.55;font-weight:normal;">(Recommended: Local Endpoint)</span></h2>
|
||||
<div class="admin-toggle-sub" style="margin-bottom:8px">Runs background tasks (compaction, cleanup, auto-naming, retrieving memories from files) on a small/local model instead of your chat model. Leave blank to use the chat model.</div>
|
||||
|
|
|
|||
|
|
@ -1273,6 +1273,10 @@ import { wireArrowUpRecall, getUserMessagesFromChatHistory } from './composerArr
|
|||
if (dc && dc.endpoint_url && dc.model) {
|
||||
try {
|
||||
window.__odysseusDefaultChat = dc;
|
||||
window.__odysseusModelControlDefaults = {
|
||||
reasoning_effort: dc.default_reasoning_effort || 'auto',
|
||||
verbosity: dc.default_verbosity || 'auto',
|
||||
};
|
||||
localStorage.setItem('odysseus-default-chat-cache', JSON.stringify(dc));
|
||||
} catch (_) {}
|
||||
}
|
||||
|
|
@ -1282,7 +1286,11 @@ import { wireArrowUpRecall, getUserMessagesFromChatHistory } from './composerArr
|
|||
}
|
||||
if (dc.endpoint_url && dc.model) {
|
||||
_sendPerf.mark('direct_chat_create_begin');
|
||||
await sessionModule.createDirectChat(dc.endpoint_url, dc.model, dc.endpoint_id, { source: 'default' });
|
||||
await sessionModule.createDirectChat(dc.endpoint_url, dc.model, dc.endpoint_id, {
|
||||
source: 'default',
|
||||
reasoning_effort: dc.default_reasoning_effort || '',
|
||||
verbosity: dc.default_verbosity || '',
|
||||
});
|
||||
_sendPerf.mark('direct_chat_create_done');
|
||||
const ok = await sessionModule.materializePendingSession();
|
||||
_sendPerf.mark('direct_chat_materialize_done');
|
||||
|
|
|
|||
|
|
@ -1801,7 +1801,11 @@ export async function loadSessions() {
|
|||
try {
|
||||
const dc = await _getPreferredDefaultChat();
|
||||
if (dc && dc.endpoint_url && dc.model) {
|
||||
await createDirectChat(dc.endpoint_url, dc.model, dc.endpoint_id, { source: 'default' });
|
||||
await createDirectChat(dc.endpoint_url, dc.model, dc.endpoint_id, {
|
||||
source: 'default',
|
||||
reasoning_effort: dc.default_reasoning_effort || '',
|
||||
verbosity: dc.default_verbosity || '',
|
||||
});
|
||||
}
|
||||
} catch (_) { /* no default model — that's fine, user can /setup */ }
|
||||
_autoCreateInProgress = false;
|
||||
|
|
@ -2148,7 +2152,7 @@ export async function selectSession(id, { keepSidebar = false, showLoading = tru
|
|||
}
|
||||
|
||||
// Pending session — stored locally until the first message is sent
|
||||
let _pendingChat = null; // { url, modelId, endpointId }
|
||||
let _pendingChat = null; // { url, modelId, endpointId, reasoning_effort, verbosity }
|
||||
let _pendingMaterializePromise = null;
|
||||
|
||||
async function _getPreferredDefaultChat() {
|
||||
|
|
@ -2161,13 +2165,25 @@ async function _getPreferredDefaultChat() {
|
|||
dc = JSON.parse(localStorage.getItem('odysseus-default-chat-cache') || 'null');
|
||||
} catch (_) {}
|
||||
}
|
||||
if (dc && dc.endpoint_url && dc.model) return dc;
|
||||
if (dc && dc.endpoint_url && dc.model) {
|
||||
try {
|
||||
window.__odysseusModelControlDefaults = {
|
||||
reasoning_effort: dc.default_reasoning_effort || 'auto',
|
||||
verbosity: dc.default_verbosity || 'auto',
|
||||
};
|
||||
} catch (_) {}
|
||||
return dc;
|
||||
}
|
||||
try {
|
||||
const dcRes = await fetch(`${API_BASE}/api/default-chat`);
|
||||
dc = await dcRes.json();
|
||||
if (dc && dc.endpoint_url && dc.model) {
|
||||
try {
|
||||
window.__odysseusDefaultChat = dc;
|
||||
window.__odysseusModelControlDefaults = {
|
||||
reasoning_effort: dc.default_reasoning_effort || 'auto',
|
||||
verbosity: dc.default_verbosity || 'auto',
|
||||
};
|
||||
localStorage.setItem('odysseus-default-chat-cache', JSON.stringify(dc));
|
||||
} catch (_) {}
|
||||
return dc;
|
||||
|
|
@ -2178,6 +2194,10 @@ async function _getPreferredDefaultChat() {
|
|||
|
||||
export function createDirectChat(url, modelId, endpointId, opts = {}) {
|
||||
const incomingSource = opts.source || 'manual';
|
||||
const initialControls = {
|
||||
reasoning_effort: opts.reasoning_effort || opts.default_reasoning_effort || '',
|
||||
verbosity: opts.verbosity || opts.default_verbosity || '',
|
||||
};
|
||||
if (
|
||||
_pendingChat &&
|
||||
_pendingChat.modelId &&
|
||||
|
|
@ -2200,14 +2220,21 @@ export function createDirectChat(url, modelId, endpointId, opts = {}) {
|
|||
}
|
||||
|
||||
// Don't hit the API — just store the model info and prepare the UI
|
||||
_pendingChat = { url, modelId, endpointId, source: incomingSource };
|
||||
_pendingChat = {
|
||||
url,
|
||||
modelId,
|
||||
endpointId,
|
||||
source: incomingSource,
|
||||
reasoning_effort: initialControls.reasoning_effort || null,
|
||||
verbosity: initialControls.verbosity || null,
|
||||
};
|
||||
_pendingMaterializePromise = null;
|
||||
if (window.odysseusModelControls && window.odysseusModelControls.applySession) {
|
||||
window.odysseusModelControls.applySession({
|
||||
model: modelId || '',
|
||||
endpoint_url: url || '',
|
||||
reasoning_effort: null,
|
||||
verbosity: null,
|
||||
reasoning_effort: initialControls.reasoning_effort || null,
|
||||
verbosity: initialControls.verbosity || null,
|
||||
});
|
||||
}
|
||||
_skipAutoSelect = true;
|
||||
|
|
@ -2281,7 +2308,11 @@ export async function materializePendingSession() {
|
|||
if (pending.endpointId) {
|
||||
fd.append('endpoint_id', pending.endpointId);
|
||||
}
|
||||
const modelControls = Storage.loadToggleState();
|
||||
const modelControls = {
|
||||
...Storage.loadToggleState(),
|
||||
reasoning_effort: pending.reasoning_effort || Storage.loadToggleState().reasoning_effort || 'auto',
|
||||
verbosity: pending.verbosity || Storage.loadToggleState().verbosity || 'auto',
|
||||
};
|
||||
const reasoningEffort = String(modelControls.reasoning_effort || 'auto').toLowerCase();
|
||||
const verbosity = String(modelControls.verbosity || 'auto').toLowerCase();
|
||||
if (reasoningEffort && reasoningEffort !== 'auto') {
|
||||
|
|
|
|||
|
|
@ -447,6 +447,9 @@ async function initDefaultChat() {
|
|||
var msg = el('set-defaultChatMsg');
|
||||
var fbContainer = el('set-defaultFallbacks');
|
||||
var addFbBtn = el('set-defaultAddFallback');
|
||||
var reasoningSel = el('set-defaultReasoningSelect');
|
||||
var verbositySel = el('set-defaultVerbositySelect');
|
||||
var controlsMsg = el('set-defaultControlsMsg');
|
||||
var _endpoints = [];
|
||||
var _fallbacks = []; // [{endpoint_id, model}] — tried in order if primary fails
|
||||
|
||||
|
|
@ -471,6 +474,28 @@ async function initDefaultChat() {
|
|||
refreshModels(selectedModel !== undefined ? selectedModel : modelSel.value);
|
||||
renderFallbacks();
|
||||
}
|
||||
function normalizeReasoningDefault(value) {
|
||||
var v = String(value || 'auto').toLowerCase().replace(/-/g, '_');
|
||||
if (v === 'none') v = 'off';
|
||||
return ['auto', 'off', 'on', 'minimal', 'low', 'medium', 'high', 'xhigh'].includes(v) ? v : 'auto';
|
||||
}
|
||||
function normalizeVerbosityDefault(value) {
|
||||
var v = String(value || 'auto').toLowerCase();
|
||||
return ['auto', 'low', 'medium', 'high'].includes(v) ? v : 'auto';
|
||||
}
|
||||
function currentControlDefaults() {
|
||||
return {
|
||||
reasoning_effort: normalizeReasoningDefault(reasoningSel ? reasoningSel.value : ''),
|
||||
verbosity: normalizeVerbosityDefault(verbositySel ? verbositySel.value : ''),
|
||||
};
|
||||
}
|
||||
function publishControlDefaults() {
|
||||
var defaults = currentControlDefaults();
|
||||
try { window.__odysseusModelControlDefaults = defaults; } catch (_) {}
|
||||
if (window.odysseusModelControls && window.odysseusModelControls.setDefaults) {
|
||||
window.odysseusModelControls.setDefaults(defaults);
|
||||
}
|
||||
}
|
||||
|
||||
// Render the fallback chain. Each row is endpoint + model + remove.
|
||||
function renderFallbacks() {
|
||||
|
|
@ -539,6 +564,9 @@ async function initDefaultChat() {
|
|||
return { endpoint_id: (f && f.endpoint_id) || '', model: (f && f.model) || '' };
|
||||
})
|
||||
: [];
|
||||
if (reasoningSel) reasoningSel.value = normalizeReasoningDefault(settings.default_reasoning_effort);
|
||||
if (verbositySel) verbositySel.value = normalizeVerbosityDefault(settings.default_verbosity);
|
||||
publishControlDefaults();
|
||||
renderFallbacks();
|
||||
} catch (e) { console.warn('Failed to load default chat settings', e); }
|
||||
|
||||
|
|
@ -553,14 +581,25 @@ async function initDefaultChat() {
|
|||
body: JSON.stringify({
|
||||
default_endpoint_id: epSel.value,
|
||||
default_model: modelSel.value,
|
||||
default_model_fallbacks: clean
|
||||
default_model_fallbacks: clean,
|
||||
default_reasoning_effort: currentControlDefaults().reasoning_effort,
|
||||
default_verbosity: currentControlDefaults().verbosity
|
||||
})
|
||||
});
|
||||
publishControlDefaults();
|
||||
if (controlsMsg) {
|
||||
controlsMsg.textContent = 'Saved';
|
||||
controlsMsg.style.color = 'var(--fg)';
|
||||
setTimeout(function() { controlsMsg.textContent = ''; }, 2000);
|
||||
}
|
||||
msg.textContent = 'Saved'; msg.style.color = 'var(--fg)';
|
||||
setTimeout(function() { msg.textContent = ''; }, 2000);
|
||||
} catch (e) { msg.textContent = 'Failed to save'; msg.style.color = 'var(--red)'; }
|
||||
}
|
||||
|
||||
if (reasoningSel) reasoningSel.addEventListener('change', saveDefault);
|
||||
if (verbositySel) verbositySel.addEventListener('change', saveDefault);
|
||||
|
||||
if (addFbBtn) addFbBtn.addEventListener('click', function() {
|
||||
var first = enabledEndpoints()[0];
|
||||
_fallbacks.push({ endpoint_id: first ? first.id : '', model: '' });
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue