mirror of
https://github.com/open-webui/open-webui.git
synced 2026-07-23 09:10:55 -05:00
refac
This commit is contained in:
@@ -984,6 +984,69 @@ class ChatTable:
|
||||
result = await session.execute(stmt)
|
||||
return [meta for meta in result.scalars().all() if isinstance(meta, dict)]
|
||||
|
||||
async def get_chats_by_model_id(
|
||||
self,
|
||||
model_id: str,
|
||||
filter: dict | None = None,
|
||||
skip: int = 0,
|
||||
limit: int = 50,
|
||||
db: AsyncSession | None = None,
|
||||
) -> dict:
|
||||
from open_webui.models.users import User
|
||||
|
||||
async with get_async_db_context(db) as session:
|
||||
chat_ids = select(ChatMessage.chat_id).filter(ChatMessage.model_id == model_id).group_by(ChatMessage.chat_id)
|
||||
|
||||
if filter:
|
||||
if filter.get('start_date'):
|
||||
chat_ids = chat_ids.filter(ChatMessage.created_at >= filter.get('start_date'))
|
||||
if filter.get('end_date'):
|
||||
chat_ids = chat_ids.filter(ChatMessage.created_at <= filter.get('end_date'))
|
||||
|
||||
chat_ids = chat_ids.subquery()
|
||||
|
||||
stmt = (
|
||||
select(Chat.id, Chat.user_id, Chat.title, Chat.updated_at, User.name.label('user_name'))
|
||||
.join(chat_ids, chat_ids.c.chat_id == Chat.id)
|
||||
.outerjoin(User, User.id == Chat.user_id)
|
||||
)
|
||||
|
||||
order_by = filter.get('order_by') if filter else None
|
||||
direction = filter.get('direction') if filter else None
|
||||
is_asc = direction == 'asc'
|
||||
|
||||
if order_by == 'title':
|
||||
primary_sort = Chat.title.asc() if is_asc else Chat.title.desc()
|
||||
elif order_by == 'user_name':
|
||||
primary_sort = User.name.asc() if is_asc else User.name.desc()
|
||||
else:
|
||||
primary_sort = Chat.updated_at.asc() if is_asc else Chat.updated_at.desc()
|
||||
|
||||
stmt = stmt.order_by(primary_sort, Chat.id.asc())
|
||||
|
||||
count_result = await session.execute(select(func.count()).select_from(stmt.subquery()))
|
||||
total = count_result.scalar()
|
||||
|
||||
if skip:
|
||||
stmt = stmt.offset(skip)
|
||||
if limit:
|
||||
stmt = stmt.limit(limit)
|
||||
|
||||
result = await session.execute(stmt)
|
||||
return {
|
||||
'items': [
|
||||
{
|
||||
'chat_id': chat.id,
|
||||
'user_id': chat.user_id,
|
||||
'user_name': chat.user_name,
|
||||
'first_message': chat.title,
|
||||
'updated_at': chat.updated_at,
|
||||
}
|
||||
for chat in result.all()
|
||||
],
|
||||
'total': total,
|
||||
}
|
||||
|
||||
# retrieve conversation
|
||||
async def get_chat_by_id(
|
||||
self,
|
||||
|
||||
@@ -279,6 +279,9 @@ class ModelChatsResponse(BaseModel):
|
||||
total: int
|
||||
|
||||
|
||||
MODEL_CHAT_ORDER_FIELDS = {'title', 'updated_at', 'user_name'}
|
||||
|
||||
|
||||
@router.get('/models/{model_id:path}/chats', response_model=ModelChatsResponse)
|
||||
async def get_model_chats(
|
||||
model_id: str,
|
||||
@@ -286,65 +289,34 @@ async def get_model_chats(
|
||||
end_date: Optional[int] = Query(None),
|
||||
skip: int = Query(0),
|
||||
limit: int = Query(50, le=100),
|
||||
order_by: str = Query('updated_at'),
|
||||
direction: str = Query('desc'),
|
||||
user=Depends(get_admin_user),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
"""Get chats that used a specific model, with preview and feedback info."""
|
||||
filter = {}
|
||||
if start_date:
|
||||
filter['start_date'] = start_date
|
||||
if end_date:
|
||||
filter['end_date'] = end_date
|
||||
if order_by in MODEL_CHAT_ORDER_FIELDS:
|
||||
filter['order_by'] = order_by
|
||||
if direction in {'asc', 'desc'}:
|
||||
filter['direction'] = direction
|
||||
|
||||
# Get chat IDs that used this model
|
||||
chat_ids = await ChatMessages.get_chat_ids_by_model_id(
|
||||
result = await Chats.get_chats_by_model_id(
|
||||
model_id=model_id,
|
||||
start_date=start_date,
|
||||
end_date=end_date,
|
||||
filter=filter,
|
||||
skip=skip,
|
||||
limit=limit,
|
||||
db=db,
|
||||
)
|
||||
|
||||
if not chat_ids:
|
||||
return ModelChatsResponse(chats=[], total=0)
|
||||
|
||||
# Get chat details from messages only
|
||||
chats_data = []
|
||||
for chat_id in chat_ids:
|
||||
messages = await ChatMessages.get_messages_by_chat_id(chat_id, db=db)
|
||||
if not messages:
|
||||
continue
|
||||
|
||||
# Get user_id from first user message
|
||||
first_user_msg = next((m for m in messages if m.role == 'user'), None)
|
||||
user_id = first_user_msg.user_id if first_user_msg else None
|
||||
|
||||
# Extract first message content as preview
|
||||
first_message = None
|
||||
if first_user_msg and first_user_msg.content:
|
||||
content = first_user_msg.content
|
||||
if isinstance(content, str):
|
||||
first_message = content[:200]
|
||||
elif isinstance(content, list):
|
||||
text_parts = [b.get('text', '') for b in content if isinstance(b, dict)]
|
||||
first_message = ' '.join(text_parts)[:200]
|
||||
|
||||
# Get user info
|
||||
user_name = None
|
||||
if user_id:
|
||||
user_info = await Users.get_user_by_id(user_id, db=db)
|
||||
user_name = user_info.name if user_info else None
|
||||
|
||||
# Timestamps from messages
|
||||
updated_at = max(m.created_at for m in messages) if messages else 0
|
||||
|
||||
chats_data.append(
|
||||
ModelChatEntry(
|
||||
chat_id=chat_id,
|
||||
user_id=user_id,
|
||||
user_name=user_name,
|
||||
first_message=first_message,
|
||||
updated_at=updated_at,
|
||||
)
|
||||
)
|
||||
|
||||
return ModelChatsResponse(chats=chats_data, total=len(chats_data))
|
||||
return ModelChatsResponse(
|
||||
chats=[ModelChatEntry.model_validate(chat) for chat in result['items']],
|
||||
total=result['total'] or 0,
|
||||
)
|
||||
|
||||
|
||||
####################
|
||||
|
||||
@@ -5,10 +5,13 @@
|
||||
import { WEBUI_API_BASE_URL } from '$lib/constants';
|
||||
import Spinner from '$lib/components/common/Spinner.svelte';
|
||||
import Loader from '$lib/components/common/Loader.svelte';
|
||||
import ChevronUp from '$lib/components/icons/ChevronUp.svelte';
|
||||
import ChevronDown from '$lib/components/icons/ChevronDown.svelte';
|
||||
|
||||
dayjs.extend(calendar);
|
||||
|
||||
const i18n = getContext('i18n');
|
||||
type SortKey = 'title' | 'updated_at' | 'user_name';
|
||||
|
||||
export let chatList: Array<{
|
||||
id: string;
|
||||
@@ -25,22 +28,89 @@
|
||||
export let emptyMessage = 'No chats found';
|
||||
export let onLoadMore: (() => void) | null = null;
|
||||
export let onChatClick: ((chatId: string) => void) | null = null;
|
||||
export let orderBy: SortKey | null = null;
|
||||
export let direction: 'asc' | 'desc' = 'desc';
|
||||
export let onSort: ((key: SortKey) => void) | null = null;
|
||||
</script>
|
||||
|
||||
<div>
|
||||
{#if chatList && chatList.length > 0}
|
||||
<div class="flex text-xs font-medium mb-1.5">
|
||||
{#if showUserInfo}
|
||||
<div class="px-1.5 py-1 w-32">
|
||||
{$i18n.t('User')}
|
||||
{#if onSort}
|
||||
<button
|
||||
type="button"
|
||||
class="px-1.5 py-1 w-32 cursor-pointer select-none"
|
||||
on:click={() => onSort?.('user_name')}
|
||||
>
|
||||
<div class="flex gap-1.5 items-center">
|
||||
{$i18n.t('User')}
|
||||
{#if orderBy === 'user_name'}
|
||||
<span class="font-normal">
|
||||
{#if direction === 'asc'}<ChevronUp className="size-2" />{:else}<ChevronDown
|
||||
className="size-2"
|
||||
/>{/if}
|
||||
</span>
|
||||
{:else}
|
||||
<span class="invisible"><ChevronUp className="size-2" /></span>
|
||||
{/if}
|
||||
</div>
|
||||
</button>
|
||||
{:else}
|
||||
<div class="px-1.5 py-1 w-32">
|
||||
{$i18n.t('User')}
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
{#if onSort}
|
||||
<button
|
||||
type="button"
|
||||
class="px-1.5 py-1 {showUserInfo
|
||||
? 'flex-1'
|
||||
: 'basis-3/5'} cursor-pointer select-none text-left"
|
||||
on:click={() => onSort?.('title')}
|
||||
>
|
||||
<div class="flex gap-1.5 items-center">
|
||||
{$i18n.t('Title')}
|
||||
{#if orderBy === 'title'}
|
||||
<span class="font-normal">
|
||||
{#if direction === 'asc'}<ChevronUp className="size-2" />{:else}<ChevronDown
|
||||
className="size-2"
|
||||
/>{/if}
|
||||
</span>
|
||||
{:else}
|
||||
<span class="invisible"><ChevronUp className="size-2" /></span>
|
||||
{/if}
|
||||
</div>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="px-1.5 py-1 hidden sm:flex {showUserInfo
|
||||
? 'w-28'
|
||||
: 'basis-2/5'} justify-end cursor-pointer select-none"
|
||||
on:click={() => onSort?.('updated_at')}
|
||||
>
|
||||
<div class="flex gap-1.5 items-center">
|
||||
{$i18n.t('Updated at')}
|
||||
{#if orderBy === 'updated_at'}
|
||||
<span class="font-normal">
|
||||
{#if direction === 'asc'}<ChevronUp className="size-2" />{:else}<ChevronDown
|
||||
className="size-2"
|
||||
/>{/if}
|
||||
</span>
|
||||
{:else}
|
||||
<span class="invisible"><ChevronUp className="size-2" /></span>
|
||||
{/if}
|
||||
</div>
|
||||
</button>
|
||||
{:else}
|
||||
<div class="px-1.5 py-1 {showUserInfo ? 'flex-1' : 'basis-3/5'}">
|
||||
{$i18n.t('Title')}
|
||||
</div>
|
||||
<div class="px-1.5 py-1 hidden sm:flex {showUserInfo ? 'w-28' : 'basis-2/5'} justify-end">
|
||||
{$i18n.t('Updated at')}
|
||||
</div>
|
||||
{/if}
|
||||
<div class="px-1.5 py-1 {showUserInfo ? 'flex-1' : 'basis-3/5'}">
|
||||
{$i18n.t('Title')}
|
||||
</div>
|
||||
<div class="px-1.5 py-1 hidden sm:flex {showUserInfo ? 'w-28' : 'basis-2/5'} justify-end">
|
||||
{$i18n.t('Updated at')}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="max-h-[22rem] overflow-y-scroll">
|
||||
|
||||
Reference in New Issue
Block a user