mirror of
https://github.com/Monadical-SAS/reflector.git
synced 2025-12-20 20:29:06 +00:00
transcript ui copy button placement (#712)
Co-authored-by: Igor Loskutov <igor.loskutoff@gmail.com>
This commit is contained in:
@@ -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<React.SetStateAction<HTMLDivElement | null>>;
|
||||
};
|
||||
|
||||
export default function FinalSummary(props: FinalSummaryProps) {
|
||||
const finalSummaryRef = useRef<HTMLParagraphElement>(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);
|
||||
}
|
||||
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) {
|
||||
>
|
||||
<LuPen />
|
||||
</IconButton>
|
||||
<ShareAndPrivacy
|
||||
finalSummaryRef={finalSummaryRef}
|
||||
transcriptResponse={props.transcriptResponse}
|
||||
topicsResponse={props.topicsResponse}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Flex>
|
||||
@@ -153,7 +144,7 @@ export default function FinalSummary(props: FinalSummaryProps) {
|
||||
mt={2}
|
||||
/>
|
||||
) : (
|
||||
<div ref={finalSummaryRef} className="markdown">
|
||||
<div ref={props.finalSummaryRef} className="markdown">
|
||||
<Markdown>{editedSummary}</Markdown>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -41,6 +41,8 @@ export default function TranscriptDetails(details: TranscriptDetails) {
|
||||
waiting || mp3.audioDeleted === true,
|
||||
);
|
||||
const useActiveTopic = useState<Topic | null>(null);
|
||||
const [finalSummaryElement, setFinalSummaryElement] =
|
||||
useState<HTMLDivElement | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (waiting) {
|
||||
@@ -124,9 +126,12 @@ export default function TranscriptDetails(details: TranscriptDetails) {
|
||||
<TranscriptTitle
|
||||
title={transcript.data?.title || "Unnamed Transcript"}
|
||||
transcriptId={transcriptId}
|
||||
onUpdate={(newTitle) => {
|
||||
onUpdate={() => {
|
||||
transcript.refetch().then(() => {});
|
||||
}}
|
||||
transcript={transcript.data || null}
|
||||
topics={topics.topics}
|
||||
finalSummaryElement={finalSummaryElement}
|
||||
/>
|
||||
</Flex>
|
||||
{mp3.audioDeleted && (
|
||||
@@ -148,11 +153,12 @@ export default function TranscriptDetails(details: TranscriptDetails) {
|
||||
{transcript.data && topics.topics ? (
|
||||
<>
|
||||
<FinalSummary
|
||||
transcriptResponse={transcript.data}
|
||||
topicsResponse={topics.topics}
|
||||
transcript={transcript.data}
|
||||
topics={topics.topics}
|
||||
onUpdate={() => {
|
||||
transcript.refetch();
|
||||
transcript.refetch().then(() => {});
|
||||
}}
|
||||
finalSummaryRef={setFinalSummaryElement}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
|
||||
@@ -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<ShareOption>(
|
||||
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) {
|
||||
<Flex gap={2} mb={2}>
|
||||
{requireLogin && (
|
||||
<ShareZulip
|
||||
transcriptResponse={props.transcriptResponse}
|
||||
topicsResponse={props.topicsResponse}
|
||||
transcript={props.transcript}
|
||||
topics={props.topics}
|
||||
disabled={toShareMode(shareMode.value) === "private"}
|
||||
/>
|
||||
)}
|
||||
<ShareCopy
|
||||
finalSummaryRef={props.finalSummaryRef}
|
||||
transcriptResponse={props.transcriptResponse}
|
||||
topicsResponse={props.topicsResponse}
|
||||
finalSummaryElement={props.finalSummaryElement}
|
||||
transcript={props.transcript}
|
||||
topics={props.topics}
|
||||
/>
|
||||
</Flex>
|
||||
|
||||
<ShareLink transcriptId={props.transcriptResponse.id} />
|
||||
<ShareLink transcriptId={props.transcript.id} />
|
||||
</Dialog.Body>
|
||||
</Dialog.Content>
|
||||
</Dialog.Positioner>
|
||||
|
||||
@@ -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, " ")
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
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<HTMLInputElement>) => {
|
||||
setDisplayedTitle(e.target.value);
|
||||
};
|
||||
|
||||
const handleKeyDown = (e) => {
|
||||
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (e.key === "Enter") {
|
||||
updateTitle(displayedTitle, props.transcriptId);
|
||||
setIsEditing(false);
|
||||
@@ -111,6 +117,13 @@ const TranscriptTitle = (props: TranscriptTitle) => {
|
||||
>
|
||||
<LuPen />
|
||||
</IconButton>
|
||||
{props.transcript && props.topics && (
|
||||
<ShareAndPrivacy
|
||||
finalSummaryElement={props.finalSummaryElement}
|
||||
transcript={props.transcript}
|
||||
topics={props.topics}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
)}
|
||||
</>
|
||||
|
||||
Reference in New Issue
Block a user