Merge pull request #388 from Monadical-SAS/fix-transcript-link

Fix transcript link to meeting
This commit is contained in:
2024-08-22 22:06:04 +02:00
committed by GitHub
15 changed files with 95 additions and 450 deletions

View File

@@ -11,7 +11,6 @@ from reflector.events import subscribers_shutdown, subscribers_startup
from reflector.logger import logger
from reflector.metrics import metrics_init
from reflector.settings import settings
from reflector.views.meetings import router as meetings_router
from reflector.views.rooms import router as rooms_router
from reflector.views.rtc_offer import router as rtc_offer_router
from reflector.views.transcripts import router as transcripts_router
@@ -70,7 +69,6 @@ metrics_init(app, instrumentator)
# register views
app.include_router(rtc_offer_router)
app.include_router(meetings_router, prefix="/v1")
app.include_router(rooms_router, prefix="/v1")
app.include_router(transcripts_router, prefix="/v1")
app.include_router(transcripts_audio_router, prefix="/v1")

View File

@@ -7,7 +7,6 @@ database = databases.Database(settings.DATABASE_URL)
metadata = sqlalchemy.MetaData()
# import models
import reflector.db.meetings # noqa
import reflector.db.rooms # noqa
import reflector.db.transcripts # noqa

View File

@@ -137,5 +137,9 @@ class Settings(BaseSettings):
WHEREBY_API_KEY: str | None = None
AWS_WHEREBY_S3_BUCKET: str | None = None
AWS_WHEREBY_ACCESS_KEY_ID: str | None = None
AWS_WHEREBY_ACCESS_KEY_SECRET: str | None = None
settings = Settings()

View File

@@ -1,56 +0,0 @@
from datetime import datetime, timedelta, timezone
from typing import Annotated, Optional
import reflector.auth as auth
from fastapi import APIRouter, Depends
from pydantic import BaseModel
from reflector.db.meetings import meetings_controller
from reflector.whereby import create_meeting
router = APIRouter()
class GetMeeting(BaseModel):
id: str
room_name: str
room_url: str
host_room_url: str
viewer_room_url: str
start_date: datetime
end_date: datetime
@router.get("/meetings/{meeting_id}", response_model=GetMeeting)
async def meeting_get(
meeting_id: str,
user: Annotated[Optional[auth.UserInfo], Depends(auth.current_user_optional)],
):
user_id = user["sub"] if user else None
return await meetings_controller.get_by_id_for_http(meeting_id, user_id=user_id)
@router.post("/meetings/", response_model=GetMeeting)
async def meeting_create(
room_id: str,
user: Annotated[Optional[auth.UserInfo], Depends(auth.current_user_optional)],
):
user_id = user["sub"] if user else None
meeting = await meetings_controller.get_latest(room_id)
if meeting is None:
start_date = datetime.now(timezone.utc)
end_date = start_date + timedelta(hours=1)
meeting = await create_meeting("", start_date=start_date, end_date=end_date)
meeting = await meetings_controller.add(
id=meeting["meetingId"],
room_name=meeting["roomName"],
room_url=meeting["roomUrl"],
host_room_url=meeting["hostRoomUrl"],
viewer_room_url=meeting["viewerRoomUrl"],
start_date=datetime.fromisoformat(meeting["startDate"]),
end_date=datetime.fromisoformat(meeting["endDate"]),
user_id=user_id,
room_id=room_id,
)
return await meetings_controller.get_by_id_for_http(meeting.id, user_id=user_id)

View File

@@ -11,7 +11,6 @@ from reflector.db import database
from reflector.db.meetings import meetings_controller
from reflector.db.rooms import rooms_controller
from reflector.settings import settings
from reflector.views.meetings import GetMeeting
from reflector.whereby import create_meeting
router = APIRouter()
@@ -24,6 +23,16 @@ class Room(BaseModel):
created_at: datetime
class Meeting(BaseModel):
id: str
room_name: str
room_url: str
host_room_url: str
viewer_room_url: str
start_date: datetime
end_date: datetime
class CreateRoom(BaseModel):
name: str
@@ -76,7 +85,7 @@ async def rooms_delete(
return DeletionStatus(status="ok")
@router.post("/rooms/{room_name}/meeting", response_model=GetMeeting)
@router.post("/rooms/{room_name}/meeting", response_model=Meeting)
async def rooms_create_meeting(
room_name: str,
user: Annotated[Optional[auth.UserInfo], Depends(auth.current_user_optional)],

View File

@@ -1,4 +1,4 @@
from datetime import datetime, timedelta, timezone
from datetime import datetime, timedelta
from typing import Annotated, Literal, Optional
import reflector.auth as auth
@@ -7,7 +7,6 @@ from fastapi_pagination import Page
from fastapi_pagination.ext.databases import paginate
from jose import jwt
from pydantic import BaseModel, Field
from reflector.db.meetings import meetings_controller
from reflector.db.transcripts import (
TranscriptParticipant,
TranscriptTopic,
@@ -16,7 +15,6 @@ from reflector.db.transcripts import (
from reflector.processors.types import Transcript as ProcessorTranscript
from reflector.processors.types import Word
from reflector.settings import settings
from reflector.whereby import create_meeting
router = APIRouter()
@@ -111,37 +109,6 @@ async def transcripts_create(
)
@router.post("/transcripts/meeting", response_model=GetTranscript)
async def transcripts_create_meeting(
info: CreateTranscript,
user: Annotated[Optional[auth.UserInfo], Depends(auth.current_user_optional)],
):
user_id = user["sub"] if user else None
start_date = datetime.now(timezone.utc)
end_date = start_date + timedelta(hours=1)
meeting = await create_meeting("", start_date=start_date, end_date=end_date)
meeting = await meetings_controller.create(
id=meeting["meetingId"],
room_name=meeting["roomName"],
room_url=meeting["roomUrl"],
host_room_url=meeting["hostRoomUrl"],
viewer_room_url=meeting["viewerRoomUrl"],
start_date=datetime.fromisoformat(meeting["startDate"]),
end_date=datetime.fromisoformat(meeting["endDate"]),
user_id=user_id,
)
return await transcripts_controller.add(
"",
source_language=info.source_language,
target_language=info.target_language,
user_id=user_id,
meeting_id=meeting.id,
share_mode="public",
)
# ==============================================================
# Single transcript
# ==============================================================

View File

@@ -19,6 +19,17 @@ async def create_meeting(
"roomMode": "normal",
"startDate": start_date.isoformat(),
"endDate": end_date.isoformat(),
"recording": {
"type": "cloud",
"destination": {
"provider": "s3",
"bucket": settings.AWS_WHEREBY_S3_BUCKET,
"accessKeyId": settings.AWS_WHEREBY_ACCESS_KEY_ID,
"accessKeySecret": settings.AWS_WHEREBY_ACCESS_KEY_SECRET,
"fileFormat": "mp4",
},
"startTrigger": "automatic-2nd-participant",
},
}
async with httpx.AsyncClient() as client:

View File

@@ -64,16 +64,14 @@ async def process_recording(bucket_name: str, object_key: str):
# extract a guid from the object key
room_name = f"/{object_key[:36]}"
meeting = await meetings_controller.get_by_room_name(room_name)
transcript = await transcripts_controller.get_by_meeting_id(meeting.id)
if transcript is None:
transcript = await transcripts_controller.add(
"",
source_language="en",
target_language="en",
user_id=meeting.user_id,
meeting_id=meeting.id,
share_mode="public",
)
transcript = await transcripts_controller.add(
"",
source_language="en",
target_language="en",
user_id=meeting.user_id,
meeting_id=meeting.id,
share_mode="public",
)
_, extension = os.path.splitext(object_key)
upload_filename = transcript.data_path / f"upload{extension}"

View File

@@ -1,34 +0,0 @@
"use client";
import "@whereby.com/browser-sdk/embed";
import { useCallback, useEffect, useRef } from "react";
import useTranscript from "../../useTranscript";
import useMeeting from "../../useMeeting";
export type TranscriptDetails = {
params: {
transcriptId: string;
};
};
export default function TranscriptMeeting(details: TranscriptDetails) {
const wherebyRef = useRef<HTMLElement>(null);
const transcript = useTranscript(details.params.transcriptId);
const meeting = useMeeting(transcript?.response?.meeting_id);
const roomUrl = meeting?.response?.host_room_url
? meeting?.response?.host_room_url
: meeting?.response?.room_url;
return (
<>
{roomUrl && (
<whereby-embed
ref={wherebyRef}
room={roomUrl}
style={{ width: "100%", height: "98%" }}
/>
)}
</>
);
}

View File

@@ -9,7 +9,6 @@ type UseCreateTranscript = {
loading: boolean;
error: Error | null;
create: (transcriptCreationDetails: CreateTranscript) => void;
createMeeting: (transcriptCreationDetails: CreateTranscript) => void;
};
const useCreateTranscript = (): UseCreateTranscript => {
@@ -40,28 +39,7 @@ const useCreateTranscript = (): UseCreateTranscript => {
});
};
const createMeeting = (transcriptCreationDetails: CreateTranscript) => {
if (loading || !api) return;
setLoading(true);
api
.v1TranscriptsCreateMeeting({ requestBody: transcriptCreationDetails })
.then((transcript) => {
setTranscript(transcript);
setLoading(false);
})
.catch((err) => {
setError(
err,
"There was an issue creating a transcript, please try again.",
);
setErrorState(err);
setLoading(false);
});
};
return { transcript, loading, error, create, createMeeting };
return { transcript, loading, error, create };
};
export default useCreateTranscript;

View File

@@ -32,7 +32,6 @@ const TranscriptCreate = () => {
const [loadingRecord, setLoadingRecord] = useState(false);
const [loadingUpload, setLoadingUpload] = useState(false);
const [loadingMeeting, setLoadingMeeting] = useState(false);
const send = () => {
if (loadingRecord || createTranscript.loading || permissionDenied) return;
@@ -46,16 +45,9 @@ const TranscriptCreate = () => {
createTranscript.create({ name, target_language: targetLanguage });
};
const startMeeting = () => {
if (loadingMeeting || createTranscript.loading || permissionDenied) return;
setLoadingMeeting(true);
createTranscript.createMeeting({ name, target_language: targetLanguage });
};
useEffect(() => {
let action = "record";
if (loadingUpload) action = "upload";
if (loadingMeeting) action = "meeting";
createTranscript.transcript &&
router.push(`/transcripts/${createTranscript.transcript.id}/${action}`);
@@ -162,23 +154,6 @@ const TranscriptCreate = () => {
>
{loadingUpload ? "Loading..." : "Upload File"}
</Button>
{requireLogin && (
<>
<Text align="center" m="2">
OR
</Text>
<Button
colorScheme="blue"
onClick={startMeeting}
isDisabled={
loadingRecord || loadingUpload || loadingMeeting
}
>
{loadingUpload ? "Loading..." : "Start Whereby Meeting"}
</Button>
</>
)}
</div>
)}
</section>

View File

@@ -1,70 +0,0 @@
import { useEffect, useState } from "react";
import { useError } from "../../(errors)/errorContext";
import { GetMeeting } from "../../api";
import { shouldShowError } from "../../lib/errorUtils";
import useApi from "../../lib/useApi";
type ErrorMeeting = {
error: Error;
loading: false;
response: null;
reload: () => void;
};
type LoadingMeeting = {
response: null;
loading: true;
error: false;
reload: () => void;
};
type SuccessMeeting = {
response: GetMeeting;
loading: false;
error: null;
reload: () => void;
};
const useMeeting = (
id: string | null | undefined,
): ErrorMeeting | LoadingMeeting | SuccessMeeting => {
const [response, setResponse] = useState<GetMeeting | null>(null);
const [loading, setLoading] = useState<boolean>(true);
const [error, setErrorState] = useState<Error | null>(null);
const [reload, setReload] = useState(0);
const { setError } = useError();
const api = useApi();
const reloadHandler = () => setReload((prev) => prev + 1);
useEffect(() => {
if (!id || !api) return;
if (!response) {
setLoading(true);
}
api
.v1MeetingGet({ meetingId: id })
.then((result) => {
setResponse(result);
setLoading(false);
console.debug("Meeting Loaded:", result);
})
.catch((error) => {
const shouldShowHuman = shouldShowError(error);
if (shouldShowHuman) {
setError(error, "There was an error loading the meeting");
} else {
setError(error);
}
setErrorState(error);
});
}, [id, !api, reload]);
return { response, loading, error, reload: reloadHandler } as
| ErrorMeeting
| LoadingMeeting
| SuccessMeeting;
};
export default useMeeting;

View File

@@ -99,52 +99,6 @@ export const $DeletionStatus = {
title: "DeletionStatus",
} as const;
export const $GetMeeting = {
properties: {
id: {
type: "string",
title: "Id",
},
room_name: {
type: "string",
title: "Room Name",
},
room_url: {
type: "string",
title: "Room Url",
},
host_room_url: {
type: "string",
title: "Host Room Url",
},
viewer_room_url: {
type: "string",
title: "Viewer Room Url",
},
start_date: {
type: "string",
format: "date-time",
title: "Start Date",
},
end_date: {
type: "string",
format: "date-time",
title: "End Date",
},
},
type: "object",
required: [
"id",
"room_name",
"room_url",
"host_room_url",
"viewer_room_url",
"start_date",
"end_date",
],
title: "GetMeeting",
} as const;
export const $GetTranscript = {
properties: {
id: {
@@ -485,6 +439,52 @@ export const $HTTPValidationError = {
title: "HTTPValidationError",
} as const;
export const $Meeting = {
properties: {
id: {
type: "string",
title: "Id",
},
room_name: {
type: "string",
title: "Room Name",
},
room_url: {
type: "string",
title: "Room Url",
},
host_room_url: {
type: "string",
title: "Host Room Url",
},
viewer_room_url: {
type: "string",
title: "Viewer Room Url",
},
start_date: {
type: "string",
format: "date-time",
title: "Start Date",
},
end_date: {
type: "string",
format: "date-time",
title: "End Date",
},
},
type: "object",
required: [
"id",
"room_name",
"room_url",
"host_room_url",
"viewer_room_url",
"start_date",
"end_date",
],
title: "Meeting",
} as const;
export const $Page_GetTranscript_ = {
properties: {
items: {

View File

@@ -4,10 +4,6 @@ import type { CancelablePromise } from "./core/CancelablePromise";
import type { BaseHttpRequest } from "./core/BaseHttpRequest";
import type {
MetricsResponse,
V1MeetingGetData,
V1MeetingGetResponse,
V1MeetingCreateData,
V1MeetingCreateResponse,
V1RoomsListData,
V1RoomsListResponse,
V1RoomsCreateData,
@@ -20,8 +16,6 @@ import type {
V1TranscriptsListResponse,
V1TranscriptsCreateData,
V1TranscriptsCreateResponse,
V1TranscriptsCreateMeetingData,
V1TranscriptsCreateMeetingResponse,
V1TranscriptGetData,
V1TranscriptGetResponse,
V1TranscriptUpdateData,
@@ -81,50 +75,6 @@ export class DefaultService {
});
}
/**
* Meeting Get
* @param data The data for the request.
* @param data.meetingId
* @returns GetMeeting Successful Response
* @throws ApiError
*/
public v1MeetingGet(
data: V1MeetingGetData,
): CancelablePromise<V1MeetingGetResponse> {
return this.httpRequest.request({
method: "GET",
url: "/v1/meetings/{meeting_id}",
path: {
meeting_id: data.meetingId,
},
errors: {
422: "Validation Error",
},
});
}
/**
* Meeting Create
* @param data The data for the request.
* @param data.roomId
* @returns GetMeeting Successful Response
* @throws ApiError
*/
public v1MeetingCreate(
data: V1MeetingCreateData,
): CancelablePromise<V1MeetingCreateResponse> {
return this.httpRequest.request({
method: "POST",
url: "/v1/meetings/",
query: {
room_id: data.roomId,
},
errors: {
422: "Validation Error",
},
});
}
/**
* Rooms List
* @param data The data for the request.
@@ -196,7 +146,7 @@ export class DefaultService {
* Rooms Create Meeting
* @param data The data for the request.
* @param data.roomName
* @returns GetMeeting Successful Response
* @returns Meeting Successful Response
* @throws ApiError
*/
public v1RoomsCreateMeeting(
@@ -259,27 +209,6 @@ export class DefaultService {
});
}
/**
* Transcripts Create Meeting
* @param data The data for the request.
* @param data.requestBody
* @returns GetTranscript Successful Response
* @throws ApiError
*/
public v1TranscriptsCreateMeeting(
data: V1TranscriptsCreateMeetingData,
): CancelablePromise<V1TranscriptsCreateMeetingResponse> {
return this.httpRequest.request({
method: "POST",
url: "/v1/transcripts/meeting",
body: data.requestBody,
mediaType: "application/json",
errors: {
422: "Validation Error",
},
});
}
/**
* Transcript Get
* @param data The data for the request.

View File

@@ -28,16 +28,6 @@ export type DeletionStatus = {
status: string;
};
export type GetMeeting = {
id: string;
room_name: string;
room_url: string;
host_room_url: string;
viewer_room_url: string;
start_date: string;
end_date: string;
};
export type GetTranscript = {
id: string;
user_id: string | null;
@@ -99,6 +89,16 @@ export type HTTPValidationError = {
detail?: Array<ValidationError>;
};
export type Meeting = {
id: string;
room_name: string;
room_url: string;
host_room_url: string;
viewer_room_url: string;
start_date: string;
end_date: string;
};
export type Page_GetTranscript_ = {
items: Array<GetTranscript>;
total: number;
@@ -197,18 +197,6 @@ export type Word = {
export type MetricsResponse = unknown;
export type V1MeetingGetData = {
meetingId: string;
};
export type V1MeetingGetResponse = GetMeeting;
export type V1MeetingCreateData = {
roomId: string;
};
export type V1MeetingCreateResponse = GetMeeting;
export type V1RoomsListData = {
/**
* Page number
@@ -238,7 +226,7 @@ export type V1RoomsCreateMeetingData = {
roomName: string;
};
export type V1RoomsCreateMeetingResponse = GetMeeting;
export type V1RoomsCreateMeetingResponse = Meeting;
export type V1TranscriptsListData = {
/**
@@ -259,12 +247,6 @@ export type V1TranscriptsCreateData = {
export type V1TranscriptsCreateResponse = GetTranscript;
export type V1TranscriptsCreateMeetingData = {
requestBody: CreateTranscript;
};
export type V1TranscriptsCreateMeetingResponse = GetTranscript;
export type V1TranscriptGetData = {
transcriptId: string;
};
@@ -415,36 +397,6 @@ export type $OpenApiTs = {
};
};
};
"/v1/meetings/{meeting_id}": {
get: {
req: V1MeetingGetData;
res: {
/**
* Successful Response
*/
200: GetMeeting;
/**
* Validation Error
*/
422: HTTPValidationError;
};
};
};
"/v1/meetings/": {
post: {
req: V1MeetingCreateData;
res: {
/**
* Successful Response
*/
200: GetMeeting;
/**
* Validation Error
*/
422: HTTPValidationError;
};
};
};
"/v1/rooms": {
get: {
req: V1RoomsListData;
@@ -495,7 +447,7 @@ export type $OpenApiTs = {
/**
* Successful Response
*/
200: GetMeeting;
200: Meeting;
/**
* Validation Error
*/
@@ -531,21 +483,6 @@ export type $OpenApiTs = {
};
};
};
"/v1/transcripts/meeting": {
post: {
req: V1TranscriptsCreateMeetingData;
res: {
/**
* Successful Response
*/
200: GetTranscript;
/**
* Validation Error
*/
422: HTTPValidationError;
};
};
};
"/v1/transcripts/{transcript_id}": {
get: {
req: V1TranscriptGetData;