Files
reflector/www/app/lib/apiHooks.ts
Sergey Mankovsky 9a71af145e fix: update transcript list on reprocess (#676)
* Update transcript list on reprocess

* Fix transcript create

* Fix multiple sockets issue

* Pass token in sec websocket protocol

* userEvent parse example

* transcript list invalidation non-abstraction

* Emit only relevant events to the user room

* Add ws close code const

* Refactor user websocket endpoint

* Refactor user events provider

---------

Co-authored-by: Igor Loskutov <igor.loskutoff@gmail.com>
2025-10-07 19:11:30 +02:00

798 lines
19 KiB
TypeScript

"use client";
import { $api } from "./apiClient";
import { useError } from "../(errors)/errorContext";
import { QueryClient, useQueryClient } from "@tanstack/react-query";
import type { components } from "../reflector-api";
import { useAuth } from "./AuthProvider";
/*
* XXX error types returned from the hooks are not always correct; declared types are ValidationError but real type could be string or any other
* this is either a limitation or incorrect usage of Python json schema generator
* or, limitation or incorrect usage of .d type generator from json schema
* */
export const useAuthReady = () => {
const auth = useAuth();
return {
isAuthenticated: auth.status === "authenticated",
isLoading: auth.status === "loading",
};
};
export function useRoomsList(page: number = 1) {
const { isAuthenticated } = useAuthReady();
return $api.useQuery(
"get",
"/v1/rooms",
{
params: {
query: { page },
},
},
{
enabled: isAuthenticated,
},
);
}
type SourceKind = components["schemas"]["SourceKind"];
export const TRANSCRIPT_SEARCH_URL = "/v1/transcripts/search" as const;
export const invalidateTranscriptLists = (queryClient: QueryClient) =>
queryClient.invalidateQueries({
queryKey: ["get", TRANSCRIPT_SEARCH_URL],
});
export function useTranscriptsSearch(
q: string = "",
options: {
limit?: number;
offset?: number;
room_id?: string;
source_kind?: SourceKind;
} = {},
) {
return $api.useQuery(
"get",
TRANSCRIPT_SEARCH_URL,
{
params: {
query: {
q,
limit: options.limit,
offset: options.offset,
room_id: options.room_id,
source_kind: options.source_kind,
},
},
},
{
enabled: true,
},
);
}
export function useTranscriptDelete() {
const { setError } = useError();
const queryClient = useQueryClient();
return $api.useMutation("delete", "/v1/transcripts/{transcript_id}", {
onSuccess: () => {
return queryClient.invalidateQueries({
queryKey: ["get", TRANSCRIPT_SEARCH_URL],
});
},
onError: (error) => {
setError(error as Error, "There was an error deleting the transcript");
},
});
}
export function useTranscriptProcess() {
const { setError } = useError();
return $api.useMutation("post", "/v1/transcripts/{transcript_id}/process", {
onError: (error) => {
setError(error as Error, "There was an error processing the transcript");
},
});
}
export function useTranscriptGet(transcriptId: string | null) {
return $api.useQuery(
"get",
"/v1/transcripts/{transcript_id}",
{
params: {
path: {
transcript_id: transcriptId!,
},
},
},
{
enabled: !!transcriptId,
},
);
}
export function useRoomGet(roomId: string | null) {
const { isAuthenticated } = useAuthReady();
return $api.useQuery(
"get",
"/v1/rooms/{room_id}",
{
params: {
path: { room_id: roomId! },
},
},
{
enabled: !!roomId && isAuthenticated,
},
);
}
export function useRoomTestWebhook() {
const { setError } = useError();
return $api.useMutation("post", "/v1/rooms/{room_id}/webhook/test", {
onError: (error) => {
setError(error as Error, "There was an error testing the webhook");
},
});
}
export function useRoomCreate() {
const { setError } = useError();
const queryClient = useQueryClient();
return $api.useMutation("post", "/v1/rooms", {
onSuccess: () => {
return queryClient.invalidateQueries({
queryKey: $api.queryOptions("get", "/v1/rooms").queryKey,
});
},
onError: (error) => {
setError(error as Error, "There was an error creating the room");
},
});
}
export function useRoomUpdate() {
const { setError } = useError();
const queryClient = useQueryClient();
return $api.useMutation("patch", "/v1/rooms/{room_id}", {
onSuccess: async (room) => {
await Promise.all([
queryClient.invalidateQueries({
queryKey: $api.queryOptions("get", "/v1/rooms").queryKey,
}),
queryClient.invalidateQueries({
queryKey: $api.queryOptions("get", "/v1/rooms/{room_id}", {
params: {
path: {
room_id: room.id,
},
},
}).queryKey,
}),
]);
},
onError: (error) => {
setError(error as Error, "There was an error updating the room");
},
});
}
export function useRoomDelete() {
const { setError } = useError();
const queryClient = useQueryClient();
return $api.useMutation("delete", "/v1/rooms/{room_id}", {
onSuccess: () => {
return queryClient.invalidateQueries({
queryKey: $api.queryOptions("get", "/v1/rooms").queryKey,
});
},
onError: (error) => {
setError(error as Error, "There was an error deleting the room");
},
});
}
export function useZulipStreams() {
const { isAuthenticated } = useAuthReady();
return $api.useQuery(
"get",
"/v1/zulip/streams",
{},
{
enabled: isAuthenticated,
},
);
}
export function useZulipTopics(streamId: number | null) {
const { isAuthenticated } = useAuthReady();
const enabled = !!streamId && isAuthenticated;
return $api.useQuery(
"get",
"/v1/zulip/streams/{stream_id}/topics",
{
params: {
path: {
stream_id: enabled ? streamId : 0,
},
},
},
{
enabled,
},
);
}
export function useTranscriptUpdate() {
const { setError } = useError();
const queryClient = useQueryClient();
return $api.useMutation("patch", "/v1/transcripts/{transcript_id}", {
onSuccess: (data, variables) => {
return queryClient.invalidateQueries({
queryKey: $api.queryOptions("get", "/v1/transcripts/{transcript_id}", {
params: {
path: { transcript_id: variables.params.path.transcript_id },
},
}).queryKey,
});
},
onError: (error) => {
setError(error as Error, "There was an error updating the transcript");
},
});
}
export function useTranscriptPostToZulip() {
const { setError } = useError();
// @ts-ignore - Zulip endpoint not in OpenAPI spec
return $api.useMutation("post", "/v1/transcripts/{transcript_id}/zulip", {
onError: (error) => {
setError(error as Error, "There was an error posting to Zulip");
},
});
}
export function useTranscriptUploadAudio() {
const { setError } = useError();
const queryClient = useQueryClient();
return $api.useMutation(
"post",
"/v1/transcripts/{transcript_id}/record/upload",
{
onSuccess: (data, variables) => {
return queryClient.invalidateQueries({
queryKey: $api.queryOptions(
"get",
"/v1/transcripts/{transcript_id}",
{
params: {
path: { transcript_id: variables.params.path.transcript_id },
},
},
).queryKey,
});
},
onError: (error) => {
setError(error as Error, "There was an error uploading the audio file");
},
},
);
}
export function useTranscriptWaveform(transcriptId: string | null) {
return $api.useQuery(
"get",
"/v1/transcripts/{transcript_id}/audio/waveform",
{
params: {
path: { transcript_id: transcriptId! },
},
},
{
enabled: !!transcriptId,
},
);
}
export function useTranscriptMP3(transcriptId: string | null) {
const { isAuthenticated } = useAuthReady();
return $api.useQuery(
"get",
"/v1/transcripts/{transcript_id}/audio/mp3",
{
params: {
path: { transcript_id: transcriptId! },
},
},
{
enabled: !!transcriptId && isAuthenticated,
},
);
}
export function useTranscriptTopics(transcriptId: string | null) {
return $api.useQuery(
"get",
"/v1/transcripts/{transcript_id}/topics",
{
params: {
path: { transcript_id: transcriptId! },
},
},
{
enabled: !!transcriptId,
},
);
}
export function useTranscriptTopicsWithWords(transcriptId: string | null) {
const { isAuthenticated } = useAuthReady();
return $api.useQuery(
"get",
"/v1/transcripts/{transcript_id}/topics/with-words",
{
params: {
path: { transcript_id: transcriptId! },
},
},
{
enabled: !!transcriptId && isAuthenticated,
},
);
}
export function useTranscriptTopicsWithWordsPerSpeaker(
transcriptId: string | null,
topicId: string | null,
) {
const { isAuthenticated } = useAuthReady();
return $api.useQuery(
"get",
"/v1/transcripts/{transcript_id}/topics/{topic_id}/words-per-speaker",
{
params: {
path: {
transcript_id: transcriptId!,
topic_id: topicId!,
},
},
},
{
enabled: !!transcriptId && !!topicId && isAuthenticated,
},
);
}
export function useTranscriptParticipants(transcriptId: string | null) {
const { isAuthenticated } = useAuthReady();
return $api.useQuery(
"get",
"/v1/transcripts/{transcript_id}/participants",
{
params: {
path: { transcript_id: transcriptId! },
},
},
{
enabled: !!transcriptId && isAuthenticated,
},
);
}
export function useTranscriptParticipantUpdate() {
const { setError } = useError();
const queryClient = useQueryClient();
return $api.useMutation(
"patch",
"/v1/transcripts/{transcript_id}/participants/{participant_id}",
{
onSuccess: (data, variables) => {
return queryClient.invalidateQueries({
queryKey: $api.queryOptions(
"get",
"/v1/transcripts/{transcript_id}/participants",
{
params: {
path: { transcript_id: variables.params.path.transcript_id },
},
},
).queryKey,
});
},
onError: (error) => {
setError(error as Error, "There was an error updating the participant");
},
},
);
}
export function useTranscriptParticipantCreate() {
const { setError } = useError();
const queryClient = useQueryClient();
return $api.useMutation(
"post",
"/v1/transcripts/{transcript_id}/participants",
{
onSuccess: (data, variables) => {
return queryClient.invalidateQueries({
queryKey: $api.queryOptions(
"get",
"/v1/transcripts/{transcript_id}/participants",
{
params: {
path: { transcript_id: variables.params.path.transcript_id },
},
},
).queryKey,
});
},
onError: (error) => {
setError(error as Error, "There was an error creating the participant");
},
},
);
}
export function useTranscriptParticipantDelete() {
const { setError } = useError();
const queryClient = useQueryClient();
return $api.useMutation(
"delete",
"/v1/transcripts/{transcript_id}/participants/{participant_id}",
{
onSuccess: (data, variables) => {
return queryClient.invalidateQueries({
queryKey: $api.queryOptions(
"get",
"/v1/transcripts/{transcript_id}/participants",
{
params: {
path: { transcript_id: variables.params.path.transcript_id },
},
},
).queryKey,
});
},
onError: (error) => {
setError(error as Error, "There was an error deleting the participant");
},
},
);
}
export function useTranscriptSpeakerAssign() {
const { setError } = useError();
const queryClient = useQueryClient();
return $api.useMutation(
"patch",
"/v1/transcripts/{transcript_id}/speaker/assign",
{
onSuccess: (data, variables) => {
return Promise.all([
queryClient.invalidateQueries({
queryKey: $api.queryOptions(
"get",
"/v1/transcripts/{transcript_id}",
{
params: {
path: { transcript_id: variables.params.path.transcript_id },
},
},
).queryKey,
}),
queryClient.invalidateQueries({
queryKey: $api.queryOptions(
"get",
"/v1/transcripts/{transcript_id}/participants",
{
params: {
path: { transcript_id: variables.params.path.transcript_id },
},
},
).queryKey,
}),
]);
},
onError: (error) => {
setError(error as Error, "There was an error assigning the speaker");
},
},
);
}
export function useTranscriptSpeakerMerge() {
const { setError } = useError();
const queryClient = useQueryClient();
return $api.useMutation(
"patch",
"/v1/transcripts/{transcript_id}/speaker/merge",
{
onSuccess: (data, variables) => {
return Promise.all([
queryClient.invalidateQueries({
queryKey: $api.queryOptions(
"get",
"/v1/transcripts/{transcript_id}",
{
params: {
path: { transcript_id: variables.params.path.transcript_id },
},
},
).queryKey,
}),
queryClient.invalidateQueries({
queryKey: $api.queryOptions(
"get",
"/v1/transcripts/{transcript_id}/participants",
{
params: {
path: { transcript_id: variables.params.path.transcript_id },
},
},
).queryKey,
}),
]);
},
onError: (error) => {
setError(error as Error, "There was an error merging speakers");
},
},
);
}
export function useMeetingAudioConsent() {
const { setError } = useError();
return $api.useMutation("post", "/v1/meetings/{meeting_id}/consent", {
onError: (error) => {
setError(error as Error, "There was an error recording consent");
},
});
}
export function useMeetingDeactivate() {
const { setError } = useError();
const queryClient = useQueryClient();
return $api.useMutation("patch", `/v1/meetings/{meeting_id}/deactivate`, {
onError: (error) => {
setError(error as Error, "Failed to end meeting");
},
onSuccess: () => {
return queryClient.invalidateQueries({
predicate: (query) => {
const key = query.queryKey;
return key.some(
(k) =>
typeof k === "string" &&
!!MEETING_LIST_PATH_PARTIALS.find((e) => k.includes(e)),
);
},
});
},
});
}
export function useTranscriptWebRTC() {
const { setError } = useError();
return $api.useMutation(
"post",
"/v1/transcripts/{transcript_id}/record/webrtc",
{
onError: (error) => {
setError(error as Error, "There was an error with WebRTC connection");
},
},
);
}
export function useTranscriptCreate() {
const { setError } = useError();
const queryClient = useQueryClient();
return $api.useMutation("post", "/v1/transcripts", {
onSuccess: () => {
return queryClient.invalidateQueries({
queryKey: ["get", TRANSCRIPT_SEARCH_URL],
});
},
onError: (error) => {
setError(error as Error, "There was an error creating the transcript");
},
});
}
export function useRoomsCreateMeeting() {
const { setError } = useError();
const queryClient = useQueryClient();
return $api.useMutation("post", "/v1/rooms/{room_name}/meeting", {
onSuccess: async (data, variables) => {
const roomName = variables.params.path.room_name;
await Promise.all([
queryClient.invalidateQueries({
queryKey: $api.queryOptions("get", "/v1/rooms").queryKey,
}),
queryClient.invalidateQueries({
queryKey: $api.queryOptions(
"get",
"/v1/rooms/{room_name}/meetings/active" satisfies `/v1/rooms/{room_name}/${typeof MEETINGS_ACTIVE_PATH_PARTIAL}`,
{
params: {
path: { room_name: roomName },
},
},
).queryKey,
}),
]);
},
onError: (error) => {
setError(error as Error, "There was an error creating the meeting");
},
});
}
// Calendar integration hooks
export function useRoomGetByName(roomName: string | null) {
return $api.useQuery(
"get",
"/v1/rooms/name/{room_name}",
{
params: {
path: { room_name: roomName! },
},
},
{
enabled: !!roomName,
},
);
}
export function useRoomUpcomingMeetings(roomName: string | null) {
const { isAuthenticated } = useAuthReady();
return $api.useQuery(
"get",
"/v1/rooms/{room_name}/meetings/upcoming" satisfies `/v1/rooms/{room_name}/${typeof MEETINGS_UPCOMING_PATH_PARTIAL}`,
{
params: {
path: { room_name: roomName! },
},
},
{
enabled: !!roomName && isAuthenticated,
},
);
}
const MEETINGS_PATH_PARTIAL = "meetings" as const;
const MEETINGS_ACTIVE_PATH_PARTIAL = `${MEETINGS_PATH_PARTIAL}/active` as const;
const MEETINGS_UPCOMING_PATH_PARTIAL =
`${MEETINGS_PATH_PARTIAL}/upcoming` as const;
const MEETING_LIST_PATH_PARTIALS = [
MEETINGS_ACTIVE_PATH_PARTIAL,
MEETINGS_UPCOMING_PATH_PARTIAL,
];
export function useRoomActiveMeetings(roomName: string | null) {
return $api.useQuery(
"get",
"/v1/rooms/{room_name}/meetings/active" satisfies `/v1/rooms/{room_name}/${typeof MEETINGS_ACTIVE_PATH_PARTIAL}`,
{
params: {
path: { room_name: roomName! },
},
},
{
enabled: !!roomName,
},
);
}
export function useRoomGetMeeting(
roomName: string | null,
meetingId: string | null,
) {
return $api.useQuery(
"get",
"/v1/rooms/{room_name}/meetings/{meeting_id}",
{
params: {
path: {
room_name: roomName!,
meeting_id: meetingId!,
},
},
},
{
enabled: !!roomName && !!meetingId,
},
);
}
export function useRoomJoinMeeting() {
const { setError } = useError();
return $api.useMutation(
"post",
"/v1/rooms/{room_name}/meetings/{meeting_id}/join",
{
onError: (error) => {
setError(error as Error, "There was an error joining the meeting");
},
},
);
}
export function useRoomIcsSync() {
const { setError } = useError();
return $api.useMutation("post", "/v1/rooms/{room_name}/ics/sync", {
onError: (error) => {
setError(error as Error, "There was an error syncing the calendar");
},
});
}
export function useRoomIcsStatus(roomName: string | null) {
const { isAuthenticated } = useAuthReady();
return $api.useQuery(
"get",
"/v1/rooms/{room_name}/ics/status",
{
params: {
path: { room_name: roomName! },
},
},
{
enabled: !!roomName && isAuthenticated,
},
);
}
export function useRoomCalendarEvents(roomName: string | null) {
const { isAuthenticated } = useAuthReady();
return $api.useQuery(
"get",
"/v1/rooms/{room_name}/meetings",
{
params: {
path: { room_name: roomName! },
},
},
{
enabled: !!roomName && isAuthenticated,
},
);
}
// End of Calendar integration hooks