Polish task UI slash commands and Ollama serving

This commit is contained in:
pewdiepie-archdaemon
2026-06-02 09:36:03 +09:00
parent ab0a480f30
commit 96618b01c0
9 changed files with 155 additions and 45 deletions
+30 -7
View File
@@ -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 });
+2 -1
View File
@@ -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)));
+1 -1
View File
@@ -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',
]);
+8 -6
View File
@@ -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
View File
@@ -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);
+10 -3
View File
@@ -10203,6 +10203,12 @@ textarea.memory-add-input {
height: 20px;
min-height: 0;
box-sizing: border-box;
position: relative;
top: -4px;
}
.task-state-badge svg {
position: relative;
top: -1px;
}
.task-status-badge:hover {
filter: brightness(1.08) saturate(1.15);
@@ -21253,6 +21259,7 @@ a.chat-link[href^="#research-"] {
}
.task-card .task-card-run-btn {
margin-right: 1px !important;
top: 0;
}
}
@@ -34765,7 +34772,7 @@ body.theme-frosted .modal {
.slash-autocomplete-popup {
position: fixed;
z-index: 9000;
background: var(--bg-elev-2, #1a1a1a);
background: var(--panel, var(--bg));
border: 1px solid var(--border, rgba(255,255,255,0.08));
border-radius: 8px;
box-shadow: 0 8px 24px rgba(0,0,0,0.35);
@@ -34793,8 +34800,8 @@ body.theme-frosted .modal {
white-space: nowrap;
overflow: hidden;
}
.slash-ac-row:hover { background: color-mix(in srgb, var(--fg) 6%, transparent); }
.slash-ac-row-sel { background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent); }
.slash-ac-row:hover { background-color: color-mix(in srgb, var(--accent, var(--red)) 10%, transparent); }
.slash-ac-row-sel { background-color: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent); }
.slash-ac-token {
font-family: 'Fira Code', ui-monospace, monospace;
color: var(--accent, var(--red));