Skip to content

Commit

Permalink
Feat: handle decode errors (#425)
Browse files Browse the repository at this point in the history
* show decode error

* handle loading error in post audio/recording

* tweak

* upgrade deps

* fix logout

* update build-enjoy-app.yml
  • Loading branch information
an-lee authored Mar 18, 2024
1 parent 251eada commit 3b770ea
Show file tree
Hide file tree
Showing 11 changed files with 441 additions and 100 deletions.
20 changes: 7 additions & 13 deletions .github/workflows/build-enjoy-app.yml
Original file line number Diff line number Diff line change
@@ -1,19 +1,13 @@
name: Build Enjoy App
on:
workflow_dispatch:
inputs:
os:
type: choice
description: Choose os
options:
- macos-12
- macos-14
- windows-latest
- ubuntu-latest

jobs:
build:
runs-on: ${{ github.event.inputs.os }}
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [macos-12, macos-14, windows-latest, ubuntu-latest]
steps:
- uses: actions/checkout@v4

Expand All @@ -31,13 +25,13 @@ jobs:
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
with:
path: "**/node_modules"
key: ${{ github.event.inputs.os }}-${{ hashFiles('**/yarn.lock') }}
key: ${{ matrix.os }}-${{ hashFiles('**/yarn.lock') }}

- name: Install dependencies
run: yarn install

- name: Install Apple certificate
if: contains(github.event.inputs.os, 'macos')
if: contains(matrix.os, 'macos')
env:
MACOS_CERTIFICATE_APPLICATION_BASE64: ${{ secrets.MACOS_CERTIFICATE_APPLICATION_BASE64 }}
MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
Expand All @@ -54,7 +48,7 @@ jobs:
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: Enjoy-${{ runner.os }}-${{ github.event.inputs.os == 'macos-14' && 'arm64' || 'x64' }}-build-${{ github.ref_name }}-${{ steps.current-time.outputs.formattedTime }}
name: Enjoy-${{ runner.os }}-${{ matrix.os == 'macos-14' && 'arm64' || 'x64' }}-build-${{ github.ref_name }}-${{ steps.current-time.outputs.formattedTime }}
path: |
enjoy/out/make/**/*.deb
enjoy/out/make/**/*.rpm
Expand Down
16 changes: 8 additions & 8 deletions enjoy/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
"@types/intl-tel-input": "^18.1.4",
"@types/lodash": "^4.17.0",
"@types/mark.js": "^8.11.12",
"@types/node": "^20.11.27",
"@types/node": "^20.11.28",
"@types/react": "^18.2.66",
"@types/react-dom": "^18.2.22",
"@types/validator": "^13.11.9",
Expand All @@ -66,7 +66,7 @@
"flora-colossus": "^2.0.0",
"octokit": "^3.1.2",
"progress": "^2.0.3",
"tailwind-merge": "^2.2.1",
"tailwind-merge": "^2.2.2",
"tailwind-scrollbar": "^3.1.0",
"tailwindcss": "^3.4.1",
"tailwindcss-animate": "^1.0.7",
Expand All @@ -82,7 +82,7 @@
"@ffmpeg/ffmpeg": "^0.12.10",
"@ffmpeg/util": "^0.12.1",
"@hookform/resolvers": "^3.3.4",
"@langchain/community": "^0.0.39",
"@langchain/community": "^0.0.40",
"@langchain/google-genai": "^0.0.10",
"@mozilla/readability": "^0.5.0",
"@radix-ui/react-accordion": "^1.1.2",
Expand Down Expand Up @@ -110,7 +110,7 @@
"@uidotdev/usehooks": "^2.4.1",
"@vidstack/react": "^1.10.9",
"autosize": "^6.0.1",
"axios": "^1.6.7",
"axios": "^1.6.8",
"camelcase": "^8.0.0",
"camelcase-keys": "^9.1.3",
"chart.js": "^4.4.2",
Expand All @@ -135,22 +135,22 @@
"html-to-text": "^9.0.5",
"https-proxy-agent": "^7.0.4",
"i18next": "^23.10.1",
"intl-tel-input": "^19.5.7",
"intl-tel-input": "^20.0.3",
"js-md5": "^0.8.3",
"langchain": "^0.1.28",
"lodash": "^4.17.21",
"lucide-react": "^0.358.0",
"mark.js": "^8.11.1",
"microsoft-cognitiveservices-speech-sdk": "^1.36.0",
"next-themes": "^0.3.0",
"openai": "^4.29.0",
"openai": "^4.29.1",
"pitchfinder": "^2.3.2",
"postcss": "^8.4.35",
"postcss": "^8.4.36",
"proxy-agent": "^6.4.0",
"react": "^18.2.0",
"react-activity-calendar": "^2.2.8",
"react-dom": "^18.2.0",
"react-hook-form": "^7.51.0",
"react-hook-form": "^7.51.1",
"react-hotkeys-hook": "^4.5.0",
"react-i18next": "^14.1.0",
"react-markdown": "^9.0.1",
Expand Down
1 change: 1 addition & 0 deletions enjoy/src/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,7 @@
"resolvingDownloadUrl": "Resolving download URL",
"waveformIsDecoded": "Waveform is decoded",
"decodingWaveform": "Decoding waveform",
"failedToDecodeWaveform": "Failed to decode waveform",
"transcribedSuccessfully": "Transcribed successfully",
"transcribing": "Transcribing",
"notTranscribedYet": "Not transcribed yet",
Expand Down
1 change: 1 addition & 0 deletions enjoy/src/i18n/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,7 @@
"resolvingDownloadUrl": "正在解析下载地址",
"waveformIsDecoded": "波形已解码",
"decodingWaveform": "正在解码波形",
"failedToDecodeWaveform": "解码波形失败",
"transcribedSuccessfully": "语音转文本成功",
"transcribing": "正在语音转文本",
"notTranscribedYet": "尚未语音转文本",
Expand Down
19 changes: 18 additions & 1 deletion enjoy/src/renderer/components/medias/media-loading-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,17 @@ import {
PingPoint,
Progress,
} from "@renderer/components/ui";
import { CheckCircleIcon, LoaderIcon } from "lucide-react";
import { CheckCircleIcon, LoaderIcon, XCircleIcon } from "lucide-react";
import { t } from "i18next";
import { useNavigate } from "react-router-dom";

export const MediaLoadingModal = () => {
const navigate = useNavigate();
const { whisperConfig } = useContext(AISettingsProviderContext);
const {
media,
decoded,
decodeError,
transcription,
transcribing,
transcribingProgress,
Expand All @@ -47,6 +49,21 @@ export const MediaLoadingModal = () => {
<CheckCircleIcon className="w-4 h-4 text-green-500" />
<span>{t("waveformIsDecoded")}</span>
</div>
) : decodeError ? (
<div className="mb-4 flex items-center space-x-4">
<div className="w-4 h-4">
<XCircleIcon className="w-4 h-4 text-destructive" />
</div>
<div className="select-text">
<div className="mb-2">
{decodeError}
</div>
<div className="text-sm text-muted-foreground">
{t("failedToDecodeWaveform")}:{" "}
<span className="break-all ">{media?.src}</span>
</div>
</div>
</div>
) : (
<div className="mb-4 flex items-center space-x-4">
<LoaderIcon className="w-4 h-4 animate-spin" />
Expand Down
5 changes: 4 additions & 1 deletion enjoy/src/renderer/components/medias/media-player.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ import {
} from "@vidstack/react/player/layouts/default";

export const MediaPlayer = () => {
const { media, setMediaProvider } = useContext(MediaPlayerProviderContext);
const { media, setMediaProvider, setDecodeError } = useContext(
MediaPlayerProviderContext
);
const mediaRemote = useMediaRemote();
if (!media?.src) return null;

Expand All @@ -31,6 +33,7 @@ export const MediaPlayer = () => {
setMediaProvider(provider.video);
}
}}
onError={(err) => setDecodeError(err.message)}
>
<MediaProvider />
<DefaultAudioLayout icons={defaultLayoutIcons} />
Expand Down
27 changes: 26 additions & 1 deletion enjoy/src/renderer/components/posts/post-audio.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import {
} from "@vidstack/react/player/layouts/default";
export const STORAGE_WORKER_ENDPOINT = "https://enjoy-storage.baizhiheizi.com";
import { TimelineEntry } from "echogarden/dist/utilities/Timeline.d.js";
import { t } from "i18next";
import { XCircleIcon } from "lucide-react";

export const PostAudio = (props: {
audio: Partial<MediumType>;
Expand All @@ -23,6 +25,7 @@ export const PostAudio = (props: {
const [currentTime, setCurrentTime] = useState<number>(0);
const { webApi } = useContext(AppSettingsProviderContext);
const [transcription, setTranscription] = useState<TranscriptionType>();
const [error, setError] = useState<string>(null);

const currentTranscription = transcription?.result["transcript"]
? (transcription.result?.timeline || []).find(
Expand All @@ -45,6 +48,22 @@ export const PostAudio = (props: {
});
}, [audio.md5]);

if (error) {
return (
<div className="w-full rounded-lg p-4 border">
<div className="flex items-center justify-center mb-2">
<XCircleIcon className="w-4 h-4 text-destructive" />
</div>
<div className="select-text break-all text-center text-sm text-muted-foreground mb-4">
{error}
</div>
<div className="flex items-center justify-center">
<Button onClick={() => setError(null)}>{t("retry")}</Button>
</div>
</div>
);
}

return (
<div className="w-full">
{audio.sourceUrl.startsWith(STORAGE_WORKER_ENDPOINT) ? (
Expand All @@ -53,13 +72,15 @@ export const PostAudio = (props: {
setCurrentTime={setCurrentTime}
audio={audio}
height={height}
onError={(err) => setError(err.message)}
/>
) : (
<MediaPlayer
onTimeUpdate={({ currentTime: _currentTime }) => {
setCurrentTime(_currentTime);
}}
src={audio.sourceUrl}
onError={(err) => setError(err.message)}
>
<MediaProvider />
<DefaultAudioLayout icons={defaultLayoutIcons} />
Expand Down Expand Up @@ -88,8 +109,9 @@ const WavesurferPlayer = (props: {
height?: number;
currentTime: number;
setCurrentTime: (currentTime: number) => void;
onError?: (error: Error) => void;
}) => {
const { audio, height = 80, currentTime, setCurrentTime } = props;
const { audio, height = 80, onError, setCurrentTime } = props;
const [initialized, setInitialized] = useState(false);
const [isPlaying, setIsPlaying] = useState(false);
const [wavesurfer, setWavesurfer] = useState(null);
Expand Down Expand Up @@ -162,6 +184,9 @@ const WavesurferPlayer = (props: {
}, 1000);
setInitialized(true);
}),
wavesurfer.on("error", (err: Error) => {
onError(err);
}),
];

return () => {
Expand Down
29 changes: 28 additions & 1 deletion enjoy/src/renderer/components/posts/post-recording.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ 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 { t } from "i18next";
import { XCircleIcon } from "lucide-react";

export const PostRecording = (props: {
recording: RecordingType;
Expand All @@ -20,6 +22,7 @@ export const PostRecording = (props: {
threshold: 1,
});
const [duration, setDuration] = useState<number>(0);
const [error, setError] = useState<string>(null);

const onPlayClick = useCallback(() => {
wavesurfer.isPlaying() ? wavesurfer.pause() : wavesurfer.play();
Expand All @@ -31,6 +34,7 @@ export const PostRecording = (props: {
if (!entry?.isIntersecting) return;
if (!recording.src) return;
if (wavesurfer) return;
if (error) return;

const ws = WaveSurfer.create({
container: containerRef.current,
Expand All @@ -48,7 +52,11 @@ export const PostRecording = (props: {
});

setWavesurfer(ws);
}, [recording.src, entry]);

return () => {
setWavesurfer(null);
};
}, [recording.src, entry, error]);

useEffect(() => {
if (!wavesurfer) return;
Expand Down Expand Up @@ -84,6 +92,9 @@ export const PostRecording = (props: {
}, 1000);
setInitialized(true);
}),
wavesurfer.on("error", (err: Error) => {
setError(err.message);
}),
];

return () => {
Expand All @@ -92,6 +103,22 @@ export const PostRecording = (props: {
};
}, [wavesurfer]);

if (error) {
return (
<div className="w-full bg-sky-500/30 rounded-lg p-4 border">
<div className="flex items-center justify-center mb-2">
<XCircleIcon className="w-4 h-4 text-destructive" />
</div>
<div className="select-text break-all text-center text-sm text-muted-foreground mb-4">
{error}
</div>
<div className="flex items-center justify-center">
<Button onClick={() => setError(null)}>{t("retry")}</Button>
</div>
</div>
);
}

return (
<div className="w-full">
<div className="flex justify-end">
Expand Down
11 changes: 1 addition & 10 deletions enjoy/src/renderer/context/app-settings-provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ export const AppSettingsProvider = ({
}: {
children: React.ReactNode;
}) => {
const [initialized, setInitialized] = useState<boolean>(false);
const [version, setVersion] = useState<string>("");
const [apiUrl, setApiUrl] = useState<string>(WEB_API_URL);
const [webApi, setWebApi] = useState<Client>(null);
Expand All @@ -62,10 +61,6 @@ export const AppSettingsProvider = ({
fetchProxyConfig();
}, []);

useEffect(() => {
validate();
}, [user, libraryPath]);

useEffect(() => {
if (!apiUrl) return;

Expand Down Expand Up @@ -192,10 +187,6 @@ export const AppSettingsProvider = ({
});
};

const validate = async () => {
setInitialized(Boolean(user && libraryPath));
};

return (
<AppSettingsProviderContext.Provider
value={{
Expand All @@ -214,7 +205,7 @@ export const AppSettingsProvider = ({
ffmpegWasm,
proxy,
setProxy: setProxyConfigHandler,
initialized,
initialized: Boolean(user && libraryPath),
}}
>
{children}
Expand Down
Loading

0 comments on commit 3b770ea

Please sign in to comment.