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("/")
|
base_url = api_url.rstrip("/")
|
||||||
if host_type == "openwebui":
|
if host_type == "openwebui":
|
||||||
base_url = base_url + "/api"
|
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:
|
if model_cfg.get("tools") is False:
|
||||||
active_tools = []
|
active_tools = []
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -1215,24 +1215,9 @@
|
|||||||
inputEl.focus();
|
inputEl.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function sendOrchestrate() {
|
// Extracted so the retry button can call it without re-adding the
|
||||||
const text = inputEl.value.trim();
|
// user message to the DOM or currentHistory.
|
||||||
if (!text || activeController) return;
|
async function _doOrchestrate(text, thinkingDiv, userMsgDiv) {
|
||||||
|
|
||||||
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…');
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch('/orchestrate', {
|
const res = await fetch('/orchestrate', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -1336,9 +1321,59 @@
|
|||||||
thinkingDiv.textContent = 'Stopped.';
|
thinkingDiv.textContent = 'Stopped.';
|
||||||
} else {
|
} else {
|
||||||
thinkingDiv.className = 'message error';
|
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;
|
activeController = null;
|
||||||
headerEmoji.classList.remove('processing');
|
headerEmoji.classList.remove('processing');
|
||||||
|
|||||||
Reference in New Issue
Block a user