Merge branch 'feat-api-speaker-reassignment' of github.com:Monadical-SAS/reflector into sara/feat-speaker-reassign

This commit is contained in:
Sara
2023-12-12 11:48:20 +01:00
16 changed files with 992 additions and 7 deletions

View File

@@ -251,6 +251,23 @@ class Transcript(BaseModel):
url += f"?token={token}" url += f"?token={token}"
return url 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: class TranscriptController:
async def get_all( async def get_all(

View File

@@ -57,6 +57,7 @@ class Word(BaseModel):
class TranscriptSegment(BaseModel): class TranscriptSegment(BaseModel):
text: str text: str
start: float start: float
end: float
speaker: int = 0 speaker: int = 0
@@ -127,6 +128,7 @@ class Transcript(BaseModel):
current_segment = TranscriptSegment( current_segment = TranscriptSegment(
text=word.text, text=word.text,
start=word.start, start=word.start,
end=word.end,
speaker=word.speaker, speaker=word.speaker,
) )
continue continue
@@ -138,6 +140,7 @@ class Transcript(BaseModel):
current_segment = TranscriptSegment( current_segment = TranscriptSegment(
text=word.text, text=word.text,
start=word.start, start=word.start,
end=word.end,
speaker=word.speaker, speaker=word.speaker,
) )
continue continue
@@ -145,6 +148,7 @@ class Transcript(BaseModel):
# if the word is the end of a sentence, and we have enough content, # if the word is the end of a sentence, and we have enough content,
# add the word to the current segment and push it # add the word to the current segment and push it
current_segment.text += word.text current_segment.text += word.text
current_segment.end = word.end
have_punc = PUNC_RE.search(word.text) have_punc = PUNC_RE.search(word.text)
if have_punc and (len(current_segment.text) > MAX_SEGMENT_LENGTH): if have_punc and (len(current_segment.text) > MAX_SEGMENT_LENGTH):

View File

@@ -122,6 +122,7 @@ class GetTranscriptTopic(BaseModel):
title: str title: str
summary: str summary: str
timestamp: float timestamp: float
duration: float | None
transcript: str transcript: str
segments: list[GetTranscriptSegmentTopic] = [] segments: list[GetTranscriptSegmentTopic] = []
@@ -131,6 +132,7 @@ class GetTranscriptTopic(BaseModel):
# In previous version, words were missing # In previous version, words were missing
# Just output a segment with speaker 0 # Just output a segment with speaker 0
text = topic.transcript text = topic.transcript
duration = None
segments = [ segments = [
GetTranscriptSegmentTopic( GetTranscriptSegmentTopic(
text=topic.transcript, text=topic.transcript,
@@ -142,6 +144,7 @@ class GetTranscriptTopic(BaseModel):
# New versions include words # New versions include words
transcript = ProcessorTranscript(words=topic.words) transcript = ProcessorTranscript(words=topic.words)
text = transcript.text text = transcript.text
duration = transcript.duration
segments = [ segments = [
GetTranscriptSegmentTopic( GetTranscriptSegmentTopic(
text=segment.text, text=segment.text,
@@ -157,6 +160,7 @@ class GetTranscriptTopic(BaseModel):
timestamp=topic.timestamp, timestamp=topic.timestamp,
transcript=text, transcript=text,
segments=segments, segments=segments,
duration=duration,
) )
@@ -171,6 +175,44 @@ class GetTranscriptTopicWithWords(GetTranscriptTopic):
return instance 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) @router.get("/transcripts/{transcript_id}", response_model=GetTranscript)
async def transcript_get( async def transcript_get(
transcript_id: str, transcript_id: str,
@@ -247,3 +289,26 @@ async def transcript_get_topics_with_words(
GetTranscriptTopicWithWords.from_transcript_topic(topic) GetTranscriptTopicWithWords.from_transcript_topic(topic)
for topic in transcript.topics 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)

View File

@@ -59,7 +59,7 @@ async def transcript_add_participant(
) )
# ensure the speaker is unique # ensure the speaker is unique
if transcript.participants: if participant.speaker is not None:
for p in transcript.participants: for p in transcript.participants:
if p.speaker == participant.speaker: if p.speaker == participant.speaker:
raise HTTPException( raise HTTPException(

View File

@@ -7,14 +7,15 @@ from typing import Annotated, Optional
import reflector.auth as auth import reflector.auth as auth
from fastapi import APIRouter, Depends, HTTPException from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel from pydantic import BaseModel, Field
from reflector.db.transcripts import transcripts_controller from reflector.db.transcripts import transcripts_controller
router = APIRouter() router = APIRouter()
class SpeakerAssignment(BaseModel): class SpeakerAssignment(BaseModel):
speaker: int speaker: Optional[int] = Field(None, ge=0)
participant: Optional[str] = Field(None)
timestamp_from: float timestamp_from: float
timestamp_to: float timestamp_to: float
@@ -23,6 +24,11 @@ class SpeakerAssignmentStatus(BaseModel):
status: str status: str
class SpeakerMerge(BaseModel):
speaker_from: int
speaker_to: int
@router.patch("/transcripts/{transcript_id}/speaker/assign") @router.patch("/transcripts/{transcript_id}/speaker/assign")
async def transcript_assign_speaker( async def transcript_assign_speaker(
transcript_id: str, transcript_id: str,
@@ -37,6 +43,44 @@ async def transcript_assign_speaker(
if not transcript: if not transcript:
raise HTTPException(status_code=404, detail="Transcript not found") 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 # reassign speakers from words in the transcript
ts_from = assignment.timestamp_from ts_from = assignment.timestamp_from
ts_to = assignment.timestamp_to ts_to = assignment.timestamp_to
@@ -45,7 +89,70 @@ async def transcript_assign_speaker(
changed = False changed = False
for word in topic.words: for word in topic.words:
if ts_from <= word.start <= ts_to: if ts_from <= word.start <= ts_to:
word.speaker = assignment.speaker 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 changed = True
if changed: if changed:
changed_topics.append(topic) changed_topics.append(topic)

View File

@@ -115,3 +115,287 @@ async def test_transcript_reassign_speaker(fake_transcript_with_topics):
assert topics[0]["segments"][0]["speaker"] == 4 assert topics[0]["segments"][0]["speaker"] == 4
assert len(topics[1]["segments"]) == 1 assert len(topics[1]["segments"]) == 1
assert topics[1]["segments"][0]["speaker"] == 4 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

View File

@@ -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

View File

@@ -9,12 +9,15 @@ models/GetTranscript.ts
models/GetTranscriptSegmentTopic.ts models/GetTranscriptSegmentTopic.ts
models/GetTranscriptTopic.ts models/GetTranscriptTopic.ts
models/GetTranscriptTopicWithWords.ts models/GetTranscriptTopicWithWords.ts
models/GetTranscriptTopicWithWordsPerSpeaker.ts
models/HTTPValidationError.ts models/HTTPValidationError.ts
models/PageGetTranscript.ts models/PageGetTranscript.ts
models/Participant.ts models/Participant.ts
models/RtcOffer.ts models/RtcOffer.ts
models/SpeakerAssignment.ts models/SpeakerAssignment.ts
models/SpeakerAssignmentStatus.ts models/SpeakerAssignmentStatus.ts
models/SpeakerMerge.ts
models/SpeakerWords.ts
models/TranscriptParticipant.ts models/TranscriptParticipant.ts
models/UpdateParticipant.ts models/UpdateParticipant.ts
models/UpdateTranscript.ts models/UpdateTranscript.ts

View File

@@ -19,12 +19,14 @@ import type {
CreateTranscript, CreateTranscript,
DeletionStatus, DeletionStatus,
GetTranscript, GetTranscript,
GetTranscriptTopicWithWordsPerSpeaker,
HTTPValidationError, HTTPValidationError,
PageGetTranscript, PageGetTranscript,
Participant, Participant,
RtcOffer, RtcOffer,
SpeakerAssignment, SpeakerAssignment,
SpeakerAssignmentStatus, SpeakerAssignmentStatus,
SpeakerMerge,
UpdateParticipant, UpdateParticipant,
UpdateTranscript, UpdateTranscript,
} from "../models"; } from "../models";
@@ -39,6 +41,8 @@ import {
DeletionStatusToJSON, DeletionStatusToJSON,
GetTranscriptFromJSON, GetTranscriptFromJSON,
GetTranscriptToJSON, GetTranscriptToJSON,
GetTranscriptTopicWithWordsPerSpeakerFromJSON,
GetTranscriptTopicWithWordsPerSpeakerToJSON,
HTTPValidationErrorFromJSON, HTTPValidationErrorFromJSON,
HTTPValidationErrorToJSON, HTTPValidationErrorToJSON,
PageGetTranscriptFromJSON, PageGetTranscriptFromJSON,
@@ -51,6 +55,8 @@ import {
SpeakerAssignmentToJSON, SpeakerAssignmentToJSON,
SpeakerAssignmentStatusFromJSON, SpeakerAssignmentStatusFromJSON,
SpeakerAssignmentStatusToJSON, SpeakerAssignmentStatusToJSON,
SpeakerMergeFromJSON,
SpeakerMergeToJSON,
UpdateParticipantFromJSON, UpdateParticipantFromJSON,
UpdateParticipantToJSON, UpdateParticipantToJSON,
UpdateTranscriptFromJSON, UpdateTranscriptFromJSON,
@@ -106,10 +112,20 @@ export interface V1TranscriptGetTopicsWithWordsRequest {
transcriptId: any; transcriptId: any;
} }
export interface V1TranscriptGetTopicsWithWordsPerSpeakerRequest {
transcriptId: any;
topicId: any;
}
export interface V1TranscriptGetWebsocketEventsRequest { export interface V1TranscriptGetWebsocketEventsRequest {
transcriptId: any; transcriptId: any;
} }
export interface V1TranscriptMergeSpeakerRequest {
transcriptId: any;
speakerMerge: SpeakerMerge;
}
export interface V1TranscriptRecordWebrtcRequest { export interface V1TranscriptRecordWebrtcRequest {
transcriptId: any; transcriptId: any;
rtcOffer: RtcOffer; rtcOffer: RtcOffer;
@@ -917,6 +933,82 @@ export class DefaultApi extends runtime.BaseAPI {
return await response.value(); return await response.value();
} }
/**
* Transcript Get Topics With Words Per Speaker
*/
async v1TranscriptGetTopicsWithWordsPerSpeakerRaw(
requestParameters: V1TranscriptGetTopicsWithWordsPerSpeakerRequest,
initOverrides?: RequestInit | runtime.InitOverrideFunction,
): Promise<runtime.ApiResponse<GetTranscriptTopicWithWordsPerSpeaker>> {
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<GetTranscriptTopicWithWordsPerSpeaker> {
const response = await this.v1TranscriptGetTopicsWithWordsPerSpeakerRaw(
requestParameters,
initOverrides,
);
return await response.value();
}
/** /**
* Transcript Get Websocket Events * Transcript Get Websocket Events
*/ */
@@ -972,6 +1064,80 @@ export class DefaultApi extends runtime.BaseAPI {
return await response.value(); return await response.value();
} }
/**
* Transcript Merge Speaker
*/
async v1TranscriptMergeSpeakerRaw(
requestParameters: V1TranscriptMergeSpeakerRequest,
initOverrides?: RequestInit | runtime.InitOverrideFunction,
): Promise<runtime.ApiResponse<SpeakerAssignmentStatus>> {
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<SpeakerAssignmentStatus> {
const response = await this.v1TranscriptMergeSpeakerRaw(
requestParameters,
initOverrides,
);
return await response.value();
}
/** /**
* Transcript Record Webrtc * Transcript Record Webrtc
*/ */

View File

@@ -43,6 +43,12 @@ export interface GetTranscriptTopic {
* @memberof GetTranscriptTopic * @memberof GetTranscriptTopic
*/ */
timestamp: any | null; timestamp: any | null;
/**
*
* @type {any}
* @memberof GetTranscriptTopic
*/
duration: any | null;
/** /**
* *
* @type {any} * @type {any}
@@ -66,6 +72,7 @@ export function instanceOfGetTranscriptTopic(value: object): boolean {
isInstance = isInstance && "title" in value; isInstance = isInstance && "title" in value;
isInstance = isInstance && "summary" in value; isInstance = isInstance && "summary" in value;
isInstance = isInstance && "timestamp" in value; isInstance = isInstance && "timestamp" in value;
isInstance = isInstance && "duration" in value;
isInstance = isInstance && "transcript" in value; isInstance = isInstance && "transcript" in value;
return isInstance; return isInstance;
@@ -87,6 +94,7 @@ export function GetTranscriptTopicFromJSONTyped(
title: json["title"], title: json["title"],
summary: json["summary"], summary: json["summary"],
timestamp: json["timestamp"], timestamp: json["timestamp"],
duration: json["duration"],
transcript: json["transcript"], transcript: json["transcript"],
segments: !exists(json, "segments") ? undefined : json["segments"], segments: !exists(json, "segments") ? undefined : json["segments"],
}; };
@@ -106,6 +114,7 @@ export function GetTranscriptTopicToJSON(
title: value.title, title: value.title,
summary: value.summary, summary: value.summary,
timestamp: value.timestamp, timestamp: value.timestamp,
duration: value.duration,
transcript: value.transcript, transcript: value.transcript,
segments: value.segments, segments: value.segments,
}; };

View File

@@ -43,6 +43,12 @@ export interface GetTranscriptTopicWithWords {
* @memberof GetTranscriptTopicWithWords * @memberof GetTranscriptTopicWithWords
*/ */
timestamp: any | null; timestamp: any | null;
/**
*
* @type {any}
* @memberof GetTranscriptTopicWithWords
*/
duration: any | null;
/** /**
* *
* @type {any} * @type {any}
@@ -72,6 +78,7 @@ export function instanceOfGetTranscriptTopicWithWords(value: object): boolean {
isInstance = isInstance && "title" in value; isInstance = isInstance && "title" in value;
isInstance = isInstance && "summary" in value; isInstance = isInstance && "summary" in value;
isInstance = isInstance && "timestamp" in value; isInstance = isInstance && "timestamp" in value;
isInstance = isInstance && "duration" in value;
isInstance = isInstance && "transcript" in value; isInstance = isInstance && "transcript" in value;
return isInstance; return isInstance;
@@ -95,6 +102,7 @@ export function GetTranscriptTopicWithWordsFromJSONTyped(
title: json["title"], title: json["title"],
summary: json["summary"], summary: json["summary"],
timestamp: json["timestamp"], timestamp: json["timestamp"],
duration: json["duration"],
transcript: json["transcript"], transcript: json["transcript"],
segments: !exists(json, "segments") ? undefined : json["segments"], segments: !exists(json, "segments") ? undefined : json["segments"],
words: !exists(json, "words") ? undefined : json["words"], words: !exists(json, "words") ? undefined : json["words"],
@@ -115,6 +123,7 @@ export function GetTranscriptTopicWithWordsToJSON(
title: value.title, title: value.title,
summary: value.summary, summary: value.summary,
timestamp: value.timestamp, timestamp: value.timestamp,
duration: value.duration,
transcript: value.transcript, transcript: value.transcript,
segments: value.segments, segments: value.segments,
words: value.words, words: value.words,

View File

@@ -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,
};
}

View File

@@ -24,7 +24,13 @@ export interface SpeakerAssignment {
* @type {any} * @type {any}
* @memberof SpeakerAssignment * @memberof SpeakerAssignment
*/ */
speaker: any | null; speaker?: any | null;
/**
*
* @type {any}
* @memberof SpeakerAssignment
*/
participant?: any | null;
/** /**
* *
* @type {any} * @type {any}
@@ -44,7 +50,6 @@ export interface SpeakerAssignment {
*/ */
export function instanceOfSpeakerAssignment(value: object): boolean { export function instanceOfSpeakerAssignment(value: object): boolean {
let isInstance = true; let isInstance = true;
isInstance = isInstance && "speaker" in value;
isInstance = isInstance && "timestampFrom" in value; isInstance = isInstance && "timestampFrom" in value;
isInstance = isInstance && "timestampTo" in value; isInstance = isInstance && "timestampTo" in value;
@@ -63,7 +68,8 @@ export function SpeakerAssignmentFromJSONTyped(
return json; return json;
} }
return { return {
speaker: json["speaker"], speaker: !exists(json, "speaker") ? undefined : json["speaker"],
participant: !exists(json, "participant") ? undefined : json["participant"],
timestampFrom: json["timestamp_from"], timestampFrom: json["timestamp_from"],
timestampTo: json["timestamp_to"], timestampTo: json["timestamp_to"],
}; };
@@ -78,6 +84,7 @@ export function SpeakerAssignmentToJSON(value?: SpeakerAssignment | null): any {
} }
return { return {
speaker: value.speaker, speaker: value.speaker,
participant: value.participant,
timestamp_from: value.timestampFrom, timestamp_from: value.timestampFrom,
timestamp_to: value.timestampTo, timestamp_to: value.timestampTo,
}; };

View File

@@ -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,
};
}

View File

@@ -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,
};
}

View File

@@ -8,12 +8,15 @@ export * from "./GetTranscript";
export * from "./GetTranscriptSegmentTopic"; export * from "./GetTranscriptSegmentTopic";
export * from "./GetTranscriptTopic"; export * from "./GetTranscriptTopic";
export * from "./GetTranscriptTopicWithWords"; export * from "./GetTranscriptTopicWithWords";
export * from "./GetTranscriptTopicWithWordsPerSpeaker";
export * from "./HTTPValidationError"; export * from "./HTTPValidationError";
export * from "./PageGetTranscript"; export * from "./PageGetTranscript";
export * from "./Participant"; export * from "./Participant";
export * from "./RtcOffer"; export * from "./RtcOffer";
export * from "./SpeakerAssignment"; export * from "./SpeakerAssignment";
export * from "./SpeakerAssignmentStatus"; export * from "./SpeakerAssignmentStatus";
export * from "./SpeakerMerge";
export * from "./SpeakerWords";
export * from "./TranscriptParticipant"; export * from "./TranscriptParticipant";
export * from "./UpdateParticipant"; export * from "./UpdateParticipant";
export * from "./UpdateTranscript"; export * from "./UpdateTranscript";