Feat: may download files(recording/audio/video/speech) (#431)
* may download recording * may download recording/audio/video/speech
This commit is contained in:
@@ -227,6 +227,8 @@
|
||||
"welcomeBack": "Welcome back! {{name}}",
|
||||
"download": "Download",
|
||||
"downloading": "Downloading {{file}}",
|
||||
"downloadedSuccessfully": "Downloaded successfully",
|
||||
"downloadFailed": "Download failed",
|
||||
"chooseAIModelDependingOnYourHardware": "Choose AI Model depending on your hardware.",
|
||||
"areYouSureToDownload": "Are you sure to download {{name}}?",
|
||||
"yourModelsWillBeDownloadedTo": "Your models will be downloaded to {{path}}",
|
||||
|
||||
@@ -227,6 +227,8 @@
|
||||
"welcomeBack": "欢迎回来, {{name}}",
|
||||
"download": "下载",
|
||||
"downloading": "正在下载 {{file}}",
|
||||
"downloadedSuccessfully": "下载成功",
|
||||
"downloadFailed": "下载失败",
|
||||
"chooseAIModelDependingOnYourHardware": "根据您的硬件选择合适的 AI 模型, 以便语音转文本服务正常工作",
|
||||
"areYouSureToDownload": "您确定要下载 {{name}} 吗?",
|
||||
"yourModelsWillBeDownloadedTo": "您的模型将下载到目录 {{path}}",
|
||||
|
||||
@@ -134,6 +134,11 @@ export class Audio extends Model<Audio> {
|
||||
return "Audio";
|
||||
}
|
||||
|
||||
@Column(DataType.VIRTUAL)
|
||||
get filename(): string {
|
||||
return this.getDataValue("md5") + this.extname;
|
||||
}
|
||||
|
||||
get extname(): string {
|
||||
return (
|
||||
this.getDataValue("metadata").extname ||
|
||||
|
||||
@@ -104,6 +104,11 @@ export class Speech extends Model<Speech> {
|
||||
)}`;
|
||||
}
|
||||
|
||||
@Column(DataType.VIRTUAL)
|
||||
get filename(): string {
|
||||
return this.getDataValue("md5") + this.getDataValue("extname");
|
||||
}
|
||||
|
||||
@Column(DataType.VIRTUAL)
|
||||
get filePath(): string {
|
||||
return path.join(
|
||||
|
||||
@@ -134,6 +134,11 @@ export class Video extends Model<Video> {
|
||||
return "Video";
|
||||
}
|
||||
|
||||
@Column(DataType.VIRTUAL)
|
||||
get filename(): string {
|
||||
return this.getDataValue("md5") + this.extname;
|
||||
}
|
||||
|
||||
get extname(): string {
|
||||
return (
|
||||
this.getDataValue("metadata").extname ||
|
||||
|
||||
@@ -24,9 +24,13 @@ class Downloader {
|
||||
webContents.downloadURL(url);
|
||||
webContents.session.on("will-download", (_event, item, _webContents) => {
|
||||
if (savePath) {
|
||||
if (fs.statSync(savePath).isDirectory()) {
|
||||
item.setSavePath(path.join(savePath, item.getFilename()));
|
||||
} else {
|
||||
try {
|
||||
if (fs.statSync(savePath).isDirectory()) {
|
||||
item.setSavePath(path.join(savePath, item.getFilename()));
|
||||
} else {
|
||||
item.setSavePath(savePath);
|
||||
}
|
||||
} catch {
|
||||
item.setSavePath(savePath);
|
||||
}
|
||||
} else {
|
||||
@@ -106,7 +110,7 @@ class Downloader {
|
||||
|
||||
registerIpcHandlers() {
|
||||
ipcMain.handle("download-start", (event, url, savePath) => {
|
||||
this.download(url, {
|
||||
return this.download(url, {
|
||||
webContents: event.sender,
|
||||
savePath,
|
||||
});
|
||||
|
||||
@@ -406,7 +406,7 @@ contextBridge.exposeInMainWorld("__ENJOY_APP__", {
|
||||
callback: (event: IpcRendererEvent, state: DownloadStateType) => void
|
||||
) => ipcRenderer.on("download-on-state", callback),
|
||||
start: (url: string, savePath?: string) => {
|
||||
ipcRenderer.invoke("download-start", url, savePath);
|
||||
return ipcRenderer.invoke("download-start", url, savePath);
|
||||
},
|
||||
cancel: (filename: string) => {
|
||||
ipcRenderer.invoke("download-cancel", filename);
|
||||
|
||||
@@ -39,6 +39,7 @@ import {
|
||||
TextCursorInputIcon,
|
||||
MicIcon,
|
||||
SquareIcon,
|
||||
DownloadIcon,
|
||||
} from "lucide-react";
|
||||
import { t } from "i18next";
|
||||
import { formatDuration } from "@renderer/lib/utils";
|
||||
@@ -195,6 +196,30 @@ export const MediaCurrentRecording = (props: { height?: number }) => {
|
||||
});
|
||||
};
|
||||
|
||||
const handleDownload = () => {
|
||||
EnjoyApp.dialog
|
||||
.showSaveDialog({
|
||||
title: t("download"),
|
||||
defaultPath: currentRecording.filename,
|
||||
})
|
||||
.then((savePath) => {
|
||||
if (!savePath) return;
|
||||
|
||||
toast.promise(
|
||||
EnjoyApp.download.start(currentRecording.src, savePath as string),
|
||||
{
|
||||
loading: t("downloading", { file: currentRecording.filename }),
|
||||
success: () => t("downloadedSuccessfully"),
|
||||
error: t("downloadFailed"),
|
||||
position: "bottom-right",
|
||||
}
|
||||
);
|
||||
})
|
||||
.catch((err) => {
|
||||
toast.error(err.message);
|
||||
});
|
||||
};
|
||||
|
||||
const calContainerWidth = () => {
|
||||
const w = document
|
||||
.querySelector(".media-recording-container")
|
||||
@@ -515,6 +540,14 @@ export const MediaCurrentRecording = (props: { height?: number }) => {
|
||||
<Share2Icon className="w-4 h-4 mr-4" />
|
||||
<span>{t("share")}</span>
|
||||
</DropdownMenuItem>
|
||||
|
||||
<DropdownMenuItem
|
||||
className="cursor-pointer"
|
||||
onClick={handleDownload}
|
||||
>
|
||||
<DownloadIcon className="w-4 h-4 mr-4" />
|
||||
<span>{t("download")}</span>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
ZoomInIcon,
|
||||
ZoomOutIcon,
|
||||
MoreVerticalIcon,
|
||||
DownloadIcon,
|
||||
} from "lucide-react";
|
||||
|
||||
const ZOOM_RATIO_OPTIONS = [
|
||||
@@ -93,6 +94,27 @@ export const MediaPlayer = () => {
|
||||
setWidth(w - 48);
|
||||
};
|
||||
|
||||
const handleDownload = () => {
|
||||
EnjoyApp.dialog
|
||||
.showSaveDialog({
|
||||
title: t("download"),
|
||||
defaultPath: media.filename,
|
||||
})
|
||||
.then((savePath) => {
|
||||
if (!savePath) return;
|
||||
|
||||
toast.promise(EnjoyApp.download.start(media.src, savePath as string), {
|
||||
loading: t("downloading", { file: media.filename }),
|
||||
success: () => t("downloadedSuccessfully"),
|
||||
error: t("downloadFailed"),
|
||||
position: "bottom-right",
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
toast.error(err.message);
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (ref?.current) {
|
||||
setRef(ref);
|
||||
@@ -233,6 +255,14 @@ export const MediaPlayer = () => {
|
||||
<Share2Icon className="w-4 h-4 mr-4" />
|
||||
<span>{t("share")}</span>
|
||||
</DropdownMenuItem>
|
||||
|
||||
<DropdownMenuItem
|
||||
className="cursor-pointer"
|
||||
onClick={handleDownload}
|
||||
>
|
||||
<DownloadIcon className="w-4 h-4 mr-4" />
|
||||
<span>{t("download")}</span>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
ForwardIcon,
|
||||
AlertCircleIcon,
|
||||
MoreVerticalIcon,
|
||||
DownloadIcon,
|
||||
} from "lucide-react";
|
||||
import { useCopyToClipboard } from "@uidotdev/usehooks";
|
||||
import { t } from "i18next";
|
||||
@@ -112,6 +113,29 @@ export const AssistantMessageComponent = (props: {
|
||||
setShadowing(true);
|
||||
};
|
||||
|
||||
const handleDownload = async () => {
|
||||
if (!speech) return;
|
||||
|
||||
EnjoyApp.dialog
|
||||
.showSaveDialog({
|
||||
title: t("download"),
|
||||
defaultPath: speech.filename,
|
||||
})
|
||||
.then((savePath) => {
|
||||
if (!savePath) return;
|
||||
|
||||
toast.promise(EnjoyApp.download.start(speech.src, savePath as string), {
|
||||
loading: t("downloading", { file: speech.filename }),
|
||||
success: () => t("downloadedSuccessfully"),
|
||||
error: t("downloadFailed"),
|
||||
position: "bottom-right",
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
toast.error(err.message);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
id={`message-${message.id}`}
|
||||
@@ -224,6 +248,15 @@ export const AssistantMessageComponent = (props: {
|
||||
className="w-3 h-3 cursor-pointer"
|
||||
/>
|
||||
))}
|
||||
{Boolean(speech) && (
|
||||
<DownloadIcon
|
||||
data-tooltip-id="global-tooltip"
|
||||
data-tooltip-content={t("download")}
|
||||
data-testid="message-start-shadow"
|
||||
onClick={handleDownload}
|
||||
className="w-3 h-3 cursor-pointer"
|
||||
/>
|
||||
)}
|
||||
|
||||
<DropdownMenuTrigger>
|
||||
<MoreVerticalIcon className="w-3 h-3" />
|
||||
|
||||
1
enjoy/src/types/audio.d.ts
vendored
1
enjoy/src/types/audio.d.ts
vendored
@@ -3,6 +3,7 @@ type AudioType = {
|
||||
id: string;
|
||||
source: string;
|
||||
name: string;
|
||||
filename: string;
|
||||
description?: string;
|
||||
src?: string;
|
||||
coverUrl?: string;
|
||||
|
||||
4
enjoy/src/types/enjoy-app.d.ts
vendored
4
enjoy/src/types/enjoy-app.d.ts
vendored
@@ -76,7 +76,7 @@ type EnjoyAppType = {
|
||||
) => Promise<string[] | undefined>;
|
||||
showSaveDialog: (
|
||||
options: Electron.SaveDialogOptions
|
||||
) => Promise<Electron.SaveDialogReturnValue>;
|
||||
) => Promise<string | undefined>;
|
||||
showMessageBox: (
|
||||
options: Electron.MessageBoxOptions
|
||||
) => Promise<Electron.MessageBoxReturnValue>;
|
||||
@@ -244,7 +244,7 @@ type EnjoyAppType = {
|
||||
};
|
||||
download: {
|
||||
onState: (callback: (event, state) => void) => void;
|
||||
start: (url: string, savePath?: string) => void;
|
||||
start: (url: string, savePath?: string) => Promise<string | undefined>;
|
||||
cancel: (filename: string) => Promise<void>;
|
||||
cancelAll: () => void;
|
||||
dashboard: () => Promise<DownloadStateType[]>;
|
||||
|
||||
1
enjoy/src/types/video.d.ts
vendored
1
enjoy/src/types/video.d.ts
vendored
@@ -3,6 +3,7 @@ type VideoType = {
|
||||
id: string;
|
||||
source: string;
|
||||
name: string;
|
||||
filename: string;
|
||||
description?: string;
|
||||
filename?: string;
|
||||
coverUrl?: string;
|
||||
|
||||
Reference in New Issue
Block a user