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 React from "react";
|
||||||
import Markdown from "react-markdown";
|
import Markdown from "react-markdown";
|
||||||
import "../../../styles/markdown.css";
|
import "../../../styles/markdown.css";
|
||||||
@@ -16,17 +16,15 @@ import {
|
|||||||
} from "@chakra-ui/react";
|
} from "@chakra-ui/react";
|
||||||
import { LuPen } from "react-icons/lu";
|
import { LuPen } from "react-icons/lu";
|
||||||
import { useError } from "../../../(errors)/errorContext";
|
import { useError } from "../../../(errors)/errorContext";
|
||||||
import ShareAndPrivacy from "../shareAndPrivacy";
|
|
||||||
|
|
||||||
type FinalSummaryProps = {
|
type FinalSummaryProps = {
|
||||||
transcriptResponse: GetTranscript;
|
transcript: GetTranscript;
|
||||||
topicsResponse: GetTranscriptTopic[];
|
topics: GetTranscriptTopic[];
|
||||||
onUpdate?: (newSummary) => void;
|
onUpdate: (newSummary: string) => void;
|
||||||
|
finalSummaryRef: React.Dispatch<React.SetStateAction<HTMLDivElement | null>>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function FinalSummary(props: FinalSummaryProps) {
|
export default function FinalSummary(props: FinalSummaryProps) {
|
||||||
const finalSummaryRef = useRef<HTMLParagraphElement>(null);
|
|
||||||
|
|
||||||
const [isEditMode, setIsEditMode] = useState(false);
|
const [isEditMode, setIsEditMode] = useState(false);
|
||||||
const [preEditSummary, setPreEditSummary] = useState("");
|
const [preEditSummary, setPreEditSummary] = useState("");
|
||||||
const [editedSummary, setEditedSummary] = useState("");
|
const [editedSummary, setEditedSummary] = useState("");
|
||||||
@@ -35,10 +33,10 @@ export default function FinalSummary(props: FinalSummaryProps) {
|
|||||||
const updateTranscriptMutation = useTranscriptUpdate();
|
const updateTranscriptMutation = useTranscriptUpdate();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setEditedSummary(props.transcriptResponse?.long_summary || "");
|
setEditedSummary(props.transcript?.long_summary || "");
|
||||||
}, [props.transcriptResponse?.long_summary]);
|
}, [props.transcript?.long_summary]);
|
||||||
|
|
||||||
if (!props.topicsResponse || !props.transcriptResponse) {
|
if (!props.topics || !props.transcript) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,9 +52,7 @@ export default function FinalSummary(props: FinalSummaryProps) {
|
|||||||
long_summary: newSummary,
|
long_summary: newSummary,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (props.onUpdate) {
|
props.onUpdate(newSummary);
|
||||||
props.onUpdate(newSummary);
|
|
||||||
}
|
|
||||||
console.log("Updated long summary:", updatedTranscript);
|
console.log("Updated long summary:", updatedTranscript);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Failed to update long summary:", err);
|
console.error("Failed to update long summary:", err);
|
||||||
@@ -75,7 +71,7 @@ export default function FinalSummary(props: FinalSummaryProps) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onSaveClick = () => {
|
const onSaveClick = () => {
|
||||||
updateSummary(editedSummary, props.transcriptResponse.id);
|
updateSummary(editedSummary, props.transcript.id);
|
||||||
setIsEditMode(false);
|
setIsEditMode(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -133,11 +129,6 @@ export default function FinalSummary(props: FinalSummaryProps) {
|
|||||||
>
|
>
|
||||||
<LuPen />
|
<LuPen />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<ShareAndPrivacy
|
|
||||||
finalSummaryRef={finalSummaryRef}
|
|
||||||
transcriptResponse={props.transcriptResponse}
|
|
||||||
topicsResponse={props.topicsResponse}
|
|
||||||
/>
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
@@ -153,7 +144,7 @@ export default function FinalSummary(props: FinalSummaryProps) {
|
|||||||
mt={2}
|
mt={2}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div ref={finalSummaryRef} className="markdown">
|
<div ref={props.finalSummaryRef} className="markdown">
|
||||||
<Markdown>{editedSummary}</Markdown>
|
<Markdown>{editedSummary}</Markdown>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -41,6 +41,8 @@ export default function TranscriptDetails(details: TranscriptDetails) {
|
|||||||
waiting || mp3.audioDeleted === true,
|
waiting || mp3.audioDeleted === true,
|
||||||
);
|
);
|
||||||
const useActiveTopic = useState<Topic | null>(null);
|
const useActiveTopic = useState<Topic | null>(null);
|
||||||
|
const [finalSummaryElement, setFinalSummaryElement] =
|
||||||
|
useState<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (waiting) {
|
if (waiting) {
|
||||||
@@ -124,9 +126,12 @@ export default function TranscriptDetails(details: TranscriptDetails) {
|
|||||||
<TranscriptTitle
|
<TranscriptTitle
|
||||||
title={transcript.data?.title || "Unnamed Transcript"}
|
title={transcript.data?.title || "Unnamed Transcript"}
|
||||||
transcriptId={transcriptId}
|
transcriptId={transcriptId}
|
||||||
onUpdate={(newTitle) => {
|
onUpdate={() => {
|
||||||
transcript.refetch().then(() => {});
|
transcript.refetch().then(() => {});
|
||||||
}}
|
}}
|
||||||
|
transcript={transcript.data || null}
|
||||||
|
topics={topics.topics}
|
||||||
|
finalSummaryElement={finalSummaryElement}
|
||||||
/>
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
{mp3.audioDeleted && (
|
{mp3.audioDeleted && (
|
||||||
@@ -148,11 +153,12 @@ export default function TranscriptDetails(details: TranscriptDetails) {
|
|||||||
{transcript.data && topics.topics ? (
|
{transcript.data && topics.topics ? (
|
||||||
<>
|
<>
|
||||||
<FinalSummary
|
<FinalSummary
|
||||||
transcriptResponse={transcript.data}
|
transcript={transcript.data}
|
||||||
topicsResponse={topics.topics}
|
topics={topics.topics}
|
||||||
onUpdate={() => {
|
onUpdate={() => {
|
||||||
transcript.refetch();
|
transcript.refetch().then(() => {});
|
||||||
}}
|
}}
|
||||||
|
finalSummaryRef={setFinalSummaryElement}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -26,9 +26,9 @@ import { useAuth } from "../../lib/AuthProvider";
|
|||||||
import { featureEnabled } from "../../lib/features";
|
import { featureEnabled } from "../../lib/features";
|
||||||
|
|
||||||
type ShareAndPrivacyProps = {
|
type ShareAndPrivacyProps = {
|
||||||
finalSummaryRef: any;
|
finalSummaryElement: HTMLDivElement | null;
|
||||||
transcriptResponse: GetTranscript;
|
transcript: GetTranscript;
|
||||||
topicsResponse: GetTranscriptTopic[];
|
topics: GetTranscriptTopic[];
|
||||||
};
|
};
|
||||||
|
|
||||||
type ShareOption = { value: ShareMode; label: string };
|
type ShareOption = { value: ShareMode; label: string };
|
||||||
@@ -48,7 +48,7 @@ export default function ShareAndPrivacy(props: ShareAndPrivacyProps) {
|
|||||||
const [isOwner, setIsOwner] = useState(false);
|
const [isOwner, setIsOwner] = useState(false);
|
||||||
const [shareMode, setShareMode] = useState<ShareOption>(
|
const [shareMode, setShareMode] = useState<ShareOption>(
|
||||||
shareOptionsData.find(
|
shareOptionsData.find(
|
||||||
(option) => option.value === props.transcriptResponse.share_mode,
|
(option) => option.value === props.transcript.share_mode,
|
||||||
) || shareOptionsData[0],
|
) || shareOptionsData[0],
|
||||||
);
|
);
|
||||||
const [shareLoading, setShareLoading] = useState(false);
|
const [shareLoading, setShareLoading] = useState(false);
|
||||||
@@ -70,7 +70,7 @@ export default function ShareAndPrivacy(props: ShareAndPrivacyProps) {
|
|||||||
try {
|
try {
|
||||||
const updatedTranscript = await updateTranscriptMutation.mutateAsync({
|
const updatedTranscript = await updateTranscriptMutation.mutateAsync({
|
||||||
params: {
|
params: {
|
||||||
path: { transcript_id: props.transcriptResponse.id },
|
path: { transcript_id: props.transcript.id },
|
||||||
},
|
},
|
||||||
body: requestBody,
|
body: requestBody,
|
||||||
});
|
});
|
||||||
@@ -90,8 +90,8 @@ export default function ShareAndPrivacy(props: ShareAndPrivacyProps) {
|
|||||||
const userId = auth.status === "authenticated" ? auth.user?.id : null;
|
const userId = auth.status === "authenticated" ? auth.user?.id : null;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setIsOwner(!!(requireLogin && userId === props.transcriptResponse.user_id));
|
setIsOwner(!!(requireLogin && userId === props.transcript.user_id));
|
||||||
}, [userId, props.transcriptResponse.user_id]);
|
}, [userId, props.transcript.user_id]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -171,19 +171,19 @@ export default function ShareAndPrivacy(props: ShareAndPrivacyProps) {
|
|||||||
<Flex gap={2} mb={2}>
|
<Flex gap={2} mb={2}>
|
||||||
{requireLogin && (
|
{requireLogin && (
|
||||||
<ShareZulip
|
<ShareZulip
|
||||||
transcriptResponse={props.transcriptResponse}
|
transcript={props.transcript}
|
||||||
topicsResponse={props.topicsResponse}
|
topics={props.topics}
|
||||||
disabled={toShareMode(shareMode.value) === "private"}
|
disabled={toShareMode(shareMode.value) === "private"}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<ShareCopy
|
<ShareCopy
|
||||||
finalSummaryRef={props.finalSummaryRef}
|
finalSummaryElement={props.finalSummaryElement}
|
||||||
transcriptResponse={props.transcriptResponse}
|
transcript={props.transcript}
|
||||||
topicsResponse={props.topicsResponse}
|
topics={props.topics}
|
||||||
/>
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
<ShareLink transcriptId={props.transcriptResponse.id} />
|
<ShareLink transcriptId={props.transcript.id} />
|
||||||
</Dialog.Body>
|
</Dialog.Body>
|
||||||
</Dialog.Content>
|
</Dialog.Content>
|
||||||
</Dialog.Positioner>
|
</Dialog.Positioner>
|
||||||
|
|||||||
@@ -5,34 +5,35 @@ type GetTranscriptTopic = components["schemas"]["GetTranscriptTopic"];
|
|||||||
import { Button, BoxProps, Box } from "@chakra-ui/react";
|
import { Button, BoxProps, Box } from "@chakra-ui/react";
|
||||||
|
|
||||||
type ShareCopyProps = {
|
type ShareCopyProps = {
|
||||||
finalSummaryRef: any;
|
finalSummaryElement: HTMLDivElement | null;
|
||||||
transcriptResponse: GetTranscript;
|
transcript: GetTranscript;
|
||||||
topicsResponse: GetTranscriptTopic[];
|
topics: GetTranscriptTopic[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function ShareCopy({
|
export default function ShareCopy({
|
||||||
finalSummaryRef,
|
finalSummaryElement,
|
||||||
transcriptResponse,
|
transcript,
|
||||||
topicsResponse,
|
topics,
|
||||||
...boxProps
|
...boxProps
|
||||||
}: ShareCopyProps & BoxProps) {
|
}: ShareCopyProps & BoxProps) {
|
||||||
const [isCopiedSummary, setIsCopiedSummary] = useState(false);
|
const [isCopiedSummary, setIsCopiedSummary] = useState(false);
|
||||||
const [isCopiedTranscript, setIsCopiedTranscript] = useState(false);
|
const [isCopiedTranscript, setIsCopiedTranscript] = useState(false);
|
||||||
|
|
||||||
const onCopySummaryClick = () => {
|
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(() => {
|
navigator.clipboard.writeText(text_to_copy).then(() => {
|
||||||
setIsCopiedSummary(true);
|
setIsCopiedSummary(true);
|
||||||
// Reset the copied state after 2 seconds
|
// Reset the copied state after 2 seconds
|
||||||
setTimeout(() => setIsCopiedSummary(false), 2000);
|
setTimeout(() => setIsCopiedSummary(false), 2000);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onCopyTranscriptClick = () => {
|
const onCopyTranscriptClick = () => {
|
||||||
let text_to_copy =
|
let text_to_copy =
|
||||||
topicsResponse
|
topics
|
||||||
?.map((topic) => topic.transcript)
|
?.map((topic) => topic.transcript)
|
||||||
.join("\n\n")
|
.join("\n\n")
|
||||||
.replace(/ +/g, " ")
|
.replace(/ +/g, " ")
|
||||||
|
|||||||
@@ -26,8 +26,8 @@ import {
|
|||||||
import { featureEnabled } from "../../lib/features";
|
import { featureEnabled } from "../../lib/features";
|
||||||
|
|
||||||
type ShareZulipProps = {
|
type ShareZulipProps = {
|
||||||
transcriptResponse: GetTranscript;
|
transcript: GetTranscript;
|
||||||
topicsResponse: GetTranscriptTopic[];
|
topics: GetTranscriptTopic[];
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -88,14 +88,14 @@ export default function ShareZulip(props: ShareZulipProps & BoxProps) {
|
|||||||
}, [stream, streams]);
|
}, [stream, streams]);
|
||||||
|
|
||||||
const handleSendToZulip = async () => {
|
const handleSendToZulip = async () => {
|
||||||
if (!props.transcriptResponse) return;
|
if (!props.transcript) return;
|
||||||
|
|
||||||
if (stream && topic) {
|
if (stream && topic) {
|
||||||
try {
|
try {
|
||||||
await postToZulipMutation.mutateAsync({
|
await postToZulipMutation.mutateAsync({
|
||||||
params: {
|
params: {
|
||||||
path: {
|
path: {
|
||||||
transcript_id: props.transcriptResponse.id,
|
transcript_id: props.transcript.id,
|
||||||
},
|
},
|
||||||
query: {
|
query: {
|
||||||
stream,
|
stream,
|
||||||
|
|||||||
@@ -2,14 +2,22 @@ import { useState } from "react";
|
|||||||
import type { components } from "../../reflector-api";
|
import type { components } from "../../reflector-api";
|
||||||
|
|
||||||
type UpdateTranscript = components["schemas"]["UpdateTranscript"];
|
type UpdateTranscript = components["schemas"]["UpdateTranscript"];
|
||||||
|
type GetTranscript = components["schemas"]["GetTranscript"];
|
||||||
|
type GetTranscriptTopic = components["schemas"]["GetTranscriptTopic"];
|
||||||
import { useTranscriptUpdate } from "../../lib/apiHooks";
|
import { useTranscriptUpdate } from "../../lib/apiHooks";
|
||||||
import { Heading, IconButton, Input, Flex, Spacer } from "@chakra-ui/react";
|
import { Heading, IconButton, Input, Flex, Spacer } from "@chakra-ui/react";
|
||||||
import { LuPen } from "react-icons/lu";
|
import { LuPen } from "react-icons/lu";
|
||||||
|
import ShareAndPrivacy from "./shareAndPrivacy";
|
||||||
|
|
||||||
type TranscriptTitle = {
|
type TranscriptTitle = {
|
||||||
title: string;
|
title: string;
|
||||||
transcriptId: 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) => {
|
const TranscriptTitle = (props: TranscriptTitle) => {
|
||||||
@@ -29,9 +37,7 @@ const TranscriptTitle = (props: TranscriptTitle) => {
|
|||||||
},
|
},
|
||||||
body: requestBody,
|
body: requestBody,
|
||||||
});
|
});
|
||||||
if (props.onUpdate) {
|
props.onUpdate(newTitle);
|
||||||
props.onUpdate(newTitle);
|
|
||||||
}
|
|
||||||
console.log("Updated transcript title:", newTitle);
|
console.log("Updated transcript title:", newTitle);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Failed to update transcript:", err);
|
console.error("Failed to update transcript:", err);
|
||||||
@@ -62,11 +68,11 @@ const TranscriptTitle = (props: TranscriptTitle) => {
|
|||||||
}
|
}
|
||||||
setIsEditing(false);
|
setIsEditing(false);
|
||||||
};
|
};
|
||||||
const handleChange = (e) => {
|
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
setDisplayedTitle(e.target.value);
|
setDisplayedTitle(e.target.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleKeyDown = (e) => {
|
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||||
if (e.key === "Enter") {
|
if (e.key === "Enter") {
|
||||||
updateTitle(displayedTitle, props.transcriptId);
|
updateTitle(displayedTitle, props.transcriptId);
|
||||||
setIsEditing(false);
|
setIsEditing(false);
|
||||||
@@ -111,6 +117,13 @@ const TranscriptTitle = (props: TranscriptTitle) => {
|
|||||||
>
|
>
|
||||||
<LuPen />
|
<LuPen />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
{props.transcript && props.topics && (
|
||||||
|
<ShareAndPrivacy
|
||||||
|
finalSummaryElement={props.finalSummaryElement}
|
||||||
|
transcript={props.transcript}
|
||||||
|
topics={props.topics}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|||||||
Reference in New Issue
Block a user