feat: new ui with greyhaven design system

This commit is contained in:
Juan
2026-04-23 15:01:05 -05:00
parent dc428b2042
commit 0956647dbc
206 changed files with 18978 additions and 24703 deletions

View File

@@ -175,6 +175,9 @@ class SearchResult(BaseModel):
total_match_count: NonNegativeInt = Field(
default=0, description="Total number of matches found in the transcript"
)
speaker_count: NonNegativeInt = Field(
default=0, description="Number of distinct speakers in the transcript"
)
change_seq: int | None = None
@field_serializer("created_at", when_used="json")
@@ -362,6 +365,7 @@ class SearchController:
transcripts.c.change_seq,
transcripts.c.webvtt,
transcripts.c.long_summary,
transcripts.c.participants,
sqlalchemy.case(
(
transcripts.c.room_id.isnot(None) & rooms.c.id.is_(None),
@@ -458,6 +462,12 @@ class SearchController:
long_summary_r: str | None = r_dict.pop("long_summary", None)
long_summary: NonEmptyString = try_parse_non_empty_string(long_summary_r)
room_name: str | None = r_dict.pop("room_name", None)
participants_raw = r_dict.pop("participants", None) or []
speaker_count = (
len({p.get("speaker") for p in participants_raw if isinstance(p, dict)})
if isinstance(participants_raw, list)
else 0
)
db_result = SearchResultDB.model_validate(r_dict)
at_least_one_source = webvtt is not None or long_summary is not None
@@ -475,6 +485,7 @@ class SearchController:
room_name=room_name,
search_snippets=snippets,
total_match_count=total_match_count,
speaker_count=speaker_count,
)
try:

View File

@@ -446,10 +446,19 @@ class TranscriptController:
col for col in transcripts.c if col.name not in exclude_columns
]
# Cheap speaker_count via JSON array length on the participants column
# (same column already stored on every transcript, no extra queries).
# COALESCE handles transcripts where participants is NULL.
speaker_count_col = sqlalchemy.func.coalesce(
sqlalchemy.func.json_array_length(transcripts.c.participants),
0,
).label("speaker_count")
query = query.with_only_columns(
transcript_columns
+ [
rooms.c.name.label("room_name"),
speaker_count_col,
]
)

View File

@@ -116,6 +116,7 @@ class GetTranscriptMinimal(BaseModel):
change_seq: int | None = None
has_cloud_video: bool = False
cloud_video_duration: int | None = None
speaker_count: int = 0
class TranscriptParticipantWithEmail(TranscriptParticipant):

View File

@@ -1,11 +1,14 @@
import logging
from typing import Annotated, Optional
import httpx
from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel
import reflector.auth as auth
from reflector.zulip import get_zulip_streams, get_zulip_topics
logger = logging.getLogger(__name__)
router = APIRouter()
@@ -23,13 +26,18 @@ async def zulip_get_streams(
user: Annotated[Optional[auth.UserInfo], Depends(auth.current_user_optional)],
) -> list[Stream]:
"""
Get all Zulip streams.
Get all Zulip streams. Returns [] if the upstream Zulip API is unreachable
or the server credentials are invalid — the client treats Zulip as an
optional integration and renders gracefully without a hard error.
"""
if not user:
raise HTTPException(status_code=403, detail="Authentication required")
streams = await get_zulip_streams()
return streams
try:
return await get_zulip_streams()
except (httpx.HTTPStatusError, httpx.RequestError, Exception) as exc:
logger.warning("zulip get_streams failed, returning []: %s", exc)
return []
@router.get("/zulip/streams/{stream_id}/topics")
@@ -38,10 +46,14 @@ async def zulip_get_topics(
user: Annotated[Optional[auth.UserInfo], Depends(auth.current_user_optional)],
) -> list[Topic]:
"""
Get all topics for a specific Zulip stream.
Get all topics for a specific Zulip stream. Returns [] on upstream failure
for the same reason as /zulip/streams above.
"""
if not user:
raise HTTPException(status_code=403, detail="Authentication required")
topics = await get_zulip_topics(stream_id)
return topics
try:
return await get_zulip_topics(stream_id)
except (httpx.HTTPStatusError, httpx.RequestError, Exception) as exc:
logger.warning("zulip get_topics(%s) failed, returning []: %s", stream_id, exc)
return []