Feat: download transcription from clound (#828)
* add transcription list * download transcription from cloud * refactor
This commit is contained in:
@@ -9,11 +9,15 @@ import {
|
||||
AlertDialogFooter,
|
||||
AlertDialogOverlay,
|
||||
Button,
|
||||
Tabs,
|
||||
TabsContent,
|
||||
TabsList,
|
||||
TabsTrigger,
|
||||
} from "@renderer/components/ui";
|
||||
import { CheckCircleIcon, LoaderIcon, XCircleIcon } from "lucide-react";
|
||||
import { t } from "i18next";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { TranscriptionCreateForm } from "../transcriptions";
|
||||
import { TranscriptionCreateForm, TranscriptionsList } from "../transcriptions";
|
||||
|
||||
export const MediaLoadingModal = () => {
|
||||
const navigate = useNavigate();
|
||||
@@ -46,21 +50,35 @@ export const MediaLoadingModal = () => {
|
||||
<span>{t("transcribedSuccessfully")}</span>
|
||||
</div>
|
||||
) : (
|
||||
<TranscriptionCreateForm
|
||||
originalText={transcription?.result?.originalText}
|
||||
onSubmit={(data) => {
|
||||
generateTranscription({
|
||||
originalText: data.text,
|
||||
language: data.language,
|
||||
service: data.service as WhisperConfigType["service"],
|
||||
isolate: data.isolate,
|
||||
});
|
||||
}}
|
||||
onCancel={() => navigate(-1)}
|
||||
transcribing={transcribing}
|
||||
transcribingProgress={transcribingProgress}
|
||||
transcribingOutput={transcribingOutput}
|
||||
/>
|
||||
<Tabs defaultValue="transcribe">
|
||||
<TabsList className="w-full grid grid-cols-2 mb-4">
|
||||
<TabsTrigger value="transcribe">{t("transcribe")}</TabsTrigger>
|
||||
<TabsTrigger value="download">{t("download")}</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="transcribe">
|
||||
<TranscriptionCreateForm
|
||||
originalText={transcription?.result?.originalText}
|
||||
onSubmit={(data) => {
|
||||
generateTranscription({
|
||||
originalText: data.text,
|
||||
language: data.language,
|
||||
service: data.service as WhisperConfigType["service"],
|
||||
isolate: data.isolate,
|
||||
});
|
||||
}}
|
||||
onCancel={() => navigate(-1)}
|
||||
transcribing={transcribing}
|
||||
transcribingProgress={transcribingProgress}
|
||||
transcribingOutput={transcribingOutput}
|
||||
/>
|
||||
</TabsContent>
|
||||
<TabsContent value="download">
|
||||
<TranscriptionsList
|
||||
media={media}
|
||||
transcription={transcription}
|
||||
/>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
)
|
||||
) : (
|
||||
<>
|
||||
|
||||
@@ -9,10 +9,17 @@ import {
|
||||
AlertDialogContent,
|
||||
AlertDialogTitle,
|
||||
AlertDialogDescription,
|
||||
Tabs,
|
||||
TabsContent,
|
||||
TabsList,
|
||||
TabsTrigger,
|
||||
toast,
|
||||
} from "@renderer/components/ui";
|
||||
import { LoaderIcon } from "lucide-react";
|
||||
import { TranscriptionCreateForm } from "../transcriptions";
|
||||
import {
|
||||
TranscriptionCreateForm,
|
||||
TranscriptionsList,
|
||||
} from "@renderer/components";
|
||||
|
||||
export const MediaTranscriptionGenerateButton = (props: {
|
||||
children: React.ReactNode;
|
||||
@@ -57,27 +64,42 @@ export const MediaTranscriptionGenerateButton = (props: {
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
|
||||
<TranscriptionCreateForm
|
||||
onCancel={() => setOpen(false)}
|
||||
onSubmit={(data) => {
|
||||
generateTranscription({
|
||||
originalText: data.text,
|
||||
language: data.language,
|
||||
service: data.service as WhisperConfigType["service"],
|
||||
isolate: data.isolate,
|
||||
})
|
||||
.then(() => {
|
||||
setOpen(false);
|
||||
})
|
||||
.catch((e) => {
|
||||
toast.error(e.message);
|
||||
});
|
||||
}}
|
||||
originalText=""
|
||||
transcribing={transcribing}
|
||||
transcribingProgress={transcribingProgress}
|
||||
transcribingOutput={transcribingOutput}
|
||||
/>
|
||||
<Tabs defaultValue="transcribe">
|
||||
<TabsList className="w-full grid grid-cols-2 mb-4">
|
||||
<TabsTrigger value="transcribe">{t("transcribe")}</TabsTrigger>
|
||||
<TabsTrigger value="download">{t("download")}</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="transcribe">
|
||||
<TranscriptionCreateForm
|
||||
onCancel={() => setOpen(false)}
|
||||
onSubmit={(data) => {
|
||||
generateTranscription({
|
||||
originalText: data.text,
|
||||
language: data.language,
|
||||
service: data.service as WhisperConfigType["service"],
|
||||
isolate: data.isolate,
|
||||
})
|
||||
.then(() => {
|
||||
setOpen(false);
|
||||
})
|
||||
.catch((e) => {
|
||||
toast.error(e.message);
|
||||
});
|
||||
}}
|
||||
originalText=""
|
||||
transcribing={transcribing}
|
||||
transcribingProgress={transcribingProgress}
|
||||
transcribingOutput={transcribingOutput}
|
||||
/>
|
||||
</TabsContent>
|
||||
<TabsContent value="download">
|
||||
<TranscriptionsList
|
||||
media={media}
|
||||
transcription={transcription}
|
||||
onFinish={() => setOpen(false)}
|
||||
/>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
);
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from "./transcription-create-form";
|
||||
export * from "./transcription-edit-button";
|
||||
export * from "./transcriptions-list";
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
import {
|
||||
AppSettingsProviderContext,
|
||||
MediaPlayerProviderContext,
|
||||
} from "@renderer/context";
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
import { LoaderSpin } from "@renderer/components";
|
||||
import {
|
||||
Button,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
toast,
|
||||
} from "@renderer/components/ui";
|
||||
import { t } from "i18next";
|
||||
import { formatDateTime } from "@renderer/lib/utils";
|
||||
import {
|
||||
CheckCircleIcon,
|
||||
ChevronLeftIcon,
|
||||
ChevronRightIcon,
|
||||
DownloadCloudIcon,
|
||||
} from "lucide-react";
|
||||
|
||||
export const TranscriptionsList = (props: {
|
||||
media: AudioType | VideoType;
|
||||
transcription?: TranscriptionType;
|
||||
onFinish?: () => void;
|
||||
}) => {
|
||||
const { media, transcription, onFinish } = props;
|
||||
const { webApi, EnjoyApp } = useContext(AppSettingsProviderContext);
|
||||
const [transcriptions, setTranscriptions] = useState<TranscriptionType[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [nextPage, setNextPage] = useState(1);
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
|
||||
const fetchTranscriptions = async (params: { page: number }) => {
|
||||
const { page = currentPage } = params;
|
||||
setLoading(true);
|
||||
webApi
|
||||
.transcriptions({
|
||||
targetMd5: media.md5,
|
||||
items: 10,
|
||||
page,
|
||||
})
|
||||
.then(({ transcriptions, next, page }) => {
|
||||
setTranscriptions(transcriptions);
|
||||
setCurrentPage(page);
|
||||
setNextPage(next);
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
const handleDownload = (tr: TranscriptionType) => {
|
||||
EnjoyApp.transcriptions
|
||||
.update(transcription.id, {
|
||||
state: "finished",
|
||||
result: tr.result,
|
||||
engine: tr.engine,
|
||||
model: tr.model,
|
||||
language: tr.language || media.language,
|
||||
})
|
||||
.then(() => {
|
||||
onFinish?.();
|
||||
})
|
||||
.catch((err) => {
|
||||
toast.error(err.message);
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchTranscriptions({ page: 1 });
|
||||
}, [media]);
|
||||
|
||||
if (loading) {
|
||||
return <LoaderSpin />;
|
||||
}
|
||||
|
||||
if (!transcriptions.length) {
|
||||
return (
|
||||
<div className="text-muted-foreground text-center text-sm py-6">
|
||||
{t("noData")}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>ID</TableHead>
|
||||
<TableHead>{t("model")}</TableHead>
|
||||
<TableHead>{t("language")}</TableHead>
|
||||
<TableHead>{t("date")}</TableHead>
|
||||
<TableHead>{t("actions")}</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{transcriptions.map((tr) => (
|
||||
<TableRow key={tr.id}>
|
||||
<TableCell className="text-xs font-mono">
|
||||
{tr.id.split("-")[0]}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div className="text-sm">{tr.engine}</div>
|
||||
<div className="text-xs text-muted-foreground">{tr.model}</div>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<span className="text-sm">{tr.language || "-"}</span>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<span className="text-xs">{formatDateTime(tr.createdAt)}</span>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{transcription?.id === tr.id ? (
|
||||
<CheckCircleIcon className="text-green-600 w-4 h-4" />
|
||||
) : (
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="icon"
|
||||
onClick={() => handleDownload(tr)}
|
||||
>
|
||||
<DownloadCloudIcon className="w-4 h-4" />
|
||||
</Button>
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
|
||||
<div className="text-muted-foreground flex items-center justify-center space-x-2 my-4">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
disabled={currentPage <= 1}
|
||||
onClick={() => fetchTranscriptions({ page: currentPage - 1 })}
|
||||
>
|
||||
<ChevronLeftIcon className="w-5 h-5" />
|
||||
</Button>
|
||||
<span className="text-sm font-mono">{currentPage}</span>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
disabled={!nextPage}
|
||||
onClick={() => fetchTranscriptions({ page: currentPage + 1 })}
|
||||
>
|
||||
<ChevronRightIcon className="w-5 h-5" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
2
enjoy/src/types/transcription.d.ts
vendored
2
enjoy/src/types/transcription.d.ts
vendored
@@ -8,6 +8,8 @@ type TranscriptionType = {
|
||||
model: string;
|
||||
language?: string;
|
||||
result: AlignmentResult & { original?: string };
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
};
|
||||
|
||||
type TranscriptionResultSegmentType = {
|
||||
|
||||
Reference in New Issue
Block a user