mirror of
https://github.com/open-webui/open-webui.git
synced 2026-11-15 09:31:21 +00:00
refac
This commit is contained in:
parent
6441877c01
commit
ddb43c83ba
13 changed files with 83 additions and 16 deletions
|
|
@ -2238,6 +2238,7 @@ async def stop_tasks_by_chat_id_endpoint(request: Request, chat_id: str, user=De
|
|||
if event_emitter:
|
||||
await event_emitter({'type': 'chat:completion', 'data': {'done': True, 'output': output}})
|
||||
await event_emitter({'type': 'chat:tasks:cancel'})
|
||||
await event_emitter({'type': 'chat:active', 'data': {'active': False}})
|
||||
|
||||
return result
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
|
|||
|
||||
from open_webui.internal.db import Base, get_async_db_context
|
||||
from open_webui.utils.response import normalize_usage
|
||||
from open_webui.utils.session_records import normalize_tool_output, recorded_timestamp
|
||||
from open_webui.utils.session_records import has_pending_tool_approval, normalize_tool_output, recorded_timestamp
|
||||
from pydantic import BaseModel, ConfigDict
|
||||
from sqlalchemy import (
|
||||
JSON,
|
||||
|
|
@ -356,6 +356,20 @@ class ChatMessageTable:
|
|||
message = await db.get(ChatMessage, id)
|
||||
return ChatMessageModel.model_validate(message) if message else None
|
||||
|
||||
async def get_chat_ids_with_pending_approval(
|
||||
self, chat_ids: list[str], db: Optional[AsyncSession] = None
|
||||
) -> set[str]:
|
||||
if not chat_ids:
|
||||
return set()
|
||||
async with get_async_db_context(db) as db:
|
||||
result = await db.execute(
|
||||
select(ChatMessage.chat_id, ChatMessage.output)
|
||||
.where(ChatMessage.chat_id.in_(chat_ids))
|
||||
.where(ChatMessage.role == 'assistant')
|
||||
.where(ChatMessage.done.is_(False))
|
||||
)
|
||||
return {chat_id for chat_id, output in result.all() if has_pending_tool_approval(output)}
|
||||
|
||||
async def has_unfinished_assistant_by_chat_id(
|
||||
self,
|
||||
chat_id: str,
|
||||
|
|
|
|||
|
|
@ -356,6 +356,7 @@ class ChatTitleIdResponse(BaseModel):
|
|||
last_read_at: int | None = None
|
||||
snippet: str | None = None
|
||||
active: bool = False
|
||||
pending_approval: bool = False
|
||||
archived: bool = False
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -179,7 +179,9 @@ def set_message_authors(chat: dict, user: UserModel) -> None:
|
|||
async def add_active_state_to_chat_list(
|
||||
request: Request, chat_list: list[ChatTitleIdResponse]
|
||||
) -> list[ChatTitleIdResponse]:
|
||||
pending_approval_ids = await ChatMessages.get_chat_ids_with_pending_approval([chat.id for chat in chat_list])
|
||||
for chat in chat_list:
|
||||
chat.pending_approval = chat.id in pending_approval_ids
|
||||
chat.active = False
|
||||
if not await has_active_tasks(request.app.state.redis, chat.id):
|
||||
continue
|
||||
|
|
|
|||
|
|
@ -619,6 +619,9 @@ async def get_shared_folder_chats(
|
|||
user_ids.update(uid for ids in participant_ids.values() for uid in ids)
|
||||
users = await Users.get_users_by_user_ids(list(user_ids), db=db) if user_ids else []
|
||||
names = {u.id: u.name for u in users}
|
||||
pending_approval_ids = await ChatMessages.get_chat_ids_with_pending_approval(
|
||||
[chat['id'] for chat in chats], db=db
|
||||
)
|
||||
for chat in chats:
|
||||
uid = chat['user_id']
|
||||
chat['owner_name'] = names.get(uid, 'Unknown')
|
||||
|
|
@ -632,6 +635,7 @@ async def get_shared_folder_chats(
|
|||
),
|
||||
)
|
||||
chat['active'] = False
|
||||
chat['pending_approval'] = chat['id'] in pending_approval_ids
|
||||
if chat['user_id'] != user.id:
|
||||
chat['last_read_at'] = chat['updated_at']
|
||||
if await has_active_tasks(request.app.state.redis, chat['id']):
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ from open_webui.env import (
|
|||
)
|
||||
from open_webui.models.access_grants import AccessGrants
|
||||
from open_webui.models.channels import Channels
|
||||
from open_webui.models.chat_messages import ChatMessages
|
||||
from open_webui.models.chats import Chats
|
||||
from open_webui.models.config import Config
|
||||
from open_webui.models.folders import Folders
|
||||
|
|
@ -1624,6 +1625,13 @@ async def get_event_emitter(request_info, update_db=True):
|
|||
if internal and event_data.get('type') == 'notification':
|
||||
return
|
||||
|
||||
if event_data.get('type') == 'chat:active' and is_saved_chat_id(chat_id):
|
||||
pending_approval_ids = await ChatMessages.get_chat_ids_with_pending_approval([chat_id])
|
||||
event_data = {
|
||||
**event_data,
|
||||
'data': {**(event_data.get('data') or {}), 'pending_approval': chat_id in pending_approval_ids},
|
||||
}
|
||||
|
||||
room = f'user:{user_id}'
|
||||
if is_temporary_chat_id(chat_id):
|
||||
owner = await get_temporary_chat(redis, chat_id)
|
||||
|
|
|
|||
|
|
@ -3327,6 +3327,9 @@ async def pause_for_tool_approval(chat_id: str, message_id: str, output: list[di
|
|||
},
|
||||
touch=False,
|
||||
)
|
||||
event_emitter = await get_event_emitter(metadata, update_db=False)
|
||||
if event_emitter:
|
||||
await event_emitter({'type': 'chat:active', 'data': {'active': True}})
|
||||
|
||||
|
||||
def get_response_data(response):
|
||||
|
|
|
|||
|
|
@ -6,6 +6,21 @@ import re
|
|||
import time
|
||||
|
||||
|
||||
def has_pending_tool_approval(output):
|
||||
if not isinstance(output, list):
|
||||
return False
|
||||
items = [item for item in output if isinstance(item, dict)]
|
||||
result_ids = {item.get('call_id') for item in items if item.get('type') == 'function_call_output'}
|
||||
return any(
|
||||
item.get('type') == 'function_call'
|
||||
and (item.get('call_id') or item.get('id'))
|
||||
and (item.get('call_id') or item.get('id')) not in result_ids
|
||||
and item.get('status') in {'pending', 'queued', 'requires_approval'}
|
||||
and item.get('approved') is not True
|
||||
for item in items
|
||||
)
|
||||
|
||||
|
||||
def recorded_timestamp(value):
|
||||
try:
|
||||
value = float(value)
|
||||
|
|
|
|||
|
|
@ -258,7 +258,7 @@
|
|||
{
|
||||
setFolderItems?: () => unknown;
|
||||
upsertChat?: (chat: Record<string, unknown>) => unknown;
|
||||
setChatActive?: (chatId: string, active: boolean) => boolean;
|
||||
setChatActive?: (chatId: string, active: boolean, pendingApproval: boolean) => boolean;
|
||||
setChatReadAt?: (chatId: string, lastReadAt: number) => boolean;
|
||||
setAllChatsRead?: () => unknown;
|
||||
}
|
||||
|
|
@ -912,6 +912,7 @@
|
|||
type: string;
|
||||
data: {
|
||||
active?: boolean;
|
||||
pending_approval?: boolean;
|
||||
folder_id?: string | null;
|
||||
last_read_at?: number;
|
||||
folder_unread_counts?: Record<string, number>;
|
||||
|
|
@ -921,10 +922,12 @@
|
|||
if (event.data?.type === 'chat:active') {
|
||||
const eventData = event.data.data ?? {};
|
||||
const active = eventData.active ?? false;
|
||||
const found = setChatActive(event.chat_id, active);
|
||||
const pendingApproval = eventData.pending_approval ?? false;
|
||||
const found = setChatActive(event.chat_id, active, pendingApproval);
|
||||
let foundInFolder = false;
|
||||
for (const folder of Object.values(folderRegistry)) {
|
||||
foundInFolder = folder?.setChatActive?.(event.chat_id, active) || foundInFolder;
|
||||
foundInFolder =
|
||||
folder?.setChatActive?.(event.chat_id, active, pendingApproval) || foundInFolder;
|
||||
}
|
||||
if (!foundInFolder && active && eventData.folder_id) {
|
||||
await folderRegistry[eventData.folder_id]?.setFolderItems?.();
|
||||
|
|
@ -1778,6 +1781,7 @@
|
|||
updatedAt={chat.updated_at}
|
||||
lastReadAt={chat.last_read_at}
|
||||
active={chat.active ?? false}
|
||||
pendingApproval={chat.pending_approval ?? false}
|
||||
{shiftKey}
|
||||
selected={selectedChatId === chat.id}
|
||||
on:select={() => {
|
||||
|
|
@ -1846,6 +1850,7 @@
|
|||
updatedAt={chat.updated_at}
|
||||
lastReadAt={chat.last_read_at}
|
||||
active={chat.active ?? false}
|
||||
pendingApproval={chat.pending_approval ?? false}
|
||||
{shiftKey}
|
||||
selected={selectedChatId === chat.id}
|
||||
on:select={() => {
|
||||
|
|
|
|||
|
|
@ -93,6 +93,7 @@
|
|||
export let updatedAt: number | null = null;
|
||||
export let lastReadAt: number | null = null;
|
||||
export let active = false;
|
||||
export let pendingApproval = false;
|
||||
|
||||
export let selected = false;
|
||||
export let shiftKey = false;
|
||||
|
|
@ -563,19 +564,26 @@
|
|||
</script>
|
||||
|
||||
{#snippet chatItemContent()}
|
||||
<!-- Loading spinner for active chat (left side) -->
|
||||
{#if active}
|
||||
{#if pendingApproval || active || unread}
|
||||
<div class="shrink-0 self-center pr-2">
|
||||
<Spinner className="size-3" />
|
||||
<div class="size-3 flex items-center justify-center">
|
||||
{#if pendingApproval}
|
||||
<div
|
||||
class="size-1.5 bg-yellow-500 rounded-full"
|
||||
role="status"
|
||||
aria-label={$i18n.t('Waiting for approval')}
|
||||
title={$i18n.t('Waiting for approval')}
|
||||
></div>
|
||||
{:else if active}
|
||||
<Spinner className="size-3" />
|
||||
{:else}
|
||||
<div class="size-1.5 bg-sky-500 rounded-full"></div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="flex self-center flex-1 w-full min-w-0">
|
||||
{#if unread}
|
||||
<div class="shrink-0 self-center pr-2.5 flex transition-opacity duration-300">
|
||||
<div class="size-1.5 bg-sky-500 rounded-full"></div>
|
||||
</div>
|
||||
{/if}
|
||||
<div
|
||||
dir="auto"
|
||||
class="text-left self-center overflow-hidden w-full h-5 truncate {unread
|
||||
|
|
|
|||
|
|
@ -383,7 +383,7 @@
|
|||
chats = mergeFolderChats(chats ?? [], [chat]);
|
||||
}
|
||||
},
|
||||
setChatActive: (chatId, active) => {
|
||||
setChatActive: (chatId, active, pendingApproval) => {
|
||||
if (chats) {
|
||||
let found = false;
|
||||
chats = sortFolderChats(
|
||||
|
|
@ -392,7 +392,7 @@
|
|||
return chat;
|
||||
}
|
||||
found = true;
|
||||
return { ...chat, active };
|
||||
return { ...chat, active, pending_approval: pendingApproval };
|
||||
})
|
||||
);
|
||||
return found;
|
||||
|
|
@ -1023,6 +1023,7 @@
|
|||
updatedAt={chat.updated_at}
|
||||
lastReadAt={chat.last_read_at}
|
||||
active={chat.active ?? false}
|
||||
pendingApproval={chat.pending_approval ?? false}
|
||||
ownerName={chat.owner_name ?? null}
|
||||
participants={chat.participants ?? []}
|
||||
ownerUserId={folders[folderId]?.showOwnerInfo ||
|
||||
|
|
|
|||
|
|
@ -136,6 +136,7 @@
|
|||
updatedAt={chat.updated_at}
|
||||
lastReadAt={chat.last_read_at}
|
||||
active={chat.active ?? false}
|
||||
pendingApproval={chat.pending_approval ?? false}
|
||||
ownerName={chat.owner_name}
|
||||
participants={chat.participants ?? []}
|
||||
ownerUserId={chat.user_id}
|
||||
|
|
|
|||
|
|
@ -116,14 +116,18 @@ export const loadNextChatListPage = async (token: string = ''): Promise<ChatList
|
|||
}
|
||||
};
|
||||
|
||||
export const setChatActive = (chatId: string, active: boolean): boolean => {
|
||||
export const setChatActive = (
|
||||
chatId: string,
|
||||
active: boolean,
|
||||
pendingApproval = false
|
||||
): boolean => {
|
||||
let found = false;
|
||||
const updateChat = (chat: ChatListItem) => {
|
||||
if (chat.id !== chatId) {
|
||||
return chat;
|
||||
}
|
||||
found = true;
|
||||
return { ...chat, active };
|
||||
return { ...chat, active, pending_approval: pendingApproval };
|
||||
};
|
||||
|
||||
chatsStore.update((items) => (items ? items.map(updateChat) : items));
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue