feat(ui,api): chat view with live tail and autoscroll, conversation rail with last message previews

This commit is contained in:
hh
2026-08-29 00:18:08 +02:00
parent 8ea4cea3ef
commit 3ca5843868
9 changed files with 532 additions and 334 deletions
+18
View File
@@ -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],
+31 -10
View File
@@ -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,