fix: browse page timestamps show UTC instead of user local time (#482)

* fix: browse page timestamps show UTC instead of user local time

Closes #474

* fix: tests
This commit is contained in:
2025-07-15 21:17:53 -06:00
committed by GitHub
parent f3ae187274
commit baf2822b81
4 changed files with 48 additions and 19 deletions

View File

@@ -3,13 +3,13 @@ import json
import os import os
import shutil import shutil
from contextlib import asynccontextmanager from contextlib import asynccontextmanager
from datetime import datetime from datetime import datetime, timezone
from pathlib import Path from pathlib import Path
from typing import Any, Literal from typing import Any, Literal
import sqlalchemy import sqlalchemy
from fastapi import HTTPException from fastapi import HTTPException
from pydantic import BaseModel, ConfigDict, Field from pydantic import BaseModel, ConfigDict, Field, field_serializer
from reflector.db import database, metadata from reflector.db import database, metadata
from reflector.processors.types import Word as ProcessorWord from reflector.processors.types import Word as ProcessorWord
from reflector.settings import settings from reflector.settings import settings
@@ -82,7 +82,7 @@ transcripts = sqlalchemy.Table(
def generate_transcript_name() -> str: def generate_transcript_name() -> str:
now = datetime.utcnow() now = datetime.now(timezone.utc)
return f"Transcript {now.strftime('%Y-%m-%d %H:%M:%S')}" return f"Transcript {now.strftime('%Y-%m-%d %H:%M:%S')}"
@@ -150,7 +150,7 @@ class Transcript(BaseModel):
status: str = "idle" status: str = "idle"
locked: bool = False locked: bool = False
duration: float = 0 duration: float = 0
created_at: datetime = Field(default_factory=datetime.utcnow) created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
title: str | None = None title: str | None = None
short_summary: str | None = None short_summary: str | None = None
long_summary: str | None = None long_summary: str | None = None
@@ -168,6 +168,12 @@ class Transcript(BaseModel):
source_kind: SourceKind source_kind: SourceKind
audio_deleted: bool | None = None audio_deleted: bool | None = None
@field_serializer("created_at", when_used="json")
def serialize_datetime(self, dt: datetime) -> str:
if dt.tzinfo is None:
dt = dt.replace(tzinfo=timezone.utc)
return dt.isoformat()
def add_event(self, event: str, data: BaseModel) -> TranscriptEvent: def add_event(self, event: str, data: BaseModel) -> TranscriptEvent:
ev = TranscriptEvent(event=event, data=data.model_dump()) ev = TranscriptEvent(event=event, data=data.model_dump())
self.events.append(ev) self.events.append(ev)

View File

@@ -1,4 +1,4 @@
from datetime import datetime, timedelta from datetime import datetime, timedelta, timezone
from typing import Annotated, Literal, Optional from typing import Annotated, Literal, Optional
import reflector.auth as auth import reflector.auth as auth
@@ -6,7 +6,7 @@ from fastapi import APIRouter, Depends, HTTPException
from fastapi_pagination import Page from fastapi_pagination import Page
from fastapi_pagination.ext.databases import paginate from fastapi_pagination.ext.databases import paginate
from jose import jwt from jose import jwt
from pydantic import BaseModel, Field from pydantic import BaseModel, Field, field_serializer
from reflector.db.meetings import meetings_controller from reflector.db.meetings import meetings_controller
from reflector.db.migrate_user import migrate_user from reflector.db.migrate_user import migrate_user
from reflector.db.rooms import rooms_controller from reflector.db.rooms import rooms_controller
@@ -61,6 +61,13 @@ class GetTranscriptMinimal(BaseModel):
target_language: str | None target_language: str | None
reviewed: bool reviewed: bool
meeting_id: str | None meeting_id: str | None
@field_serializer("created_at", when_used="json")
def serialize_datetime(self, dt: datetime) -> str:
if dt.tzinfo is None:
dt = dt.replace(tzinfo=timezone.utc)
return dt.isoformat()
source_kind: SourceKind source_kind: SourceKind
room_id: str | None = None room_id: str | None = None
room_name: str | None = None room_name: str | None = None

View File

@@ -46,7 +46,7 @@ import useSessionUser from "../../lib/useSessionUser";
import NextLink from "next/link"; import NextLink from "next/link";
import { Room, GetTranscriptMinimal } from "../../api"; import { Room, GetTranscriptMinimal } from "../../api";
import Pagination from "./pagination"; import Pagination from "./pagination";
import { formatTimeMs } from "../../lib/time"; import { formatTimeMs, formatLocalDate } from "../../lib/time";
import useApi from "../../lib/useApi"; import useApi from "../../lib/useApi";
import { useError } from "../../(errors)/errorContext"; import { useError } from "../../(errors)/errorContext";
import { SourceKind } from "../../api"; import { SourceKind } from "../../api";
@@ -381,15 +381,7 @@ export default function TranscriptBrowser() {
? item.room_name ? item.room_name
: item.source_kind} : item.source_kind}
</Td> </Td>
<Td> <Td>{formatLocalDate(item.created_at)}</Td>
{new Date(item.created_at).toLocaleString("en-US", {
year: "numeric",
month: "long",
day: "numeric",
hour: "numeric",
minute: "numeric",
})}
</Td>
<Td>{formatTimeMs(item.duration)}</Td> <Td>{formatTimeMs(item.duration)}</Td>
<Td> <Td>
<Menu closeOnSelect={true}> <Menu closeOnSelect={true}>
@@ -466,9 +458,7 @@ export default function TranscriptBrowser() {
? item.room_name ? item.room_name
: item.source_kind} : item.source_kind}
</Text> </Text>
<Text> <Text>Date: {formatLocalDate(item.created_at)}</Text>
Date: {new Date(item.created_at).toLocaleString()}
</Text>
<Text>Duration: {formatTimeMs(item.duration)}</Text> <Text>Duration: {formatTimeMs(item.duration)}</Text>
</Box> </Box>
<Menu> <Menu>

View File

@@ -28,3 +28,29 @@ export const formatTimeDifference = (seconds: number): string => {
return timeString; return timeString;
}; };
export const formatRelativeTime = (dateString: string): string => {
const now = new Date();
const past = new Date(dateString);
const diffMs = now.getTime() - past.getTime();
const diffSeconds = Math.floor(diffMs / 1000);
const diffMinutes = Math.floor(diffSeconds / 60);
const diffHours = Math.floor(diffMinutes / 60);
const diffDays = Math.floor(diffHours / 24);
if (diffSeconds < 60) return `${diffSeconds}s ago`;
if (diffMinutes < 60) return `${diffMinutes}m ago`;
if (diffHours < 24) return `${diffHours}h ago`;
return `${diffDays}d ago`;
};
export const formatLocalDate = (dateString: string): string => {
return new Date(dateString).toLocaleString(navigator.language || "en-US", {
year: "numeric",
month: "long",
day: "numeric",
hour: "numeric",
minute: "numeric",
});
};