feat(ui,api): chat view with live tail and autoscroll, conversation rail with last message previews
This commit is contained in:
@@ -12,6 +12,7 @@ from __future__ import annotations
|
||||
from datetime import UTC, datetime
|
||||
from typing import TYPE_CHECKING, Literal
|
||||
|
||||
from sqlalchemy import func
|
||||
from sqlmodel import col, select
|
||||
|
||||
from beaver_gateway.storage.models import InjectQueueItem
|
||||
@@ -105,6 +106,23 @@ class InjectQueue:
|
||||
)
|
||||
return list(result.all())
|
||||
|
||||
async def latest(
|
||||
self, conversation_ids: Iterable[int]
|
||||
) -> dict[int, InjectQueueItem]:
|
||||
ids = list(conversation_ids)
|
||||
if not ids:
|
||||
return {}
|
||||
newest = (
|
||||
select(func.max(InjectQueueItem.id))
|
||||
.where(col(InjectQueueItem.conversation_id).in_(ids))
|
||||
.group_by(col(InjectQueueItem.conversation_id))
|
||||
)
|
||||
async with self._db.session() as session:
|
||||
result = await session.exec(
|
||||
select(InjectQueueItem).where(col(InjectQueueItem.id).in_(newest))
|
||||
)
|
||||
return {row.conversation_id: row for row in result.all()}
|
||||
|
||||
async def _mark(
|
||||
self,
|
||||
items: Iterable[InjectQueueItem],
|
||||
|
||||
@@ -46,7 +46,13 @@ from beaver_gateway.storage import (
|
||||
list_tokens,
|
||||
revoke_token,
|
||||
)
|
||||
from beaver_gateway.storage.models import Conversation, RateLimit, Token, Usage
|
||||
from beaver_gateway.storage.models import (
|
||||
Conversation,
|
||||
InjectQueueItem,
|
||||
RateLimit,
|
||||
Token,
|
||||
Usage,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import AsyncIterator, Iterable, Sequence
|
||||
@@ -220,7 +226,16 @@ def build_app(runtime: GatewayRuntime, *, memory_root: Path | None = None) -> Fa
|
||||
kind=q.get("kind"),
|
||||
limit=query_int(request, "limit", 200),
|
||||
)
|
||||
return {"conversations": [conversations.public(r) for r in rows]}
|
||||
latest = await conversations.queue.latest(cast("int", r.id) for r in rows)
|
||||
return {
|
||||
"conversations": [
|
||||
{
|
||||
**conversations.public(r),
|
||||
"last_item": _queue_item(latest.get(cast("int", r.id))),
|
||||
}
|
||||
for r in rows
|
||||
]
|
||||
}
|
||||
|
||||
@app.post("/api/conversations", status_code=status.HTTP_201_CREATED)
|
||||
async def create_conversation(request: Request) -> dict[str, Any]:
|
||||
@@ -272,14 +287,7 @@ def build_app(runtime: GatewayRuntime, *, memory_root: Path | None = None) -> Fa
|
||||
conv = await conv_of(public_id)
|
||||
out = await conversations.describe(conv)
|
||||
out["queue"] = [
|
||||
{
|
||||
"id": i.id,
|
||||
"priority": i.priority,
|
||||
"origin": i.origin,
|
||||
"status": i.status,
|
||||
"created_at": _iso(i.created_at),
|
||||
"text": i.text[:200],
|
||||
}
|
||||
_queue_item(i)
|
||||
for i in await conversations.queue.recent(cast("int", conv.id), limit=20)
|
||||
]
|
||||
return out
|
||||
@@ -859,6 +867,19 @@ async def _conversation_titles(
|
||||
}
|
||||
|
||||
|
||||
def _queue_item(item: InjectQueueItem | None) -> dict[str, Any] | None:
|
||||
if item is None:
|
||||
return None
|
||||
return {
|
||||
"id": item.id,
|
||||
"priority": item.priority,
|
||||
"origin": item.origin,
|
||||
"status": item.status,
|
||||
"created_at": _iso(item.created_at),
|
||||
"text": item.text[:200],
|
||||
}
|
||||
|
||||
|
||||
def _limit_public(row: RateLimit) -> dict[str, Any]:
|
||||
return {
|
||||
"id": row.id,
|
||||
|
||||
Reference in New Issue
Block a user