From ddfb55894842ac836cf25759ace6072c819f4ad9 Mon Sep 17 00:00:00 2001 From: Ruslan Bakiev Date: Thu, 19 Feb 2026 14:05:54 +0700 Subject: [PATCH] Persist call transcripts and source call audio from DB --- Frontend/app.vue | 17 +++++++++-- .../update-communication-transcript.graphql | 6 ++++ Frontend/server/graphql/schema.ts | 29 +++++++++++++++++++ 3 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 Frontend/graphql/operations/update-communication-transcript.graphql diff --git a/Frontend/app.vue b/Frontend/app.vue index 053021c..8d180ff 100644 --- a/Frontend/app.vue +++ b/Frontend/app.vue @@ -8,6 +8,7 @@ import logoutMutation from "./graphql/operations/logout.graphql?raw"; import logPilotNoteMutation from "./graphql/operations/log-pilot-note.graphql?raw"; import createCalendarEventMutation from "./graphql/operations/create-calendar-event.graphql?raw"; import createCommunicationMutation from "./graphql/operations/create-communication.graphql?raw"; +import updateCommunicationTranscriptMutation from "./graphql/operations/update-communication-transcript.graphql?raw"; import updateFeedDecisionMutation from "./graphql/operations/update-feed-decision.graphql?raw"; import chatConversationsQuery from "./graphql/operations/chat-conversations.graphql?raw"; import createChatConversationMutation from "./graphql/operations/create-chat-conversation.graphql?raw"; @@ -1982,6 +1983,13 @@ async function transcribeCallItem(item: CommItem) { const itemId = item.id; if (callTranscriptLoading.value[itemId]) return; if (callTranscriptText.value[itemId]) return; + if (Array.isArray(item.transcript) && item.transcript.length) { + const persisted = item.transcript.map((line) => String(line ?? "").trim()).filter(Boolean).join("\n"); + if (persisted) { + callTranscriptText.value[itemId] = persisted; + return; + } + } const audioUrl = getCallAudioUrl(item); if (!audioUrl) { @@ -2003,6 +2011,11 @@ async function transcribeCallItem(item: CommItem) { }); const text = String(result?.text ?? "").trim(); callTranscriptText.value[itemId] = text || "(empty transcript)"; + await gqlFetch<{ updateCommunicationTranscript: { ok: boolean; id: string } }>(updateCommunicationTranscriptMutation, { + id: itemId, + transcript: text ? [text] : [], + }); + await refreshCrmData(); } catch (error: any) { callTranscriptError.value[itemId] = String(error?.message ?? error ?? "Transcription failed"); } finally { @@ -3221,6 +3234,7 @@ async function decideFeedCard(card: FeedCard, decision: "accepted" | "rejected") + {{ latestPinnedLabel }} {{ selectedCommPinnedStream.length }} @@ -3231,8 +3245,7 @@ async function decideFeedCard(card: FeedCard, decision: "accepted" | "rejected") >
-

Pinned note

-

{{ entry.text }}

+

{{ entry.text }}

diff --git a/Frontend/graphql/operations/update-communication-transcript.graphql b/Frontend/graphql/operations/update-communication-transcript.graphql new file mode 100644 index 0000000..2555d63 --- /dev/null +++ b/Frontend/graphql/operations/update-communication-transcript.graphql @@ -0,0 +1,6 @@ +mutation UpdateCommunicationTranscriptMutation($id: ID!, $transcript: [String!]!) { + updateCommunicationTranscript(id: $id, transcript: $transcript) { + ok + id + } +} diff --git a/Frontend/server/graphql/schema.ts b/Frontend/server/graphql/schema.ts index 7016089..5857dc5 100644 --- a/Frontend/server/graphql/schema.ts +++ b/Frontend/server/graphql/schema.ts @@ -552,6 +552,29 @@ async function createCommunication(auth: AuthContext | null, input: { return { ok: true, id: created.id }; } +async function updateCommunicationTranscript(auth: AuthContext | null, id: string, transcript: string[]) { + const ctx = requireAuth(auth); + const messageId = String(id ?? "").trim(); + if (!messageId) throw new Error("id is required"); + + const lines = Array.isArray(transcript) + ? transcript.map((line) => String(line ?? "").trim()).filter(Boolean) + : []; + + const updated = await prisma.contactMessage.updateMany({ + where: { + id: messageId, + contact: { teamId: ctx.teamId }, + }, + data: { + transcriptJson: lines, + }, + }); + + if (!updated.count) throw new Error("communication not found"); + return { ok: true, id: messageId }; +} + async function updateFeedDecision(auth: AuthContext | null, id: string, decision: "accepted" | "rejected" | "pending", decisionNote?: string) { const ctx = requireAuth(auth); @@ -769,6 +792,7 @@ export const crmGraphqlSchema = buildSchema(` toggleContactPin(contact: String!, text: String!): PinToggleResult! createCalendarEvent(input: CreateCalendarEventInput!): CalendarEvent! createCommunication(input: CreateCommunicationInput!): MutationWithIdResult! + updateCommunicationTranscript(id: ID!, transcript: [String!]!): MutationWithIdResult! updateFeedDecision(id: ID!, decision: String!, decisionNote: String): MutationWithIdResult! } @@ -1027,6 +1051,11 @@ export const crmGraphqlRoot = { context: GraphQLContext, ) => createCommunication(context.auth, args.input), + updateCommunicationTranscript: async ( + args: { id: string; transcript: string[] }, + context: GraphQLContext, + ) => updateCommunicationTranscript(context.auth, args.id, args.transcript), + updateFeedDecision: async ( args: { id: string; decision: "accepted" | "rejected" | "pending"; decisionNote?: string }, context: GraphQLContext,