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.logger import logger
from reflector.metrics import metrics_init from reflector.metrics import metrics_init
from reflector.settings import settings 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.rooms import router as rooms_router
from reflector.views.rtc_offer import router as rtc_offer_router from reflector.views.rtc_offer import router as rtc_offer_router
from reflector.views.transcripts import router as transcripts_router from reflector.views.transcripts import router as transcripts_router
@@ -70,7 +69,6 @@ metrics_init(app, instrumentator)
# register views # register views
app.include_router(rtc_offer_router) app.include_router(rtc_offer_router)
app.include_router(meetings_router, prefix="/v1")
app.include_router(rooms_router, prefix="/v1") app.include_router(rooms_router, prefix="/v1")
app.include_router(transcripts_router, prefix="/v1") app.include_router(transcripts_router, prefix="/v1")
app.include_router(transcripts_audio_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() metadata = sqlalchemy.MetaData()
# import models # import models
import reflector.db.meetings # noqa
import reflector.db.rooms # noqa import reflector.db.rooms # noqa
import reflector.db.transcripts # noqa import reflector.db.transcripts # noqa

View File

@@ -137,5 +137,9 @@ class Settings(BaseSettings):
WHEREBY_API_KEY: str | None = None 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() 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.meetings import meetings_controller
from reflector.db.rooms import rooms_controller from reflector.db.rooms import rooms_controller
from reflector.settings import settings from reflector.settings import settings
from reflector.views.meetings import GetMeeting
from reflector.whereby import create_meeting from reflector.whereby import create_meeting
router = APIRouter() router = APIRouter()
@@ -24,6 +23,16 @@ class Room(BaseModel):
created_at: datetime 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): class CreateRoom(BaseModel):
name: str name: str
@@ -76,7 +85,7 @@ async def rooms_delete(
return DeletionStatus(status="ok") 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( async def rooms_create_meeting(
room_name: str, room_name: str,
user: Annotated[Optional[auth.UserInfo], Depends(auth.current_user_optional)], 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 from typing import Annotated, Literal, Optional
import reflector.auth as auth import reflector.auth as auth
@@ -7,7 +7,6 @@ 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
from reflector.db.meetings import meetings_controller
from reflector.db.transcripts import ( from reflector.db.transcripts import (
TranscriptParticipant, TranscriptParticipant,
TranscriptTopic, TranscriptTopic,
@@ -16,7 +15,6 @@ from reflector.db.transcripts import (
from reflector.processors.types import Transcript as ProcessorTranscript from reflector.processors.types import Transcript as ProcessorTranscript
from reflector.processors.types import Word from reflector.processors.types import Word
from reflector.settings import settings from reflector.settings import settings
from reflector.whereby import create_meeting
router = APIRouter() 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 # Single transcript
# ============================================================== # ==============================================================

View File

@@ -19,6 +19,17 @@ async def create_meeting(
"roomMode": "normal", "roomMode": "normal",
"startDate": start_date.isoformat(), "startDate": start_date.isoformat(),
"endDate": end_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: 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 # extract a guid from the object key
room_name = f"/{object_key[:36]}" room_name = f"/{object_key[:36]}"
meeting = await meetings_controller.get_by_room_name(room_name) meeting = await meetings_controller.get_by_room_name(room_name)
transcript = await transcripts_controller.get_by_meeting_id(meeting.id) transcript = await transcripts_controller.add(
if transcript is None: "",
transcript = await transcripts_controller.add( source_language="en",
"", target_language="en",
source_language="en", user_id=meeting.user_id,
target_language="en", meeting_id=meeting.id,
user_id=meeting.user_id, share_mode="public",
meeting_id=meeting.id, )
share_mode="public",
)
_, extension = os.path.splitext(object_key) _, extension = os.path.splitext(object_key)
upload_filename = transcript.data_path / f"upload{extension}" 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; loading: boolean;
error: Error | null; error: Error | null;
create: (transcriptCreationDetails: CreateTranscript) => void; create: (transcriptCreationDetails: CreateTranscript) => void;
createMeeting: (transcriptCreationDetails: CreateTranscript) => void;
}; };
const useCreateTranscript = (): UseCreateTranscript => { const useCreateTranscript = (): UseCreateTranscript => {
@@ -40,28 +39,7 @@ const useCreateTranscript = (): UseCreateTranscript => {
}); });
}; };
const createMeeting = (transcriptCreationDetails: CreateTranscript) => { return { transcript, loading, error, create };
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 };
}; };
export default useCreateTranscript; export default useCreateTranscript;

View File

@@ -32,7 +32,6 @@ const TranscriptCreate = () => {
const [loadingRecord, setLoadingRecord] = useState(false); const [loadingRecord, setLoadingRecord] = useState(false);
const [loadingUpload, setLoadingUpload] = useState(false); const [loadingUpload, setLoadingUpload] = useState(false);
const [loadingMeeting, setLoadingMeeting] = useState(false);
const send = () => { const send = () => {
if (loadingRecord || createTranscript.loading || permissionDenied) return; if (loadingRecord || createTranscript.loading || permissionDenied) return;
@@ -46,16 +45,9 @@ const TranscriptCreate = () => {
createTranscript.create({ name, target_language: targetLanguage }); createTranscript.create({ name, target_language: targetLanguage });
}; };
const startMeeting = () => {
if (loadingMeeting || createTranscript.loading || permissionDenied) return;
setLoadingMeeting(true);
createTranscript.createMeeting({ name, target_language: targetLanguage });
};
useEffect(() => { useEffect(() => {
let action = "record"; let action = "record";
if (loadingUpload) action = "upload"; if (loadingUpload) action = "upload";
if (loadingMeeting) action = "meeting";
createTranscript.transcript && createTranscript.transcript &&
router.push(`/transcripts/${createTranscript.transcript.id}/${action}`); router.push(`/transcripts/${createTranscript.transcript.id}/${action}`);
@@ -162,23 +154,6 @@ const TranscriptCreate = () => {
> >
{loadingUpload ? "Loading..." : "Upload File"} {loadingUpload ? "Loading..." : "Upload File"}
</Button> </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> </div>
)} )}
</section> </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", title: "DeletionStatus",
} as const; } 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 = { export const $GetTranscript = {
properties: { properties: {
id: { id: {
@@ -485,6 +439,52 @@ export const $HTTPValidationError = {
title: "HTTPValidationError", title: "HTTPValidationError",
} as const; } 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_ = { export const $Page_GetTranscript_ = {
properties: { properties: {
items: { items: {

View File

@@ -4,10 +4,6 @@ import type { CancelablePromise } from "./core/CancelablePromise";
import type { BaseHttpRequest } from "./core/BaseHttpRequest"; import type { BaseHttpRequest } from "./core/BaseHttpRequest";
import type { import type {
MetricsResponse, MetricsResponse,
V1MeetingGetData,
V1MeetingGetResponse,
V1MeetingCreateData,
V1MeetingCreateResponse,
V1RoomsListData, V1RoomsListData,
V1RoomsListResponse, V1RoomsListResponse,
V1RoomsCreateData, V1RoomsCreateData,
@@ -20,8 +16,6 @@ import type {
V1TranscriptsListResponse, V1TranscriptsListResponse,
V1TranscriptsCreateData, V1TranscriptsCreateData,
V1TranscriptsCreateResponse, V1TranscriptsCreateResponse,
V1TranscriptsCreateMeetingData,
V1TranscriptsCreateMeetingResponse,
V1TranscriptGetData, V1TranscriptGetData,
V1TranscriptGetResponse, V1TranscriptGetResponse,
V1TranscriptUpdateData, 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 * Rooms List
* @param data The data for the request. * @param data The data for the request.
@@ -196,7 +146,7 @@ export class DefaultService {
* Rooms Create Meeting * Rooms Create Meeting
* @param data The data for the request. * @param data The data for the request.
* @param data.roomName * @param data.roomName
* @returns GetMeeting Successful Response * @returns Meeting Successful Response
* @throws ApiError * @throws ApiError
*/ */
public v1RoomsCreateMeeting( 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 * Transcript Get
* @param data The data for the request. * @param data The data for the request.

View File

@@ -28,16 +28,6 @@ export type DeletionStatus = {
status: string; 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 = { export type GetTranscript = {
id: string; id: string;
user_id: string | null; user_id: string | null;
@@ -99,6 +89,16 @@ export type HTTPValidationError = {
detail?: Array<ValidationError>; 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_ = { export type Page_GetTranscript_ = {
items: Array<GetTranscript>; items: Array<GetTranscript>;
total: number; total: number;
@@ -197,18 +197,6 @@ export type Word = {
export type MetricsResponse = unknown; 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 = { export type V1RoomsListData = {
/** /**
* Page number * Page number
@@ -238,7 +226,7 @@ export type V1RoomsCreateMeetingData = {
roomName: string; roomName: string;
}; };
export type V1RoomsCreateMeetingResponse = GetMeeting; export type V1RoomsCreateMeetingResponse = Meeting;
export type V1TranscriptsListData = { export type V1TranscriptsListData = {
/** /**
@@ -259,12 +247,6 @@ export type V1TranscriptsCreateData = {
export type V1TranscriptsCreateResponse = GetTranscript; export type V1TranscriptsCreateResponse = GetTranscript;
export type V1TranscriptsCreateMeetingData = {
requestBody: CreateTranscript;
};
export type V1TranscriptsCreateMeetingResponse = GetTranscript;
export type V1TranscriptGetData = { export type V1TranscriptGetData = {
transcriptId: string; 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": { "/v1/rooms": {
get: { get: {
req: V1RoomsListData; req: V1RoomsListData;
@@ -495,7 +447,7 @@ export type $OpenApiTs = {
/** /**
* Successful Response * Successful Response
*/ */
200: GetMeeting; 200: Meeting;
/** /**
* Validation Error * 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}": { "/v1/transcripts/{transcript_id}": {
get: { get: {
req: V1TranscriptGetData; req: V1TranscriptGetData;