mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-06-15 17:25:26 -04:00
Merge branch 'main' of github.com:pewdiepie-archdaemon/odysseus
# Conflicts: # static/js/cookbookRunning.js
This commit is contained in:
@@ -1876,11 +1876,12 @@ function _wireAll(body) {
|
||||
}
|
||||
try {
|
||||
const tz = Intl.DateTimeFormat().resolvedOptions().timeZone || '';
|
||||
const tzOffset = -new Date().getTimezoneOffset();
|
||||
const res = await fetch(`${API_BASE}/api/calendar/quick-parse`, {
|
||||
method: 'POST',
|
||||
credentials: 'same-origin',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ text, tz }),
|
||||
body: JSON.stringify({ text, tz, tz_offset: tzOffset }),
|
||||
});
|
||||
const data = await res.json().catch(() => ({}));
|
||||
if (!res.ok || !data.ok) {
|
||||
|
||||
+26
-5
@@ -530,6 +530,9 @@ import createResearchSynapse from './researchSynapse.js';
|
||||
let _renderStream = () => {};
|
||||
let _cancelThinkingTimer = () => {};
|
||||
let _removeThinkingSpinner = () => {};
|
||||
let timeoutId = null;
|
||||
let responseTimeoutCleared = false;
|
||||
let clearResponseTimeout = () => {};
|
||||
const clearProcessingProbe = () => {
|
||||
if (processingProbeTimer) {
|
||||
clearTimeout(processingProbeTimer);
|
||||
@@ -790,13 +793,26 @@ import createResearchSynapse from './researchSynapse.js';
|
||||
|
||||
// Timeout: 6 min for research and agent mode, 3 min otherwise
|
||||
const timeoutMs = el('research-toggle').checked || _isAgent ? RESEARCH_TIMEOUT_MS : DEFAULT_TIMEOUT_MS;
|
||||
const timeoutId = setTimeout(() => {
|
||||
timeoutId = setTimeout(() => {
|
||||
if (!abortCtrl.signal.aborted) {
|
||||
timedOut = true;
|
||||
abortCtrl._reason = 'timeout';
|
||||
try {
|
||||
if (streamSessionId) {
|
||||
fetch(`/api/chat/stop/${encodeURIComponent(streamSessionId)}`, {
|
||||
method: 'POST',
|
||||
credentials: 'same-origin',
|
||||
}).catch(() => {});
|
||||
}
|
||||
} catch (_) {}
|
||||
abortCtrl.abort();
|
||||
}
|
||||
}, timeoutMs);
|
||||
clearResponseTimeout = () => {
|
||||
if (responseTimeoutCleared) return;
|
||||
responseTimeoutCleared = true;
|
||||
clearTimeout(timeoutId);
|
||||
};
|
||||
|
||||
const box = el('chat-history');
|
||||
holder = document.createElement('div');
|
||||
@@ -922,16 +938,19 @@ import createResearchSynapse from './researchSynapse.js';
|
||||
// the agent so natural-language times like "today at 9pm" are
|
||||
// interpreted in YOUR timezone, not the server's.
|
||||
const _tzOffsetMin = -new Date().getTimezoneOffset();
|
||||
const _tzName = (() => {
|
||||
try { return Intl.DateTimeFormat().resolvedOptions().timeZone || ''; }
|
||||
catch { return ''; }
|
||||
})();
|
||||
const res = await fetch(`${API_BASE}/api/chat_stream`, {
|
||||
method: 'POST',
|
||||
body: fd,
|
||||
headers: { 'X-Tz-Offset': String(_tzOffsetMin) },
|
||||
headers: { 'X-Tz-Offset': String(_tzOffsetMin), 'X-Tz-Name': _tzName },
|
||||
signal: abortCtrl.signal
|
||||
});
|
||||
|
||||
clearTimeout(timeoutId);
|
||||
|
||||
if (!res.ok) {
|
||||
clearResponseTimeout();
|
||||
if (res.status === 404) {
|
||||
// Session was deleted (e.g. by AI) — reload and go to welcome
|
||||
holder.remove();
|
||||
@@ -1359,7 +1378,8 @@ import createResearchSynapse from './researchSynapse.js';
|
||||
typewriterInto(roundHolder.querySelector('.body'), errMsg);
|
||||
break;
|
||||
}
|
||||
if (json.delta || json.type === 'tool_start' || json.type === 'agent_step' || json.type === 'doc_stream_delta') {
|
||||
if (json.delta || json.type === 'tool_start' || json.type === 'tool_output' || json.type === 'tool_progress' || json.type === 'agent_step' || json.type === 'doc_stream_open' || json.type === 'doc_stream_delta' || json.type === 'research_progress') {
|
||||
clearResponseTimeout();
|
||||
clearProcessingProbe();
|
||||
}
|
||||
if (json.delta) {
|
||||
@@ -2710,6 +2730,7 @@ import createResearchSynapse from './researchSynapse.js';
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
clearResponseTimeout();
|
||||
clearProcessingProbe();
|
||||
// Streaming done — let screen readers announce the settled response.
|
||||
const _chatLogDone = document.getElementById('chat-history');
|
||||
|
||||
@@ -66,6 +66,23 @@ function _clearPillLabel(task) {
|
||||
return 'clear';
|
||||
}
|
||||
|
||||
// A pip dependency/driver install (payload._dep) reports success with the
|
||||
// runner's "=== Process exited with code 0 ===" sentinel and pip's
|
||||
// "Successfully installed" line — never the HuggingFace download markers
|
||||
// (DONE / 100% / /snapshots/ / DOWNLOAD_OK) that the download heuristics look
|
||||
// for. Without this, a clean install whose tmux pane has already gone away is
|
||||
// misread as crashed/stopped even though pip exited 0. Prefer the authoritative
|
||||
// exit-code sentinel; fall back to pip's success line when no sentinel was
|
||||
// captured (and there's no install error in the same output).
|
||||
function _depInstallSucceeded(output) {
|
||||
const text = String(output || '');
|
||||
if (!text) return false;
|
||||
const exitMatch = text.match(/=== Process exited with code (-?\d+) ===/);
|
||||
if (exitMatch) return Number(exitMatch[1]) === 0;
|
||||
return /\b(?:Successfully installed|Requirement already satisfied)\b/.test(text)
|
||||
&& !/\bERROR\b|No matching distribution|Could not find a version|Traceback \(most recent call last\)/.test(text);
|
||||
}
|
||||
|
||||
function _shouldOfferCrashReport(task) {
|
||||
if (!task) return false;
|
||||
if (task._unreachable && task.type === 'serve') return true;
|
||||
@@ -2495,9 +2512,15 @@ async function _reconnectTask(el, task) {
|
||||
&& /Successfully installed|Requirement already (?:satisfied|up-to-date)/i.test(lastOutput)
|
||||
&& !/error:|ERROR:/.test(lastOutput.slice(-1024));
|
||||
const serveLooksReady = task.type === 'serve' && _serveOutputLooksReady({ ...task, output: lastOutput });
|
||||
const looksSuccessful = task.type === 'download'
|
||||
? downloadLooksSuccessful
|
||||
: (_isPipTask ? pipLooksSuccessful : serveLooksReady);
|
||||
// Dependency installs are tracked as download tasks but finish with a
|
||||
// pip exit-0 sentinel, not HF download markers — check that too.
|
||||
// Standalone pip-* serves finish with pip's own success line, not
|
||||
// HF or "Uvicorn running on".
|
||||
const depInstallSucceeded = !!task.payload?._dep && _depInstallSucceeded(lastOutput);
|
||||
const looksSuccessful = depInstallSucceeded
|
||||
|| (task.type === 'download'
|
||||
? downloadLooksSuccessful
|
||||
: (_isPipTask ? pipLooksSuccessful : serveLooksReady));
|
||||
if (!lastOutput.trim() || !looksSuccessful) {
|
||||
_updateTask(task.sessionId, { status: 'crashed' });
|
||||
el.dataset.status = 'crashed';
|
||||
@@ -3411,11 +3434,18 @@ async function _pollBackgroundStatus() {
|
||||
const live = statusById.get(task.sessionId);
|
||||
if (!live) continue;
|
||||
const updates = {};
|
||||
// A finished dependency install whose tmux pane is gone is reported
|
||||
// "stopped" by the backend (its pip package is never in the HF cache the
|
||||
// dead-session check inspects). Recover "done" from the retained output's
|
||||
// exit-0 sentinel so a clean install isn't downgraded to crashed.
|
||||
const depDone = !!task.payload?._dep && _depInstallSucceeded(task.output);
|
||||
const nextStatus = live.status === 'completed'
|
||||
? 'done'
|
||||
: (live.status === 'error'
|
||||
? 'error'
|
||||
: (live.status === 'stopped' ? (task.type === 'download' ? 'crashed' : 'stopped') : null));
|
||||
: (live.status === 'stopped'
|
||||
? (depDone ? 'done' : (task.type === 'download' ? 'crashed' : 'stopped'))
|
||||
: null));
|
||||
if (nextStatus && task.status !== nextStatus) {
|
||||
updates.status = nextStatus;
|
||||
if (nextStatus === 'done' && task.payload?._dep) completedDeps.push(task);
|
||||
|
||||
@@ -8554,6 +8554,9 @@ import * as Modals from './modalManager.js';
|
||||
if (window.hljs) {
|
||||
preview.querySelectorAll('pre code').forEach(b => window.hljs.highlightElement(b));
|
||||
}
|
||||
if (markdownModule && markdownModule.renderMermaid) {
|
||||
markdownModule.renderMermaid(preview);
|
||||
}
|
||||
preview.style.display = '';
|
||||
wrap.style.display = 'none';
|
||||
} else {
|
||||
|
||||
@@ -17,6 +17,10 @@ let API_BASE = '';
|
||||
let _uploadSpinners = [];
|
||||
const _previewUrls = new WeakMap();
|
||||
|
||||
const MAX_FILES = 10;
|
||||
const MAX_VISIBLE = 3;
|
||||
let _expanded = false;
|
||||
|
||||
function _getPreviewUrl(f) {
|
||||
if (!f) return '';
|
||||
let url = _previewUrls.get(f);
|
||||
@@ -49,10 +53,6 @@ export function openPicker() {
|
||||
document.getElementById('file-input').click();
|
||||
}
|
||||
|
||||
const MAX_VISIBLE = 3;
|
||||
const MAX_EXPAND = 6; // beyond this, the badge stays collapsed (too many chips to preview)
|
||||
let _expanded = false;
|
||||
|
||||
/**
|
||||
* Render the attachment strip with pending files.
|
||||
* 1-3 files: show individual chips.
|
||||
@@ -80,11 +80,9 @@ export function renderAttachStrip() {
|
||||
label.className = 'thumb-collapsed-label';
|
||||
badge.appendChild(label);
|
||||
badge.title = pendingFiles.map(f => f.name || 'pasted-image').join('\n');
|
||||
const canExpand = total <= MAX_EXPAND;
|
||||
badge.style.cursor = canExpand ? 'pointer' : 'default';
|
||||
badge.style.cursor = 'pointer';
|
||||
badge.addEventListener('click', (e) => {
|
||||
if (e.target.closest('.thumb-collapsed-x')) return;
|
||||
if (!canExpand) return; // too many files — don't expand into chips
|
||||
_expanded = true;
|
||||
renderAttachStrip();
|
||||
});
|
||||
@@ -201,8 +199,6 @@ export async function uploadPending() {
|
||||
}
|
||||
}
|
||||
|
||||
const MAX_FILES = 10;
|
||||
|
||||
/**
|
||||
* Add files to pending list (capped at MAX_FILES)
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user