mirror of
https://github.com/Monadical-SAS/reflector.git
synced 2026-05-06 11:15:18 +00:00
feat: new ui with greyhaven design system
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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,
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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 []
|
||||
|
||||
Reference in New Issue
Block a user