Skip to content

Commit

Permalink
Loccus enhancements (#539)
Browse files Browse the repository at this point in the history
* Tools Refactoring (#519)

* Synthetic image detection UX improvements

* refactoring + updated offline translations

* wip

* Refactored footer types and Footer, Navbar, DrawerItem, AllTools. Highlight the tool used in the side menu

* Added missing package, fixed scroll bars and enhanced general layout

* Improved naming to have TopMenu, MainContentMenu, and SideMenu, fixed some variables, code cleanup

* Improved alignment

* Fixed RTL padding

* Fixed RTL chevron

* Improved naming

* Fixed lint warning

* Added jsdoc

* Cleanup

* Fixed error

* add v0.81 manifest

* Added chartJs, added chart with detection percentage over time

* Removed console.log

* Added chart gradient based on detection score

* Updated packages

* Moved variables

* Fixed lint warnings

* Bumped version, added missing keyword, added rtl support

* Added missing controller

* Added missing controller + fixed warning

* Removed console.log

* Improved error handling

* Added scale modal to Loccus, refactoring, added download buttons to loccus

* Refactoring

---------

Co-authored-by: Bertrand Goupil <[email protected]>
  • Loading branch information
Sallaa and AFPMedialab authored Jun 17, 2024
1 parent c6ade2f commit 7d4a856
Show file tree
Hide file tree
Showing 6 changed files with 304 additions and 174 deletions.
32 changes: 23 additions & 9 deletions src/components/NavItems/tools/Loccus/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,11 @@ const Loccus = () => {
);
const result = useSelector((state) => state.syntheticAudioDetection.result);
const url = useSelector((state) => state.syntheticAudioDetection.url);
const chunks = useSelector((state) => state.syntheticAudioDetection.chunks);
const authenticatedRequest = useAuthenticatedRequest();
const [input, setInput] = useState(url ? url : "");
const [audioFile, setAudioFile] = useState(AUDIO_FILE_DEFAULT_STATE);

const [chunks, setChunks] = useState([]);

const dispatch = useDispatch();

const useGetVoiceCloningScore = async (url, processURL, dispatch) => {
Expand Down Expand Up @@ -179,14 +178,11 @@ const Loccus = () => {

const res3 = await authenticatedRequest(config3);

console.log(res3.data);

setChunks(res3.data);

dispatch(
setLoccusResult({
url: audioFile ? URL.createObjectURL(audioFile) : url,
result: res2.data,
chunks: res3.data,
}),
);
} catch (error) {
Expand All @@ -208,17 +204,27 @@ const Loccus = () => {
const preprocessLoccusUpload = async (file) => {
if (!(file instanceof File)) {
dispatch(setError(keyword("error_invalid_file")));
return Error(keyword("error_invalid_file"));
return new Error(keyword("error_invalid_file"));
}

if (!file.type.includes("audio")) {
dispatch(setError(keyword("error_invalid_media_file")));
return Error(keyword("error_invalid_media_file"));
return new Error(keyword("error_invalid_media_file"));
}

const isChromium = !!window.chrome;

// TODO: Use ffmpeg to convert the m4a files if possible
if (file.type.includes("m4a")) {
if (
isChromium &&
(file.type.includes("m4a") ||
file.type.includes("basic") ||
file.type.includes("aiff"))
) {
dispatch(setError(keyword("error_invalid_audio_file")));

handleClose();

return Error(keyword("error_invalid_audio_file"));
}

Expand Down Expand Up @@ -246,8 +252,16 @@ const Loccus = () => {
},
);
};
}).catch((error) => {
console.log(error);
dispatch(setError(keyword("loccus_error_unable_to_read_file")));
return Error(error);
});

if (!(audioBuffer instanceof AudioBuffer)) {
return audioBuffer;
}

const durationInSeconds = audioBuffer.duration;

if (durationInSeconds >= 300) {
Expand Down
119 changes: 92 additions & 27 deletions src/components/NavItems/tools/Loccus/loccusResults.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ import {
Grid,
IconButton,
Stack,
Tooltip,
Typography,
} from "@mui/material";
import { Close, ExpandMore } from "@mui/icons-material";
import { Close, Download, ExpandMore } from "@mui/icons-material";
import { i18nLoadNamespace } from "components/Shared/Languages/i18nLoadNamespace";
import { useSelector } from "react-redux";
import { useTrackEvent } from "Hooks/useAnalytics";
Expand All @@ -31,12 +32,14 @@ import {
PointElement,
TimeSeriesScale,
Title,
Tooltip,
Tooltip as ChartTooltip,
} from "chart.js";
import { Chart } from "react-chartjs-2";
import "chartjs-adapter-dayjs-4/dist/chartjs-adapter-dayjs-4.esm";
import dayjs from "dayjs";
import duration from "dayjs/plugin/duration";
import { exportReactElementAsJpg } from "../../../Shared/Utils/htmlUtils";
import GaugeChartModalExplanation from "../../../Shared/GaugeChartModalExplanation";

const LoccusResults = (props) => {
dayjs.extend(duration);
Expand All @@ -54,6 +57,9 @@ const LoccusResults = (props) => {
const [voiceCloningScore, setVoiceCloningScore] = useState(null);
const [voiceRecordingScore, setVoiceRecordingScore] = useState(null);

const gaugeChartRef = useRef(null);
const chunksChartRef = useRef(null);

const DETECTION_TYPES = {
VOICE_CLONING: "synthetic",
VOICE_RECORDING: "replay",
Expand Down Expand Up @@ -83,7 +89,7 @@ const LoccusResults = (props) => {
LineController,
LineElement,
Title,
Tooltip,
ChartTooltip,
Legend,
TimeSeriesScale,
);
Expand Down Expand Up @@ -121,7 +127,6 @@ const LoccusResults = (props) => {
scales: {
x: {
type: "time",
beginAtZero: true,
time: {
unit: "second",
},
Expand Down Expand Up @@ -270,6 +275,14 @@ const LoccusResults = (props) => {
dragToSeek: true,
});

const keywords = [
"loccus_scale_modal_explanation_rating_1",
"loccus_scale_modal_explanation_rating_2",
"loccus_scale_modal_explanation_rating_3",
"loccus_scale_modal_explanation_rating_4",
];
const colors = ["#00FF00", "#AAFF03", "#FFA903", "#FF0000"];

return (
<Stack
direction="row"
Expand All @@ -279,7 +292,7 @@ const LoccusResults = (props) => {
>
<Card sx={{ width: "100%" }}>
<CardHeader
style={{ borderRadius: "4px 4px 0px 0px" }}
style={{ borderRadius: "4px 4p x 0px 0px" }}
title={keyword("loccus_title")}
action={
<IconButton aria-label="close" onClick={props.handleClose}>
Expand All @@ -305,51 +318,102 @@ const LoccusResults = (props) => {
<Grid item width="100%">
<div ref={audioContainerRef} />
</Grid>
<Grid item width="100%" height="300px">
<Grid item ref={chunksChartRef} width="100%" height="300px">
<Chart
type={"line"}
data={getChartDataFromChunks(props.chunks)}
options={chartConfig}
/>
</Grid>
<Grid item>
<Tooltip
title={keyword("loccus_download_chunks_chart_button")}
>
<IconButton
color="primary"
aria-label="download chart"
onClick={async () =>
await exportReactElementAsJpg(
chunksChartRef,
"loccus_detection_chart",
)
}
>
<Download />
</IconButton>
</Tooltip>
</Grid>
</Grid>
</Box>
</Grid>
<Grid item sm={12} md={6}>
<Stack direction="column" spacing={4}>
<Stack direction="column" p={4} spacing={2}>
<Stack direction="column" p={4} spacing={4}>
<Typography variant="h5">
{keyword("loccus_voice_cloning_detection_title")}
</Typography>
<Stack
direction="column"
direction={{ sm: "column", md: "row" }}
alignItems={{ sm: "start", md: "center" }}
justifyContent="center"
alignItems="center"
spacing={0}
width="100%"
>
<GaugeChart
id={"gauge-chart"}
animate={false}
nrOfLevels={4}
textColor={"black"}
arcsLength={[0.1, 0.2, 0.3, 0.4]}
percent={voiceCloningScore / 100}
style={{ width: 250 }}
/>
<Stack
direction="row"
direction="column"
justifyContent="center"
alignItems="center"
spacing={10}
spacing={0}
ref={gaugeChartRef}
>
<Typography variant="subtitle2">
{keyword("loccus_gauge_no_detection")}
</Typography>
<Typography variant="subtitle2">
{keyword("loccus_gauge_detection")}
</Typography>
<GaugeChart
id={"gauge-chart"}
animate={false}
nrOfLevels={4}
textColor={"black"}
arcsLength={[0.1, 0.2, 0.3, 0.4]}
percent={voiceCloningScore / 100}
style={{ width: 250 }}
/>
<Stack
direction="row"
justifyContent="center"
alignItems="center"
spacing={10}
>
<Typography variant="subtitle2">
{keyword("loccus_gauge_no_detection")}
</Typography>
<Typography variant="subtitle2">
{keyword("loccus_gauge_detection")}
</Typography>
</Stack>
</Stack>
<Box alignSelf={{ sm: "flex-start", md: "flex-end" }}>
<Tooltip title={keyword("loccus_download_gauge_button")}>
<IconButton
color="primary"
aria-label="download chart"
onClick={async () =>
await exportReactElementAsJpg(
gaugeChartRef,
"gauge_chart",
)
}
>
<Download />
</IconButton>
</Tooltip>
</Box>
</Stack>

<GaugeChartModalExplanation
keyword={keyword}
keywordsArr={keywords}
keywordLink={"loccus_scale_explanation_link"}
keywordModalTitle={"loccus_scale_modal_explanation_title"}
colors={colors}
/>

<CustomAlertScore
score={voiceCloningScore}
detectionType={DETECTION_TYPES.VOICE_CLONING}
Expand Down Expand Up @@ -395,6 +459,7 @@ const LoccusResults = (props) => {
percent={voiceRecordingScore / 100}
style={{ width: 250 }}
/>

<Stack
direction="row"
justifyContent="center"
Expand Down
Loading

0 comments on commit 7d4a856

Please sign in to comment.