diff --git a/www/app/(app)/transcripts/[transcriptId]/finalSummary.tsx b/www/app/(app)/transcripts/[transcriptId]/finalSummary.tsx index b1f61d43..02951390 100644 --- a/www/app/(app)/transcripts/[transcriptId]/finalSummary.tsx +++ b/www/app/(app)/transcripts/[transcriptId]/finalSummary.tsx @@ -14,7 +14,10 @@ import { Textarea, Spacer, } from "@chakra-ui/react"; -import { LuPen } from "react-icons/lu"; +import { LuPen, LuCopy, LuCheck } from "react-icons/lu"; +import { buildTranscriptWithTopics } from "../buildTranscriptWithTopics"; +import { useTranscriptParticipants } from "../../../lib/apiHooks"; +import { toaster } from "../../../components/ui/toaster"; import { useError } from "../../../(errors)/errorContext"; import ShareAndPrivacy from "../shareAndPrivacy"; @@ -33,6 +36,9 @@ export default function FinalSummary(props: FinalSummaryProps) { const { setError } = useError(); const updateTranscriptMutation = useTranscriptUpdate(); + const participantsQuery = useTranscriptParticipants( + props.transcriptResponse?.id || null, + ); useEffect(() => { setEditedSummary(props.transcriptResponse?.long_summary || ""); @@ -125,6 +131,50 @@ export default function FinalSummary(props: FinalSummaryProps) { {!isEditMode && ( <> + { + const text = buildTranscriptWithTopics( + props.topicsResponse || [], + participantsQuery?.data || null, + props.transcriptResponse?.title || null, + ); + if (!text) return; + navigator.clipboard + .writeText(text) + .then(() => { + toaster + .create({ + placement: "top", + duration: 2500, + render: () => ( +
+
+ Transcript copied +
+
+ ), + }) + .then(() => {}); + }) + .catch(() => {}); + }} + > + +
p.speaker === speakerNumber)?.name; + return name && name.trim().length > 0 ? name : `Speaker ${speakerNumber}`; +} + +export function buildTranscriptWithTopics( + topics: GetTranscriptTopic[], + participants?: Participant[] | null, + transcriptTitle?: string | null, +): string { + const blocks: string[] = []; + + if (transcriptTitle && transcriptTitle.trim()) { + blocks.push(`# ${transcriptTitle.trim()}`); + blocks.push(""); + } + + for (const topic of topics) { + // Topic header + const topicTime = formatTime(Math.floor(topic.timestamp || 0)); + const title = topic.title?.trim() || "Untitled Topic"; + blocks.push(`## ${title} [${topicTime}]`); + + if (topic.segments && topic.segments.length > 0) { + for (const seg of topic.segments) { + const ts = formatTime(Math.floor(seg.start || 0)); + const speaker = getSpeakerName(seg.speaker as number, participants); + const text = (seg.text || "").replace(/\s+/g, " ").trim(); + if (text) { + blocks.push(`[${ts}] ${speaker}: ${text}`); + } + } + } else if (topic.transcript) { + // Fallback: plain transcript when segments are not present + const text = topic.transcript.replace(/\s+/g, " ").trim(); + if (text) { + blocks.push(text); + } + } + + // Blank line between topics + blocks.push(""); + } + + // Trim trailing blank line + while (blocks.length > 0 && blocks[blocks.length - 1] === "") { + blocks.pop(); + } + + return blocks.join("\n"); +} diff --git a/www/app/(app)/transcripts/shareCopy.tsx b/www/app/(app)/transcripts/shareCopy.tsx index dd56f213..6b7564f1 100644 --- a/www/app/(app)/transcripts/shareCopy.tsx +++ b/www/app/(app)/transcripts/shareCopy.tsx @@ -3,6 +3,8 @@ import type { components } from "../../reflector-api"; type GetTranscript = components["schemas"]["GetTranscript"]; type GetTranscriptTopic = components["schemas"]["GetTranscriptTopic"]; import { Button, BoxProps, Box } from "@chakra-ui/react"; +import { buildTranscriptWithTopics } from "./buildTranscriptWithTopics"; +import { useTranscriptParticipants } from "../../lib/apiHooks"; type ShareCopyProps = { finalSummaryRef: any; @@ -18,6 +20,9 @@ export default function ShareCopy({ }: ShareCopyProps & BoxProps) { const [isCopiedSummary, setIsCopiedSummary] = useState(false); const [isCopiedTranscript, setIsCopiedTranscript] = useState(false); + const participantsQuery = useTranscriptParticipants( + transcriptResponse?.id || null, + ); const onCopySummaryClick = () => { let text_to_copy = finalSummaryRef.current?.innerText; @@ -31,12 +36,12 @@ export default function ShareCopy({ }; const onCopyTranscriptClick = () => { - let text_to_copy = - topicsResponse - ?.map((topic) => topic.transcript) - .join("\n\n") - .replace(/ +/g, " ") - .trim() || ""; + const text_to_copy = + buildTranscriptWithTopics( + topicsResponse || [], + participantsQuery?.data || null, + transcriptResponse?.title || null, + ) || ""; text_to_copy && navigator.clipboard.writeText(text_to_copy).then(() => {