feat: retry button for orchestrator errors + explicit client timeout
Extract orchestrator inner loop into _doOrchestrate() so the retry button can re-run without re-adding the user message to DOM or history — same pattern as the existing chat retry. Also set AsyncOpenAI(timeout=settings.timeout_local) so slow remote models (OpenRouter/DeepSeek) get the same 300s budget as local chat calls instead of the SDK default which varies by connection. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -405,7 +405,7 @@ def _build_client(
|
||||
base_url = api_url.rstrip("/")
|
||||
if host_type == "openwebui":
|
||||
base_url = base_url + "/api"
|
||||
client = AsyncOpenAI(base_url=base_url, api_key=api_key)
|
||||
client = AsyncOpenAI(base_url=base_url, api_key=api_key, timeout=settings.timeout_local)
|
||||
if model_cfg.get("tools") is False:
|
||||
active_tools = []
|
||||
else:
|
||||
|
||||
@@ -1215,24 +1215,9 @@
|
||||
inputEl.focus();
|
||||
}
|
||||
|
||||
async function sendOrchestrate() {
|
||||
const text = inputEl.value.trim();
|
||||
if (!text || activeController) return;
|
||||
|
||||
inputEl.value = '';
|
||||
syncHeight();
|
||||
sendBtn.style.display = 'none';
|
||||
stopBtn.style.display = 'flex';
|
||||
headerEmoji.classList.add('processing');
|
||||
|
||||
activeController = new AbortController();
|
||||
|
||||
currentHistory.push({ role: 'user', content: text });
|
||||
const userMsgDiv = addMessage('user', text);
|
||||
scrollToBottom();
|
||||
|
||||
const thinkingDiv = addMessage('assistant thinking', '⚡ working…');
|
||||
|
||||
// Extracted so the retry button can call it without re-adding the
|
||||
// user message to the DOM or currentHistory.
|
||||
async function _doOrchestrate(text, thinkingDiv, userMsgDiv) {
|
||||
try {
|
||||
const res = await fetch('/orchestrate', {
|
||||
method: 'POST',
|
||||
@@ -1336,9 +1321,59 @@
|
||||
thinkingDiv.textContent = 'Stopped.';
|
||||
} else {
|
||||
thinkingDiv.className = 'message error';
|
||||
thinkingDiv.textContent = `Error: ${err.message}`;
|
||||
thinkingDiv.innerHTML = '';
|
||||
|
||||
const errSpan = document.createElement('span');
|
||||
errSpan.textContent = `Error: ${err.message}`;
|
||||
thinkingDiv.appendChild(errSpan);
|
||||
|
||||
const retryBtn = document.createElement('button');
|
||||
retryBtn.className = 'retry-btn';
|
||||
retryBtn.textContent = '↺ Retry';
|
||||
retryBtn.addEventListener('click', async () => {
|
||||
if (currentHistory.at(-1)?.role === 'user') currentHistory.pop();
|
||||
currentHistory.push({ role: 'user', content: text });
|
||||
|
||||
thinkingDiv.className = 'message assistant thinking';
|
||||
thinkingDiv.textContent = '⚡ working…';
|
||||
|
||||
activeController = new AbortController();
|
||||
sendBtn.style.display = 'none';
|
||||
stopBtn.style.display = 'flex';
|
||||
headerEmoji.classList.add('processing');
|
||||
|
||||
await _doOrchestrate(text, thinkingDiv, userMsgDiv);
|
||||
|
||||
activeController = null;
|
||||
headerEmoji.classList.remove('processing');
|
||||
sendBtn.style.display = 'block';
|
||||
stopBtn.style.display = 'none';
|
||||
inputEl.focus();
|
||||
});
|
||||
thinkingDiv.appendChild(retryBtn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function sendOrchestrate() {
|
||||
const text = inputEl.value.trim();
|
||||
if (!text || activeController) return;
|
||||
|
||||
inputEl.value = '';
|
||||
syncHeight();
|
||||
sendBtn.style.display = 'none';
|
||||
stopBtn.style.display = 'flex';
|
||||
headerEmoji.classList.add('processing');
|
||||
|
||||
activeController = new AbortController();
|
||||
|
||||
currentHistory.push({ role: 'user', content: text });
|
||||
const userMsgDiv = addMessage('user', text);
|
||||
scrollToBottom();
|
||||
|
||||
const thinkingDiv = addMessage('assistant thinking', '⚡ working…');
|
||||
|
||||
await _doOrchestrate(text, thinkingDiv, userMsgDiv);
|
||||
|
||||
activeController = null;
|
||||
headerEmoji.classList.remove('processing');
|
||||
|
||||
Reference in New Issue
Block a user