diff --git a/enjoy/components.json b/enjoy/components.json
index 84987e9b..8b573d4a 100644
--- a/enjoy/components.json
+++ b/enjoy/components.json
@@ -7,7 +7,7 @@
"config": "tailwind.config.js",
"css": "src/index.css",
"baseColor": "zinc",
- "cssVariables": true
+ "cssVariables": false
},
"aliases": {
"components": "src/renderer/components",
diff --git a/enjoy/src/main/db/index.ts b/enjoy/src/main/db/index.ts
index a42883a4..e183c70f 100644
--- a/enjoy/src/main/db/index.ts
+++ b/enjoy/src/main/db/index.ts
@@ -51,9 +51,6 @@ db.connect = async () => {
db.connection = sequelize;
- // vacuum the database
- await sequelize.query("VACUUM");
-
const umzug = new Umzug({
migrations: { glob: __dirname + "/migrations/*.js" },
context: sequelize.getQueryInterface(),
@@ -68,6 +65,23 @@ db.connect = async () => {
await sequelize.sync();
await sequelize.authenticate();
+ // TODO:
+ // clear the large waveform data in DB.
+ // Remove this in next release
+ const caches = await CacheObject.findAll({
+ attributes: ["id", "key"],
+ });
+ const cacheIds: string[] = [];
+ caches.forEach((cache) => {
+ if (cache.key.startsWith("waveform")) {
+ cacheIds.push(cache.id);
+ }
+ });
+ await CacheObject.destroy({ where: { id: cacheIds } });
+
+ // vacuum the database
+ await sequelize.query("VACUUM");
+
// register handlers
audiosHandler.register();
cacheObjectsHandler.register();
diff --git a/enjoy/src/main/waveform.ts b/enjoy/src/main/waveform.ts
new file mode 100644
index 00000000..e4b6a6a9
--- /dev/null
+++ b/enjoy/src/main/waveform.ts
@@ -0,0 +1,38 @@
+import { ipcMain } from "electron";
+import settings from "@main/settings";
+import path from "path";
+import fs from "fs-extra";
+
+export class Waveform {
+ public dir = path.join(settings.libraryPath(), "waveforms");
+
+ constructor() {
+ fs.ensureDirSync(this.dir);
+ }
+
+ find(id: string) {
+ const file = path.join(this.dir, id + ".waveform.json");
+
+ if (fs.existsSync(file)) {
+ return fs.readJsonSync(file);
+ } else {
+ return null;
+ }
+ }
+
+ save(id: string, data: WaveFormDataType) {
+ const file = path.join(this.dir, id + ".waveform.json");
+
+ fs.writeJsonSync(file, data);
+ }
+
+ registerIpcHandlers() {
+ ipcMain.handle("waveforms-find", async (_event, id) => {
+ return this.find(id);
+ });
+
+ ipcMain.handle("waveforms-save", (_event, id, data) => {
+ return this.save(id, data);
+ });
+ }
+}
diff --git a/enjoy/src/main/window.ts b/enjoy/src/main/window.ts
index 5c3b69c2..804e8170 100644
--- a/enjoy/src/main/window.ts
+++ b/enjoy/src/main/window.ts
@@ -18,6 +18,7 @@ import log from "electron-log/main";
import { WEB_API_URL } from "@/constants";
import { AudibleProvider, TedProvider } from "@main/providers";
import { FfmpegDownloader } from "@main/ffmpeg";
+import { Waveform } from "./waveform";
log.initialize({ preload: true });
const logger = log.scope("window");
@@ -25,6 +26,7 @@ const logger = log.scope("window");
const audibleProvider = new AudibleProvider();
const tedProvider = new TedProvider();
const ffmpegDownloader = new FfmpegDownloader();
+const waveform = new Waveform();
const main = {
win: null as BrowserWindow | null,
@@ -46,6 +48,9 @@ main.init = () => {
// Whisper
whisper.registerIpcHandlers();
+ // Waveform
+ waveform.registerIpcHandlers();
+
// Downloader
downloader.registerIpcHandlers();
diff --git a/enjoy/src/preload.ts b/enjoy/src/preload.ts
index 4fbfabc2..9d53e6f2 100644
--- a/enjoy/src/preload.ts
+++ b/enjoy/src/preload.ts
@@ -399,4 +399,12 @@ contextBridge.exposeInMainWorld("__ENJOY_APP__", {
return ipcRenderer.invoke("transcriptions-update", id, params);
},
},
+ waveforms: {
+ find: (id: string) => {
+ return ipcRenderer.invoke("waveforms-find", id);
+ },
+ save: (id: string, data: WaveFormDataType) => {
+ return ipcRenderer.invoke("waveforms-save", id, data);
+ },
+ }
});
diff --git a/enjoy/src/renderer/app.tsx b/enjoy/src/renderer/app.tsx
index 5c919c40..baba26ba 100644
--- a/enjoy/src/renderer/app.tsx
+++ b/enjoy/src/renderer/app.tsx
@@ -53,7 +53,7 @@ function App() {
-
+
diff --git a/enjoy/src/renderer/components/audios/audio-detail.tsx b/enjoy/src/renderer/components/audios/audio-detail.tsx
index 96da1e2c..bb03a39c 100644
--- a/enjoy/src/renderer/components/audios/audio-detail.tsx
+++ b/enjoy/src/renderer/components/audios/audio-detail.tsx
@@ -132,7 +132,7 @@ export const AudioDetail = (props: { id?: string; md5?: string }) => {
mediaId={audio.id}
mediaType="Audio"
mediaUrl={audio.src}
- waveformCacheKey={`waveform-audio-${audio.md5}`}
+ mediaMd5={audio.md5}
transcription={transcription}
currentTime={currentTime}
setCurrentTime={setCurrentTime}
@@ -207,7 +207,7 @@ export const AudioDetail = (props: { id?: string; md5?: string }) => {
{!initialized && (
-
+
)}
diff --git a/enjoy/src/renderer/components/conversations/conversations-shortcut.tsx b/enjoy/src/renderer/components/conversations/conversations-shortcut.tsx
index 081871b6..2d570928 100644
--- a/enjoy/src/renderer/components/conversations/conversations-shortcut.tsx
+++ b/enjoy/src/renderer/components/conversations/conversations-shortcut.tsx
@@ -48,7 +48,7 @@ export const ConversationsShortcut = (props: {
ask(conversation)}
- className="bg-white text-primary rounded-full w-full mb-2 py-2 px-4 hover:bg-primary hover:text-white cursor-pointer flex items-center border"
+ className="bg-background text-primary rounded-full w-full mb-2 py-2 px-4 hover:bg-primary hover:text-white cursor-pointer flex items-center border"
style={{
borderLeftColor: `#${conversation.id
.replaceAll("-", "")
diff --git a/enjoy/src/renderer/components/conversations/speech-player.tsx b/enjoy/src/renderer/components/conversations/speech-player.tsx
index af646183..76e0881a 100644
--- a/enjoy/src/renderer/components/conversations/speech-player.tsx
+++ b/enjoy/src/renderer/components/conversations/speech-player.tsx
@@ -89,7 +89,7 @@ export const SpeechPlayer = (props: {
{!initialized && (
diff --git a/enjoy/src/renderer/components/medias/media-player.tsx b/enjoy/src/renderer/components/medias/media-player.tsx
index 788917c2..28ef95cc 100644
--- a/enjoy/src/renderer/components/medias/media-player.tsx
+++ b/enjoy/src/renderer/components/medias/media-player.tsx
@@ -34,7 +34,7 @@ export const MediaPlayer = (props: {
mediaId: string;
mediaType: "Audio" | "Video";
mediaUrl: string;
- waveformCacheKey: string;
+ mediaMd5?: string;
transcription: TranscriptionType;
// player controls
currentTime: number;
@@ -67,7 +67,7 @@ export const MediaPlayer = (props: {
mediaId,
mediaType,
mediaUrl,
- waveformCacheKey,
+ mediaMd5,
transcription,
height = 200,
currentTime,
@@ -94,12 +94,7 @@ export const MediaPlayer = (props: {
if (!mediaUrl) return;
const [wavesurfer, setWavesurfer] = useState(null);
- const [waveform, setWaveForm] = useState<{
- peaks: number[];
- duration: number;
- frequencies: number[];
- sampleRate: number;
- }>(null);
+ const [waveform, setWaveForm] = useState
(null);
const containerRef = useRef();
const [mediaProvider, setMediaProvider] = useState<
HTMLAudioElement | HTMLVideoElement
@@ -181,7 +176,7 @@ export const MediaPlayer = (props: {
const renderPitchContour = (region: RegionType) => {
if (!region) return;
- if (!waveform.frequencies.length) return;
+ if (!waveform?.frequencies?.length) return;
if (!wavesurfer) return;
const duration = wavesurfer.getDuration();
@@ -280,7 +275,6 @@ export const MediaPlayer = (props: {
const ws = WaveSurfer.create({
container: containerRef.current,
height,
- url: mediaUrl,
waveColor: "#ddd",
progressColor: "rgba(0, 0, 0, 0.25)",
cursorColor: "#dc143c",
@@ -324,6 +318,7 @@ export const MediaPlayer = (props: {
const subscriptions = [
wavesurfer.on("play", () => setIsPlaying(true)),
wavesurfer.on("pause", () => setIsPlaying(false)),
+ wavesurfer.on("loading", (percent: number) => console.log(percent)),
wavesurfer.on("timeupdate", (time: number) => setCurrentTime(time)),
wavesurfer.on("decode", () => {
if (waveform?.frequencies) return;
@@ -340,7 +335,7 @@ export const MediaPlayer = (props: {
sampleRate,
frequencies: _frequencies,
};
- EnjoyApp.cacheObjects.set(waveformCacheKey, _waveform);
+ EnjoyApp.waveforms.save(mediaMd5, _waveform);
setWaveForm(_waveform);
}),
wavesurfer.on("ready", () => {
@@ -479,10 +474,8 @@ export const MediaPlayer = (props: {
}, [wavesurfer, isPlaying]);
useEffect(() => {
- EnjoyApp.cacheObjects.get(waveformCacheKey).then((cached) => {
- if (!cached) return;
-
- setWaveForm(cached);
+ EnjoyApp.waveforms.find(mediaMd5).then((waveform) => {
+ setWaveForm(waveform);
});
}, []);
diff --git a/enjoy/src/renderer/components/messages/assistant-message.tsx b/enjoy/src/renderer/components/messages/assistant-message.tsx
index da61bbf9..712b2051 100644
--- a/enjoy/src/renderer/components/messages/assistant-message.tsx
+++ b/enjoy/src/renderer/components/messages/assistant-message.tsx
@@ -90,11 +90,11 @@ export const AssistantMessageComponent = (props: {
id={`message-${message.id}`}
className="flex items-end space-x-2 pr-10"
>
-
+
- AI
+ AI
-
+
{configuration?.autoSpeech && speeching ? (
diff --git a/enjoy/src/renderer/components/messages/user-message.tsx b/enjoy/src/renderer/components/messages/user-message.tsx
index c2cbf637..7b3b3a45 100644
--- a/enjoy/src/renderer/components/messages/user-message.tsx
+++ b/enjoy/src/renderer/components/messages/user-message.tsx
@@ -32,6 +32,7 @@ import {
} from "lucide-react";
import { useCopyToClipboard } from "@uidotdev/usehooks";
import { t } from "i18next";
+import { useNavigate } from "react-router-dom";
import Markdown from "react-markdown";
export const UserMessageComponent = (props: {
@@ -45,6 +46,7 @@ export const UserMessageComponent = (props: {
const { user, webApi } = useContext(AppSettingsProviderContext);
const [_, copyToClipboard] = useCopyToClipboard();
const [copied, setCopied] = useState
(false);
+ const navigate = useNavigate();
const handleShare = async () => {
if (message.role === "user") {
@@ -57,7 +59,18 @@ export const UserMessageComponent = (props: {
},
})
.then(() => {
- toast(t("sharedSuccessfully"), { description: t("sharedPrompt") });
+ toast.success(t("sharedSuccessfully"), {
+ description: t("sharedPrompt"),
+ action: {
+ label: t("view"),
+ onClick: () => {
+ navigate("/community");
+ },
+ },
+ actionButtonStyle: {
+ backgroundColor: "var(--primary)",
+ },
+ });
})
.catch((err) => {
toast.error(t("shareFailed"), { description: err.message });
@@ -155,7 +168,7 @@ export const UserMessageComponent = (props: {
-
+
{user.name[0]}
diff --git a/enjoy/src/renderer/components/posts/post-audio.tsx b/enjoy/src/renderer/components/posts/post-audio.tsx
index f2bee1e3..3fb165e0 100644
--- a/enjoy/src/renderer/components/posts/post-audio.tsx
+++ b/enjoy/src/renderer/components/posts/post-audio.tsx
@@ -165,7 +165,7 @@ const WavesurferPlayer = (props: {
{!initialized && (
diff --git a/enjoy/src/renderer/components/posts/post-card.tsx b/enjoy/src/renderer/components/posts/post-card.tsx
index 50da0fb2..3e44427b 100644
--- a/enjoy/src/renderer/components/posts/post-card.tsx
+++ b/enjoy/src/renderer/components/posts/post-card.tsx
@@ -20,7 +20,7 @@ export const PostCard = (props: {
const { user } = useContext(AppSettingsProviderContext);
return (
-
+
diff --git a/enjoy/src/renderer/components/pronunciation-assessments/pronunciation-assessment-score-result.tsx b/enjoy/src/renderer/components/pronunciation-assessments/pronunciation-assessment-score-result.tsx
index b7a45538..23d2e3f6 100644
--- a/enjoy/src/renderer/components/pronunciation-assessments/pronunciation-assessment-score-result.tsx
+++ b/enjoy/src/renderer/components/pronunciation-assessments/pronunciation-assessment-score-result.tsx
@@ -96,7 +96,7 @@ export const PronunciationAssessmentScoreResult = (props: {
{!pronunciationScore && (
-
+