refac
This commit is contained in:
@@ -1804,6 +1804,16 @@ async def chat_completion(
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.debug(f"Error cleaning up: {e}")
|
log.debug(f"Error cleaning up: {e}")
|
||||||
pass
|
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 (
|
if (
|
||||||
metadata.get("session_id")
|
metadata.get("session_id")
|
||||||
@@ -1816,6 +1826,12 @@ async def chat_completion(
|
|||||||
process_chat(request, form_data, user, metadata, model),
|
process_chat(request, form_data, user, metadata, model),
|
||||||
id=metadata["chat_id"],
|
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}
|
return {"status": True, "task_id": task_id}
|
||||||
else:
|
else:
|
||||||
return await process_chat(request, form_data, user, metadata, model)
|
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")
|
@router.get("/config")
|
||||||
async def get_task_config(request: Request, user=Depends(get_verified_user)):
|
async def get_task_config(request: Request, user=Depends(get_verified_user)):
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -183,3 +183,18 @@ async def stop_item_tasks(redis: Redis, item_id: str):
|
|||||||
return result # Return the first failure
|
return result # Return the first failure
|
||||||
|
|
||||||
return {"status": True, "message": f"All tasks for item {item_id} stopped."}
|
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,
|
models,
|
||||||
selectedFolder,
|
selectedFolder,
|
||||||
WEBUI_NAME,
|
WEBUI_NAME,
|
||||||
sidebarWidth
|
sidebarWidth,
|
||||||
|
activeChatIds
|
||||||
} from '$lib/stores';
|
} from '$lib/stores';
|
||||||
import { onMount, getContext, tick, onDestroy } from 'svelte';
|
import { onMount, getContext, tick, onDestroy } from 'svelte';
|
||||||
|
|
||||||
@@ -42,6 +43,7 @@
|
|||||||
importChats
|
importChats
|
||||||
} from '$lib/apis/chats';
|
} from '$lib/apis/chats';
|
||||||
import { createNewFolder, getFolders, updateFolderParentIdById } from '$lib/apis/folders';
|
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 { WEBUI_API_BASE_URL, WEBUI_BASE_URL } from '$lib/constants';
|
||||||
|
|
||||||
import ArchivedChatsModal from './ArchivedChatsModal.svelte';
|
import ArchivedChatsModal from './ArchivedChatsModal.svelte';
|
||||||
@@ -469,6 +471,17 @@
|
|||||||
await initChannels();
|
await initChannels();
|
||||||
}
|
}
|
||||||
await initChatList();
|
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) => {
|
settings.subscribe((value) => {
|
||||||
@@ -493,8 +506,32 @@
|
|||||||
dropZone?.addEventListener('dragover', onDragOver);
|
dropZone?.addEventListener('dragover', onDragOver);
|
||||||
dropZone?.addEventListener('drop', onDrop);
|
dropZone?.addEventListener('drop', onDrop);
|
||||||
dropZone?.addEventListener('dragleave', onDragLeave);
|
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(() => {
|
onDestroy(() => {
|
||||||
if (unsubscribers && unsubscribers.length > 0) {
|
if (unsubscribers && unsubscribers.length > 0) {
|
||||||
unsubscribers.forEach((unsubscriber) => {
|
unsubscribers.forEach((unsubscriber) => {
|
||||||
@@ -518,6 +555,9 @@
|
|||||||
dropZone?.removeEventListener('dragover', onDragOver);
|
dropZone?.removeEventListener('dragover', onDragOver);
|
||||||
dropZone?.removeEventListener('drop', onDrop);
|
dropZone?.removeEventListener('drop', onDrop);
|
||||||
dropZone?.removeEventListener('dragleave', onDragLeave);
|
dropZone?.removeEventListener('dragleave', onDragLeave);
|
||||||
|
|
||||||
|
// Clean up socket listener
|
||||||
|
$socket?.off('events', chatActiveEventHandler);
|
||||||
});
|
});
|
||||||
|
|
||||||
const newChatHandler = async () => {
|
const newChatHandler = async () => {
|
||||||
@@ -1244,7 +1284,7 @@
|
|||||||
className=""
|
className=""
|
||||||
id={chat.id}
|
id={chat.id}
|
||||||
title={chat.title}
|
title={chat.title}
|
||||||
updatedAt={chat.updated_at}
|
createdAt={chat.created_at}
|
||||||
{shiftKey}
|
{shiftKey}
|
||||||
selected={selectedChatId === chat.id}
|
selected={selectedChatId === chat.id}
|
||||||
on:select={() => {
|
on:select={() => {
|
||||||
@@ -1305,7 +1345,7 @@
|
|||||||
className=""
|
className=""
|
||||||
id={chat.id}
|
id={chat.id}
|
||||||
title={chat.title}
|
title={chat.title}
|
||||||
updatedAt={chat.updated_at}
|
createdAt={chat.created_at}
|
||||||
{shiftKey}
|
{shiftKey}
|
||||||
selected={selectedChatId === chat.id}
|
selected={selectedChatId === chat.id}
|
||||||
on:select={() => {
|
on:select={() => {
|
||||||
|
|||||||
@@ -27,7 +27,8 @@
|
|||||||
showSidebar,
|
showSidebar,
|
||||||
currentChatPage,
|
currentChatPage,
|
||||||
tags,
|
tags,
|
||||||
selectedFolder
|
selectedFolder,
|
||||||
|
activeChatIds
|
||||||
} from '$lib/stores';
|
} from '$lib/stores';
|
||||||
|
|
||||||
import ChatMenu from './ChatMenu.svelte';
|
import ChatMenu from './ChatMenu.svelte';
|
||||||
@@ -41,13 +42,14 @@
|
|||||||
import XMark from '$lib/components/icons/XMark.svelte';
|
import XMark from '$lib/components/icons/XMark.svelte';
|
||||||
import Document from '$lib/components/icons/Document.svelte';
|
import Document from '$lib/components/icons/Document.svelte';
|
||||||
import Sparkles from '$lib/components/icons/Sparkles.svelte';
|
import Sparkles from '$lib/components/icons/Sparkles.svelte';
|
||||||
|
import Spinner from '$lib/components/common/Spinner.svelte';
|
||||||
import { generateTitle } from '$lib/apis';
|
import { generateTitle } from '$lib/apis';
|
||||||
|
|
||||||
export let className = '';
|
export let className = '';
|
||||||
|
|
||||||
export let id;
|
export let id;
|
||||||
export let title;
|
export let title;
|
||||||
export let updatedAt: number | null = null;
|
export let createdAt: number | null = null;
|
||||||
|
|
||||||
export let selected = false;
|
export let selected = false;
|
||||||
export let shiftKey = false;
|
export let shiftKey = false;
|
||||||
@@ -73,6 +75,7 @@
|
|||||||
return '1m';
|
return '1m';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
let chat = null;
|
let chat = null;
|
||||||
|
|
||||||
let mouseOver = false;
|
let mouseOver = false;
|
||||||
@@ -443,6 +446,13 @@
|
|||||||
on:focus={(e) => {}}
|
on:focus={(e) => {}}
|
||||||
draggable="false"
|
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 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">
|
<div dir="auto" class="text-left self-center overflow-hidden w-full h-[20px] truncate">
|
||||||
{title}
|
{title}
|
||||||
@@ -450,9 +460,9 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Time ago indicator -->
|
<!-- 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">
|
<div class="shrink-0 self-center text-[10px] text-gray-400 dark:text-gray-500 pl-2">
|
||||||
{formatTimeAgo(updatedAt)}
|
{formatTimeAgo(createdAt)}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -643,7 +643,7 @@
|
|||||||
<ChatItem
|
<ChatItem
|
||||||
id={chat.id}
|
id={chat.id}
|
||||||
title={chat.title}
|
title={chat.title}
|
||||||
updatedAt={chat.updated_at}
|
createdAt={chat.created_at}
|
||||||
{shiftKey}
|
{shiftKey}
|
||||||
on:change={(e) => {
|
on:change={(e) => {
|
||||||
dispatch('change', e.detail);
|
dispatch('change', e.detail);
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ export const mobile = writable(false);
|
|||||||
|
|
||||||
export const socket: Writable<null | Socket> = writable(null);
|
export const socket: Writable<null | Socket> = writable(null);
|
||||||
export const activeUserIds: Writable<null | string[]> = 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 USAGE_POOL: Writable<null | string[]> = writable(null);
|
||||||
|
|
||||||
export const theme = writable('system');
|
export const theme = writable('system');
|
||||||
|
|||||||
Reference in New Issue
Block a user