mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-06-15 09:15:29 -04:00
Restore dropped regression fixes
This commit is contained in:
@@ -1130,8 +1130,8 @@ def _create_email_draft_document(
|
||||
def _draft_reply_to_email(uid, body, folder="INBOX", reply_all=False, account=None, title=None):
|
||||
"""Create a threaded Odysseus reply draft document. Does not send."""
|
||||
conn = _imap_connect(account)
|
||||
conn.select(folder, readonly=True)
|
||||
status, msg_data = conn.uid("FETCH", _b(uid), "(RFC822)")
|
||||
conn.select(_q(folder), readonly=True)
|
||||
status, msg_data = conn.uid("FETCH", _b(uid), "(BODY.PEEK[])")
|
||||
conn.logout()
|
||||
if status != "OK" or not msg_data or not msg_data[0]:
|
||||
return {"error": f"Failed to fetch email UID {uid}"}
|
||||
|
||||
@@ -1219,6 +1219,12 @@ def setup_cookbook_routes() -> APIRouter:
|
||||
# pip cache so they don't fail mid-build with "No space left" (#1219)
|
||||
# and leave the dep installed-but-unusable (#1459).
|
||||
req.cmd = _pip_install_no_cache(req.cmd)
|
||||
# Accept common aliases and enforce server extras for llama-cpp so
|
||||
# `python -m llama_cpp.server` has all runtime dependencies.
|
||||
req.cmd = re.sub(r"(?<![A-Za-z0-9_.-])llama_cpp(?![A-Za-z0-9_.-])", "llama-cpp-python[server]", req.cmd)
|
||||
req.cmd = re.sub(r"(?<![A-Za-z0-9_.-])llama-cpp-python(?!\[)", "llama-cpp-python[server]", req.cmd)
|
||||
if "llama-cpp-python" in req.cmd and "--extra-index-url" not in req.cmd:
|
||||
req.cmd += " --extra-index-url https://abetlen.github.io/llama-cpp-python/whl/cpu"
|
||||
# PEP-508-style package spec — letters, digits, `.-_` for the
|
||||
# name; `[` `]` for extras; `<>=!~,` for version specifiers.
|
||||
# v2 review HIGH-14: tightened from the previous regex which
|
||||
|
||||
@@ -503,6 +503,24 @@ def _is_chat_model(model_id: str) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
def _delete_orphaned_provider_auth(db, auth_id: Optional[str], exclude_ep_id: Optional[str] = None) -> bool:
|
||||
"""Delete a ProviderAuthSession once no endpoint still references it."""
|
||||
if not auth_id:
|
||||
return False
|
||||
from core.database import ProviderAuthSession
|
||||
still_referenced = db.query(ModelEndpoint.id).filter(
|
||||
ModelEndpoint.provider_auth_id == auth_id,
|
||||
ModelEndpoint.id != exclude_ep_id,
|
||||
).first()
|
||||
if still_referenced is not None:
|
||||
return False
|
||||
auth_row = db.query(ProviderAuthSession).filter(ProviderAuthSession.id == auth_id).first()
|
||||
if auth_row is None:
|
||||
return False
|
||||
db.delete(auth_row)
|
||||
return True
|
||||
|
||||
|
||||
def _safe_detect_provider(base_url: str) -> str:
|
||||
"""Best-effort provider detection that must not break endpoint probing."""
|
||||
try:
|
||||
@@ -2173,7 +2191,9 @@ def setup_model_routes(model_discovery):
|
||||
cleared_user_preferences = _clear_user_prefs_for_endpoint(ep_id)
|
||||
cleared_sessions = _clear_sessions_for_endpoint(db, ep.base_url)
|
||||
cleared_loaded_sessions = _clear_loaded_sessions_for_endpoint(ep.base_url)
|
||||
auth_id = getattr(ep, "provider_auth_id", None)
|
||||
db.delete(ep)
|
||||
cleared_provider_auth = _delete_orphaned_provider_auth(db, auth_id, exclude_ep_id=ep_id)
|
||||
db.commit()
|
||||
_invalidate_models_cache()
|
||||
_local_probe_cache["data"] = None
|
||||
@@ -2183,6 +2203,7 @@ def setup_model_routes(model_discovery):
|
||||
"cleared_user_preferences": cleared_user_preferences,
|
||||
"cleared_sessions": cleared_sessions,
|
||||
"cleared_loaded_sessions": cleared_loaded_sessions,
|
||||
"cleared_provider_auth": cleared_provider_auth,
|
||||
}
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
+34
-11
@@ -117,7 +117,7 @@ function _isLocalEntry(s) { return !s || !s.host || s.host === 'local' || s.host
|
||||
// Resolve a dropdown option value to a server entry. New option values are
|
||||
// stable per-profile keys, so same-host SSH profiles stay distinguishable.
|
||||
// Host strings and numeric indices remain accepted for stale saved state.
|
||||
function _serverKey(s) {
|
||||
export function _serverKey(s) {
|
||||
if (_isLocalEntry(s)) return 'local';
|
||||
return 'srv:' + [
|
||||
s?.name || '',
|
||||
@@ -128,7 +128,7 @@ function _serverKey(s) {
|
||||
].map(v => encodeURIComponent(String(v).trim())).join('|');
|
||||
}
|
||||
|
||||
function _serverByVal(val) {
|
||||
export function _serverByVal(val) {
|
||||
if (val == null || val === 'local' || val === '') return null;
|
||||
const raw = String(val);
|
||||
let s = _envState.servers.find(x => _serverKey(x) === raw);
|
||||
@@ -138,7 +138,7 @@ function _serverByVal(val) {
|
||||
return s || null;
|
||||
}
|
||||
|
||||
function _selectedServer() {
|
||||
export function _selectedServer() {
|
||||
if (_envState.remoteServerKey) {
|
||||
const keyed = _serverByVal(_envState.remoteServerKey);
|
||||
if (keyed) return keyed;
|
||||
@@ -147,12 +147,25 @@ function _selectedServer() {
|
||||
return null;
|
||||
}
|
||||
|
||||
function _currentServerValue() {
|
||||
export function _currentServerValue() {
|
||||
const selected = _selectedServer();
|
||||
if (selected) return _serverKey(selected);
|
||||
return _envState.remoteHost || 'local';
|
||||
}
|
||||
|
||||
const GEMMA4_THINKING_CHAT_TEMPLATE = `{% for message in messages %}{% if message['role'] == 'system' %}<|turn>system\n<|think|>{{ message['content'] }}<turn|>\n{% elif message['role'] == 'user' %}<|turn>user\n{{ message['content'] }}<turn|>\n{% elif message['role'] == 'assistant' %}<|turn>model\n{{ message['content'] }}<turn|>\n{% endif %}{% endfor %}{% if add_generation_prompt %}<|turn>model\n<|channel>thought{% endif %}`;
|
||||
|
||||
function _isGemma4ThinkingModel(modelName) {
|
||||
const n = (modelName || '').toLowerCase();
|
||||
return n.includes('gemma-4') || n.includes('gemma4');
|
||||
}
|
||||
|
||||
function _gemma4ThinkingChatTemplateArg(modelName) {
|
||||
return _isGemma4ThinkingModel(modelName)
|
||||
? _shellQuote(GEMMA4_THINKING_CHAT_TEMPLATE)
|
||||
: '';
|
||||
}
|
||||
|
||||
function _buildServerOpts(excludeLocal = false) {
|
||||
// The local server is ALWAYS represented by the synthetic value="local" option
|
||||
// (showing its custom name from the "server name" feature). We must therefore
|
||||
@@ -188,16 +201,18 @@ export function _sshCmd(host, cmd, port) {
|
||||
/** Get SSH port for a given host (or task object) */
|
||||
function _getPort(hostOrTask) {
|
||||
if (!hostOrTask) return '';
|
||||
if (typeof hostOrTask === 'object') return hostOrTask.sshPort || _getPort(hostOrTask.remoteHost);
|
||||
const srv = _envState.servers.find(s => s.host === hostOrTask);
|
||||
if (typeof hostOrTask === 'object') return hostOrTask.sshPort || _getPort(hostOrTask.remoteServerKey || hostOrTask.remoteHost);
|
||||
const selected = hostOrTask === _envState.remoteHost ? _selectedServer() : null;
|
||||
const srv = selected || _serverByVal(hostOrTask);
|
||||
return srv?.port || '';
|
||||
}
|
||||
|
||||
/** Get platform for a given host (or task object). Returns 'windows', 'termux', 'linux', or '' */
|
||||
export function _getPlatform(hostOrTask) {
|
||||
if (!hostOrTask) return _envState.platform || '';
|
||||
if (typeof hostOrTask === 'object') return hostOrTask.platform || _getPlatform(hostOrTask.remoteHost);
|
||||
const srv = _envState.servers.find(s => s.host === hostOrTask);
|
||||
if (typeof hostOrTask === 'object') return hostOrTask.platform || _getPlatform(hostOrTask.remoteServerKey || hostOrTask.remoteHost);
|
||||
const selected = hostOrTask === _envState.remoteHost ? _selectedServer() : null;
|
||||
const srv = selected || _serverByVal(hostOrTask);
|
||||
return srv?.platform || '';
|
||||
}
|
||||
|
||||
@@ -416,6 +431,8 @@ export function _buildServeCmd(f, modelName, backend) {
|
||||
const _extraEnv = (f.extra_env ?? '').toString().replace(/\s+/g, ' ').trim();
|
||||
if (_extraEnv) cmd += _extraEnv + ' ';
|
||||
cmd += `${_vllmBin} serve ${modelName} --host 0.0.0.0 --port ${f.port || '8000'}`;
|
||||
const _gemma4ChatTemplate = _gemma4ThinkingChatTemplateArg(modelName);
|
||||
if (_gemma4ChatTemplate) cmd += ` --chat-template ${_gemma4ChatTemplate}`;
|
||||
cmd += ` --tensor-parallel-size ${f.tp || '1'}`;
|
||||
cmd += ` --max-model-len ${f.ctx || '8192'}`;
|
||||
cmd += ` --gpu-memory-utilization ${f.gpu_mem || '0.90'}`;
|
||||
@@ -446,6 +463,8 @@ export function _buildServeCmd(f, modelName, backend) {
|
||||
const _extraEnv = (f.extra_env ?? '').toString().replace(/\s+/g, ' ').trim();
|
||||
if (_extraEnv) cmd += _extraEnv + ' ';
|
||||
cmd += `${_py3Bin} -m sglang.launch_server --model-path ${modelName} --host 0.0.0.0 --port ${f.port || '30000'}`;
|
||||
const _gemma4ChatTemplate = _gemma4ThinkingChatTemplateArg(modelName);
|
||||
if (_gemma4ChatTemplate) cmd += ` --chat-template ${_gemma4ChatTemplate}`;
|
||||
if (f.tp && f.tp !== '1') cmd += ` --tp ${f.tp}`;
|
||||
if (f.ctx) cmd += ` --context-length ${f.ctx}`;
|
||||
if (f.gpu_mem && f.gpu_mem !== '0.90') cmd += ` --mem-fraction-static ${f.gpu_mem}`;
|
||||
@@ -910,6 +929,7 @@ async function _fetchDependencies() {
|
||||
function _applyServerSelection(val) {
|
||||
if (val === 'local') {
|
||||
_envState.remoteHost = '';
|
||||
_envState.remoteServerKey = '';
|
||||
_envState.env = 'none';
|
||||
_envState.envPath = '';
|
||||
_envState.platform = '';
|
||||
@@ -917,6 +937,7 @@ function _applyServerSelection(val) {
|
||||
const s = _serverByVal(val);
|
||||
if (s) {
|
||||
_envState.remoteHost = s.host;
|
||||
_envState.remoteServerKey = _serverKey(s);
|
||||
_envState.env = s.env || 'none';
|
||||
_envState.envPath = s.envPath || '';
|
||||
_envState.platform = s.platform || '';
|
||||
@@ -927,7 +948,7 @@ function _applyServerSelection(val) {
|
||||
// bug: the Download/Cache/Deps dropdowns set the host but never saved it, so
|
||||
// it silently reverted and downloads/scans hit the wrong server).
|
||||
_persistEnvState();
|
||||
const _want = _envState.remoteHost || 'local';
|
||||
const _want = _currentServerValue();
|
||||
document.querySelectorAll('#hwfit-server-select, #hwfit-dl-server, #hwfit-cache-server, #hwfit-deps-server').forEach(sel => {
|
||||
if (!sel || sel.tagName !== 'SELECT') return;
|
||||
// Option values are host strings now ('local' for the local box).
|
||||
@@ -1038,7 +1059,7 @@ function _wireTabEvents(body) {
|
||||
// UI matches the resolved host. Done in a microtask so the dropdowns
|
||||
// exist by the time we set their .value.
|
||||
Promise.resolve().then(() => {
|
||||
const _want = _envState.remoteHost || 'local';
|
||||
const _want = _currentServerValue();
|
||||
document.querySelectorAll('#hwfit-server-select, #hwfit-dl-server, #hwfit-cache-server, #hwfit-deps-server').forEach(sel => {
|
||||
if (sel && sel.tagName === 'SELECT') sel.value = _want;
|
||||
});
|
||||
@@ -2265,6 +2286,8 @@ const shared = {
|
||||
_sshCmd,
|
||||
_getPort,
|
||||
_sshPrefix,
|
||||
_serverByVal,
|
||||
_selectedServer,
|
||||
_getPlatform,
|
||||
_isWindows,
|
||||
_isMetal,
|
||||
@@ -2317,7 +2340,7 @@ export {
|
||||
_startBackgroundMonitor,
|
||||
_setPanelField, _setPanelCheckbox,
|
||||
_wirePanelEvents, _runPanelCmd, _runModelDownload, _buildDownloadCmd,
|
||||
_serverByVal, _serverKey, _currentServerValue, _selectedServer, _isLocalEntry,
|
||||
_isLocalEntry,
|
||||
};
|
||||
|
||||
const cookbookModule = { open, close, isVisible, startBackgroundMonitor: _startBackgroundMonitor };
|
||||
|
||||
@@ -97,14 +97,14 @@ function _selectedServeTarget(panel) {
|
||||
const select = document.getElementById('hwfit-server-select') || document.getElementById('hwfit-dl-server');
|
||||
const servers = Array.isArray(_envState.servers) ? _envState.servers : [];
|
||||
let host = _envState.remoteHost || '';
|
||||
let server = host ? servers.find(s => s.host === host) : null;
|
||||
let server = host ? (_serverByVal?.(_envState.remoteServerKey || host) || servers.find(s => s.host === host)) : null;
|
||||
if (select && select.value != null) {
|
||||
if (select.value === 'local') {
|
||||
host = '';
|
||||
server = servers.find(s => !s.host || s.host === 'local') || null;
|
||||
} else {
|
||||
const idx = /^\d+$/.test(String(select.value)) ? parseInt(select.value, 10) : -1;
|
||||
server = servers.find(s => s.host === select.value) || (idx >= 0 ? servers[idx] : null) || null;
|
||||
server = _serverByVal?.(select.value) || (idx >= 0 ? servers[idx] : null) || null;
|
||||
host = server?.host || '';
|
||||
}
|
||||
}
|
||||
@@ -512,7 +512,7 @@ function _rerenderCachedModels() {
|
||||
// The venv set per-server in Settings (server.envPath). Used as the venv
|
||||
// field default when the global active env path isn't carrying it, so a
|
||||
// configured server venv shows up without re-typing it.
|
||||
const _selSrv = (_es.servers || []).find(s => s.host === (_es.remoteHost || '')) || {};
|
||||
const _selSrv = _serverByVal?.(_es.remoteServerKey || _es.remoteHost || '') || {};
|
||||
const _srvVenv = _selSrv.envPath || '';
|
||||
// Serve state schema: { _byRepo: { <repo>: {...} }, _lastUsed: {...} }.
|
||||
// Loading priority: this-repo's saved settings → last-used (from any
|
||||
@@ -1771,7 +1771,7 @@ function _rerenderCachedModels() {
|
||||
const _probeParams = new URLSearchParams();
|
||||
if (_probeHost) {
|
||||
_probeParams.set('host', _probeHost);
|
||||
const _sp = (_envState.servers || []).find(s => s.host === _probeHost)?.port;
|
||||
const _sp = (_serverByVal?.(_envState.remoteServerKey || _probeHost) || {}).port;
|
||||
if (_sp) _probeParams.set('ssh_port', _sp);
|
||||
}
|
||||
const _probeRes = await fetch('/api/cookbook/gpus' + (_probeParams.toString() ? '?' + _probeParams : ''), { credentials: 'same-origin' });
|
||||
@@ -1871,8 +1871,7 @@ function _rerenderCachedModels() {
|
||||
if (_ssEl && _ssEl.value != null) {
|
||||
if (_ssEl.value === 'local') serveHost = '';
|
||||
else {
|
||||
// Values are host strings now; resolve by host (numeric fallback).
|
||||
const _srv = _envState.servers.find(s => s.host === _ssEl.value) || _envState.servers[parseInt(_ssEl.value)];
|
||||
const _srv = _serverByVal?.(_ssEl.value) || _envState.servers[parseInt(_ssEl.value)];
|
||||
if (_srv) {
|
||||
serveHost = _srv.host;
|
||||
_srvEnv = _srv.env || '';
|
||||
@@ -1931,7 +1930,7 @@ function _resolveCacheHost() {
|
||||
|
||||
function _serverByCacheValue(val) {
|
||||
if (val === 'local') return null;
|
||||
const found = _envState.servers.find(x => x.host === val)
|
||||
const found = _serverByVal?.(val)
|
||||
|| (/^\d+$/.test(String(val)) ? _envState.servers[parseInt(val)] : null)
|
||||
|| _envState.servers.find(x => x.name === val)
|
||||
|| null;
|
||||
@@ -2151,7 +2150,7 @@ export async function _fetchCachedModels() {
|
||||
let selectedServer = null;
|
||||
const _serverByCacheValue = (val) => {
|
||||
if (val === 'local') return null;
|
||||
return _envState.servers.find(x => x.host === val)
|
||||
return _serverByVal?.(val)
|
||||
|| (/^\d+$/.test(String(val)) ? _envState.servers[parseInt(val)] : null)
|
||||
|| _envState.servers.find(x => x.name === val)
|
||||
|| null;
|
||||
|
||||
Reference in New Issue
Block a user