Skip to content

Commit

Permalink
Add basic interface for annotation
Browse files Browse the repository at this point in the history
  • Loading branch information
alexfauquette committed Nov 1, 2023
1 parent 9b7aa24 commit 0355ace
Show file tree
Hide file tree
Showing 4 changed files with 135 additions and 71 deletions.
108 changes: 108 additions & 0 deletions src/pages/ingredients/ImageAnnotation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import * as React from "react";
import Button from "@mui/material/Button";
import useRobotoffPrediction, { DataType } from "./useRobotoffPrediction";
import { Box, Stack, TextField } from "@mui/material";

type ImageAnnotationProps = {
fetchDataUrl: string;
};

type AnnotationProps = {
data: null | DataType;
error: null | string;
isLoading: boolean;
};

function Annotation({ data, isLoading, error }: AnnotationProps) {
const [editedState, setEditedState] = React.useState<null | DataType>(null);

React.useEffect(() => {
setEditedState(data);
}, [data]);

if (editedState === null) {
return null;
}
if (isLoading) {
return <p>loading ...</p>;
}
if (error !== null) {
return (
<>
<p>An error occured X{"("}</p>
<p>{error}</p>
</>
);
}
if (Object.keys(editedState).length === 0) {
return <p>No ingredients found</p>;
}

return (
<React.Fragment>
{Object.entries(editedState).map(([lang, text], index) => (
<Stack direction="column" key={index}>
<TextField
multiline
label={lang}
minRows={3}
onChange={(event) => {
setEditedState((prev) => ({
...prev,
[lang]: event.target.value,
}));
}}
value={text}
sx={{ mt: 2 }}
/>
<Stack direction="row">
<Button
onClick={() => {
setEditedState((prev) => ({
...prev,
[lang]: data[lang],
}));
}}
variant="contained"
fullWidth
>
Revert
</Button>
<Button
onClick={() => {
console.log(
`send to server lang: ${lang} and ingredients: ${text}`
);
}}
variant="contained"
color="success"
fullWidth
>
Send
</Button>
</Stack>
</Stack>
))}
</React.Fragment>
);
}

export default function ImageAnnotation({
fetchDataUrl,
}: ImageAnnotationProps) {
const [data, getData, isLoading, error] = useRobotoffPrediction(fetchDataUrl);

return (
<Box sx={{ px: 1 }}>
<Annotation data={data} isLoading={isLoading} error={error} />
<Button
fullWidth
disabled={isLoading || error !== null || data !== null}
onClick={getData}
variant="outlined"
>
Get prediction
</Button>
</Box>
);
}
42 changes: 3 additions & 39 deletions src/pages/ingredients/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,14 @@ import Loader from "../loader";
import off from "../../off";
import { useTranslation } from "react-i18next";
import useData from "./useData";
import useRobotoffPrediction from "./useRobotoffPrediction";
import ImageAnnotation from "./ImageAnnotation";

function ProductInterface(props) {
const {
product: { selectedImages, product_name, code },
next,
} = props;

const [predictions, getPreidiction] = useRobotoffPrediction(); // This could be simplified if each image had it's own component (and so its own state)

return (
<div>
<Typography>
Expand All @@ -26,45 +24,11 @@ function ProductInterface(props) {
{selectedImages.map(
({ countryCode, imageUrl, fetchDataUrl }, index) => {
return (
<Stack direction="column" key={index}>
<Stack direction="column" key={`${code}-${index}`}>
<Typography>{countryCode}</Typography>

<img src={imageUrl} />

{predictions[fetchDataUrl]?.loading && <p>loading ...</p>}
{predictions[fetchDataUrl]?.loading === false &&
predictions[fetchDataUrl]?.data === null && (
<p>An error occured X{"("}</p>
)}
{predictions[fetchDataUrl]?.loading === false &&
predictions[fetchDataUrl]?.data !== null &&
Object.keys(predictions[fetchDataUrl]?.data).length === 0 && (
<p>No ingredients found</p>
)}
{predictions[fetchDataUrl]?.loading === false &&
predictions[fetchDataUrl]?.data !== null &&
Object.keys(predictions[fetchDataUrl]?.data).length > 0 && (
<React.Fragment>
{Object.entries(predictions[fetchDataUrl]?.data).map(
([lang, text], index) => (
<div
key={index}
style={{ margin: 4, border: "solid black 1px" }}
>
<p>{lang}</p>
<p>{text}</p>
</div>
)
)}
</React.Fragment>
)}
<button
onClick={() => {
getPreidiction(fetchDataUrl);
}}
>
fetch
</button>
<ImageAnnotation fetchDataUrl={fetchDataUrl} />
</Stack>
);
}
Expand Down
4 changes: 3 additions & 1 deletion src/pages/ingredients/useData.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,9 @@ const formatData = (product) => {
export default function useData() {
const [data, setData] = React.useState([]);
const [isLoading, setIsLoading] = React.useState(true);
const [page, setPage] = React.useState(0);
const [page, setPage] = React.useState(() => {
return new Date().getMilliseconds() % 50;
});
const seenCodes = React.useRef([]);

React.useEffect(() => {
Expand Down
52 changes: 21 additions & 31 deletions src/pages/ingredients/useRobotoffPrediction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,51 +23,41 @@ function isError(
return (rep as GetIngredientsError).error !== undefined;
}

export default function useRobotoffPrediction() {
const [data, setData] = React.useState<{
[key: string]: {
loading: boolean;
data: null | { [lang: string]: string };
};
}>({});
export type DataType = { [lang: string]: string };

async function getData(fetchUrl: string) {
setData((prev) => ({
...prev,
[fetchUrl]: {
loading: true,
data: null,
},
}));
export default function useRobotoffPrediction(
fetchUrl: string
): [null | DataType, () => void, boolean, null | string] {
const [isLoading, setIsLoading] = React.useState(false);
const [error, setError] = React.useState(null);
const [data, setData] = React.useState<null | { [lang: string]: string }>(
null
);

const getData = React.useCallback(() => {
setIsLoading(true);

axios
.get<
GetIngredientsResponse | GetIngredientsError // That's not clean, but errors return a 200
>(fetchUrl)
.then((result) => {
if (isError(result.data)) {
setData((prev) => ({
...prev,
[fetchUrl]: {
loading: false,
data: null,
},
}));
setIsLoading(false);
setError(result.data.error);
setData(null);
return;
}

const rep = {};
result.data.entities.map((entity) => {
rep[entity.lang.lang] = entity.text;
});
setData((prev) => ({
...prev,
[fetchUrl]: {
loading: false,
data: rep,
},
}));
setIsLoading(false);
setError(null);
setData(rep);
});
}
return [data, getData];
}, [fetchUrl]);

return [data, getData, isLoading, error];
}

0 comments on commit 0355ace

Please sign in to comment.