refac
This commit is contained in:
@@ -1804,6 +1804,16 @@ async def chat_completion(
|
||||
except Exception as e:
|
||||
log.debug(f"Error cleaning up: {e}")
|
||||
pass
|
||||
# Emit chat:active=false when task completes
|
||||
try:
|
||||
if metadata.get("chat_id"):
|
||||
event_emitter = get_event_emitter(metadata, update_db=False)
|
||||
if event_emitter:
|
||||
await event_emitter(
|
||||
{"type": "chat:active", "data": {"active": False}}
|
||||
)
|
||||
except Exception as e:
|
||||
log.debug(f"Error emitting chat:active: {e}")
|
||||
|
||||
if (
|
||||
metadata.get("session_id")
|
||||
@@ -1816,6 +1826,12 @@ async def chat_completion(
|
||||
process_chat(request, form_data, user, metadata, model),
|
||||
id=metadata["chat_id"],
|
||||
)
|
||||
# Emit chat:active=true when task starts
|
||||
event_emitter = get_event_emitter(metadata, update_db=False)
|
||||
if event_emitter:
|
||||
await event_emitter(
|
||||
{"type": "chat:active", "data": {"active": True}}
|
||||
)
|
||||
return {"status": True, "task_id": task_id}
|
||||
else:
|
||||
return await process_chat(request, form_data, user, metadata, model)
|
||||
|
||||
@@ -49,6 +49,21 @@ router = APIRouter()
|
||||
##################################
|
||||
|
||||
|
||||
class ActiveChatsForm(BaseModel):
|
||||
chat_ids: list[str]
|
||||
|
||||
|
||||
@router.post("/active/chats")
|
||||
async def check_active_chats(
|
||||
request: Request, form_data: ActiveChatsForm, user=Depends(get_verified_user)
|
||||
):
|
||||
"""Check which chat IDs have active tasks."""
|
||||
from open_webui.tasks import get_active_chat_ids
|
||||
|
||||
active = await get_active_chat_ids(request.app.state.redis, form_data.chat_ids)
|
||||
return {"active_chat_ids": active}
|
||||
|
||||
|
||||
@router.get("/config")
|
||||
async def get_task_config(request: Request, user=Depends(get_verified_user)):
|
||||
return {
|
||||
|
||||
@@ -183,3 +183,18 @@ async def stop_item_tasks(redis: Redis, item_id: str):
|
||||
return result # Return the first failure
|
||||
|
||||
return {"status": True, "message": f"All tasks for item {item_id} stopped."}
|
||||
|
||||
|
||||
async def has_active_tasks(redis, chat_id: str) -> bool:
|
||||
"""Check if a chat has any active tasks."""
|
||||
task_ids = await list_task_ids_by_item_id(redis, chat_id)
|
||||
return len(task_ids) > 0
|
||||
|
||||
|
||||
async def get_active_chat_ids(redis, chat_ids: List[str]) -> List[str]:
|
||||
"""Filter a list of chat_ids to only those with active tasks."""
|
||||
active = []
|
||||
for chat_id in chat_ids:
|
||||
if await has_active_tasks(redis, chat_id):
|
||||
active.append(chat_id)
|
||||
return active
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import { WEBUI_API_BASE_URL } from '$lib/constants';
|
||||
|
||||
export const checkActiveChats = async (token: string, chatIds: string[]) => {
|
||||
const res = await fetch(`${WEBUI_API_BASE_URL}/tasks/active/chats`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${token}`
|
||||
},
|
||||
body: JSON.stringify({ chat_ids: chatIds })
|
||||
});
|
||||
if (!res.ok) throw await res.json();
|
||||
return res.json();
|
||||
};
|
||||
@@ -26,7 +26,8 @@
|
||||
models,
|
||||
selectedFolder,
|
||||
WEBUI_NAME,
|
||||
sidebarWidth
|
||||
sidebarWidth,
|
||||
activeChatIds
|
||||
} from '$lib/stores';
|
||||
import { onMount, getContext, tick, onDestroy } from 'svelte';
|
||||
|
||||
@@ -42,6 +43,7 @@
|
||||
importChats
|
||||
} from '$lib/apis/chats';
|
||||
import { createNewFolder, getFolders, updateFolderParentIdById } from '$lib/apis/folders';
|
||||
import { checkActiveChats } from '$lib/apis/tasks';
|
||||
import { WEBUI_API_BASE_URL, WEBUI_BASE_URL } from '$lib/constants';
|
||||
|
||||
import ArchivedChatsModal from './ArchivedChatsModal.svelte';
|
||||
@@ -469,6 +471,17 @@
|
||||
await initChannels();
|
||||
}
|
||||
await initChatList();
|
||||
|
||||
// Check which chats have active tasks
|
||||
const allChatIds = [...$chats.map((c) => c.id), ...$pinnedChats.map((c) => c.id)];
|
||||
if (allChatIds.length > 0) {
|
||||
try {
|
||||
const res = await checkActiveChats(localStorage.token, allChatIds);
|
||||
activeChatIds.set(new Set(res.active_chat_ids || []));
|
||||
} catch (e) {
|
||||
console.debug('Failed to check active chats:', e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
settings.subscribe((value) => {
|
||||
@@ -493,8 +506,32 @@
|
||||
dropZone?.addEventListener('dragover', onDragOver);
|
||||
dropZone?.addEventListener('drop', onDrop);
|
||||
dropZone?.addEventListener('dragleave', onDragLeave);
|
||||
|
||||
// Listen for real-time chat:active events via the events channel
|
||||
$socket?.off('events', chatActiveEventHandler);
|
||||
$socket?.on('events', chatActiveEventHandler);
|
||||
});
|
||||
|
||||
// Handler for chat:active events (defined outside onMount for proper cleanup)
|
||||
const chatActiveEventHandler = (event: {
|
||||
chat_id: string;
|
||||
message_id: string;
|
||||
data: { type: string; data: any };
|
||||
}) => {
|
||||
if (event.data?.type === 'chat:active') {
|
||||
const { active } = event.data.data;
|
||||
activeChatIds.update((ids) => {
|
||||
const newSet = new Set(ids);
|
||||
if (active) {
|
||||
newSet.add(event.chat_id);
|
||||
} else {
|
||||
newSet.delete(event.chat_id);
|
||||
}
|
||||
return newSet;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
onDestroy(() => {
|
||||
if (unsubscribers && unsubscribers.length > 0) {
|
||||
unsubscribers.forEach((unsubscriber) => {
|
||||
@@ -518,6 +555,9 @@
|
||||
dropZone?.removeEventListener('dragover', onDragOver);
|
||||
dropZone?.removeEventListener('drop', onDrop);
|
||||
dropZone?.removeEventListener('dragleave', onDragLeave);
|
||||
|
||||
// Clean up socket listener
|
||||
$socket?.off('events', chatActiveEventHandler);
|
||||
});
|
||||
|
||||
const newChatHandler = async () => {
|
||||
@@ -1244,7 +1284,7 @@
|
||||
className=""
|
||||
id={chat.id}
|
||||
title={chat.title}
|
||||
updatedAt={chat.updated_at}
|
||||
createdAt={chat.created_at}
|
||||
{shiftKey}
|
||||
selected={selectedChatId === chat.id}
|
||||
on:select={() => {
|
||||
@@ -1305,7 +1345,7 @@
|
||||
className=""
|
||||
id={chat.id}
|
||||
title={chat.title}
|
||||
updatedAt={chat.updated_at}
|
||||
createdAt={chat.created_at}
|
||||
{shiftKey}
|
||||
selected={selectedChatId === chat.id}
|
||||
on:select={() => {
|
||||
|
||||
@@ -27,7 +27,8 @@
|
||||
showSidebar,
|
||||
currentChatPage,
|
||||
tags,
|
||||
selectedFolder
|
||||
selectedFolder,
|
||||
activeChatIds
|
||||
} from '$lib/stores';
|
||||
|
||||
import ChatMenu from './ChatMenu.svelte';
|
||||
@@ -41,13 +42,14 @@
|
||||
import XMark from '$lib/components/icons/XMark.svelte';
|
||||
import Document from '$lib/components/icons/Document.svelte';
|
||||
import Sparkles from '$lib/components/icons/Sparkles.svelte';
|
||||
import Spinner from '$lib/components/common/Spinner.svelte';
|
||||
import { generateTitle } from '$lib/apis';
|
||||
|
||||
export let className = '';
|
||||
|
||||
export let id;
|
||||
export let title;
|
||||
export let updatedAt: number | null = null;
|
||||
export let createdAt: number | null = null;
|
||||
|
||||
export let selected = false;
|
||||
export let shiftKey = false;
|
||||
@@ -73,6 +75,7 @@
|
||||
return '1m';
|
||||
}
|
||||
|
||||
|
||||
let chat = null;
|
||||
|
||||
let mouseOver = false;
|
||||
@@ -443,6 +446,13 @@
|
||||
on:focus={(e) => {}}
|
||||
draggable="false"
|
||||
>
|
||||
<!-- Loading spinner for active chat (left side) -->
|
||||
{#if $activeChatIds.has(id)}
|
||||
<div class="shrink-0 self-center pr-2">
|
||||
<Spinner className="size-3" />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="flex self-center flex-1 w-full min-w-0">
|
||||
<div dir="auto" class="text-left self-center overflow-hidden w-full h-[20px] truncate">
|
||||
{title}
|
||||
@@ -450,9 +460,9 @@
|
||||
</div>
|
||||
|
||||
<!-- Time ago indicator -->
|
||||
{#if updatedAt && !mouseOver}
|
||||
{#if createdAt && !mouseOver}
|
||||
<div class="shrink-0 self-center text-[10px] text-gray-400 dark:text-gray-500 pl-2">
|
||||
{formatTimeAgo(updatedAt)}
|
||||
{formatTimeAgo(createdAt)}
|
||||
</div>
|
||||
{/if}
|
||||
</a>
|
||||
|
||||
@@ -643,7 +643,7 @@
|
||||
<ChatItem
|
||||
id={chat.id}
|
||||
title={chat.title}
|
||||
updatedAt={chat.updated_at}
|
||||
createdAt={chat.created_at}
|
||||
{shiftKey}
|
||||
on:change={(e) => {
|
||||
dispatch('change', e.detail);
|
||||
|
||||
@@ -27,6 +27,7 @@ export const mobile = writable(false);
|
||||
|
||||
export const socket: Writable<null | Socket> = writable(null);
|
||||
export const activeUserIds: Writable<null | string[]> = writable(null);
|
||||
export const activeChatIds: Writable<Set<string>> = writable(new Set());
|
||||
export const USAGE_POOL: Writable<null | string[]> = writable(null);
|
||||
|
||||
export const theme = writable('system');
|
||||
|
||||
Reference in New Issue
Block a user