diff --git a/server/migrations/versions/b9348748bbbc_reviewed.py b/server/migrations/versions/b9348748bbbc_reviewed.py new file mode 100644 index 00000000..f9f23c3e --- /dev/null +++ b/server/migrations/versions/b9348748bbbc_reviewed.py @@ -0,0 +1,29 @@ +"""reviewed + +Revision ID: b9348748bbbc +Revises: 125031f7cb78 +Create Date: 2023-12-13 15:37:51.303970 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + +# revision identifiers, used by Alembic. +revision: str = 'b9348748bbbc' +down_revision: Union[str, None] = '125031f7cb78' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('transcript', sa.Column('reviewed', sa.Boolean(), server_default=sa.text('0'), nullable=False)) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('transcript', 'reviewed') + # ### end Alembic commands ### diff --git a/server/reflector/app.py b/server/reflector/app.py index 9235a578..166899d9 100644 --- a/server/reflector/app.py +++ b/server/reflector/app.py @@ -17,6 +17,7 @@ from reflector.views.transcripts_audio import router as transcripts_audio_router from reflector.views.transcripts_participants import ( router as transcripts_participants_router, ) +from reflector.views.transcripts_speaker import router as transcripts_speaker_router from reflector.views.transcripts_upload import router as transcripts_upload_router from reflector.views.transcripts_webrtc import router as transcripts_webrtc_router from reflector.views.transcripts_websocket import router as transcripts_websocket_router @@ -69,6 +70,7 @@ app.include_router(rtc_offer_router) app.include_router(transcripts_router, prefix="/v1") app.include_router(transcripts_audio_router, prefix="/v1") app.include_router(transcripts_participants_router, prefix="/v1") +app.include_router(transcripts_speaker_router, prefix="/v1") app.include_router(transcripts_upload_router, prefix="/v1") app.include_router(transcripts_websocket_router, prefix="/v1") app.include_router(transcripts_webrtc_router, prefix="/v1") diff --git a/server/reflector/db/transcripts.py b/server/reflector/db/transcripts.py index 970393d5..423f8af6 100644 --- a/server/reflector/db/transcripts.py +++ b/server/reflector/db/transcripts.py @@ -12,6 +12,7 @@ from reflector.db import database, metadata from reflector.processors.types import Word as ProcessorWord from reflector.settings import settings from reflector.storage import Storage +from sqlalchemy.sql import false transcripts = sqlalchemy.Table( "transcript", @@ -30,6 +31,9 @@ transcripts = sqlalchemy.Table( sqlalchemy.Column("participants", sqlalchemy.JSON), sqlalchemy.Column("source_language", sqlalchemy.String, nullable=True), sqlalchemy.Column("target_language", sqlalchemy.String, nullable=True), + sqlalchemy.Column( + "reviewed", sqlalchemy.Boolean, nullable=False, server_default=false() + ), sqlalchemy.Column( "audio_location", sqlalchemy.String, @@ -138,6 +142,7 @@ class Transcript(BaseModel): target_language: str = "en" share_mode: Literal["private", "semi-private", "public"] = "private" audio_location: str = "local" + reviewed: bool = False def add_event(self, event: str, data: BaseModel) -> TranscriptEvent: ev = TranscriptEvent(event=event, data=data.model_dump()) @@ -248,6 +253,23 @@ class Transcript(BaseModel): url += f"?token={token}" return url + def find_empty_speaker(self) -> int: + """ + Find an empty speaker seat + """ + speakers = set( + word.speaker + for topic in self.topics + for word in topic.words + if word.speaker is not None + ) + i = 0 + while True: + if i not in speakers: + return i + i += 1 + raise Exception("No empty speaker found") + class TranscriptController: async def get_all( @@ -362,7 +384,7 @@ class TranscriptController: await database.execute(query) return transcript - async def update(self, transcript: Transcript, values: dict): + async def update(self, transcript: Transcript, values: dict, mutate=True): """ Update a transcript fields with key/values in values """ @@ -372,8 +394,9 @@ class TranscriptController: .values(**values) ) await database.execute(query) - for key, value in values.items(): - setattr(transcript, key, value) + if mutate: + for key, value in values.items(): + setattr(transcript, key, value) async def remove_by_id( self, @@ -410,7 +433,11 @@ class TranscriptController: Append an event to a transcript """ resp = transcript.add_event(event=event, data=data) - await self.update(transcript, {"events": transcript.events_dump()}) + await self.update( + transcript, + {"events": transcript.events_dump()}, + mutate=False, + ) return resp async def upsert_topic( @@ -422,7 +449,11 @@ class TranscriptController: Append an event to a transcript """ transcript.upsert_topic(topic) - await self.update(transcript, {"topics": transcript.topics_dump()}) + await self.update( + transcript, + {"topics": transcript.topics_dump()}, + mutate=False, + ) async def move_mp3_to_storage(self, transcript: Transcript): """ @@ -450,7 +481,11 @@ class TranscriptController: Add/update a participant to a transcript """ result = transcript.upsert_participant(participant) - await self.update(transcript, {"participants": transcript.participants_dump()}) + await self.update( + transcript, + {"participants": transcript.participants_dump()}, + mutate=False, + ) return result async def delete_participant( @@ -462,7 +497,11 @@ class TranscriptController: Delete a participant from a transcript """ transcript.delete_participant(participant_id) - await self.update(transcript, {"participants": transcript.participants_dump()}) + await self.update( + transcript, + {"participants": transcript.participants_dump()}, + mutate=False, + ) transcripts_controller = TranscriptController() diff --git a/server/reflector/processors/types.py b/server/reflector/processors/types.py index 93e565df..cedb23f9 100644 --- a/server/reflector/processors/types.py +++ b/server/reflector/processors/types.py @@ -57,6 +57,7 @@ class Word(BaseModel): class TranscriptSegment(BaseModel): text: str start: float + end: float speaker: int = 0 @@ -127,6 +128,7 @@ class Transcript(BaseModel): current_segment = TranscriptSegment( text=word.text, start=word.start, + end=word.end, speaker=word.speaker, ) continue @@ -138,6 +140,7 @@ class Transcript(BaseModel): current_segment = TranscriptSegment( text=word.text, start=word.start, + end=word.end, speaker=word.speaker, ) continue @@ -145,6 +148,7 @@ class Transcript(BaseModel): # if the word is the end of a sentence, and we have enough content, # add the word to the current segment and push it current_segment.text += word.text + current_segment.end = word.end have_punc = PUNC_RE.search(word.text) if have_punc and (len(current_segment.text) > MAX_SEGMENT_LENGTH): diff --git a/server/reflector/views/transcripts.py b/server/reflector/views/transcripts.py index 9e62192b..171e04d7 100644 --- a/server/reflector/views/transcripts.py +++ b/server/reflector/views/transcripts.py @@ -13,6 +13,7 @@ from reflector.db.transcripts import ( transcripts_controller, ) from reflector.processors.types import Transcript as ProcessorTranscript +from reflector.processors.types import Word from reflector.settings import settings router = APIRouter() @@ -49,6 +50,7 @@ class GetTranscript(BaseModel): source_language: str | None target_language: str | None participants: list[TranscriptParticipant] | None + reviewed: bool class CreateTranscript(BaseModel): @@ -65,6 +67,7 @@ class UpdateTranscript(BaseModel): long_summary: Optional[str] = Field(None) share_mode: Optional[Literal["public", "semi-private", "private"]] = Field(None) participants: Optional[list[TranscriptParticipant]] = Field(None) + reviewed: Optional[bool] = Field(None) class DeletionStatus(BaseModel): @@ -121,6 +124,7 @@ class GetTranscriptTopic(BaseModel): title: str summary: str timestamp: float + duration: float | None transcript: str segments: list[GetTranscriptSegmentTopic] = [] @@ -130,6 +134,7 @@ class GetTranscriptTopic(BaseModel): # In previous version, words were missing # Just output a segment with speaker 0 text = topic.transcript + duration = None segments = [ GetTranscriptSegmentTopic( text=topic.transcript, @@ -141,6 +146,7 @@ class GetTranscriptTopic(BaseModel): # New versions include words transcript = ProcessorTranscript(words=topic.words) text = transcript.text + duration = transcript.duration segments = [ GetTranscriptSegmentTopic( text=segment.text, @@ -156,9 +162,59 @@ class GetTranscriptTopic(BaseModel): timestamp=topic.timestamp, transcript=text, segments=segments, + duration=duration, ) +class GetTranscriptTopicWithWords(GetTranscriptTopic): + words: list[Word] = [] + + @classmethod + def from_transcript_topic(cls, topic: TranscriptTopic): + instance = super().from_transcript_topic(topic) + if topic.words: + instance.words = topic.words + return instance + + +class SpeakerWords(BaseModel): + speaker: int + words: list[Word] + + +class GetTranscriptTopicWithWordsPerSpeaker(GetTranscriptTopic): + words_per_speaker: list[SpeakerWords] = [] + + @classmethod + def from_transcript_topic(cls, topic: TranscriptTopic): + instance = super().from_transcript_topic(topic) + if topic.words: + words_per_speakers = [] + # group words by speaker + words = [] + for word in topic.words: + if words and words[-1].speaker != word.speaker: + words_per_speakers.append( + SpeakerWords( + speaker=words[-1].speaker, + words=words, + ) + ) + words = [] + words.append(word) + if words: + words_per_speakers.append( + SpeakerWords( + speaker=words[-1].speaker, + words=words, + ) + ) + + instance.words_per_speaker = words_per_speakers + + return instance + + @router.get("/transcripts/{transcript_id}", response_model=GetTranscript) async def transcript_get( transcript_id: str, @@ -215,3 +271,46 @@ async def transcript_get_topics( return [ GetTranscriptTopic.from_transcript_topic(topic) for topic in transcript.topics ] + + +@router.get( + "/transcripts/{transcript_id}/topics/with-words", + response_model=list[GetTranscriptTopicWithWords], +) +async def transcript_get_topics_with_words( + transcript_id: str, + user: Annotated[Optional[auth.UserInfo], Depends(auth.current_user_optional)], +): + user_id = user["sub"] if user else None + transcript = await transcripts_controller.get_by_id_for_http( + transcript_id, user_id=user_id + ) + + # convert to GetTranscriptTopicWithWords + return [ + GetTranscriptTopicWithWords.from_transcript_topic(topic) + for topic in transcript.topics + ] + + +@router.get( + "/transcripts/{transcript_id}/topics/{topic_id}/words-per-speaker", + response_model=GetTranscriptTopicWithWordsPerSpeaker, +) +async def transcript_get_topics_with_words_per_speaker( + transcript_id: str, + topic_id: str, + user: Annotated[Optional[auth.UserInfo], Depends(auth.current_user_optional)], +): + user_id = user["sub"] if user else None + transcript = await transcripts_controller.get_by_id_for_http( + transcript_id, user_id=user_id + ) + + # get the topic from the transcript + topic = next((t for t in transcript.topics if t.id == topic_id), None) + if not topic: + raise HTTPException(status_code=404, detail="Topic not found") + + # convert to GetTranscriptTopicWithWordsPerSpeaker + return GetTranscriptTopicWithWordsPerSpeaker.from_transcript_topic(topic) diff --git a/server/reflector/views/transcripts_participants.py b/server/reflector/views/transcripts_participants.py index 318d6018..fd08405c 100644 --- a/server/reflector/views/transcripts_participants.py +++ b/server/reflector/views/transcripts_participants.py @@ -59,12 +59,13 @@ async def transcript_add_participant( ) # ensure the speaker is unique - for p in transcript.participants: - if p.speaker == participant.speaker: - raise HTTPException( - status_code=400, - detail="Speaker already assigned", - ) + if participant.speaker is not None: + for p in transcript.participants: + if p.speaker == participant.speaker: + raise HTTPException( + status_code=400, + detail="Speaker already assigned", + ) obj = await transcripts_controller.upsert_participant( transcript, TranscriptParticipant(**participant.dict()) diff --git a/server/reflector/views/transcripts_speaker.py b/server/reflector/views/transcripts_speaker.py new file mode 100644 index 00000000..0bddad5e --- /dev/null +++ b/server/reflector/views/transcripts_speaker.py @@ -0,0 +1,170 @@ +""" +Reassign speakers in a transcript +================================= + +""" +from typing import Annotated, Optional + +import reflector.auth as auth +from fastapi import APIRouter, Depends, HTTPException +from pydantic import BaseModel, Field +from reflector.db.transcripts import transcripts_controller + +router = APIRouter() + + +class SpeakerAssignment(BaseModel): + speaker: Optional[int] = Field(None, ge=0) + participant: Optional[str] = Field(None) + timestamp_from: float + timestamp_to: float + + +class SpeakerAssignmentStatus(BaseModel): + status: str + + +class SpeakerMerge(BaseModel): + speaker_from: int + speaker_to: int + + +@router.patch("/transcripts/{transcript_id}/speaker/assign") +async def transcript_assign_speaker( + transcript_id: str, + assignment: SpeakerAssignment, + user: Annotated[Optional[auth.UserInfo], Depends(auth.current_user_optional)], +) -> SpeakerAssignmentStatus: + user_id = user["sub"] if user else None + transcript = await transcripts_controller.get_by_id_for_http( + transcript_id, user_id=user_id + ) + + if not transcript: + raise HTTPException(status_code=404, detail="Transcript not found") + + if assignment.speaker is None and assignment.participant is None: + raise HTTPException( + status_code=400, + detail="Either speaker or participant must be provided", + ) + + if assignment.speaker is not None and assignment.participant is not None: + raise HTTPException( + status_code=400, + detail="Only one of speaker or participant must be provided", + ) + + # if it's a participant, search for it + if assignment.speaker is not None: + speaker = assignment.speaker + + elif assignment.participant is not None: + participant = next( + ( + participant + for participant in transcript.participants + if participant.id == assignment.participant + ), + None, + ) + if not participant: + raise HTTPException( + status_code=404, + detail="Participant not found", + ) + + # if the participant does not have a speaker, create one + if participant.speaker is None: + participant.speaker = transcript.find_empty_speaker() + await transcripts_controller.upsert_participant(transcript, participant) + + speaker = participant.speaker + + # reassign speakers from words in the transcript + ts_from = assignment.timestamp_from + ts_to = assignment.timestamp_to + changed_topics = [] + for topic in transcript.topics: + changed = False + for word in topic.words: + if ts_from <= word.start <= ts_to: + word.speaker = speaker + changed = True + if changed: + changed_topics.append(topic) + + # batch changes + for topic in changed_topics: + transcript.upsert_topic(topic) + await transcripts_controller.update( + transcript, + { + "topics": transcript.topics_dump(), + }, + ) + + return SpeakerAssignmentStatus(status="ok") + + +@router.patch("/transcripts/{transcript_id}/speaker/merge") +async def transcript_merge_speaker( + transcript_id: str, + merge: SpeakerMerge, + user: Annotated[Optional[auth.UserInfo], Depends(auth.current_user_optional)], +) -> SpeakerAssignmentStatus: + user_id = user["sub"] if user else None + transcript = await transcripts_controller.get_by_id_for_http( + transcript_id, user_id=user_id + ) + + if not transcript: + raise HTTPException(status_code=404, detail="Transcript not found") + + # ensure both speaker are not assigned to the 2 differents participants + participant_from = next( + ( + participant + for participant in transcript.participants + if participant.speaker == merge.speaker_from + ), + None, + ) + participant_to = next( + ( + participant + for participant in transcript.participants + if participant.speaker == merge.speaker_to + ), + None, + ) + if participant_from and participant_to: + raise HTTPException( + status_code=400, + detail="Both speakers are assigned to participants", + ) + + # reassign speakers from words in the transcript + speaker_from = merge.speaker_from + speaker_to = merge.speaker_to + changed_topics = [] + for topic in transcript.topics: + changed = False + for word in topic.words: + if word.speaker == speaker_from: + word.speaker = speaker_to + changed = True + if changed: + changed_topics.append(topic) + + # batch changes + for topic in changed_topics: + transcript.upsert_topic(topic) + await transcripts_controller.update( + transcript, + { + "topics": transcript.topics_dump(), + }, + ) + + return SpeakerAssignmentStatus(status="ok") diff --git a/server/tests/conftest.py b/server/tests/conftest.py index 0acff79e..d25801bf 100644 --- a/server/tests/conftest.py +++ b/server/tests/conftest.py @@ -177,3 +177,64 @@ def fake_mp3_upload(): ) as mock_move: mock_move.return_value = True yield + + +@pytest.fixture +async def fake_transcript_with_topics(tmpdir): + from reflector.settings import settings + from reflector.app import app + from reflector.views.transcripts import transcripts_controller + from reflector.db.transcripts import TranscriptTopic + from reflector.processors.types import Word + from pathlib import Path + from httpx import AsyncClient + import shutil + + settings.DATA_DIR = Path(tmpdir) + + # create a transcript + ac = AsyncClient(app=app, base_url="http://test/v1") + response = await ac.post("/transcripts", json={"name": "Test audio download"}) + assert response.status_code == 200 + tid = response.json()["id"] + + transcript = await transcripts_controller.get_by_id(tid) + assert transcript is not None + + await transcripts_controller.update(transcript, {"status": "finished"}) + + # manually copy a file at the expected location + audio_filename = transcript.audio_mp3_filename + path = Path(__file__).parent / "records" / "test_mathieu_hello.mp3" + audio_filename.parent.mkdir(parents=True, exist_ok=True) + shutil.copy(path, audio_filename) + + # create some topics + await transcripts_controller.upsert_topic( + transcript, + TranscriptTopic( + title="Topic 1", + summary="Topic 1 summary", + timestamp=0, + transcript="Hello world", + words=[ + Word(text="Hello", start=0, end=1, speaker=0), + Word(text="world", start=1, end=2, speaker=0), + ], + ), + ) + await transcripts_controller.upsert_topic( + transcript, + TranscriptTopic( + title="Topic 2", + summary="Topic 2 summary", + timestamp=2, + transcript="Hello world", + words=[ + Word(text="Hello", start=2, end=3, speaker=0), + Word(text="world", start=3, end=4, speaker=0), + ], + ), + ) + + yield transcript diff --git a/server/tests/test_transcripts.py b/server/tests/test_transcripts.py index 800d7a5c..c708d57e 100644 --- a/server/tests/test_transcripts.py +++ b/server/tests/test_transcripts.py @@ -196,3 +196,29 @@ async def test_transcript_delete(): response = await ac.get(f"/transcripts/{tid}") assert response.status_code == 404 + + +@pytest.mark.asyncio +async def test_transcript_mark_reviewed(): + from reflector.app import app + + async with AsyncClient(app=app, base_url="http://test/v1") as ac: + response = await ac.post("/transcripts", json={"name": "test"}) + assert response.status_code == 200 + assert response.json()["name"] == "test" + assert response.json()["reviewed"] is False + + tid = response.json()["id"] + + response = await ac.get(f"/transcripts/{tid}") + assert response.status_code == 200 + assert response.json()["name"] == "test" + assert response.json()["reviewed"] is False + + response = await ac.patch(f"/transcripts/{tid}", json={"reviewed": True}) + assert response.status_code == 200 + assert response.json()["reviewed"] is True + + response = await ac.get(f"/transcripts/{tid}") + assert response.status_code == 200 + assert response.json()["reviewed"] is True diff --git a/server/tests/test_transcripts_speaker.py b/server/tests/test_transcripts_speaker.py new file mode 100644 index 00000000..e3e8034a --- /dev/null +++ b/server/tests/test_transcripts_speaker.py @@ -0,0 +1,401 @@ +import pytest +from httpx import AsyncClient + + +@pytest.mark.asyncio +async def test_transcript_reassign_speaker(fake_transcript_with_topics): + from reflector.app import app + + transcript_id = fake_transcript_with_topics.id + + async with AsyncClient(app=app, base_url="http://test/v1") as ac: + # check the transcript exists + response = await ac.get(f"/transcripts/{transcript_id}") + assert response.status_code == 200 + + # check initial topics of the transcript + response = await ac.get(f"/transcripts/{transcript_id}/topics/with-words") + assert response.status_code == 200 + topics = response.json() + assert len(topics) == 2 + + # check through words + assert topics[0]["words"][0]["speaker"] == 0 + assert topics[0]["words"][1]["speaker"] == 0 + assert topics[1]["words"][0]["speaker"] == 0 + assert topics[1]["words"][1]["speaker"] == 0 + # check through segments + assert len(topics[0]["segments"]) == 1 + assert topics[0]["segments"][0]["speaker"] == 0 + assert len(topics[1]["segments"]) == 1 + assert topics[1]["segments"][0]["speaker"] == 0 + + # reassign speaker + response = await ac.patch( + f"/transcripts/{transcript_id}/speaker/assign", + json={ + "speaker": 1, + "timestamp_from": 0, + "timestamp_to": 1, + }, + ) + assert response.status_code == 200 + + # check topics again + response = await ac.get(f"/transcripts/{transcript_id}/topics/with-words") + assert response.status_code == 200 + topics = response.json() + assert len(topics) == 2 + + # check through words + assert topics[0]["words"][0]["speaker"] == 1 + assert topics[0]["words"][1]["speaker"] == 1 + assert topics[1]["words"][0]["speaker"] == 0 + assert topics[1]["words"][1]["speaker"] == 0 + # check segments + assert len(topics[0]["segments"]) == 1 + assert topics[0]["segments"][0]["speaker"] == 1 + assert len(topics[1]["segments"]) == 1 + assert topics[1]["segments"][0]["speaker"] == 0 + + # reassign speaker, middle of 2 topics + response = await ac.patch( + f"/transcripts/{transcript_id}/speaker/assign", + json={ + "speaker": 2, + "timestamp_from": 1, + "timestamp_to": 2.5, + }, + ) + assert response.status_code == 200 + + # check topics again + response = await ac.get(f"/transcripts/{transcript_id}/topics/with-words") + assert response.status_code == 200 + topics = response.json() + assert len(topics) == 2 + + # check through words + assert topics[0]["words"][0]["speaker"] == 1 + assert topics[0]["words"][1]["speaker"] == 2 + assert topics[1]["words"][0]["speaker"] == 2 + assert topics[1]["words"][1]["speaker"] == 0 + # check segments + assert len(topics[0]["segments"]) == 2 + assert topics[0]["segments"][0]["speaker"] == 1 + assert topics[0]["segments"][1]["speaker"] == 2 + assert len(topics[1]["segments"]) == 2 + assert topics[1]["segments"][0]["speaker"] == 2 + assert topics[1]["segments"][1]["speaker"] == 0 + + # reassign speaker, everything + response = await ac.patch( + f"/transcripts/{transcript_id}/speaker/assign", + json={ + "speaker": 4, + "timestamp_from": 0, + "timestamp_to": 100, + }, + ) + assert response.status_code == 200 + + # check topics again + response = await ac.get(f"/transcripts/{transcript_id}/topics/with-words") + assert response.status_code == 200 + topics = response.json() + assert len(topics) == 2 + + # check through words + assert topics[0]["words"][0]["speaker"] == 4 + assert topics[0]["words"][1]["speaker"] == 4 + assert topics[1]["words"][0]["speaker"] == 4 + assert topics[1]["words"][1]["speaker"] == 4 + # check segments + assert len(topics[0]["segments"]) == 1 + assert topics[0]["segments"][0]["speaker"] == 4 + assert len(topics[1]["segments"]) == 1 + assert topics[1]["segments"][0]["speaker"] == 4 + + +@pytest.mark.asyncio +async def test_transcript_merge_speaker(fake_transcript_with_topics): + from reflector.app import app + + transcript_id = fake_transcript_with_topics.id + + async with AsyncClient(app=app, base_url="http://test/v1") as ac: + # check the transcript exists + response = await ac.get(f"/transcripts/{transcript_id}") + assert response.status_code == 200 + + # check initial topics of the transcript + response = await ac.get(f"/transcripts/{transcript_id}/topics/with-words") + assert response.status_code == 200 + topics = response.json() + assert len(topics) == 2 + + # check through words + assert topics[0]["words"][0]["speaker"] == 0 + assert topics[0]["words"][1]["speaker"] == 0 + assert topics[1]["words"][0]["speaker"] == 0 + assert topics[1]["words"][1]["speaker"] == 0 + + # reassign speaker + response = await ac.patch( + f"/transcripts/{transcript_id}/speaker/assign", + json={ + "speaker": 1, + "timestamp_from": 0, + "timestamp_to": 1, + }, + ) + assert response.status_code == 200 + + # check topics again + response = await ac.get(f"/transcripts/{transcript_id}/topics/with-words") + assert response.status_code == 200 + topics = response.json() + assert len(topics) == 2 + + # check through words + assert topics[0]["words"][0]["speaker"] == 1 + assert topics[0]["words"][1]["speaker"] == 1 + assert topics[1]["words"][0]["speaker"] == 0 + assert topics[1]["words"][1]["speaker"] == 0 + + # merge speakers + response = await ac.patch( + f"/transcripts/{transcript_id}/speaker/merge", + json={ + "speaker_from": 1, + "speaker_to": 0, + }, + ) + assert response.status_code == 200 + + # check topics again + response = await ac.get(f"/transcripts/{transcript_id}/topics/with-words") + assert response.status_code == 200 + topics = response.json() + assert len(topics) == 2 + + # check through words + assert topics[0]["words"][0]["speaker"] == 0 + assert topics[0]["words"][1]["speaker"] == 0 + assert topics[1]["words"][0]["speaker"] == 0 + assert topics[1]["words"][1]["speaker"] == 0 + + +@pytest.mark.asyncio +async def test_transcript_reassign_with_participant(fake_transcript_with_topics): + from reflector.app import app + + transcript_id = fake_transcript_with_topics.id + + async with AsyncClient(app=app, base_url="http://test/v1") as ac: + # check the transcript exists + response = await ac.get(f"/transcripts/{transcript_id}") + assert response.status_code == 200 + transcript = response.json() + assert len(transcript["participants"]) == 0 + + # create 2 participants + response = await ac.post( + f"/transcripts/{transcript_id}/participants", + json={ + "name": "Participant 1", + }, + ) + assert response.status_code == 200 + participant1_id = response.json()["id"] + + response = await ac.post( + f"/transcripts/{transcript_id}/participants", + json={ + "name": "Participant 2", + }, + ) + assert response.status_code == 200 + participant2_id = response.json()["id"] + + # check participants speakers + response = await ac.get(f"/transcripts/{transcript_id}/participants") + assert response.status_code == 200 + participants = response.json() + assert len(participants) == 2 + assert participants[0]["name"] == "Participant 1" + assert participants[0]["speaker"] is None + assert participants[1]["name"] == "Participant 2" + assert participants[1]["speaker"] is None + + # check initial topics of the transcript + response = await ac.get(f"/transcripts/{transcript_id}/topics/with-words") + assert response.status_code == 200 + topics = response.json() + assert len(topics) == 2 + + # check through words + assert topics[0]["words"][0]["speaker"] == 0 + assert topics[0]["words"][1]["speaker"] == 0 + assert topics[1]["words"][0]["speaker"] == 0 + assert topics[1]["words"][1]["speaker"] == 0 + # check through segments + assert len(topics[0]["segments"]) == 1 + assert topics[0]["segments"][0]["speaker"] == 0 + assert len(topics[1]["segments"]) == 1 + assert topics[1]["segments"][0]["speaker"] == 0 + + # reassign speaker from a participant + response = await ac.patch( + f"/transcripts/{transcript_id}/speaker/assign", + json={ + "participant": participant1_id, + "timestamp_from": 0, + "timestamp_to": 1, + }, + ) + assert response.status_code == 200 + + # check participants if speaker has been assigned + # first participant should have 1, because it's not used yet. + response = await ac.get(f"/transcripts/{transcript_id}/participants") + assert response.status_code == 200 + participants = response.json() + assert len(participants) == 2 + assert participants[0]["name"] == "Participant 1" + assert participants[0]["speaker"] == 1 + assert participants[1]["name"] == "Participant 2" + assert participants[1]["speaker"] is None + + # check topics again + response = await ac.get(f"/transcripts/{transcript_id}/topics/with-words") + assert response.status_code == 200 + topics = response.json() + assert len(topics) == 2 + + # check through words + assert topics[0]["words"][0]["speaker"] == 1 + assert topics[0]["words"][1]["speaker"] == 1 + assert topics[1]["words"][0]["speaker"] == 0 + assert topics[1]["words"][1]["speaker"] == 0 + # check segments + assert len(topics[0]["segments"]) == 1 + assert topics[0]["segments"][0]["speaker"] == 1 + assert len(topics[1]["segments"]) == 1 + assert topics[1]["segments"][0]["speaker"] == 0 + + # reassign participant, middle of 2 topics + response = await ac.patch( + f"/transcripts/{transcript_id}/speaker/assign", + json={ + "participant": participant2_id, + "timestamp_from": 1, + "timestamp_to": 2.5, + }, + ) + assert response.status_code == 200 + + # check participants if speaker has been assigned + # first participant should have 1, because it's not used yet. + response = await ac.get(f"/transcripts/{transcript_id}/participants") + assert response.status_code == 200 + participants = response.json() + assert len(participants) == 2 + assert participants[0]["name"] == "Participant 1" + assert participants[0]["speaker"] == 1 + assert participants[1]["name"] == "Participant 2" + assert participants[1]["speaker"] == 2 + + # check topics again + response = await ac.get(f"/transcripts/{transcript_id}/topics/with-words") + assert response.status_code == 200 + topics = response.json() + assert len(topics) == 2 + + # check through words + assert topics[0]["words"][0]["speaker"] == 1 + assert topics[0]["words"][1]["speaker"] == 2 + assert topics[1]["words"][0]["speaker"] == 2 + assert topics[1]["words"][1]["speaker"] == 0 + # check segments + assert len(topics[0]["segments"]) == 2 + assert topics[0]["segments"][0]["speaker"] == 1 + assert topics[0]["segments"][1]["speaker"] == 2 + assert len(topics[1]["segments"]) == 2 + assert topics[1]["segments"][0]["speaker"] == 2 + assert topics[1]["segments"][1]["speaker"] == 0 + + # reassign speaker, everything + response = await ac.patch( + f"/transcripts/{transcript_id}/speaker/assign", + json={ + "participant": participant1_id, + "timestamp_from": 0, + "timestamp_to": 100, + }, + ) + assert response.status_code == 200 + + # check topics again + response = await ac.get(f"/transcripts/{transcript_id}/topics/with-words") + assert response.status_code == 200 + topics = response.json() + assert len(topics) == 2 + + # check through words + assert topics[0]["words"][0]["speaker"] == 1 + assert topics[0]["words"][1]["speaker"] == 1 + assert topics[1]["words"][0]["speaker"] == 1 + assert topics[1]["words"][1]["speaker"] == 1 + # check segments + assert len(topics[0]["segments"]) == 1 + assert topics[0]["segments"][0]["speaker"] == 1 + assert len(topics[1]["segments"]) == 1 + assert topics[1]["segments"][0]["speaker"] == 1 + + +@pytest.mark.asyncio +async def test_transcript_reassign_edge_cases(fake_transcript_with_topics): + from reflector.app import app + + transcript_id = fake_transcript_with_topics.id + + async with AsyncClient(app=app, base_url="http://test/v1") as ac: + # check the transcript exists + response = await ac.get(f"/transcripts/{transcript_id}") + assert response.status_code == 200 + transcript = response.json() + assert len(transcript["participants"]) == 0 + + # try reassign without any participant_id or speaker + response = await ac.patch( + f"/transcripts/{transcript_id}/speaker/assign", + json={ + "timestamp_from": 0, + "timestamp_to": 1, + }, + ) + assert response.status_code == 400 + + # try reassing with both participant_id and speaker + response = await ac.patch( + f"/transcripts/{transcript_id}/speaker/assign", + json={ + "participant": "123", + "speaker": 1, + "timestamp_from": 0, + "timestamp_to": 1, + }, + ) + assert response.status_code == 400 + + # try reassing with non-existing participant_id + response = await ac.patch( + f"/transcripts/{transcript_id}/speaker/assign", + json={ + "participant": "123", + "timestamp_from": 0, + "timestamp_to": 1, + }, + ) + assert response.status_code == 404 diff --git a/server/tests/test_transcripts_topics.py b/server/tests/test_transcripts_topics.py new file mode 100644 index 00000000..cd845b3f --- /dev/null +++ b/server/tests/test_transcripts_topics.py @@ -0,0 +1,26 @@ +import pytest +from httpx import AsyncClient + + +@pytest.mark.asyncio +async def test_transcript_topics(fake_transcript_with_topics): + from reflector.app import app + + transcript_id = fake_transcript_with_topics.id + + async with AsyncClient(app=app, base_url="http://test/v1") as ac: + # check the transcript exists + response = await ac.get(f"/transcripts/{transcript_id}/topics") + assert response.status_code == 200 + assert len(response.json()) == 2 + topic_id = response.json()[0]["id"] + + # get words per speakers + response = await ac.get( + f"/transcripts/{transcript_id}/topics/{topic_id}/words-per-speaker" + ) + assert response.status_code == 200 + data = response.json() + assert len(data["words_per_speaker"]) == 1 + assert data["words_per_speaker"][0]["speaker"] == 0 + assert len(data["words_per_speaker"][0]["words"]) == 2 diff --git a/www/app/[domain]/transcripts/useWebSockets.ts b/www/app/[domain]/transcripts/useWebSockets.ts index 1e59781c..26b69c05 100644 --- a/www/app/[domain]/transcripts/useWebSockets.ts +++ b/www/app/[domain]/transcripts/useWebSockets.ts @@ -62,6 +62,7 @@ export const useWebSockets = (transcriptId: string | null): UseWebSockets => { { id: "1", timestamp: 10, + duration: 10, summary: "This is test topic 1", title: "Topic 1: Introduction to Quantum Mechanics", transcript: @@ -103,6 +104,7 @@ export const useWebSockets = (transcriptId: string | null): UseWebSockets => { { id: "2", timestamp: 20, + duration: 10, summary: "This is test topic 2", title: "Topic 2: Machine Learning Algorithms", transcript: @@ -123,6 +125,7 @@ export const useWebSockets = (transcriptId: string | null): UseWebSockets => { { id: "3", timestamp: 30, + duration: 10, summary: "This is test topic 3", title: "Topic 3: Mental Health Awareness", transcript: "Ways to improve mental health and reduce stigma.", @@ -142,6 +145,7 @@ export const useWebSockets = (transcriptId: string | null): UseWebSockets => { { id: "4", timestamp: 40, + duration: 10, summary: "This is test topic 4", title: "Topic 4: Basics of Productivity", transcript: "Tips and tricks to increase daily productivity.", @@ -161,6 +165,7 @@ export const useWebSockets = (transcriptId: string | null): UseWebSockets => { { id: "5", timestamp: 50, + duration: 10, summary: "This is test topic 5", title: "Topic 5: Future of Aviation", transcript: @@ -190,6 +195,7 @@ export const useWebSockets = (transcriptId: string | null): UseWebSockets => { { id: "1", timestamp: 10, + duration: 10, summary: "This is test topic 1", title: "Topic 1: Introduction to Quantum Mechanics, a brief overview of quantum mechanics and its principles.", @@ -211,6 +217,7 @@ export const useWebSockets = (transcriptId: string | null): UseWebSockets => { { id: "2", timestamp: 20, + duration: 10, summary: "This is test topic 2", title: "Topic 2: Machine Learning Algorithms, understanding the different types of machine learning algorithms.", @@ -232,6 +239,7 @@ export const useWebSockets = (transcriptId: string | null): UseWebSockets => { { id: "3", timestamp: 30, + duration: 10, summary: "This is test topic 3", title: "Topic 3: Mental Health Awareness, ways to improve mental health and reduce stigma.", @@ -252,6 +260,7 @@ export const useWebSockets = (transcriptId: string | null): UseWebSockets => { { id: "4", timestamp: 40, + duration: 10, summary: "This is test topic 4", title: "Topic 4: Basics of Productivity, tips and tricks to increase daily productivity.", @@ -272,6 +281,7 @@ export const useWebSockets = (transcriptId: string | null): UseWebSockets => { { id: "5", timestamp: 50, + duration: 10, summary: "This is test topic 5", title: "Topic 5: Future of Aviation, exploring the advancements and possibilities in aviation.", diff --git a/www/app/api/.openapi-generator/FILES b/www/app/api/.openapi-generator/FILES index 532a6a16..0ba155bf 100644 --- a/www/app/api/.openapi-generator/FILES +++ b/www/app/api/.openapi-generator/FILES @@ -2,16 +2,27 @@ apis/DefaultApi.ts apis/index.ts index.ts models/AudioWaveform.ts +models/CreateParticipant.ts models/CreateTranscript.ts models/DeletionStatus.ts models/GetTranscript.ts models/GetTranscriptSegmentTopic.ts models/GetTranscriptTopic.ts +models/GetTranscriptTopicWithWords.ts +models/GetTranscriptTopicWithWordsPerSpeaker.ts models/HTTPValidationError.ts models/PageGetTranscript.ts +models/Participant.ts models/RtcOffer.ts +models/SpeakerAssignment.ts +models/SpeakerAssignmentStatus.ts +models/SpeakerMerge.ts +models/SpeakerWords.ts +models/TranscriptParticipant.ts +models/UpdateParticipant.ts models/UpdateTranscript.ts models/UserInfo.ts models/ValidationError.ts +models/Word.ts models/index.ts runtime.ts diff --git a/www/app/api/apis/DefaultApi.ts b/www/app/api/apis/DefaultApi.ts index 5bb2e7e9..855f7cda 100644 --- a/www/app/api/apis/DefaultApi.ts +++ b/www/app/api/apis/DefaultApi.ts @@ -15,37 +15,73 @@ import * as runtime from "../runtime"; import type { AudioWaveform, + CreateParticipant, CreateTranscript, DeletionStatus, GetTranscript, + GetTranscriptTopicWithWordsPerSpeaker, HTTPValidationError, PageGetTranscript, + Participant, RtcOffer, + SpeakerAssignment, + SpeakerAssignmentStatus, + SpeakerMerge, + UpdateParticipant, UpdateTranscript, } from "../models"; import { AudioWaveformFromJSON, AudioWaveformToJSON, + CreateParticipantFromJSON, + CreateParticipantToJSON, CreateTranscriptFromJSON, CreateTranscriptToJSON, DeletionStatusFromJSON, DeletionStatusToJSON, GetTranscriptFromJSON, GetTranscriptToJSON, + GetTranscriptTopicWithWordsPerSpeakerFromJSON, + GetTranscriptTopicWithWordsPerSpeakerToJSON, HTTPValidationErrorFromJSON, HTTPValidationErrorToJSON, PageGetTranscriptFromJSON, PageGetTranscriptToJSON, + ParticipantFromJSON, + ParticipantToJSON, RtcOfferFromJSON, RtcOfferToJSON, + SpeakerAssignmentFromJSON, + SpeakerAssignmentToJSON, + SpeakerAssignmentStatusFromJSON, + SpeakerAssignmentStatusToJSON, + SpeakerMergeFromJSON, + SpeakerMergeToJSON, + UpdateParticipantFromJSON, + UpdateParticipantToJSON, UpdateTranscriptFromJSON, UpdateTranscriptToJSON, } from "../models"; +export interface V1TranscriptAddParticipantRequest { + transcriptId: any; + createParticipant: CreateParticipant; +} + +export interface V1TranscriptAssignSpeakerRequest { + transcriptId: any; + speakerAssignment: SpeakerAssignment; +} + export interface V1TranscriptDeleteRequest { transcriptId: any; } +export interface V1TranscriptDeleteParticipantRequest { + transcriptId: any; + participantId: any; +} + export interface V1TranscriptGetRequest { transcriptId: any; } @@ -59,14 +95,37 @@ export interface V1TranscriptGetAudioWaveformRequest { transcriptId: any; } +export interface V1TranscriptGetParticipantRequest { + transcriptId: any; + participantId: any; +} + +export interface V1TranscriptGetParticipantsRequest { + transcriptId: any; +} + export interface V1TranscriptGetTopicsRequest { transcriptId: any; } +export interface V1TranscriptGetTopicsWithWordsRequest { + transcriptId: any; +} + +export interface V1TranscriptGetTopicsWithWordsPerSpeakerRequest { + transcriptId: any; + topicId: any; +} + export interface V1TranscriptGetWebsocketEventsRequest { transcriptId: any; } +export interface V1TranscriptMergeSpeakerRequest { + transcriptId: any; + speakerMerge: SpeakerMerge; +} + export interface V1TranscriptRecordWebrtcRequest { transcriptId: any; rtcOffer: RtcOffer; @@ -77,6 +136,12 @@ export interface V1TranscriptUpdateRequest { updateTranscript: UpdateTranscript; } +export interface V1TranscriptUpdateParticipantRequest { + transcriptId: any; + participantId: any; + updateParticipant: UpdateParticipant; +} + export interface V1TranscriptsCreateRequest { createTranscript: CreateTranscript; } @@ -129,6 +194,154 @@ export class DefaultApi extends runtime.BaseAPI { return await response.value(); } + /** + * Transcript Add Participant + */ + async v1TranscriptAddParticipantRaw( + requestParameters: V1TranscriptAddParticipantRequest, + initOverrides?: RequestInit | runtime.InitOverrideFunction, + ): Promise> { + if ( + requestParameters.transcriptId === null || + requestParameters.transcriptId === undefined + ) { + throw new runtime.RequiredError( + "transcriptId", + "Required parameter requestParameters.transcriptId was null or undefined when calling v1TranscriptAddParticipant.", + ); + } + + if ( + requestParameters.createParticipant === null || + requestParameters.createParticipant === undefined + ) { + throw new runtime.RequiredError( + "createParticipant", + "Required parameter requestParameters.createParticipant was null or undefined when calling v1TranscriptAddParticipant.", + ); + } + + const queryParameters: any = {}; + + const headerParameters: runtime.HTTPHeaders = {}; + + headerParameters["Content-Type"] = "application/json"; + + if (this.configuration && this.configuration.accessToken) { + // oauth required + headerParameters["Authorization"] = await this.configuration.accessToken( + "OAuth2AuthorizationCodeBearer", + [], + ); + } + + const response = await this.request( + { + path: `/v1/transcripts/{transcript_id}/participants`.replace( + `{${"transcript_id"}}`, + encodeURIComponent(String(requestParameters.transcriptId)), + ), + method: "POST", + headers: headerParameters, + query: queryParameters, + body: CreateParticipantToJSON(requestParameters.createParticipant), + }, + initOverrides, + ); + + return new runtime.JSONApiResponse(response, (jsonValue) => + ParticipantFromJSON(jsonValue), + ); + } + + /** + * Transcript Add Participant + */ + async v1TranscriptAddParticipant( + requestParameters: V1TranscriptAddParticipantRequest, + initOverrides?: RequestInit | runtime.InitOverrideFunction, + ): Promise { + const response = await this.v1TranscriptAddParticipantRaw( + requestParameters, + initOverrides, + ); + return await response.value(); + } + + /** + * Transcript Assign Speaker + */ + async v1TranscriptAssignSpeakerRaw( + requestParameters: V1TranscriptAssignSpeakerRequest, + initOverrides?: RequestInit | runtime.InitOverrideFunction, + ): Promise> { + if ( + requestParameters.transcriptId === null || + requestParameters.transcriptId === undefined + ) { + throw new runtime.RequiredError( + "transcriptId", + "Required parameter requestParameters.transcriptId was null or undefined when calling v1TranscriptAssignSpeaker.", + ); + } + + if ( + requestParameters.speakerAssignment === null || + requestParameters.speakerAssignment === undefined + ) { + throw new runtime.RequiredError( + "speakerAssignment", + "Required parameter requestParameters.speakerAssignment was null or undefined when calling v1TranscriptAssignSpeaker.", + ); + } + + const queryParameters: any = {}; + + const headerParameters: runtime.HTTPHeaders = {}; + + headerParameters["Content-Type"] = "application/json"; + + if (this.configuration && this.configuration.accessToken) { + // oauth required + headerParameters["Authorization"] = await this.configuration.accessToken( + "OAuth2AuthorizationCodeBearer", + [], + ); + } + + const response = await this.request( + { + path: `/v1/transcripts/{transcript_id}/speaker/assign`.replace( + `{${"transcript_id"}}`, + encodeURIComponent(String(requestParameters.transcriptId)), + ), + method: "PATCH", + headers: headerParameters, + query: queryParameters, + body: SpeakerAssignmentToJSON(requestParameters.speakerAssignment), + }, + initOverrides, + ); + + return new runtime.JSONApiResponse(response, (jsonValue) => + SpeakerAssignmentStatusFromJSON(jsonValue), + ); + } + + /** + * Transcript Assign Speaker + */ + async v1TranscriptAssignSpeaker( + requestParameters: V1TranscriptAssignSpeakerRequest, + initOverrides?: RequestInit | runtime.InitOverrideFunction, + ): Promise { + const response = await this.v1TranscriptAssignSpeakerRaw( + requestParameters, + initOverrides, + ); + return await response.value(); + } + /** * Transcript Delete */ @@ -190,6 +403,82 @@ export class DefaultApi extends runtime.BaseAPI { return await response.value(); } + /** + * Transcript Delete Participant + */ + async v1TranscriptDeleteParticipantRaw( + requestParameters: V1TranscriptDeleteParticipantRequest, + initOverrides?: RequestInit | runtime.InitOverrideFunction, + ): Promise> { + if ( + requestParameters.transcriptId === null || + requestParameters.transcriptId === undefined + ) { + throw new runtime.RequiredError( + "transcriptId", + "Required parameter requestParameters.transcriptId was null or undefined when calling v1TranscriptDeleteParticipant.", + ); + } + + if ( + requestParameters.participantId === null || + requestParameters.participantId === undefined + ) { + throw new runtime.RequiredError( + "participantId", + "Required parameter requestParameters.participantId was null or undefined when calling v1TranscriptDeleteParticipant.", + ); + } + + const queryParameters: any = {}; + + const headerParameters: runtime.HTTPHeaders = {}; + + if (this.configuration && this.configuration.accessToken) { + // oauth required + headerParameters["Authorization"] = await this.configuration.accessToken( + "OAuth2AuthorizationCodeBearer", + [], + ); + } + + const response = await this.request( + { + path: `/v1/transcripts/{transcript_id}/participants/{participant_id}` + .replace( + `{${"transcript_id"}}`, + encodeURIComponent(String(requestParameters.transcriptId)), + ) + .replace( + `{${"participant_id"}}`, + encodeURIComponent(String(requestParameters.participantId)), + ), + method: "DELETE", + headers: headerParameters, + query: queryParameters, + }, + initOverrides, + ); + + return new runtime.JSONApiResponse(response, (jsonValue) => + DeletionStatusFromJSON(jsonValue), + ); + } + + /** + * Transcript Delete Participant + */ + async v1TranscriptDeleteParticipant( + requestParameters: V1TranscriptDeleteParticipantRequest, + initOverrides?: RequestInit | runtime.InitOverrideFunction, + ): Promise { + const response = await this.v1TranscriptDeleteParticipantRaw( + requestParameters, + initOverrides, + ); + return await response.value(); + } + /** * Transcript Get */ @@ -379,6 +668,145 @@ export class DefaultApi extends runtime.BaseAPI { return await response.value(); } + /** + * Transcript Get Participant + */ + async v1TranscriptGetParticipantRaw( + requestParameters: V1TranscriptGetParticipantRequest, + initOverrides?: RequestInit | runtime.InitOverrideFunction, + ): Promise> { + if ( + requestParameters.transcriptId === null || + requestParameters.transcriptId === undefined + ) { + throw new runtime.RequiredError( + "transcriptId", + "Required parameter requestParameters.transcriptId was null or undefined when calling v1TranscriptGetParticipant.", + ); + } + + if ( + requestParameters.participantId === null || + requestParameters.participantId === undefined + ) { + throw new runtime.RequiredError( + "participantId", + "Required parameter requestParameters.participantId was null or undefined when calling v1TranscriptGetParticipant.", + ); + } + + const queryParameters: any = {}; + + const headerParameters: runtime.HTTPHeaders = {}; + + if (this.configuration && this.configuration.accessToken) { + // oauth required + headerParameters["Authorization"] = await this.configuration.accessToken( + "OAuth2AuthorizationCodeBearer", + [], + ); + } + + const response = await this.request( + { + path: `/v1/transcripts/{transcript_id}/participants/{participant_id}` + .replace( + `{${"transcript_id"}}`, + encodeURIComponent(String(requestParameters.transcriptId)), + ) + .replace( + `{${"participant_id"}}`, + encodeURIComponent(String(requestParameters.participantId)), + ), + method: "GET", + headers: headerParameters, + query: queryParameters, + }, + initOverrides, + ); + + return new runtime.JSONApiResponse(response, (jsonValue) => + ParticipantFromJSON(jsonValue), + ); + } + + /** + * Transcript Get Participant + */ + async v1TranscriptGetParticipant( + requestParameters: V1TranscriptGetParticipantRequest, + initOverrides?: RequestInit | runtime.InitOverrideFunction, + ): Promise { + const response = await this.v1TranscriptGetParticipantRaw( + requestParameters, + initOverrides, + ); + return await response.value(); + } + + /** + * Transcript Get Participants + */ + async v1TranscriptGetParticipantsRaw( + requestParameters: V1TranscriptGetParticipantsRequest, + initOverrides?: RequestInit | runtime.InitOverrideFunction, + ): Promise> { + if ( + requestParameters.transcriptId === null || + requestParameters.transcriptId === undefined + ) { + throw new runtime.RequiredError( + "transcriptId", + "Required parameter requestParameters.transcriptId was null or undefined when calling v1TranscriptGetParticipants.", + ); + } + + const queryParameters: any = {}; + + const headerParameters: runtime.HTTPHeaders = {}; + + if (this.configuration && this.configuration.accessToken) { + // oauth required + headerParameters["Authorization"] = await this.configuration.accessToken( + "OAuth2AuthorizationCodeBearer", + [], + ); + } + + const response = await this.request( + { + path: `/v1/transcripts/{transcript_id}/participants`.replace( + `{${"transcript_id"}}`, + encodeURIComponent(String(requestParameters.transcriptId)), + ), + method: "GET", + headers: headerParameters, + query: queryParameters, + }, + initOverrides, + ); + + if (this.isJsonMime(response.headers.get("content-type"))) { + return new runtime.JSONApiResponse(response); + } else { + return new runtime.TextApiResponse(response) as any; + } + } + + /** + * Transcript Get Participants + */ + async v1TranscriptGetParticipants( + requestParameters: V1TranscriptGetParticipantsRequest, + initOverrides?: RequestInit | runtime.InitOverrideFunction, + ): Promise { + const response = await this.v1TranscriptGetParticipantsRaw( + requestParameters, + initOverrides, + ); + return await response.value(); + } + /** * Transcript Get Topics */ @@ -442,6 +870,145 @@ export class DefaultApi extends runtime.BaseAPI { return await response.value(); } + /** + * Transcript Get Topics With Words + */ + async v1TranscriptGetTopicsWithWordsRaw( + requestParameters: V1TranscriptGetTopicsWithWordsRequest, + initOverrides?: RequestInit | runtime.InitOverrideFunction, + ): Promise> { + if ( + requestParameters.transcriptId === null || + requestParameters.transcriptId === undefined + ) { + throw new runtime.RequiredError( + "transcriptId", + "Required parameter requestParameters.transcriptId was null or undefined when calling v1TranscriptGetTopicsWithWords.", + ); + } + + const queryParameters: any = {}; + + const headerParameters: runtime.HTTPHeaders = {}; + + if (this.configuration && this.configuration.accessToken) { + // oauth required + headerParameters["Authorization"] = await this.configuration.accessToken( + "OAuth2AuthorizationCodeBearer", + [], + ); + } + + const response = await this.request( + { + path: `/v1/transcripts/{transcript_id}/topics/with-words`.replace( + `{${"transcript_id"}}`, + encodeURIComponent(String(requestParameters.transcriptId)), + ), + method: "GET", + headers: headerParameters, + query: queryParameters, + }, + initOverrides, + ); + + if (this.isJsonMime(response.headers.get("content-type"))) { + return new runtime.JSONApiResponse(response); + } else { + return new runtime.TextApiResponse(response) as any; + } + } + + /** + * Transcript Get Topics With Words + */ + async v1TranscriptGetTopicsWithWords( + requestParameters: V1TranscriptGetTopicsWithWordsRequest, + initOverrides?: RequestInit | runtime.InitOverrideFunction, + ): Promise { + const response = await this.v1TranscriptGetTopicsWithWordsRaw( + requestParameters, + initOverrides, + ); + return await response.value(); + } + + /** + * Transcript Get Topics With Words Per Speaker + */ + async v1TranscriptGetTopicsWithWordsPerSpeakerRaw( + requestParameters: V1TranscriptGetTopicsWithWordsPerSpeakerRequest, + initOverrides?: RequestInit | runtime.InitOverrideFunction, + ): Promise> { + if ( + requestParameters.transcriptId === null || + requestParameters.transcriptId === undefined + ) { + throw new runtime.RequiredError( + "transcriptId", + "Required parameter requestParameters.transcriptId was null or undefined when calling v1TranscriptGetTopicsWithWordsPerSpeaker.", + ); + } + + if ( + requestParameters.topicId === null || + requestParameters.topicId === undefined + ) { + throw new runtime.RequiredError( + "topicId", + "Required parameter requestParameters.topicId was null or undefined when calling v1TranscriptGetTopicsWithWordsPerSpeaker.", + ); + } + + const queryParameters: any = {}; + + const headerParameters: runtime.HTTPHeaders = {}; + + if (this.configuration && this.configuration.accessToken) { + // oauth required + headerParameters["Authorization"] = await this.configuration.accessToken( + "OAuth2AuthorizationCodeBearer", + [], + ); + } + + const response = await this.request( + { + path: `/v1/transcripts/{transcript_id}/topics/{topic_id}/words-per-speaker` + .replace( + `{${"transcript_id"}}`, + encodeURIComponent(String(requestParameters.transcriptId)), + ) + .replace( + `{${"topic_id"}}`, + encodeURIComponent(String(requestParameters.topicId)), + ), + method: "GET", + headers: headerParameters, + query: queryParameters, + }, + initOverrides, + ); + + return new runtime.JSONApiResponse(response, (jsonValue) => + GetTranscriptTopicWithWordsPerSpeakerFromJSON(jsonValue), + ); + } + + /** + * Transcript Get Topics With Words Per Speaker + */ + async v1TranscriptGetTopicsWithWordsPerSpeaker( + requestParameters: V1TranscriptGetTopicsWithWordsPerSpeakerRequest, + initOverrides?: RequestInit | runtime.InitOverrideFunction, + ): Promise { + const response = await this.v1TranscriptGetTopicsWithWordsPerSpeakerRaw( + requestParameters, + initOverrides, + ); + return await response.value(); + } + /** * Transcript Get Websocket Events */ @@ -497,6 +1064,80 @@ export class DefaultApi extends runtime.BaseAPI { return await response.value(); } + /** + * Transcript Merge Speaker + */ + async v1TranscriptMergeSpeakerRaw( + requestParameters: V1TranscriptMergeSpeakerRequest, + initOverrides?: RequestInit | runtime.InitOverrideFunction, + ): Promise> { + if ( + requestParameters.transcriptId === null || + requestParameters.transcriptId === undefined + ) { + throw new runtime.RequiredError( + "transcriptId", + "Required parameter requestParameters.transcriptId was null or undefined when calling v1TranscriptMergeSpeaker.", + ); + } + + if ( + requestParameters.speakerMerge === null || + requestParameters.speakerMerge === undefined + ) { + throw new runtime.RequiredError( + "speakerMerge", + "Required parameter requestParameters.speakerMerge was null or undefined when calling v1TranscriptMergeSpeaker.", + ); + } + + const queryParameters: any = {}; + + const headerParameters: runtime.HTTPHeaders = {}; + + headerParameters["Content-Type"] = "application/json"; + + if (this.configuration && this.configuration.accessToken) { + // oauth required + headerParameters["Authorization"] = await this.configuration.accessToken( + "OAuth2AuthorizationCodeBearer", + [], + ); + } + + const response = await this.request( + { + path: `/v1/transcripts/{transcript_id}/speaker/merge`.replace( + `{${"transcript_id"}}`, + encodeURIComponent(String(requestParameters.transcriptId)), + ), + method: "PATCH", + headers: headerParameters, + query: queryParameters, + body: SpeakerMergeToJSON(requestParameters.speakerMerge), + }, + initOverrides, + ); + + return new runtime.JSONApiResponse(response, (jsonValue) => + SpeakerAssignmentStatusFromJSON(jsonValue), + ); + } + + /** + * Transcript Merge Speaker + */ + async v1TranscriptMergeSpeaker( + requestParameters: V1TranscriptMergeSpeakerRequest, + initOverrides?: RequestInit | runtime.InitOverrideFunction, + ): Promise { + const response = await this.v1TranscriptMergeSpeakerRaw( + requestParameters, + initOverrides, + ); + return await response.value(); + } + /** * Transcript Record Webrtc */ @@ -647,6 +1288,95 @@ export class DefaultApi extends runtime.BaseAPI { return await response.value(); } + /** + * Transcript Update Participant + */ + async v1TranscriptUpdateParticipantRaw( + requestParameters: V1TranscriptUpdateParticipantRequest, + initOverrides?: RequestInit | runtime.InitOverrideFunction, + ): Promise> { + if ( + requestParameters.transcriptId === null || + requestParameters.transcriptId === undefined + ) { + throw new runtime.RequiredError( + "transcriptId", + "Required parameter requestParameters.transcriptId was null or undefined when calling v1TranscriptUpdateParticipant.", + ); + } + + if ( + requestParameters.participantId === null || + requestParameters.participantId === undefined + ) { + throw new runtime.RequiredError( + "participantId", + "Required parameter requestParameters.participantId was null or undefined when calling v1TranscriptUpdateParticipant.", + ); + } + + if ( + requestParameters.updateParticipant === null || + requestParameters.updateParticipant === undefined + ) { + throw new runtime.RequiredError( + "updateParticipant", + "Required parameter requestParameters.updateParticipant was null or undefined when calling v1TranscriptUpdateParticipant.", + ); + } + + const queryParameters: any = {}; + + const headerParameters: runtime.HTTPHeaders = {}; + + headerParameters["Content-Type"] = "application/json"; + + if (this.configuration && this.configuration.accessToken) { + // oauth required + headerParameters["Authorization"] = await this.configuration.accessToken( + "OAuth2AuthorizationCodeBearer", + [], + ); + } + + const response = await this.request( + { + path: `/v1/transcripts/{transcript_id}/participants/{participant_id}` + .replace( + `{${"transcript_id"}}`, + encodeURIComponent(String(requestParameters.transcriptId)), + ) + .replace( + `{${"participant_id"}}`, + encodeURIComponent(String(requestParameters.participantId)), + ), + method: "PATCH", + headers: headerParameters, + query: queryParameters, + body: UpdateParticipantToJSON(requestParameters.updateParticipant), + }, + initOverrides, + ); + + return new runtime.JSONApiResponse(response, (jsonValue) => + ParticipantFromJSON(jsonValue), + ); + } + + /** + * Transcript Update Participant + */ + async v1TranscriptUpdateParticipant( + requestParameters: V1TranscriptUpdateParticipantRequest, + initOverrides?: RequestInit | runtime.InitOverrideFunction, + ): Promise { + const response = await this.v1TranscriptUpdateParticipantRaw( + requestParameters, + initOverrides, + ); + return await response.value(); + } + /** * Transcripts Create */ diff --git a/www/app/api/models/CreateParticipant.ts b/www/app/api/models/CreateParticipant.ts new file mode 100644 index 00000000..489047c6 --- /dev/null +++ b/www/app/api/models/CreateParticipant.ts @@ -0,0 +1,74 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * FastAPI + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 0.1.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { exists, mapValues } from "../runtime"; +/** + * + * @export + * @interface CreateParticipant + */ +export interface CreateParticipant { + /** + * + * @type {any} + * @memberof CreateParticipant + */ + speaker?: any | null; + /** + * + * @type {any} + * @memberof CreateParticipant + */ + name: any | null; +} + +/** + * Check if a given object implements the CreateParticipant interface. + */ +export function instanceOfCreateParticipant(value: object): boolean { + let isInstance = true; + isInstance = isInstance && "name" in value; + + return isInstance; +} + +export function CreateParticipantFromJSON(json: any): CreateParticipant { + return CreateParticipantFromJSONTyped(json, false); +} + +export function CreateParticipantFromJSONTyped( + json: any, + ignoreDiscriminator: boolean, +): CreateParticipant { + if (json === undefined || json === null) { + return json; + } + return { + speaker: !exists(json, "speaker") ? undefined : json["speaker"], + name: json["name"], + }; +} + +export function CreateParticipantToJSON(value?: CreateParticipant | null): any { + if (value === undefined) { + return undefined; + } + if (value === null) { + return null; + } + return { + speaker: value.speaker, + name: value.name, + }; +} diff --git a/www/app/api/models/GetTranscript.ts b/www/app/api/models/GetTranscript.ts index 3c03c689..44f36bda 100644 --- a/www/app/api/models/GetTranscript.ts +++ b/www/app/api/models/GetTranscript.ts @@ -97,6 +97,12 @@ export interface GetTranscript { * @memberof GetTranscript */ targetLanguage: any | null; + /** + * + * @type {any} + * @memberof GetTranscript + */ + participants: any | null; } /** @@ -116,6 +122,7 @@ export function instanceOfGetTranscript(value: object): boolean { isInstance = isInstance && "createdAt" in value; isInstance = isInstance && "sourceLanguage" in value; isInstance = isInstance && "targetLanguage" in value; + isInstance = isInstance && "participants" in value; return isInstance; } @@ -145,6 +152,7 @@ export function GetTranscriptFromJSONTyped( shareMode: !exists(json, "share_mode") ? undefined : json["share_mode"], sourceLanguage: json["source_language"], targetLanguage: json["target_language"], + participants: json["participants"], }; } @@ -169,5 +177,6 @@ export function GetTranscriptToJSON(value?: GetTranscript | null): any { share_mode: value.shareMode, source_language: value.sourceLanguage, target_language: value.targetLanguage, + participants: value.participants, }; } diff --git a/www/app/api/models/GetTranscriptTopic.ts b/www/app/api/models/GetTranscriptTopic.ts index 460b8b39..10dcb102 100644 --- a/www/app/api/models/GetTranscriptTopic.ts +++ b/www/app/api/models/GetTranscriptTopic.ts @@ -43,6 +43,12 @@ export interface GetTranscriptTopic { * @memberof GetTranscriptTopic */ timestamp: any | null; + /** + * + * @type {any} + * @memberof GetTranscriptTopic + */ + duration: any | null; /** * * @type {any} @@ -66,6 +72,7 @@ export function instanceOfGetTranscriptTopic(value: object): boolean { isInstance = isInstance && "title" in value; isInstance = isInstance && "summary" in value; isInstance = isInstance && "timestamp" in value; + isInstance = isInstance && "duration" in value; isInstance = isInstance && "transcript" in value; return isInstance; @@ -87,6 +94,7 @@ export function GetTranscriptTopicFromJSONTyped( title: json["title"], summary: json["summary"], timestamp: json["timestamp"], + duration: json["duration"], transcript: json["transcript"], segments: !exists(json, "segments") ? undefined : json["segments"], }; @@ -106,6 +114,7 @@ export function GetTranscriptTopicToJSON( title: value.title, summary: value.summary, timestamp: value.timestamp, + duration: value.duration, transcript: value.transcript, segments: value.segments, }; diff --git a/www/app/api/models/GetTranscriptTopicWithWords.ts b/www/app/api/models/GetTranscriptTopicWithWords.ts new file mode 100644 index 00000000..7a5b21b4 --- /dev/null +++ b/www/app/api/models/GetTranscriptTopicWithWords.ts @@ -0,0 +1,131 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * FastAPI + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 0.1.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { exists, mapValues } from "../runtime"; +/** + * + * @export + * @interface GetTranscriptTopicWithWords + */ +export interface GetTranscriptTopicWithWords { + /** + * + * @type {any} + * @memberof GetTranscriptTopicWithWords + */ + id: any | null; + /** + * + * @type {any} + * @memberof GetTranscriptTopicWithWords + */ + title: any | null; + /** + * + * @type {any} + * @memberof GetTranscriptTopicWithWords + */ + summary: any | null; + /** + * + * @type {any} + * @memberof GetTranscriptTopicWithWords + */ + timestamp: any | null; + /** + * + * @type {any} + * @memberof GetTranscriptTopicWithWords + */ + duration: any | null; + /** + * + * @type {any} + * @memberof GetTranscriptTopicWithWords + */ + transcript: any | null; + /** + * + * @type {any} + * @memberof GetTranscriptTopicWithWords + */ + segments?: any | null; + /** + * + * @type {any} + * @memberof GetTranscriptTopicWithWords + */ + words?: any | null; +} + +/** + * Check if a given object implements the GetTranscriptTopicWithWords interface. + */ +export function instanceOfGetTranscriptTopicWithWords(value: object): boolean { + let isInstance = true; + isInstance = isInstance && "id" in value; + isInstance = isInstance && "title" in value; + isInstance = isInstance && "summary" in value; + isInstance = isInstance && "timestamp" in value; + isInstance = isInstance && "duration" in value; + isInstance = isInstance && "transcript" in value; + + return isInstance; +} + +export function GetTranscriptTopicWithWordsFromJSON( + json: any, +): GetTranscriptTopicWithWords { + return GetTranscriptTopicWithWordsFromJSONTyped(json, false); +} + +export function GetTranscriptTopicWithWordsFromJSONTyped( + json: any, + ignoreDiscriminator: boolean, +): GetTranscriptTopicWithWords { + if (json === undefined || json === null) { + return json; + } + return { + id: json["id"], + title: json["title"], + summary: json["summary"], + timestamp: json["timestamp"], + duration: json["duration"], + transcript: json["transcript"], + segments: !exists(json, "segments") ? undefined : json["segments"], + words: !exists(json, "words") ? undefined : json["words"], + }; +} + +export function GetTranscriptTopicWithWordsToJSON( + value?: GetTranscriptTopicWithWords | null, +): any { + if (value === undefined) { + return undefined; + } + if (value === null) { + return null; + } + return { + id: value.id, + title: value.title, + summary: value.summary, + timestamp: value.timestamp, + duration: value.duration, + transcript: value.transcript, + segments: value.segments, + words: value.words, + }; +} diff --git a/www/app/api/models/GetTranscriptTopicWithWordsPerSpeaker.ts b/www/app/api/models/GetTranscriptTopicWithWordsPerSpeaker.ts new file mode 100644 index 00000000..845192c5 --- /dev/null +++ b/www/app/api/models/GetTranscriptTopicWithWordsPerSpeaker.ts @@ -0,0 +1,135 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * FastAPI + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 0.1.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { exists, mapValues } from "../runtime"; +/** + * + * @export + * @interface GetTranscriptTopicWithWordsPerSpeaker + */ +export interface GetTranscriptTopicWithWordsPerSpeaker { + /** + * + * @type {any} + * @memberof GetTranscriptTopicWithWordsPerSpeaker + */ + id: any | null; + /** + * + * @type {any} + * @memberof GetTranscriptTopicWithWordsPerSpeaker + */ + title: any | null; + /** + * + * @type {any} + * @memberof GetTranscriptTopicWithWordsPerSpeaker + */ + summary: any | null; + /** + * + * @type {any} + * @memberof GetTranscriptTopicWithWordsPerSpeaker + */ + timestamp: any | null; + /** + * + * @type {any} + * @memberof GetTranscriptTopicWithWordsPerSpeaker + */ + duration: any | null; + /** + * + * @type {any} + * @memberof GetTranscriptTopicWithWordsPerSpeaker + */ + transcript: any | null; + /** + * + * @type {any} + * @memberof GetTranscriptTopicWithWordsPerSpeaker + */ + segments?: any | null; + /** + * + * @type {any} + * @memberof GetTranscriptTopicWithWordsPerSpeaker + */ + wordsPerSpeaker?: any | null; +} + +/** + * Check if a given object implements the GetTranscriptTopicWithWordsPerSpeaker interface. + */ +export function instanceOfGetTranscriptTopicWithWordsPerSpeaker( + value: object, +): boolean { + let isInstance = true; + isInstance = isInstance && "id" in value; + isInstance = isInstance && "title" in value; + isInstance = isInstance && "summary" in value; + isInstance = isInstance && "timestamp" in value; + isInstance = isInstance && "duration" in value; + isInstance = isInstance && "transcript" in value; + + return isInstance; +} + +export function GetTranscriptTopicWithWordsPerSpeakerFromJSON( + json: any, +): GetTranscriptTopicWithWordsPerSpeaker { + return GetTranscriptTopicWithWordsPerSpeakerFromJSONTyped(json, false); +} + +export function GetTranscriptTopicWithWordsPerSpeakerFromJSONTyped( + json: any, + ignoreDiscriminator: boolean, +): GetTranscriptTopicWithWordsPerSpeaker { + if (json === undefined || json === null) { + return json; + } + return { + id: json["id"], + title: json["title"], + summary: json["summary"], + timestamp: json["timestamp"], + duration: json["duration"], + transcript: json["transcript"], + segments: !exists(json, "segments") ? undefined : json["segments"], + wordsPerSpeaker: !exists(json, "words_per_speaker") + ? undefined + : json["words_per_speaker"], + }; +} + +export function GetTranscriptTopicWithWordsPerSpeakerToJSON( + value?: GetTranscriptTopicWithWordsPerSpeaker | null, +): any { + if (value === undefined) { + return undefined; + } + if (value === null) { + return null; + } + return { + id: value.id, + title: value.title, + summary: value.summary, + timestamp: value.timestamp, + duration: value.duration, + transcript: value.transcript, + segments: value.segments, + words_per_speaker: value.wordsPerSpeaker, + }; +} diff --git a/www/app/api/models/Participant.ts b/www/app/api/models/Participant.ts new file mode 100644 index 00000000..03a3ffab --- /dev/null +++ b/www/app/api/models/Participant.ts @@ -0,0 +1,84 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * FastAPI + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 0.1.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { exists, mapValues } from "../runtime"; +/** + * + * @export + * @interface Participant + */ +export interface Participant { + /** + * + * @type {any} + * @memberof Participant + */ + id: any | null; + /** + * + * @type {any} + * @memberof Participant + */ + speaker: any | null; + /** + * + * @type {any} + * @memberof Participant + */ + name: any | null; +} + +/** + * Check if a given object implements the Participant interface. + */ +export function instanceOfParticipant(value: object): boolean { + let isInstance = true; + isInstance = isInstance && "id" in value; + isInstance = isInstance && "speaker" in value; + isInstance = isInstance && "name" in value; + + return isInstance; +} + +export function ParticipantFromJSON(json: any): Participant { + return ParticipantFromJSONTyped(json, false); +} + +export function ParticipantFromJSONTyped( + json: any, + ignoreDiscriminator: boolean, +): Participant { + if (json === undefined || json === null) { + return json; + } + return { + id: json["id"], + speaker: json["speaker"], + name: json["name"], + }; +} + +export function ParticipantToJSON(value?: Participant | null): any { + if (value === undefined) { + return undefined; + } + if (value === null) { + return null; + } + return { + id: value.id, + speaker: value.speaker, + name: value.name, + }; +} diff --git a/www/app/api/models/SpeakerAssignment.ts b/www/app/api/models/SpeakerAssignment.ts new file mode 100644 index 00000000..b541ba9a --- /dev/null +++ b/www/app/api/models/SpeakerAssignment.ts @@ -0,0 +1,91 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * FastAPI + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 0.1.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { exists, mapValues } from "../runtime"; +/** + * + * @export + * @interface SpeakerAssignment + */ +export interface SpeakerAssignment { + /** + * + * @type {any} + * @memberof SpeakerAssignment + */ + speaker?: any | null; + /** + * + * @type {any} + * @memberof SpeakerAssignment + */ + participant?: any | null; + /** + * + * @type {any} + * @memberof SpeakerAssignment + */ + timestampFrom: any | null; + /** + * + * @type {any} + * @memberof SpeakerAssignment + */ + timestampTo: any | null; +} + +/** + * Check if a given object implements the SpeakerAssignment interface. + */ +export function instanceOfSpeakerAssignment(value: object): boolean { + let isInstance = true; + isInstance = isInstance && "timestampFrom" in value; + isInstance = isInstance && "timestampTo" in value; + + return isInstance; +} + +export function SpeakerAssignmentFromJSON(json: any): SpeakerAssignment { + return SpeakerAssignmentFromJSONTyped(json, false); +} + +export function SpeakerAssignmentFromJSONTyped( + json: any, + ignoreDiscriminator: boolean, +): SpeakerAssignment { + if (json === undefined || json === null) { + return json; + } + return { + speaker: !exists(json, "speaker") ? undefined : json["speaker"], + participant: !exists(json, "participant") ? undefined : json["participant"], + timestampFrom: json["timestamp_from"], + timestampTo: json["timestamp_to"], + }; +} + +export function SpeakerAssignmentToJSON(value?: SpeakerAssignment | null): any { + if (value === undefined) { + return undefined; + } + if (value === null) { + return null; + } + return { + speaker: value.speaker, + participant: value.participant, + timestamp_from: value.timestampFrom, + timestamp_to: value.timestampTo, + }; +} diff --git a/www/app/api/models/SpeakerAssignmentStatus.ts b/www/app/api/models/SpeakerAssignmentStatus.ts new file mode 100644 index 00000000..797c7ed3 --- /dev/null +++ b/www/app/api/models/SpeakerAssignmentStatus.ts @@ -0,0 +1,70 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * FastAPI + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 0.1.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { exists, mapValues } from "../runtime"; +/** + * + * @export + * @interface SpeakerAssignmentStatus + */ +export interface SpeakerAssignmentStatus { + /** + * + * @type {any} + * @memberof SpeakerAssignmentStatus + */ + status: any | null; +} + +/** + * Check if a given object implements the SpeakerAssignmentStatus interface. + */ +export function instanceOfSpeakerAssignmentStatus(value: object): boolean { + let isInstance = true; + isInstance = isInstance && "status" in value; + + return isInstance; +} + +export function SpeakerAssignmentStatusFromJSON( + json: any, +): SpeakerAssignmentStatus { + return SpeakerAssignmentStatusFromJSONTyped(json, false); +} + +export function SpeakerAssignmentStatusFromJSONTyped( + json: any, + ignoreDiscriminator: boolean, +): SpeakerAssignmentStatus { + if (json === undefined || json === null) { + return json; + } + return { + status: json["status"], + }; +} + +export function SpeakerAssignmentStatusToJSON( + value?: SpeakerAssignmentStatus | null, +): any { + if (value === undefined) { + return undefined; + } + if (value === null) { + return null; + } + return { + status: value.status, + }; +} diff --git a/www/app/api/models/SpeakerMerge.ts b/www/app/api/models/SpeakerMerge.ts new file mode 100644 index 00000000..809d32a1 --- /dev/null +++ b/www/app/api/models/SpeakerMerge.ts @@ -0,0 +1,75 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * FastAPI + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 0.1.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { exists, mapValues } from "../runtime"; +/** + * + * @export + * @interface SpeakerMerge + */ +export interface SpeakerMerge { + /** + * + * @type {any} + * @memberof SpeakerMerge + */ + speakerFrom: any | null; + /** + * + * @type {any} + * @memberof SpeakerMerge + */ + speakerTo: any | null; +} + +/** + * Check if a given object implements the SpeakerMerge interface. + */ +export function instanceOfSpeakerMerge(value: object): boolean { + let isInstance = true; + isInstance = isInstance && "speakerFrom" in value; + isInstance = isInstance && "speakerTo" in value; + + return isInstance; +} + +export function SpeakerMergeFromJSON(json: any): SpeakerMerge { + return SpeakerMergeFromJSONTyped(json, false); +} + +export function SpeakerMergeFromJSONTyped( + json: any, + ignoreDiscriminator: boolean, +): SpeakerMerge { + if (json === undefined || json === null) { + return json; + } + return { + speakerFrom: json["speaker_from"], + speakerTo: json["speaker_to"], + }; +} + +export function SpeakerMergeToJSON(value?: SpeakerMerge | null): any { + if (value === undefined) { + return undefined; + } + if (value === null) { + return null; + } + return { + speaker_from: value.speakerFrom, + speaker_to: value.speakerTo, + }; +} diff --git a/www/app/api/models/SpeakerWords.ts b/www/app/api/models/SpeakerWords.ts new file mode 100644 index 00000000..52f8b162 --- /dev/null +++ b/www/app/api/models/SpeakerWords.ts @@ -0,0 +1,75 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * FastAPI + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 0.1.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { exists, mapValues } from "../runtime"; +/** + * + * @export + * @interface SpeakerWords + */ +export interface SpeakerWords { + /** + * + * @type {any} + * @memberof SpeakerWords + */ + speaker: any | null; + /** + * + * @type {any} + * @memberof SpeakerWords + */ + words: any | null; +} + +/** + * Check if a given object implements the SpeakerWords interface. + */ +export function instanceOfSpeakerWords(value: object): boolean { + let isInstance = true; + isInstance = isInstance && "speaker" in value; + isInstance = isInstance && "words" in value; + + return isInstance; +} + +export function SpeakerWordsFromJSON(json: any): SpeakerWords { + return SpeakerWordsFromJSONTyped(json, false); +} + +export function SpeakerWordsFromJSONTyped( + json: any, + ignoreDiscriminator: boolean, +): SpeakerWords { + if (json === undefined || json === null) { + return json; + } + return { + speaker: json["speaker"], + words: json["words"], + }; +} + +export function SpeakerWordsToJSON(value?: SpeakerWords | null): any { + if (value === undefined) { + return undefined; + } + if (value === null) { + return null; + } + return { + speaker: value.speaker, + words: value.words, + }; +} diff --git a/www/app/api/models/TranscriptParticipant.ts b/www/app/api/models/TranscriptParticipant.ts new file mode 100644 index 00000000..76fd5d2b --- /dev/null +++ b/www/app/api/models/TranscriptParticipant.ts @@ -0,0 +1,87 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * FastAPI + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 0.1.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { exists, mapValues } from "../runtime"; +/** + * + * @export + * @interface TranscriptParticipant + */ +export interface TranscriptParticipant { + /** + * + * @type {any} + * @memberof TranscriptParticipant + */ + id?: any | null; + /** + * + * @type {any} + * @memberof TranscriptParticipant + */ + speaker: any | null; + /** + * + * @type {any} + * @memberof TranscriptParticipant + */ + name: any | null; +} + +/** + * Check if a given object implements the TranscriptParticipant interface. + */ +export function instanceOfTranscriptParticipant(value: object): boolean { + let isInstance = true; + isInstance = isInstance && "speaker" in value; + isInstance = isInstance && "name" in value; + + return isInstance; +} + +export function TranscriptParticipantFromJSON( + json: any, +): TranscriptParticipant { + return TranscriptParticipantFromJSONTyped(json, false); +} + +export function TranscriptParticipantFromJSONTyped( + json: any, + ignoreDiscriminator: boolean, +): TranscriptParticipant { + if (json === undefined || json === null) { + return json; + } + return { + id: !exists(json, "id") ? undefined : json["id"], + speaker: json["speaker"], + name: json["name"], + }; +} + +export function TranscriptParticipantToJSON( + value?: TranscriptParticipant | null, +): any { + if (value === undefined) { + return undefined; + } + if (value === null) { + return null; + } + return { + id: value.id, + speaker: value.speaker, + name: value.name, + }; +} diff --git a/www/app/api/models/UpdateParticipant.ts b/www/app/api/models/UpdateParticipant.ts new file mode 100644 index 00000000..2a8f4fd6 --- /dev/null +++ b/www/app/api/models/UpdateParticipant.ts @@ -0,0 +1,73 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * FastAPI + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 0.1.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { exists, mapValues } from "../runtime"; +/** + * + * @export + * @interface UpdateParticipant + */ +export interface UpdateParticipant { + /** + * + * @type {any} + * @memberof UpdateParticipant + */ + speaker?: any | null; + /** + * + * @type {any} + * @memberof UpdateParticipant + */ + name?: any | null; +} + +/** + * Check if a given object implements the UpdateParticipant interface. + */ +export function instanceOfUpdateParticipant(value: object): boolean { + let isInstance = true; + + return isInstance; +} + +export function UpdateParticipantFromJSON(json: any): UpdateParticipant { + return UpdateParticipantFromJSONTyped(json, false); +} + +export function UpdateParticipantFromJSONTyped( + json: any, + ignoreDiscriminator: boolean, +): UpdateParticipant { + if (json === undefined || json === null) { + return json; + } + return { + speaker: !exists(json, "speaker") ? undefined : json["speaker"], + name: !exists(json, "name") ? undefined : json["name"], + }; +} + +export function UpdateParticipantToJSON(value?: UpdateParticipant | null): any { + if (value === undefined) { + return undefined; + } + if (value === null) { + return null; + } + return { + speaker: value.speaker, + name: value.name, + }; +} diff --git a/www/app/api/models/UpdateTranscript.ts b/www/app/api/models/UpdateTranscript.ts index a710af69..b4965eab 100644 --- a/www/app/api/models/UpdateTranscript.ts +++ b/www/app/api/models/UpdateTranscript.ts @@ -55,6 +55,12 @@ export interface UpdateTranscript { * @memberof UpdateTranscript */ shareMode?: any | null; + /** + * + * @type {any} + * @memberof UpdateTranscript + */ + participants?: any | null; } /** @@ -88,6 +94,9 @@ export function UpdateTranscriptFromJSONTyped( ? undefined : json["long_summary"], shareMode: !exists(json, "share_mode") ? undefined : json["share_mode"], + participants: !exists(json, "participants") + ? undefined + : json["participants"], }; } @@ -105,5 +114,6 @@ export function UpdateTranscriptToJSON(value?: UpdateTranscript | null): any { short_summary: value.shortSummary, long_summary: value.longSummary, share_mode: value.shareMode, + participants: value.participants, }; } diff --git a/www/app/api/models/Word.ts b/www/app/api/models/Word.ts new file mode 100644 index 00000000..85607f2e --- /dev/null +++ b/www/app/api/models/Word.ts @@ -0,0 +1,92 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * FastAPI + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 0.1.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { exists, mapValues } from "../runtime"; +/** + * + * @export + * @interface Word + */ +export interface Word { + /** + * + * @type {any} + * @memberof Word + */ + text: any | null; + /** + * + * @type {any} + * @memberof Word + */ + start: any | null; + /** + * + * @type {any} + * @memberof Word + */ + end: any | null; + /** + * + * @type {any} + * @memberof Word + */ + speaker?: any | null; +} + +/** + * Check if a given object implements the Word interface. + */ +export function instanceOfWord(value: object): boolean { + let isInstance = true; + isInstance = isInstance && "text" in value; + isInstance = isInstance && "start" in value; + isInstance = isInstance && "end" in value; + + return isInstance; +} + +export function WordFromJSON(json: any): Word { + return WordFromJSONTyped(json, false); +} + +export function WordFromJSONTyped( + json: any, + ignoreDiscriminator: boolean, +): Word { + if (json === undefined || json === null) { + return json; + } + return { + text: json["text"], + start: json["start"], + end: json["end"], + speaker: !exists(json, "speaker") ? undefined : json["speaker"], + }; +} + +export function WordToJSON(value?: Word | null): any { + if (value === undefined) { + return undefined; + } + if (value === null) { + return null; + } + return { + text: value.text, + start: value.start, + end: value.end, + speaker: value.speaker, + }; +} diff --git a/www/app/api/models/index.ts b/www/app/api/models/index.ts index 9e691b09..6bb9444e 100644 --- a/www/app/api/models/index.ts +++ b/www/app/api/models/index.ts @@ -1,14 +1,25 @@ /* tslint:disable */ /* eslint-disable */ export * from "./AudioWaveform"; +export * from "./CreateParticipant"; export * from "./CreateTranscript"; export * from "./DeletionStatus"; export * from "./GetTranscript"; export * from "./GetTranscriptSegmentTopic"; export * from "./GetTranscriptTopic"; +export * from "./GetTranscriptTopicWithWords"; +export * from "./GetTranscriptTopicWithWordsPerSpeaker"; export * from "./HTTPValidationError"; export * from "./PageGetTranscript"; +export * from "./Participant"; export * from "./RtcOffer"; +export * from "./SpeakerAssignment"; +export * from "./SpeakerAssignmentStatus"; +export * from "./SpeakerMerge"; +export * from "./SpeakerWords"; +export * from "./TranscriptParticipant"; +export * from "./UpdateParticipant"; export * from "./UpdateTranscript"; export * from "./UserInfo"; export * from "./ValidationError"; +export * from "./Word";