From 0baff7abf7478c5ec96864e46d321a8500588897 Mon Sep 17 00:00:00 2001 From: Igor Monadical Date: Fri, 24 Oct 2025 16:52:02 -0400 Subject: [PATCH] transcript ui copy button placement (#712) Co-authored-by: Igor Loskutov --- .../[transcriptId]/finalSummary.tsx | 31 +++++++------------ .../(app)/transcripts/[transcriptId]/page.tsx | 14 ++++++--- www/app/(app)/transcripts/shareAndPrivacy.tsx | 26 ++++++++-------- www/app/(app)/transcripts/shareCopy.tsx | 19 ++++++------ www/app/(app)/transcripts/shareZulip.tsx | 8 ++--- www/app/(app)/transcripts/transcriptTitle.tsx | 25 +++++++++++---- 6 files changed, 67 insertions(+), 56 deletions(-) diff --git a/www/app/(app)/transcripts/[transcriptId]/finalSummary.tsx b/www/app/(app)/transcripts/[transcriptId]/finalSummary.tsx index b1f61d43..d7ba37dc 100644 --- a/www/app/(app)/transcripts/[transcriptId]/finalSummary.tsx +++ b/www/app/(app)/transcripts/[transcriptId]/finalSummary.tsx @@ -1,4 +1,4 @@ -import { useEffect, useRef, useState } from "react"; +import { useEffect, useState } from "react"; import React from "react"; import Markdown from "react-markdown"; import "../../../styles/markdown.css"; @@ -16,17 +16,15 @@ import { } from "@chakra-ui/react"; import { LuPen } from "react-icons/lu"; import { useError } from "../../../(errors)/errorContext"; -import ShareAndPrivacy from "../shareAndPrivacy"; type FinalSummaryProps = { - transcriptResponse: GetTranscript; - topicsResponse: GetTranscriptTopic[]; - onUpdate?: (newSummary) => void; + transcript: GetTranscript; + topics: GetTranscriptTopic[]; + onUpdate: (newSummary: string) => void; + finalSummaryRef: React.Dispatch>; }; export default function FinalSummary(props: FinalSummaryProps) { - const finalSummaryRef = useRef(null); - const [isEditMode, setIsEditMode] = useState(false); const [preEditSummary, setPreEditSummary] = useState(""); const [editedSummary, setEditedSummary] = useState(""); @@ -35,10 +33,10 @@ export default function FinalSummary(props: FinalSummaryProps) { const updateTranscriptMutation = useTranscriptUpdate(); useEffect(() => { - setEditedSummary(props.transcriptResponse?.long_summary || ""); - }, [props.transcriptResponse?.long_summary]); + setEditedSummary(props.transcript?.long_summary || ""); + }, [props.transcript?.long_summary]); - if (!props.topicsResponse || !props.transcriptResponse) { + if (!props.topics || !props.transcript) { return null; } @@ -54,9 +52,7 @@ export default function FinalSummary(props: FinalSummaryProps) { long_summary: newSummary, }, }); - if (props.onUpdate) { - props.onUpdate(newSummary); - } + props.onUpdate(newSummary); console.log("Updated long summary:", updatedTranscript); } catch (err) { console.error("Failed to update long summary:", err); @@ -75,7 +71,7 @@ export default function FinalSummary(props: FinalSummaryProps) { }; const onSaveClick = () => { - updateSummary(editedSummary, props.transcriptResponse.id); + updateSummary(editedSummary, props.transcript.id); setIsEditMode(false); }; @@ -133,11 +129,6 @@ export default function FinalSummary(props: FinalSummaryProps) { > - )} @@ -153,7 +144,7 @@ export default function FinalSummary(props: FinalSummaryProps) { mt={2} /> ) : ( -
+
{editedSummary}
)} diff --git a/www/app/(app)/transcripts/[transcriptId]/page.tsx b/www/app/(app)/transcripts/[transcriptId]/page.tsx index f06e8935..ec5f9ebb 100644 --- a/www/app/(app)/transcripts/[transcriptId]/page.tsx +++ b/www/app/(app)/transcripts/[transcriptId]/page.tsx @@ -41,6 +41,8 @@ export default function TranscriptDetails(details: TranscriptDetails) { waiting || mp3.audioDeleted === true, ); const useActiveTopic = useState(null); + const [finalSummaryElement, setFinalSummaryElement] = + useState(null); useEffect(() => { if (waiting) { @@ -124,9 +126,12 @@ export default function TranscriptDetails(details: TranscriptDetails) { { + onUpdate={() => { transcript.refetch().then(() => {}); }} + transcript={transcript.data || null} + topics={topics.topics} + finalSummaryElement={finalSummaryElement} /> {mp3.audioDeleted && ( @@ -148,11 +153,12 @@ export default function TranscriptDetails(details: TranscriptDetails) { {transcript.data && topics.topics ? ( <> { - transcript.refetch(); + transcript.refetch().then(() => {}); }} + finalSummaryRef={setFinalSummaryElement} /> ) : ( diff --git a/www/app/(app)/transcripts/shareAndPrivacy.tsx b/www/app/(app)/transcripts/shareAndPrivacy.tsx index 8580015d..04cda920 100644 --- a/www/app/(app)/transcripts/shareAndPrivacy.tsx +++ b/www/app/(app)/transcripts/shareAndPrivacy.tsx @@ -26,9 +26,9 @@ import { useAuth } from "../../lib/AuthProvider"; import { featureEnabled } from "../../lib/features"; type ShareAndPrivacyProps = { - finalSummaryRef: any; - transcriptResponse: GetTranscript; - topicsResponse: GetTranscriptTopic[]; + finalSummaryElement: HTMLDivElement | null; + transcript: GetTranscript; + topics: GetTranscriptTopic[]; }; type ShareOption = { value: ShareMode; label: string }; @@ -48,7 +48,7 @@ export default function ShareAndPrivacy(props: ShareAndPrivacyProps) { const [isOwner, setIsOwner] = useState(false); const [shareMode, setShareMode] = useState( shareOptionsData.find( - (option) => option.value === props.transcriptResponse.share_mode, + (option) => option.value === props.transcript.share_mode, ) || shareOptionsData[0], ); const [shareLoading, setShareLoading] = useState(false); @@ -70,7 +70,7 @@ export default function ShareAndPrivacy(props: ShareAndPrivacyProps) { try { const updatedTranscript = await updateTranscriptMutation.mutateAsync({ params: { - path: { transcript_id: props.transcriptResponse.id }, + path: { transcript_id: props.transcript.id }, }, body: requestBody, }); @@ -90,8 +90,8 @@ export default function ShareAndPrivacy(props: ShareAndPrivacyProps) { const userId = auth.status === "authenticated" ? auth.user?.id : null; useEffect(() => { - setIsOwner(!!(requireLogin && userId === props.transcriptResponse.user_id)); - }, [userId, props.transcriptResponse.user_id]); + setIsOwner(!!(requireLogin && userId === props.transcript.user_id)); + }, [userId, props.transcript.user_id]); return ( <> @@ -171,19 +171,19 @@ export default function ShareAndPrivacy(props: ShareAndPrivacyProps) { {requireLogin && ( )} - + diff --git a/www/app/(app)/transcripts/shareCopy.tsx b/www/app/(app)/transcripts/shareCopy.tsx index dd56f213..fb1b5f68 100644 --- a/www/app/(app)/transcripts/shareCopy.tsx +++ b/www/app/(app)/transcripts/shareCopy.tsx @@ -5,34 +5,35 @@ type GetTranscriptTopic = components["schemas"]["GetTranscriptTopic"]; import { Button, BoxProps, Box } from "@chakra-ui/react"; type ShareCopyProps = { - finalSummaryRef: any; - transcriptResponse: GetTranscript; - topicsResponse: GetTranscriptTopic[]; + finalSummaryElement: HTMLDivElement | null; + transcript: GetTranscript; + topics: GetTranscriptTopic[]; }; export default function ShareCopy({ - finalSummaryRef, - transcriptResponse, - topicsResponse, + finalSummaryElement, + transcript, + topics, ...boxProps }: ShareCopyProps & BoxProps) { const [isCopiedSummary, setIsCopiedSummary] = useState(false); const [isCopiedTranscript, setIsCopiedTranscript] = useState(false); const onCopySummaryClick = () => { - let text_to_copy = finalSummaryRef.current?.innerText; + const text_to_copy = finalSummaryElement?.innerText; - text_to_copy && + if (text_to_copy) { navigator.clipboard.writeText(text_to_copy).then(() => { setIsCopiedSummary(true); // Reset the copied state after 2 seconds setTimeout(() => setIsCopiedSummary(false), 2000); }); + } }; const onCopyTranscriptClick = () => { let text_to_copy = - topicsResponse + topics ?.map((topic) => topic.transcript) .join("\n\n") .replace(/ +/g, " ") diff --git a/www/app/(app)/transcripts/shareZulip.tsx b/www/app/(app)/transcripts/shareZulip.tsx index bee14822..c3efe3ab 100644 --- a/www/app/(app)/transcripts/shareZulip.tsx +++ b/www/app/(app)/transcripts/shareZulip.tsx @@ -26,8 +26,8 @@ import { import { featureEnabled } from "../../lib/features"; type ShareZulipProps = { - transcriptResponse: GetTranscript; - topicsResponse: GetTranscriptTopic[]; + transcript: GetTranscript; + topics: GetTranscriptTopic[]; disabled: boolean; }; @@ -88,14 +88,14 @@ export default function ShareZulip(props: ShareZulipProps & BoxProps) { }, [stream, streams]); const handleSendToZulip = async () => { - if (!props.transcriptResponse) return; + if (!props.transcript) return; if (stream && topic) { try { await postToZulipMutation.mutateAsync({ params: { path: { - transcript_id: props.transcriptResponse.id, + transcript_id: props.transcript.id, }, query: { stream, diff --git a/www/app/(app)/transcripts/transcriptTitle.tsx b/www/app/(app)/transcripts/transcriptTitle.tsx index 72421f48..1ac32b02 100644 --- a/www/app/(app)/transcripts/transcriptTitle.tsx +++ b/www/app/(app)/transcripts/transcriptTitle.tsx @@ -2,14 +2,22 @@ import { useState } from "react"; import type { components } from "../../reflector-api"; type UpdateTranscript = components["schemas"]["UpdateTranscript"]; +type GetTranscript = components["schemas"]["GetTranscript"]; +type GetTranscriptTopic = components["schemas"]["GetTranscriptTopic"]; import { useTranscriptUpdate } from "../../lib/apiHooks"; import { Heading, IconButton, Input, Flex, Spacer } from "@chakra-ui/react"; import { LuPen } from "react-icons/lu"; +import ShareAndPrivacy from "./shareAndPrivacy"; type TranscriptTitle = { title: string; transcriptId: string; - onUpdate?: (newTitle: string) => void; + onUpdate: (newTitle: string) => void; + + // share props + transcript: GetTranscript | null; + topics: GetTranscriptTopic[] | null; + finalSummaryElement: HTMLDivElement | null; }; const TranscriptTitle = (props: TranscriptTitle) => { @@ -29,9 +37,7 @@ const TranscriptTitle = (props: TranscriptTitle) => { }, body: requestBody, }); - if (props.onUpdate) { - props.onUpdate(newTitle); - } + props.onUpdate(newTitle); console.log("Updated transcript title:", newTitle); } catch (err) { console.error("Failed to update transcript:", err); @@ -62,11 +68,11 @@ const TranscriptTitle = (props: TranscriptTitle) => { } setIsEditing(false); }; - const handleChange = (e) => { + const handleChange = (e: React.ChangeEvent) => { setDisplayedTitle(e.target.value); }; - const handleKeyDown = (e) => { + const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === "Enter") { updateTitle(displayedTitle, props.transcriptId); setIsEditing(false); @@ -111,6 +117,13 @@ const TranscriptTitle = (props: TranscriptTitle) => { > + {props.transcript && props.topics && ( + + )} )}