This commit is contained in:
Timothy Jaeryang Baek
2026-05-09 07:56:58 +09:00
parent 485d689cfd
commit 04bd0425ea
2 changed files with 34 additions and 31 deletions

View File

@@ -583,6 +583,8 @@ from open_webui.utils.redis import get_redis_connection
from open_webui.tasks import ( from open_webui.tasks import (
redis_task_command_listener, redis_task_command_listener,
list_task_ids_by_item_id, list_task_ids_by_item_id,
has_active_tasks,
cleanup_task,
create_task, create_task,
stop_task, stop_task,
stop_item_tasks, stop_item_tasks,
@@ -2063,22 +2065,21 @@ async def chat_completion(
except BaseException as e: except BaseException as e:
log.debug(f'Error cleaning up MCP clients: {e}') log.debug(f'Error cleaning up MCP clients: {e}')
# Deregister this task, then emit chat:active=false if no others remain
try: try:
if metadata.get('chat_id'): chat_id = metadata.get('chat_id')
task_id = metadata.get('task_id')
async def emit_inactive_event(): if chat_id and task_id:
try: await cleanup_task(request.app.state.redis, task_id, chat_id)
event_emitter = await get_event_emitter(metadata, update_db=False) if not await has_active_tasks(request.app.state.redis, chat_id):
if event_emitter: event_emitter = await get_event_emitter(metadata, update_db=False)
await event_emitter({'type': 'chat:active', 'data': {'active': False}}) if event_emitter:
except Exception: try:
pass await asyncio.shield(
event_emitter({'type': 'chat:active', 'data': {'active': False}})
try: )
# Shield the event emission so it finishes even if the main task is cancelled except asyncio.CancelledError:
await asyncio.shield(emit_inactive_event()) pass
except asyncio.CancelledError:
pass
except Exception: except Exception:
pass pass
@@ -2128,6 +2129,7 @@ async def chat_completion(
), ),
id=chat_id, id=chat_id,
) )
per_model_metadata['task_id'] = task_id
task_ids.append(task_id) task_ids.append(task_id)
# Emit chat:active=true # Emit chat:active=true

View File

@@ -1398,6 +1398,7 @@
autoScroll = true; autoScroll = true;
await tick(); await tick();
// Mark all non-current assistant messages as done
if (history.currentId) { if (history.currentId) {
for (const message of Object.values(history.messages)) { for (const message of Object.values(history.messages)) {
if ( if (
@@ -1411,23 +1412,23 @@
} }
} }
const taskRes = await getTaskIdsByChatId(localStorage.token, $chatId).catch((error) => { // Reconcile active tasks with message state:
return null; // If the response is already done, remaining tasks are just background
}); // work (follow-ups, title gen) that shouldn't block the input.
const pendingTaskIds = await getTaskIdsByChatId(localStorage.token, $chatId)
if (taskRes) { .then((res) => res?.task_ids ?? [])
taskIds = taskRes.task_ids; .catch(() => []);
}
// If no active tasks and current message is incomplete, generation was interrupted
const currentMessage = history.currentId ? history.messages[history.currentId] : null; const currentMessage = history.currentId ? history.messages[history.currentId] : null;
if ( const responseComplete = currentMessage?.role === 'assistant' && currentMessage?.done;
currentMessage &&
currentMessage.role === 'assistant' && if (pendingTaskIds.length > 0 && !responseComplete) {
!currentMessage.done && taskIds = pendingTaskIds;
(!taskIds || taskIds.length === 0) } else {
) { taskIds = null;
currentMessage.done = true; // No active tasks and message incomplete → generation was interrupted
if (currentMessage?.role === 'assistant' && !currentMessage.done) {
currentMessage.done = true;
}
} }
await tick(); await tick();