Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
"dev": "set \"VITE_BLOCKLY_API=http://localhost:8080\" && npm start",
"start": "vite",
"build": "vite build",
"build:tutorial": "WIDGET=tutorial vite build --config vite.config.tutorial.js",
"serve": "vite preview",
"e2e": "cypress run"
},
Expand All @@ -92,4 +93,4 @@
"prettier": "^3.9.1",
"vite": "^7.3.2"
}
}
}
6 changes: 4 additions & 2 deletions src/components/Pages/TeachableSensebox/HelpButton.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, { useState, useCallback, useEffect, useRef } from "react";
import { IconButton, Tooltip, useMediaQuery, useTheme } from "@mui/material";
import { faQuestionCircle } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useHideHelp } from "./HelpContext";

// ─── Help-blink hooks ──────────────────────────────────────────────────────────

Expand Down Expand Up @@ -129,9 +130,10 @@ const HelpButton = ({
}) => {
const theme = useTheme();
const isWideScreen = useMediaQuery(theme.breakpoints.up("lg"));
const hideHelp = useHideHelp();

// Only render on wide screens
if (!isWideScreen) {
// Only render on wide screens and when not hidden
if (!isWideScreen || hideHelp) {
return null;
}

Expand Down
13 changes: 13 additions & 0 deletions src/components/Pages/TeachableSensebox/HelpContext.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import React, { createContext, useContext } from "react";

const HelpContext = createContext(false);

export function HelpProvider({ children, hideHelp = false }) {
return (
<HelpContext.Provider value={hideHelp}>{children}</HelpContext.Provider>
);
}

export function useHideHelp() {
return useContext(HelpContext);
}
192 changes: 22 additions & 170 deletions src/components/Pages/TeachableSensebox/image/ImageClassification.jsx
Original file line number Diff line number Diff line change
@@ -1,187 +1,39 @@
import React, { useState, useRef, useCallback, useEffect } from "react";
import React from "react";
import { useNavigate } from "react-router-dom";
import {
Container,
Typography,
Box,
Paper,
Button,
useMediaQuery,
useTheme,
Box,
} from "@mui/material";
import { ArrowBack as ArrowBackIcon } from "@mui/icons-material";
import { useSelector } from "react-redux";
import { getImageTranslations } from "./translations";
import ModelTrainer from "./ModelTrainer";
import ConvertDeploy from "./ConvertDeploy";
import HelpSidebar, { SIDEBAR_WIDTH } from "../HelpSidebar";
import HelpButton, { useHelpBlink, markHelpSeen } from "../HelpButton";
import ImageClassificationTool from "./ImageClassificationTool";

const TeachableSensebox = () => {
const ImageClassification = () => {
const navigate = useNavigate();
const [trainedModel, setTrainedModel] = useState(null);
const [isTraining, setIsTraining] = useState(false);
const [trainingError, setTrainingError] = useState(null);
const isMountedRef = useRef(true);
const language = useSelector((s) => s.general.language);
const t = getImageTranslations(language);

// Help sidebar state
const [helpSidebarOpen, setHelpSidebarOpen] = useState(false);
const [currentHelpTopic, setCurrentHelpTopic] = useState(null);
const theme = useTheme();
const isWideScreen = useMediaQuery(theme.breakpoints.up("lg"));

const handleOpenHelp = useCallback((topic) => {
markHelpSeen(topic);
setCurrentHelpTopic(topic);
setHelpSidebarOpen(true);
}, []);

const {
isBlinking: imageClassBlinking,
trigger: triggerImageClass,
markSeen: markImageClassSeen,
} = useHelpBlink("image/imageClassification");

const {
isBlinking: deployBlinking,
trigger: triggerDeploy,
markSeen: markDeploySeen,
} = useHelpBlink("image/deployModel");

useEffect(() => {
triggerImageClass();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

useEffect(() => {
if (trainedModel) {
triggerDeploy();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [trainedModel]);

const handleCloseHelp = useCallback(() => {
setHelpSidebarOpen(false);
}, []);

const handleModelTrained = useCallback((model) => {
if (isMountedRef.current) {
setTrainedModel(model);
setIsTraining(false);
setTrainingError(null);
}
}, []);

const handleTrainingStart = useCallback(() => {
if (isMountedRef.current) {
setIsTraining(true);
setTrainingError(null);
}
}, []);

const handleTrainingError = useCallback((error) => {
if (isMountedRef.current) {
setTrainingError(error);
setIsTraining(false);
}
}, []);

// Cleanup on unmount
useEffect(() => {
return () => {
isMountedRef.current = false;
};
}, []);

return (
<>
{/* Help Sidebar */}
<HelpSidebar
open={helpSidebarOpen}
onClose={handleCloseHelp}
helpTopic={currentHelpTopic}
/>

<Container
maxWidth="lg"
sx={{
py: 4,
pb: 10,
mr: isWideScreen && helpSidebarOpen ? `${SIDEBAR_WIDTH}px` : "auto",
transition: "margin-right 0.3s ease",
}}
key={language}
>
<Box sx={{ mb: 4 }}>
<Button
startIcon={<ArrowBackIcon />}
onClick={() => navigate("/teachable")}
sx={{ mb: 2 }}
>
{t.landing?.title || "Machine Learning"}
</Button>
<Box sx={{ display: "flex", alignItems: "center", mb: 1 }}>
<Typography variant="h3" component="h1">
{t.title}
</Typography>
<HelpButton
onClick={() => {
markImageClassSeen();
handleOpenHelp("image/imageClassification");
}}
isBlinking={imageClassBlinking}
tooltip={
t.training?.tooltip?.helpMain || "Was ist Bildklassifizierung?"
}
/>
</Box>
<Typography variant="body1" color="text.secondary" sx={{ mb: 3 }}>
{t.description}
</Typography>
</Box>

<Box sx={{ display: "flex", flexDirection: "column", gap: 3 }}>
{/* Model Training Section */}
<Paper elevation={2} sx={{ p: 3 }}>
<Typography variant="h5" gutterBottom>
{t.training.title}
</Typography>
<ModelTrainer
onModelTrained={handleModelTrained}
onTrainingStart={handleTrainingStart}
onTrainingError={handleTrainingError}
isTraining={isTraining}
disabled={isTraining}
onOpenHelp={handleOpenHelp}
/>
</Paper>

{/* Blockly Integration Section */}
{trainedModel && (
<Paper elevation={2} sx={{ p: 3 }}>
<Typography variant="h5" gutterBottom>
{t.integration.title}
<HelpButton
onClick={() => {
markDeploySeen();
handleOpenHelp("image/deployModel");
}}
isBlinking={deployBlinking}
tooltip={
t.training?.tooltip?.helpDeployModel ||
"Help with deploying the model"
}
/>
</Typography>
<ConvertDeploy model={trainedModel} />
</Paper>
)}
</Box>
</Container>
</>
<Container
maxWidth="lg"
sx={{
display: "flex",
flexDirection: "column",
}}
>
<Box sx={{ mb: 2, pt: 4 }}>
<Button
startIcon={<ArrowBackIcon />}
onClick={() => navigate("/teachable")}
>
{t.landing?.title || "Machine Learning"}
</Button>
</Box>
<ImageClassificationTool />
</Container>
);
};

export default TeachableSensebox;
export default ImageClassification;
Loading
Loading