@@ -117,6 +117,7 @@
|
||||
"lucide-react": "^0.308.0",
|
||||
"mark.js": "^8.11.1",
|
||||
"microsoft-cognitiveservices-speech-sdk": "^1.34.0",
|
||||
"next-themes": "^0.2.1",
|
||||
"openai": "^4.24.1",
|
||||
"pitchfinder": "^2.3.2",
|
||||
"postcss": "^8.4.33",
|
||||
@@ -133,6 +134,7 @@
|
||||
"rimraf": "^5.0.5",
|
||||
"sequelize": "^6.35.2",
|
||||
"sequelize-typescript": "^2.1.6",
|
||||
"sonner": "^1.3.1",
|
||||
"sqlite3": "^5.1.7",
|
||||
"tailwind-scrollbar-hide": "^1.1.7",
|
||||
"umzug": "^3.5.0",
|
||||
|
||||
@@ -197,6 +197,7 @@
|
||||
"youAreReadyToGo": "You are ready to go",
|
||||
"welcomeBack": "Welcome back! {{name}}",
|
||||
"download": "Download",
|
||||
"downloading": "Downloading {{file}}",
|
||||
"chooseAIModelDependingOnYourHardware": "Choose AI Model depending on your hardware",
|
||||
"areYouSureToDownload": "Are you sure to download {{name}}?",
|
||||
"yourModelsWillBeDownloadedTo": "Your models will be downloaded to {{path}}",
|
||||
|
||||
@@ -197,6 +197,7 @@
|
||||
"youAreReadyToGo": "您已准备就绪",
|
||||
"welcomeBack": "欢迎回来, {{name}}",
|
||||
"download": "下载",
|
||||
"downloading": "正在下载 {{file}}",
|
||||
"chooseAIModelDependingOnYourHardware": "根据您的硬件选择合适的 AI 模型",
|
||||
"areYouSureToDownload": "您确定要下载 {{name}} 吗?",
|
||||
"yourModelsWillBeDownloadedTo": "您的模型将下载到目录 {{path}}",
|
||||
|
||||
@@ -6,19 +6,29 @@ import {
|
||||
} from "@renderer/context";
|
||||
import router from "./router";
|
||||
import { RouterProvider } from "react-router-dom";
|
||||
import { Toaster, useToast } from "@renderer/components/ui";
|
||||
import { t } from "i18next";
|
||||
import { Toaster, toast } from "@renderer/components/ui";
|
||||
import { Tooltip } from "react-tooltip";
|
||||
import { useHotkeys } from "react-hotkeys-hook";
|
||||
|
||||
function App() {
|
||||
const { toast } = useToast();
|
||||
window.__ENJOY_APP__.onNotification((_event, notification) => {
|
||||
toast({
|
||||
title: t(notification.type),
|
||||
description: notification.message,
|
||||
variant: notification.type === "error" ? "destructive" : "default",
|
||||
});
|
||||
switch (notification.type) {
|
||||
case "success":
|
||||
toast.success(notification.message);
|
||||
break;
|
||||
case "error":
|
||||
toast.error(notification.message);
|
||||
break;
|
||||
case "info":
|
||||
toast.info(notification.message);
|
||||
break;
|
||||
case "warning":
|
||||
toast.warning(notification.message);
|
||||
break;
|
||||
default:
|
||||
toast.message(notification.message);
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
const ControlOrCommand = navigator.platform.includes("Mac")
|
||||
@@ -43,7 +53,7 @@ function App() {
|
||||
<AISettingsProvider>
|
||||
<DbProvider>
|
||||
<RouterProvider router={router} />
|
||||
<Toaster />
|
||||
<Toaster richColors />
|
||||
<Tooltip id="global-tooltip" />
|
||||
</DbProvider>
|
||||
</AISettingsProvider>
|
||||
|
||||
@@ -21,13 +21,12 @@ import {
|
||||
AlertDialogCancel,
|
||||
Button,
|
||||
ScrollArea,
|
||||
useToast,
|
||||
toast,
|
||||
} from "@renderer/components/ui";
|
||||
import { t } from "i18next";
|
||||
|
||||
export const AudioDetail = (props: { id?: string; md5?: string }) => {
|
||||
const { id, md5 } = props;
|
||||
const { toast } = useToast();
|
||||
const { addDblistener, removeDbListener } = useContext(DbProviderContext);
|
||||
const { EnjoyApp, webApi } = useContext(AppSettingsProviderContext);
|
||||
|
||||
@@ -62,8 +61,7 @@ export const AudioDetail = (props: { id?: string; md5?: string }) => {
|
||||
try {
|
||||
await EnjoyApp.audios.upload(audio.id);
|
||||
} catch (err) {
|
||||
toast({
|
||||
title: t("shareFailed"),
|
||||
toast.error(t("shareFailed"), {
|
||||
description: err.message,
|
||||
});
|
||||
return;
|
||||
@@ -75,14 +73,12 @@ export const AudioDetail = (props: { id?: string; md5?: string }) => {
|
||||
targetId: audio.id,
|
||||
})
|
||||
.then(() => {
|
||||
toast({
|
||||
title: t("shared"),
|
||||
toast.success(t("sharedSuccessfully"), {
|
||||
description: t("sharedAudio"),
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
toast({
|
||||
title: t("shareFailed"),
|
||||
toast.error(t("shareFailed"), {
|
||||
description: err.message,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -25,7 +25,7 @@ import {
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
useToast,
|
||||
toast,
|
||||
} from "@renderer/components/ui";
|
||||
import {
|
||||
DbProviderContext,
|
||||
@@ -48,7 +48,6 @@ export const AudiosComponent = () => {
|
||||
const { EnjoyApp } = useContext(AppSettingsProviderContext);
|
||||
const [offset, setOffest] = useState(0);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const { toast } = useToast();
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
@@ -87,10 +86,7 @@ export const AudiosComponent = () => {
|
||||
dispatchAudios({ type: "append", records: _audios });
|
||||
})
|
||||
.catch((err) => {
|
||||
toast({
|
||||
description: err.message,
|
||||
variant: "destructive",
|
||||
});
|
||||
toast.error(err.message);
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
import { AppSettingsProviderContext } from "@renderer/context";
|
||||
import { ScrollArea } from "@renderer/components/ui";
|
||||
import { ScrollArea, toast } from "@renderer/components/ui";
|
||||
import { LoaderSpin } from "@renderer/components";
|
||||
import { MessageCircleIcon } from "lucide-react";
|
||||
|
||||
@@ -20,11 +20,10 @@ export const ConversationsShortcut = (props: {
|
||||
content: prompt,
|
||||
})
|
||||
.then((replies) => {
|
||||
console.log(replies);
|
||||
onReply(replies);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
toast.error(error.message);
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { Button, useToast } from "@renderer/components/ui";
|
||||
import { Button, toast } from "@renderer/components/ui";
|
||||
import { useContext, useEffect } from "react";
|
||||
import { AppSettingsProviderContext } from "@renderer/context";
|
||||
import { t } from "i18next";
|
||||
|
||||
export const LoginForm = () => {
|
||||
const { toast } = useToast();
|
||||
const { EnjoyApp, login, webApi } = useContext(AppSettingsProviderContext);
|
||||
|
||||
const handleMixinLogin = () => {
|
||||
@@ -21,11 +20,7 @@ export const LoginForm = () => {
|
||||
const { state, url, error } = event;
|
||||
|
||||
if (error) {
|
||||
toast({
|
||||
title: t("error"),
|
||||
description: error,
|
||||
variant: "destructive",
|
||||
});
|
||||
toast.error(error);
|
||||
EnjoyApp.view.hide();
|
||||
return;
|
||||
}
|
||||
@@ -35,11 +30,7 @@ export const LoginForm = () => {
|
||||
const code = new URL(url).searchParams.get("code");
|
||||
|
||||
if (!url.startsWith(webApi.baseUrl)) {
|
||||
toast({
|
||||
title: t("error"),
|
||||
description: t("invalidRedirectUrl"),
|
||||
variant: "destructive",
|
||||
});
|
||||
toast.error(t("invalidRedirectUrl"));
|
||||
EnjoyApp.view.hide();
|
||||
}
|
||||
|
||||
@@ -53,11 +44,7 @@ export const LoginForm = () => {
|
||||
EnjoyApp.view.hide();
|
||||
});
|
||||
} else {
|
||||
toast({
|
||||
title: t("error"),
|
||||
description: t("failedToLogin"),
|
||||
variant: "destructive",
|
||||
});
|
||||
toast.error(t("failedToLogin"));
|
||||
EnjoyApp.view.hide();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuSeparator,
|
||||
useToast,
|
||||
toast,
|
||||
} from "@renderer/components/ui";
|
||||
import { SpeechPlayer } from "@renderer/components";
|
||||
import { useContext, useState } from "react";
|
||||
@@ -45,7 +45,6 @@ export const UserMessageComponent = (props: {
|
||||
const { user, webApi } = useContext(AppSettingsProviderContext);
|
||||
const [_, copyToClipboard] = useCopyToClipboard();
|
||||
const [copied, setCopied] = useState<boolean>(false);
|
||||
const { toast } = useToast();
|
||||
|
||||
const handleShare = async () => {
|
||||
if (message.role === "user") {
|
||||
@@ -58,15 +57,10 @@ export const UserMessageComponent = (props: {
|
||||
},
|
||||
})
|
||||
.then(() => {
|
||||
toast({
|
||||
description: t("sharedPrompt"),
|
||||
});
|
||||
toast(t("sharedSuccessfully"), { description: t("sharedPrompt") });
|
||||
})
|
||||
.catch((err) => {
|
||||
toast({
|
||||
title: t("shareFailed"),
|
||||
description: err.message,
|
||||
});
|
||||
toast.error(t("shareFailed"), { description: err.message });
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
import { useContext, useState } from "react";
|
||||
import { AppSettingsProviderContext } from "@renderer/context";
|
||||
import { ConversationsShortcut } from "@renderer/components";
|
||||
import {
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
ScrollArea,
|
||||
useToast,
|
||||
toast,
|
||||
} from "@renderer/components/ui";
|
||||
import { t } from "i18next";
|
||||
import Markdown from "react-markdown";
|
||||
@@ -37,7 +37,6 @@ export const PostActions = (props: { post: PostType }) => {
|
||||
const [_, copyToClipboard] = useCopyToClipboard();
|
||||
const [copied, setCopied] = useState<boolean>(false);
|
||||
const { EnjoyApp } = useContext(AppSettingsProviderContext);
|
||||
const { toast } = useToast();
|
||||
const [asking, setAsking] = useState<boolean>(false);
|
||||
const [aiReplies, setAiReplies] = useState<MessageType[]>([]);
|
||||
|
||||
@@ -50,9 +49,7 @@ export const PostActions = (props: { post: PostType }) => {
|
||||
try {
|
||||
const video = await EnjoyApp.videos.findOne({ md5: medium.md5 });
|
||||
if (video) {
|
||||
toast({
|
||||
description: t("videoAlreadyAddedToLibrary"),
|
||||
});
|
||||
toast.info(t("videoAlreadyAddedToLibrary"));
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -65,21 +62,17 @@ export const PostActions = (props: { post: PostType }) => {
|
||||
md5: medium.md5,
|
||||
})
|
||||
.then(() => {
|
||||
toast({
|
||||
description: t("videoSuccessfullyAddedToLibrary"),
|
||||
});
|
||||
toast.success(t("videoSuccessfullyAddedToLibrary"));
|
||||
});
|
||||
} else if (medium.mediumType === "Audio") {
|
||||
try {
|
||||
const audio = await EnjoyApp.audios.findOne({ md5: medium.md5 });
|
||||
if (audio) {
|
||||
toast({
|
||||
description: t("audioAlreadyAddedToLibrary"),
|
||||
});
|
||||
toast.info(t("audioAlreadyAddedToLibrary"));
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
toast.error(error.message);
|
||||
}
|
||||
|
||||
EnjoyApp.audios
|
||||
@@ -88,9 +81,7 @@ export const PostActions = (props: { post: PostType }) => {
|
||||
md5: medium.md5,
|
||||
})
|
||||
.then(() => {
|
||||
toast({
|
||||
description: t("audioSuccessfullyAddedToLibrary"),
|
||||
});
|
||||
toast.success(t("audioSuccessfullyAddedToLibrary"));
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -6,12 +6,83 @@ import { Button, Skeleton } from "@renderer/components/ui";
|
||||
import { PlayIcon, PauseIcon } from "lucide-react";
|
||||
import { useIntersectionObserver } from "@uidotdev/usehooks";
|
||||
import { secondsToTimestamp } from "@renderer/lib/utils";
|
||||
import { MediaPlayer, MediaProvider } from "@vidstack/react";
|
||||
import {
|
||||
DefaultAudioLayout,
|
||||
defaultLayoutIcons,
|
||||
} from "@vidstack/react/player/layouts/default";
|
||||
export const STORAGE_WORKER_ENDPOINT = "https://enjoy-storage.baizhiheizi.com";
|
||||
|
||||
export const PostAudio = (props: {
|
||||
audio: Partial<MediumType>;
|
||||
height?: number;
|
||||
}) => {
|
||||
const { audio, height = 80 } = props;
|
||||
const [currentTime, setCurrentTime] = useState<number>(0);
|
||||
const { webApi } = useContext(AppSettingsProviderContext);
|
||||
const [transcription, setTranscription] = useState<TranscriptionType>();
|
||||
|
||||
const currentTranscription = (transcription?.result || []).find(
|
||||
(s) =>
|
||||
currentTime >= s.offsets.from / 1000.0 &&
|
||||
currentTime <= s.offsets.to / 1000.0
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
webApi
|
||||
.transcriptions({
|
||||
targetMd5: audio.md5,
|
||||
})
|
||||
.then((response) => {
|
||||
setTranscription(response?.transcriptions?.[0]);
|
||||
});
|
||||
}, [audio.md5]);
|
||||
|
||||
return (
|
||||
<div className="w-full">
|
||||
{audio.sourceUrl.startsWith(STORAGE_WORKER_ENDPOINT) ? (
|
||||
<WavesurferPlayer
|
||||
currentTime={currentTime}
|
||||
setCurrentTime={setCurrentTime}
|
||||
audio={audio}
|
||||
height={height}
|
||||
/>
|
||||
) : (
|
||||
<MediaPlayer
|
||||
onTimeUpdate={({ currentTime: _currentTime }) => {
|
||||
setCurrentTime(_currentTime);
|
||||
}}
|
||||
src={audio.sourceUrl}
|
||||
>
|
||||
<MediaProvider />
|
||||
<DefaultAudioLayout icons={defaultLayoutIcons} />
|
||||
</MediaPlayer>
|
||||
)}
|
||||
|
||||
{currentTranscription && (
|
||||
<div className="mt-2 bg-muted px-4 py-2 rounded">
|
||||
<div className="text-muted-foreground text-center font-serif">
|
||||
{currentTranscription.text}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{audio.coverUrl && (
|
||||
<div className="mt-2">
|
||||
<img src={audio.coverUrl} className="w-full rounded" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const WavesurferPlayer = (props: {
|
||||
audio: Partial<MediumType>;
|
||||
height?: number;
|
||||
currentTime: number;
|
||||
setCurrentTime: (currentTime: number) => void;
|
||||
}) => {
|
||||
const { audio, height = 80, currentTime, setCurrentTime } = props;
|
||||
const [initialized, setInitialized] = useState(false);
|
||||
const [isPlaying, setIsPlaying] = useState(false);
|
||||
const [wavesurfer, setWavesurfer] = useState(null);
|
||||
@@ -20,15 +91,6 @@ export const PostAudio = (props: {
|
||||
threshold: 1,
|
||||
});
|
||||
const [duration, setDuration] = useState<number>(0);
|
||||
const { webApi } = useContext(AppSettingsProviderContext);
|
||||
const [currentTime, setCurrentTime] = useState<number>(0);
|
||||
const [transcription, setTranscription] = useState<TranscriptionType>();
|
||||
|
||||
const currentTranscription = (transcription?.result || []).find(
|
||||
(s) =>
|
||||
currentTime >= s.offsets.from / 1000.0 &&
|
||||
currentTime <= s.offsets.to / 1000.0
|
||||
);
|
||||
|
||||
const onPlayClick = useCallback(() => {
|
||||
wavesurfer.isPlaying() ? wavesurfer.pause() : wavesurfer.play();
|
||||
@@ -93,18 +155,8 @@ export const PostAudio = (props: {
|
||||
};
|
||||
}, [wavesurfer]);
|
||||
|
||||
useEffect(() => {
|
||||
webApi
|
||||
.transcriptions({
|
||||
targetMd5: audio.md5,
|
||||
})
|
||||
.then((response) => {
|
||||
setTranscription(response?.transcriptions?.[0]);
|
||||
});
|
||||
}, [audio.md5]);
|
||||
|
||||
return (
|
||||
<div className="w-full">
|
||||
<>
|
||||
<div className="flex justify-end">
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{secondsToTimestamp(duration)}
|
||||
@@ -141,20 +193,6 @@ export const PostAudio = (props: {
|
||||
ref={containerRef}
|
||||
></div>
|
||||
</div>
|
||||
|
||||
{currentTranscription && (
|
||||
<div className="mt-2 bg-muted px-4 py-2 rounded">
|
||||
<div className="text-muted-foreground text-center font-serif">
|
||||
{currentTranscription.text}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{audio.coverUrl && (
|
||||
<div className="">
|
||||
<img src={audio.coverUrl} className="w-full rounded" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -37,7 +37,7 @@ export const PostMedium = (props: { medium: MediumType }) => {
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{t("sharedAudio")}
|
||||
</div>
|
||||
<PostAudio audio={medium as Partial<AudioType>} />
|
||||
<PostAudio audio={medium} />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
import { AppSettingsProviderContext } from "@renderer/context";
|
||||
import { PostCard, LoaderSpin } from "@renderer/components";
|
||||
import { useToast, Button } from "@renderer/components//ui";
|
||||
import { toast, Button } from "@renderer/components//ui";
|
||||
import { t } from "i18next";
|
||||
|
||||
export const Posts = () => {
|
||||
@@ -9,23 +9,16 @@ export const Posts = () => {
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
const [posts, setPosts] = useState<PostType[]>([]);
|
||||
const [nextPage, setNextPage] = useState(1);
|
||||
const { toast } = useToast();
|
||||
|
||||
const handleDelete = (id: string) => {
|
||||
webApi
|
||||
.deletePost(id)
|
||||
.then(() => {
|
||||
toast({
|
||||
description: t("removeSharingSuccessfully"),
|
||||
});
|
||||
toast.success(t("removeSharingSuccessfully"));
|
||||
setPosts(posts.filter((post) => post.id !== id));
|
||||
})
|
||||
.catch((error) => {
|
||||
toast({
|
||||
title: t("removeSharingFailed"),
|
||||
description: error.message,
|
||||
variant: "destructive",
|
||||
});
|
||||
toast.error(t("removeSharingFailed"), { description: error.message });
|
||||
});
|
||||
};
|
||||
|
||||
@@ -42,10 +35,7 @@ export const Posts = () => {
|
||||
setNextPage(res.next);
|
||||
})
|
||||
.catch((err) => {
|
||||
toast({
|
||||
description: err.message,
|
||||
variant: "destructive",
|
||||
});
|
||||
toast.error(err.message);
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { t } from "i18next";
|
||||
import { Button, useToast } from "@renderer/components/ui";
|
||||
import { Button, toast } from "@renderer/components/ui";
|
||||
import { AppSettingsProviderContext } from "@renderer/context";
|
||||
import { useState, useContext } from "react";
|
||||
import { LoaderIcon } from "lucide-react";
|
||||
@@ -7,14 +7,11 @@ import { LoaderIcon } from "lucide-react";
|
||||
export const About = () => {
|
||||
const { version } = useContext(AppSettingsProviderContext);
|
||||
const [checking, setChecking] = useState<boolean>(false);
|
||||
const { toast } = useToast();
|
||||
const checkUpdate = () => {
|
||||
setChecking(true);
|
||||
setTimeout(() => {
|
||||
setChecking(false);
|
||||
toast({
|
||||
description: t("alreadyLatestVersion"),
|
||||
});
|
||||
toast.info(t("alreadyLatestVersion"));
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ import {
|
||||
Input,
|
||||
Label,
|
||||
Separator,
|
||||
useToast,
|
||||
toast,
|
||||
} from "@renderer/components/ui";
|
||||
import { WhisperModelOptions } from "@renderer/components";
|
||||
import {
|
||||
@@ -196,7 +196,6 @@ const OpenaiSettings = () => {
|
||||
const { openai, setOpenai } = useContext(AISettingsProviderContext);
|
||||
const [editing, setEditing] = useState(false);
|
||||
const ref = useRef<HTMLInputElement>();
|
||||
const { toast } = useToast();
|
||||
|
||||
const handleSave = () => {
|
||||
if (!ref.current) return;
|
||||
@@ -206,10 +205,7 @@ const OpenaiSettings = () => {
|
||||
});
|
||||
setEditing(false);
|
||||
|
||||
toast({
|
||||
title: t("success"),
|
||||
description: t("openaiKeySaved"),
|
||||
});
|
||||
toast.success(t("openaiKeySaved"));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
@@ -264,7 +260,6 @@ const GoogleGenerativeAiSettings = () => {
|
||||
);
|
||||
const [editing, setEditing] = useState(false);
|
||||
const ref = useRef<HTMLInputElement>();
|
||||
const { toast } = useToast();
|
||||
|
||||
const handleSave = () => {
|
||||
if (!ref.current) return;
|
||||
@@ -274,10 +269,7 @@ const GoogleGenerativeAiSettings = () => {
|
||||
});
|
||||
setEditing(false);
|
||||
|
||||
toast({
|
||||
title: t("success"),
|
||||
description: t("googleGenerativeAiKeySaved"),
|
||||
});
|
||||
toast.success(t("googleGenerativeAiKeySaved"));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useState, useEffect, useRef } from "react";
|
||||
import RecordPlugin from "wavesurfer.js/dist/plugins/record";
|
||||
import WaveSurfer from "wavesurfer.js";
|
||||
import { cn } from "@renderer/lib/utils";
|
||||
import { RadialProgress, useToast } from "@renderer/components/ui";
|
||||
import { RadialProgress, toast } from "@renderer/components/ui";
|
||||
import { useHotkeys } from "react-hotkeys-hook";
|
||||
|
||||
export const RecordButton = (props: {
|
||||
@@ -16,7 +16,6 @@ export const RecordButton = (props: {
|
||||
const { className, disabled, onRecordBegin, onRecordEnd } = props;
|
||||
const [isRecording, setIsRecording] = useState<boolean>(false);
|
||||
const [duration, setDuration] = useState<number>(0);
|
||||
const { toast } = useToast();
|
||||
|
||||
useHotkeys(["command+alt+r", "control+alt+r"], () => {
|
||||
if (disabled) return;
|
||||
@@ -67,10 +66,7 @@ export const RecordButton = (props: {
|
||||
if (duration > 1000) {
|
||||
onRecordEnd(blob, duration);
|
||||
} else {
|
||||
toast({
|
||||
description: t("recordTooShort"),
|
||||
variant: "warning",
|
||||
});
|
||||
toast.warning(t("recordTooShort"));
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
useToast,
|
||||
toast,
|
||||
} from "@renderer/components/ui";
|
||||
import {
|
||||
MoreHorizontalIcon,
|
||||
@@ -36,7 +36,6 @@ export const RecordingCard = (props: {
|
||||
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false);
|
||||
const { EnjoyApp, webApi } = useContext(AppSettingsProviderContext);
|
||||
const [isPlaying, setIsPlaying] = useState(false);
|
||||
const { toast } = useToast();
|
||||
|
||||
const handleDelete = () => {
|
||||
EnjoyApp.recordings.destroy(recording.id);
|
||||
@@ -46,11 +45,7 @@ export const RecordingCard = (props: {
|
||||
try {
|
||||
await EnjoyApp.recordings.upload(recording.id);
|
||||
} catch (error) {
|
||||
toast({
|
||||
title: t("shareFailed"),
|
||||
description: error.message,
|
||||
variant: "destructive",
|
||||
});
|
||||
toast.error(t("shareFailed"), { description: error.message });
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -61,14 +56,13 @@ export const RecordingCard = (props: {
|
||||
targetType: "Recording",
|
||||
})
|
||||
.then(() => {
|
||||
toast({
|
||||
toast.success(t("sharedSuccessfully"), {
|
||||
description: t("sharedRecording"),
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
toast({
|
||||
toast.error(t("shareFailed"), {
|
||||
description: error.message,
|
||||
variant: "destructive",
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@@ -13,9 +13,9 @@ export * from "./input";
|
||||
export * from "./avatar";
|
||||
export * from "./alert-dialog";
|
||||
export * from "./card";
|
||||
export * from "./toast";
|
||||
export * from "./use-toast";
|
||||
export * from "./toaster";
|
||||
// export * from "./toast";
|
||||
// export * from "./use-toast";
|
||||
// export * from "./toaster";
|
||||
export * from "./toggle";
|
||||
export * from "./radio-group";
|
||||
export * from "./scroll-area";
|
||||
@@ -33,3 +33,4 @@ export * from "./select";
|
||||
export * from "./sheet";
|
||||
export * from "./hover-card";
|
||||
export * from "./floating-toolbar";
|
||||
export * from "./sonner";
|
||||
|
||||
31
enjoy/src/renderer/components/ui/sonner.tsx
Normal file
31
enjoy/src/renderer/components/ui/sonner.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
"use client";
|
||||
|
||||
import { useTheme } from "next-themes";
|
||||
import { Toaster as Sonner, toast } from "sonner";
|
||||
|
||||
type ToasterProps = React.ComponentProps<typeof Sonner>;
|
||||
|
||||
const Toaster = ({ ...props }: ToasterProps) => {
|
||||
const { theme = "system" } = useTheme();
|
||||
|
||||
return (
|
||||
<Sonner
|
||||
theme={theme as ToasterProps["theme"]}
|
||||
className="toaster group"
|
||||
toastOptions={{
|
||||
classNames: {
|
||||
toast:
|
||||
"group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg",
|
||||
description: "group-[.toast]:text-muted-foreground",
|
||||
actionButton:
|
||||
"group-[.toast]:bg-primary group-[.toast]:text-primary-foreground",
|
||||
cancelButton:
|
||||
"group-[.toast]:bg-muted group-[.toast]:text-muted-foreground",
|
||||
},
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export { Toaster, toast };
|
||||
@@ -21,13 +21,12 @@ import {
|
||||
AlertDialogCancel,
|
||||
Button,
|
||||
ScrollArea,
|
||||
useToast,
|
||||
toast,
|
||||
} from "@renderer/components/ui";
|
||||
import { t } from "i18next";
|
||||
|
||||
export const VideoDetail = (props: { id?: string; md5?: string }) => {
|
||||
const { id, md5 } = props;
|
||||
const { toast } = useToast();
|
||||
const { addDblistener, removeDbListener } = useContext(DbProviderContext);
|
||||
const { EnjoyApp, webApi } = useContext(AppSettingsProviderContext);
|
||||
|
||||
@@ -61,8 +60,7 @@ export const VideoDetail = (props: { id?: string; md5?: string }) => {
|
||||
|
||||
const handleShare = async () => {
|
||||
if (!video.source.startsWith("http")) {
|
||||
toast({
|
||||
title: t("shareFailed"),
|
||||
toast.error(t("shareFailed"), {
|
||||
description: t("cannotShareLocalVideo"),
|
||||
});
|
||||
return;
|
||||
@@ -72,10 +70,7 @@ export const VideoDetail = (props: { id?: string; md5?: string }) => {
|
||||
try {
|
||||
await EnjoyApp.videos.upload(video.id);
|
||||
} catch (err) {
|
||||
toast({
|
||||
title: t("shareFailed"),
|
||||
description: err.message,
|
||||
});
|
||||
toast.error(t("shareFailed"), { description: err.message });
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -86,15 +81,10 @@ export const VideoDetail = (props: { id?: string; md5?: string }) => {
|
||||
targetId: video.id,
|
||||
})
|
||||
.then(() => {
|
||||
toast({
|
||||
description: t("sharedVideo"),
|
||||
});
|
||||
toast.success(t("sharedSuccessfully"), { description: t("sharedVideo") });
|
||||
})
|
||||
.catch((err) => {
|
||||
toast({
|
||||
title: t("shareFailed"),
|
||||
description: err.message,
|
||||
});
|
||||
toast.error(t("shareFailed"), { description: err.message });
|
||||
});
|
||||
setSharing(false);
|
||||
};
|
||||
|
||||
@@ -25,7 +25,7 @@ import {
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
useToast,
|
||||
toast,
|
||||
} from "@renderer/components/ui";
|
||||
import {
|
||||
DbProviderContext,
|
||||
@@ -48,7 +48,6 @@ export const VideosComponent = () => {
|
||||
const { EnjoyApp } = useContext(AppSettingsProviderContext);
|
||||
const [offset, setOffest] = useState(0);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const { toast } = useToast();
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
@@ -87,10 +86,7 @@ export const VideosComponent = () => {
|
||||
dispatchVideos({ type: "append", records: _videos });
|
||||
})
|
||||
.catch((err) => {
|
||||
toast({
|
||||
description: err.message,
|
||||
variant: "destructive",
|
||||
});
|
||||
toast.error(err.message);
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
CardContent,
|
||||
CardFooter,
|
||||
ScrollArea,
|
||||
useToast,
|
||||
toast,
|
||||
Progress,
|
||||
} from "@renderer/components/ui";
|
||||
import { t } from "i18next";
|
||||
@@ -67,10 +67,8 @@ export const WhisperModelOptionsPanel = () => {
|
||||
export const WhisperModelOptions = () => {
|
||||
const [selectingModel, setSelectingModel] = useState<ModelType | null>(null);
|
||||
const [availableModels, setAvailableModels] = useState<ModelType[]>([]);
|
||||
const { whisperModelsPath, whisperModel, setWhisperModel, EnjoyApp } = useContext(
|
||||
AppSettingsProviderContext
|
||||
);
|
||||
const { toast } = useToast();
|
||||
const { whisperModelsPath, whisperModel, setWhisperModel, EnjoyApp } =
|
||||
useContext(AppSettingsProviderContext);
|
||||
|
||||
useEffect(() => {
|
||||
updateAvailableModels();
|
||||
@@ -126,10 +124,7 @@ export const WhisperModelOptions = () => {
|
||||
if (option.downloaded) {
|
||||
setWhisperModel(option.name);
|
||||
} else if (option.downloadState) {
|
||||
toast({
|
||||
title: "Downloading",
|
||||
description: `${option.name} is downloading...`,
|
||||
});
|
||||
toast.warning(t("downloading", { file: option.name }));
|
||||
} else {
|
||||
setSelectingModel(option);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
Sheet,
|
||||
SheetContent,
|
||||
SheetTrigger,
|
||||
useToast,
|
||||
toast,
|
||||
} from "@renderer/components/ui";
|
||||
import { MessageComponent, ConversationForm } from "@renderer/components";
|
||||
import { SendIcon, BotIcon, LoaderIcon, SettingsIcon } from "lucide-react";
|
||||
@@ -29,8 +29,6 @@ export default () => {
|
||||
const [content, setConent] = useState<string>("");
|
||||
const [submitting, setSubmitting] = useState<boolean>(false);
|
||||
|
||||
const { toast } = useToast();
|
||||
|
||||
const [messages, dispatchMessages] = useReducer(messagesReducer, []);
|
||||
const [offset, setOffest] = useState(0);
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
@@ -79,10 +77,7 @@ export default () => {
|
||||
|
||||
const handleSubmit = async (text?: string, file?: string) => {
|
||||
if (submitting) {
|
||||
toast({
|
||||
title: t("warning"),
|
||||
description: t("anotherRequestIsPending"),
|
||||
});
|
||||
toast.warning(t("anotherRequestIsPending"));
|
||||
}
|
||||
text = text ? text : content;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Input, Button, ScrollArea, useToast } from "@renderer/components/ui";
|
||||
import { Input, Button, ScrollArea, toast } from "@renderer/components/ui";
|
||||
import {
|
||||
LoaderSpin,
|
||||
StoryViewer,
|
||||
@@ -27,7 +27,6 @@ export default () => {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [readable, setReadable] = useState(true);
|
||||
const { EnjoyApp, webApi } = useContext(AppSettingsProviderContext);
|
||||
const { toast } = useToast();
|
||||
const [meanings, setMeanings] = useState<MeaningType[]>([]);
|
||||
const [marked, setMarked] = useState<boolean>(false);
|
||||
const [doc, setDoc] = useState<any>(null);
|
||||
@@ -73,10 +72,7 @@ export default () => {
|
||||
if (state == "did-fail-load") {
|
||||
setLoading(false);
|
||||
if (error) {
|
||||
toast({
|
||||
title: error,
|
||||
variant: "destructive",
|
||||
});
|
||||
toast.error(error);
|
||||
setError(error);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { t } from "i18next";
|
||||
import { ScrollArea, useToast } from "@renderer/components/ui";
|
||||
import { ScrollArea, toast } from "@renderer/components/ui";
|
||||
import {
|
||||
LoaderSpin,
|
||||
PagePlaceholder,
|
||||
@@ -24,7 +24,6 @@ export default () => {
|
||||
const [scanning, setScanning] = useState<boolean>(false);
|
||||
const [marked, setMarked] = useState<boolean>(true);
|
||||
const [doc, setDoc] = useState<any>(null);
|
||||
const { toast } = useToast();
|
||||
|
||||
const fetchStory = async () => {
|
||||
webApi
|
||||
@@ -123,15 +122,11 @@ export default () => {
|
||||
webApi
|
||||
.createPost({ targetId: story.id, targetType: "Story" })
|
||||
.then(() => {
|
||||
toast({
|
||||
description: t("sharedStory"),
|
||||
});
|
||||
toast.success(t("sharedStory"));
|
||||
})
|
||||
.catch((error) => {
|
||||
toast({
|
||||
title: t("shareFailed"),
|
||||
toast.error(t("shareFailed"), {
|
||||
description: error.message,
|
||||
variant: "destructive",
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
23
yarn.lock
23
yarn.lock
@@ -5818,6 +5818,7 @@ __metadata:
|
||||
lucide-react: "npm:^0.308.0"
|
||||
mark.js: "npm:^8.11.1"
|
||||
microsoft-cognitiveservices-speech-sdk: "npm:^1.34.0"
|
||||
next-themes: "npm:^0.2.1"
|
||||
octokit: "npm:^3.1.2"
|
||||
openai: "npm:^4.24.1"
|
||||
pitchfinder: "npm:^2.3.2"
|
||||
@@ -5835,6 +5836,7 @@ __metadata:
|
||||
rimraf: "npm:^5.0.5"
|
||||
sequelize: "npm:^6.35.2"
|
||||
sequelize-typescript: "npm:^2.1.6"
|
||||
sonner: "npm:^1.3.1"
|
||||
sqlite3: "npm:^5.1.7"
|
||||
tailwind-merge: "npm:^2.2.0"
|
||||
tailwind-scrollbar-hide: "npm:^1.1.7"
|
||||
@@ -9561,6 +9563,17 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"next-themes@npm:^0.2.1":
|
||||
version: 0.2.1
|
||||
resolution: "next-themes@npm:0.2.1"
|
||||
peerDependencies:
|
||||
next: "*"
|
||||
react: "*"
|
||||
react-dom: "*"
|
||||
checksum: 979dec0a2de049ce7d1b5da835e7f7dc3b7ec83ba9e464348f497a52a6a6e5b5c395c97f071f66a63f50f22cce89fb6d19061ec7e75643b0eab215b21794bde7
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"nice-try@npm:^1.0.4":
|
||||
version: 1.0.5
|
||||
resolution: "nice-try@npm:1.0.5"
|
||||
@@ -11552,6 +11565,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"sonner@npm:^1.3.1":
|
||||
version: 1.3.1
|
||||
resolution: "sonner@npm:1.3.1"
|
||||
peerDependencies:
|
||||
react: ^18.0.0
|
||||
react-dom: ^18.0.0
|
||||
checksum: 324d2b2edb8bf15a38239f67e533bbf9f38425adf432c9542e7a9610b7451ec5d4082d2e8ddb564096f63f44ba7ec4d2e137f8b0af1786a0becac346fae2b28a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"source-map-js@npm:^1.0.2":
|
||||
version: 1.0.2
|
||||
resolution: "source-map-js@npm:1.0.2"
|
||||
|
||||
Reference in New Issue
Block a user