mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-06-17 02:05:22 -04:00
Polish task UI slash commands and Ollama serving
This commit is contained in:
@@ -169,6 +169,9 @@ export function _parseServePhase(snapshot) {
|
||||
if (flat.includes('Application startup complete')) {
|
||||
return { phase: 'ready', status: 'ready' };
|
||||
}
|
||||
if (/Ollama API ready on port\s+\d+/i.test(flat)) {
|
||||
return { phase: 'ready', status: 'ready' };
|
||||
}
|
||||
// HTTP access logs (e.g. GET /v1/models 200 OK) mean the server is up
|
||||
if (/(?:GET|POST)\s+\/[^\s]*\s+HTTP\/[\d.]+"\s*\d{3}/.test(flat)) {
|
||||
return { phase: 'idle', status: 'ready' };
|
||||
@@ -2295,15 +2298,24 @@ async function _reconnectTask(el, task) {
|
||||
if (task.type === 'serve' && !task._endpointAdded && !task._endpointAddInFlight && task._serveReady) {
|
||||
task._endpointAddInFlight = true;
|
||||
const rawHost = task.remoteHost || 'localhost';
|
||||
const host = rawHost.includes('@') ? rawHost.split('@').pop() : rawHost;
|
||||
let host = rawHost.includes('@') ? rawHost.split('@').pop() : rawHost;
|
||||
const portMatch = task.payload?._cmd?.match(/--port[=\s]+(\d+)/)
|
||||
|| task.payload?._cmd?.match(/(?:^|\s)-p[=\s]+(\d+)/)
|
||||
|| snapshot.match(/Uvicorn running on\D*?:(\d+)/i)
|
||||
|| snapshot.match(/running on\D*?:(\d+)/i)
|
||||
|| snapshot.match(/listening on\D*?:(\d+)/i)
|
||||
|| snapshot.match(/port[:=\s]+(\d+)/i);
|
||||
const port = portMatch ? portMatch[1] : '8000';
|
||||
const baseUrl = `http://${host}:${port}/v1`;
|
||||
let port = portMatch ? portMatch[1] : '8000';
|
||||
let baseUrl = `http://${host}:${port}/v1`;
|
||||
const ollamaUrlMatch = snapshot.match(/Ollama API ready on port\s+\d+:\s*(http:\/\/[^\s]+)/i);
|
||||
if (ollamaUrlMatch) {
|
||||
try {
|
||||
const u = new URL(ollamaUrlMatch[1]);
|
||||
host = u.hostname || host;
|
||||
port = u.port || '11434';
|
||||
baseUrl = `${u.origin}/v1`;
|
||||
} catch {}
|
||||
}
|
||||
fetch('/api/model-endpoints', { credentials: 'same-origin' })
|
||||
.then(r => r.json())
|
||||
.then(async (eps) => {
|
||||
@@ -2642,10 +2654,21 @@ async function _pollBackgroundStatus() {
|
||||
if (localTask && localTask._endpointAdded) continue;
|
||||
|
||||
const rawHost = localTask?.remoteHost || t.remote || 'localhost';
|
||||
const host = rawHost.includes('@') ? rawHost.split('@').pop() : (rawHost === 'local' ? 'localhost' : rawHost);
|
||||
const portMatch = localTask?.payload?._cmd?.match(/--port\s+(\d+)/);
|
||||
const port = portMatch ? portMatch[1] : '8000';
|
||||
const baseUrl = `http://${host}:${port}/v1`;
|
||||
let host = rawHost.includes('@') ? rawHost.split('@').pop() : (rawHost === 'local' ? 'localhost' : rawHost);
|
||||
const portMatch = localTask?.payload?._cmd?.match(/--port\s+(\d+)/)
|
||||
|| localTask?.payload?._cmd?.match(/OLLAMA_HOST=[^\s:]+:(\d+)/);
|
||||
let port = portMatch ? portMatch[1] : '8000';
|
||||
let baseUrl = `http://${host}:${port}/v1`;
|
||||
const snapshot = t.output || localTask?.output || '';
|
||||
const ollamaUrlMatch = snapshot.match(/Ollama API ready on port\s+\d+:\s*(http:\/\/[^\s]+)/i);
|
||||
if (ollamaUrlMatch) {
|
||||
try {
|
||||
const u = new URL(ollamaUrlMatch[1]);
|
||||
host = u.hostname || host;
|
||||
port = u.port || '11434';
|
||||
baseUrl = `${u.origin}/v1`;
|
||||
} catch {}
|
||||
}
|
||||
const _isDiffusion = localTask?.payload?._cmd?.includes('diffusion_server');
|
||||
|
||||
_updateTask(t.session_id, { _serveReady: true, _endpointAdded: true });
|
||||
|
||||
@@ -391,7 +391,8 @@ function _rerenderCachedModels() {
|
||||
panelHtml += `<label>${_l('Backend','Inference engine: vLLM, SGLang, llama.cpp, Ollama, or Diffusers')}<select class="hwfit-sf" data-field="backend">${backendOpts}</select></label>`;
|
||||
panelHtml += `<input type="hidden" class="hwfit-sf" data-field="host" value="${esc(_es.remoteHost || '')}" />`;
|
||||
panelHtml += `<label>${_l('venv','Path to Python venv or conda env activate script')}<input type="text" class="hwfit-sf hwfit-sf-wide" data-field="venv" value="${esc(sv('venv', _es.envPath || _srvVenv || ''))}" placeholder="~/venv" /></label>`;
|
||||
panelHtml += `<label>${_l('Port','HTTP port for the API server')}<input type="text" class="hwfit-sf" data-field="port" value="${esc(sv('port', _nextAvailablePort()))}" /></label>`;
|
||||
const defaultPort = defaultBackend === 'ollama' ? '11434' : _nextAvailablePort();
|
||||
panelHtml += `<label>${_l('Port','HTTP port for the API server')}<input type="text" class="hwfit-sf" data-field="port" value="${esc(sv('port', defaultPort))}" /></label>`;
|
||||
const _activeGpus = (defaultGpus || '').split(',').map(s => s.trim()).filter(Boolean);
|
||||
const detectedGpuCount = Number(_getGpuToggleTotal?.() || 0);
|
||||
const _gpuMax = Math.max(detectedGpuCount || 8, ...(_activeGpus.map(Number).filter(n => !isNaN(n)).map(n => n + 1)));
|
||||
|
||||
@@ -18,7 +18,7 @@ const EXCLUDED = new Set(['flip','roll','8ball','fortune','odyssey','ascii']);
|
||||
// are the short forms people will actually type (/new, /clear, /web, etc.)
|
||||
// rather than the full /chats new, /toggle web equivalents.
|
||||
const PROMOTED_ALIASES = new Set([
|
||||
'new','clear','rename','fork','export','archive','important','star',
|
||||
'new','clear','rename','fork','export','archive','favorite','unfavorite',
|
||||
'web','bash','research','doc',
|
||||
'memories','forget',
|
||||
]);
|
||||
|
||||
@@ -5393,8 +5393,8 @@ const COMMANDS = {
|
||||
'delete': { handler: _cmdSessionDelete, alias: ['del','rm'], help: 'Delete chat', usage: '/chats delete [id]' },
|
||||
'archive': { handler: _cmdSessionArchive, alias: ['tar'], help: 'Archive chat', usage: '/chats archive [id]' },
|
||||
'rename': { handler: _cmdSessionRename, alias: ['mv'], help: 'Rename current chat', usage: '/chats rename Name' },
|
||||
'important': { handler: _cmdSessionImportant, alias: ['pin'], help: 'Mark as important', usage: '/chats important' },
|
||||
'unimportant': { handler: _cmdSessionUnimportant, alias: ['unpin'], help: 'Unmark important', usage: '/chats unimportant' },
|
||||
'favorite': { handler: _cmdSessionImportant, alias: ['pin','important'], help: 'Mark as favorite', usage: '/chats favorite' },
|
||||
'unfavorite': { handler: _cmdSessionUnimportant, alias: ['unpin','unimportant'], help: 'Unmark favorite', usage: '/chats unfavorite' },
|
||||
'fork': { handler: _cmdSessionFork, alias: ['cp'], help: 'Fork chat (keep first N msgs)', usage: '/chats fork [N]' },
|
||||
'truncate': { handler: _cmdSessionTruncate, alias: [], help: 'Delete older messages, keep last N', usage: '/chats truncate N' },
|
||||
'switch': { handler: _cmdSessionSwitch, alias: ['goto','cd'], help: 'Switch to chat by name/id', usage: '/chats switch name' },
|
||||
@@ -5732,10 +5732,12 @@ export const LEGACY_ALIASES = {
|
||||
'del': { parent: 'chats', sub: 'delete' },
|
||||
'archive': { parent: 'chats', sub: 'archive' },
|
||||
'rename': { parent: 'chats', sub: 'rename' },
|
||||
'important': { parent: 'chats', sub: 'important' },
|
||||
'star': { parent: 'chats', sub: 'important' },
|
||||
'unimportant': { parent: 'chats', sub: 'unimportant' },
|
||||
'unstar': { parent: 'chats', sub: 'unimportant' },
|
||||
'favorite': { parent: 'chats', sub: 'favorite' },
|
||||
'important': { parent: 'chats', sub: 'favorite' },
|
||||
'star': { parent: 'chats', sub: 'favorite' },
|
||||
'unfavorite': { parent: 'chats', sub: 'unfavorite' },
|
||||
'unimportant': { parent: 'chats', sub: 'unfavorite' },
|
||||
'unstar': { parent: 'chats', sub: 'unfavorite' },
|
||||
'fork': { parent: 'chats', sub: 'fork' },
|
||||
'truncate': { parent: 'chats', sub: 'truncate' },
|
||||
'sessions': { parent: 'chats', sub: 'info' },
|
||||
|
||||
+15
-2
@@ -349,10 +349,23 @@ function _taskIcon(task) {
|
||||
return `<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="opacity:0.4;flex-shrink:0;position:relative;top:-4px;">${path}</svg>`;
|
||||
}
|
||||
|
||||
const _MODEL_BACKED_ACTIONS = new Set([
|
||||
'summarize_emails',
|
||||
'draft_email_replies',
|
||||
'extract_email_events',
|
||||
'classify_events',
|
||||
'mark_email_boundaries',
|
||||
'learn_sender_signatures',
|
||||
'check_email_urgency',
|
||||
'test_skills',
|
||||
'audit_skills',
|
||||
'consolidate_memory',
|
||||
]);
|
||||
|
||||
function _taskAiMark(task) {
|
||||
const kind = task?.task_type || task?.kind || '';
|
||||
const action = task?.action || '';
|
||||
const aiAction = /(^|_)(ai|summarize|summary|draft|reply|classify|triage|audit|research|brief|skills?)($|_)/i.test(action);
|
||||
const aiAction = _MODEL_BACKED_ACTIONS.has(action);
|
||||
if (!(kind === 'llm' || kind === 'research' || task?.model || task?.endpointUrl || aiAction)) return '';
|
||||
return '<svg class="task-ai-mark" width="10" height="10" viewBox="0 0 24 24" fill="currentColor" aria-label="Uses model" title="Uses model"><path d="M12 0L14.59 8.41L23 12L14.59 15.59L12 24L9.41 15.59L1 12L9.41 8.41Z"/></svg>';
|
||||
}
|
||||
@@ -708,7 +721,7 @@ function _renderList() {
|
||||
const runBtn = document.createElement('button');
|
||||
runBtn.className = 'task-status-badge task-run-now-badge task-card-run-btn';
|
||||
runBtn.title = 'Run now';
|
||||
runBtn.style.cssText = 'position:relative;top:4px;margin-right:4px;';
|
||||
runBtn.style.cssText = 'position:relative;top:1px;margin-right:4px;';
|
||||
runBtn.innerHTML = '<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><polyline points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/></svg><span>Run</span>';
|
||||
runBtn.addEventListener('click', (e) => { e.stopPropagation(); _doRunNow(task.id); });
|
||||
actionsWrap.insertBefore(runBtn, menuBtn);
|
||||
|
||||
Reference in New Issue
Block a user