Skip to content
Merged
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
2 changes: 2 additions & 0 deletions DashAI/back/converters/base_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ def get_metadata(cls) -> Dict[str, Any]:
meta["category"] = cls.CATEGORY if cls.CATEGORY else "Other"
meta["icon"] = cls.ICON if cls.ICON else Icon.Extension.value
meta["color"] = cls.COLOR if cls.COLOR else "rgb(255, 255, 255)"
meta["requires_download"] = bool(getattr(cls, "REQUIRES_DOWNLOAD", False))
meta["download_size_bytes"] = getattr(cls, "DOWNLOAD_SIZE_BYTES", None)
meta["supervised"] = cls.SUPERVISED
meta["changes_row_count"] = cls.CHANGES_ROW_COUNT
meta["n_components_features_bounded"] = getattr(
Expand Down
2 changes: 2 additions & 0 deletions DashAI/back/exploration/base_explorer.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ def get_metadata(cls) -> Dict[str, Any]:
meta["category"] = cls.CATEGORY if cls.CATEGORY else "Other"
meta["icon"] = cls.ICON if cls.ICON else Icon.Extension.value
meta["color"] = cls.COLOR if cls.COLOR else "rgb(255, 255, 255)"
meta["requires_download"] = bool(getattr(cls, "REQUIRES_DOWNLOAD", False))
meta["download_size_bytes"] = getattr(cls, "DOWNLOAD_SIZE_BYTES", None)

if meta.get("input_cardinality") is None:
meta["input_cardinality"] = {"min": 1}
Expand Down
59 changes: 50 additions & 9 deletions DashAI/front/src/components/notebooks/tool/ToolGrid.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,30 @@ import { useTourContext } from "../../tour/TourProvider";
import { groupByCategory, sortCategories } from "./toolCategories";
import { useTranslation } from "react-i18next";
import { useTheme } from "@mui/material/styles";
import { useSnackbar } from "notistack";
import { useExplorersAndConverters } from "../context/ExplorersAndConvertersContext";
import { startComponentDownload } from "../../models/model/ComponentDownloadControl";
import CredentialsDialog from "../../credentials/CredentialsDialog";
import { useToolGate } from "./useToolGate";

function ResolveDrop({ tool, onUse, onDownload, onNeedsCredentials }) {
const gate = useToolGate(tool);
useEffect(() => {
gate.resolve({ onUse, onDownload, onNeedsCredentials });
// Resolve once when the dropped tool changes; re-resolving on every state
// change could restart a download or reopen the dialog.
}, [tool?.name]);
return null;
}

export default function ToolGrid({ tools, notebook, FormComponent }) {
const [open, setOpen] = useState(false);
const [selectedTool, setSelectedTool] = useState(null);
const [credentialsDialogOpen, setCredentialsDialogOpen] = useState(false);
const tourContext = useTourContext();
const { t } = useTranslation(["datasets", "common"]);
const theme = useTheme();
const { enqueueSnackbar } = useSnackbar();
const { pendingDropTool, setPendingDropTool } = useExplorersAndConverters();

const grouped = useMemo(() => groupByCategory(tools), [tools]);
Expand All @@ -22,7 +38,7 @@ export default function ToolGrid({ tools, notebook, FormComponent }) {
[grouped],
);

const handleToolClick = (tool) => {
const handleUseTool = (tool) => {
setSelectedTool(tool);
setOpen(true);

Expand All @@ -41,14 +57,23 @@ export default function ToolGrid({ tools, notebook, FormComponent }) {
}
};

const handleDownloadTool = (tool) => {
startComponentDownload({ component: tool, enqueueSnackbar, t });
};

const handleNeedsCredentials = () => {
setCredentialsDialogOpen(true);
};

const droppedTool = useMemo(
() => tools.find((tool) => tool.name === pendingDropTool?.name),
[pendingDropTool, tools],
);

useEffect(() => {
if (!pendingDropTool) return;
const match = tools.find((t) => t.name === pendingDropTool.name);
if (match) {
handleToolClick(match);
setPendingDropTool(null);
}
}, [pendingDropTool, tools]);
if (!droppedTool) return;
setPendingDropTool(null);
}, [droppedTool, setPendingDropTool]);

if (!tools || tools.length === 0) {
return (
Expand Down Expand Up @@ -114,14 +139,25 @@ export default function ToolGrid({ tools, notebook, FormComponent }) {
key={tool.name}
tool={tool}
disabled={tool.disabled}
onClick={() => handleToolClick(tool)}
onUse={() => handleUseTool(tool)}
onDownload={() => handleDownloadTool(tool)}
onNeedsCredentials={handleNeedsCredentials}
/>
))}
</Box>
</Box>
);
})}

{droppedTool && (
<ResolveDrop
tool={droppedTool}
onUse={() => handleUseTool(droppedTool)}
onDownload={() => handleDownloadTool(droppedTool)}
onNeedsCredentials={handleNeedsCredentials}
/>
)}

{selectedTool && (
<ConfigureToolModal
open={open}
Expand All @@ -134,6 +170,11 @@ export default function ToolGrid({ tools, notebook, FormComponent }) {
FormSection={FormComponent}
/>
)}

<CredentialsDialog
open={credentialsDialogOpen}
onClose={() => setCredentialsDialogOpen(false)}
/>
</Box>
);
}
101 changes: 81 additions & 20 deletions DashAI/front/src/components/notebooks/tool/ToolGridItem.jsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,32 @@
import React, { useState } from "react";
import { Box, Typography, Chip, Tooltip } from "@mui/material";
import { Box, Typography, Tooltip, Stack } from "@mui/material";
import { VpnKeyOutlined as KeyIcon } from "@mui/icons-material";
import HoverToolInfo from "./HoverToolInfo";
import api from "../../../api/api";
import { CategoryIcon } from "./CategoryIcon";
import { useTranslation } from "react-i18next";
import { useTheme } from "@mui/material/styles";
import { setCustomDragImage } from "../../../utils/dragImage";
import ModelDownloadStatusIcon from "../../models/model/ModelDownloadStatusIcon";
import { useToolGate } from "./useToolGate";

export default function ToolGridItem({ tool, disabled, onClick }) {
export default function ToolGridItem({
tool,
disabled,
onUse,
onDownload,
onNeedsCredentials,
}) {
const [anchorEl, setAnchorEl] = useState(null);
const [hoveredTool, setHoveredTool] = useState(null);
const { t } = useTranslation(["common"]);
const { t } = useTranslation(["common", "credentials"]);
const theme = useTheme();

const gate = useToolGate({ ...tool, disabled });

const handleClick = () =>
gate.resolve({ onUse, onDownload, onNeedsCredentials });

const handleMouseEnter = (event, tool) => {
if (!disabled) {
setAnchorEl(event.currentTarget);
Expand All @@ -25,6 +39,24 @@ export default function ToolGridItem({ tool, disabled, onClick }) {
setHoveredTool(null);
};

const action =
gate.locked || gate.requiresDownload ? (
<Stack direction="row" spacing={0.5} alignItems="center">
{gate.locked && (
<Tooltip
title={t("credentials:requiredTooltip", {
platform: gate.requiredPlatforms,
})}
>
<KeyIcon fontSize="small" color="warning" />
</Tooltip>
)}
{gate.requiresDownload && (
<ModelDownloadStatusIcon model={tool} disabled={gate.locked} />
)}
</Stack>
) : null;

return (
<>
<Tooltip
Expand Down Expand Up @@ -52,9 +84,9 @@ export default function ToolGridItem({ tool, disabled, onClick }) {
>
<Box
key={tool.id}
draggable={!disabled}
draggable={!gate.blocked}
onDragStart={
!disabled
!gate.blocked
? (e) => {
e.dataTransfer.setData(
"application/x-dashai-tool",
Expand All @@ -67,19 +99,21 @@ export default function ToolGridItem({ tool, disabled, onClick }) {
}
onMouseEnter={(e) => handleMouseEnter(e, tool)}
onMouseLeave={handleMouseLeave}
onClick={disabled ? null : onClick}
onClick={handleClick}
sx={{
position: "relative",
bgcolor: disabled
bgcolor: gate.blocked
? theme.palette.ui.disabled
: theme.palette.ui.box,
border: `1px solid ${theme.palette.ui.border}`,
borderRadius: 1.5,
overflow: "hidden",
cursor: disabled ? "not-allowed" : "grab",
cursor: gate.blocked
? gate.gated
? "pointer"
: "not-allowed"
: "grab",
transition: "all 0.2s",
opacity: disabled ? 0.5 : 1,
filter: disabled ? "grayscale(0.6)" : "none",
"&:hover": {
bgcolor: disabled
? theme.palette.ui.disabled
Expand All @@ -90,7 +124,7 @@ export default function ToolGridItem({ tool, disabled, onClick }) {
transform: disabled ? "none" : "translateY(-4px)",
boxShadow: disabled ? "none" : `0 8px 16px rgba(0, 0, 0, 0.2)`,
},
"&::after": disabled
"&::after": gate.blocked
? {
content: '""',
position: "absolute",
Expand All @@ -104,17 +138,23 @@ export default function ToolGridItem({ tool, disabled, onClick }) {
: {},
}}
>
{/* Preview Image */}
{/* Preview Image — dimmed when blocked; the download/credential icons
below are kept out of every dimmed subtree so they keep full
color. */}
<Box
sx={{
width: "100%",
height: 100,
bgcolor: disabled
bgcolor: gate.blocked
? theme.palette.ui.disabled
: theme.palette.ui.border,
borderBottom: `1px solid ${
disabled ? theme.palette.ui.disabled : theme.palette.ui.border
gate.blocked
? theme.palette.ui.disabled
: theme.palette.ui.border
}`,
opacity: gate.blocked ? 0.5 : 1,
filter: gate.blocked ? "grayscale(0.6)" : "none",
}}
>
<img
Expand All @@ -124,7 +164,7 @@ export default function ToolGridItem({ tool, disabled, onClick }) {
width: "100%",
height: "100%",
objectFit: "cover",
opacity: disabled ? 0.4 : 1,
opacity: gate.blocked ? 0.4 : 1,
}}
/>
</Box>
Expand All @@ -142,33 +182,53 @@ export default function ToolGridItem({ tool, disabled, onClick }) {
height: 28,
p: 4,
borderRadius: 0.75,
bgcolor: disabled
bgcolor: gate.blocked
? theme.palette.ui.disabled
: theme.palette.ui.border,
color: disabled
color: gate.blocked
? theme.palette.text.disabled
: theme.palette.text.primary,
flexShrink: 0,
opacity: gate.blocked ? 0.5 : 1,
filter: gate.blocked ? "grayscale(0.6)" : "none",
}}
>
<CategoryIcon
icon={tool.metadata.icon}
color={
disabled ? theme.palette.text.disabled : tool.metadata.color
gate.blocked
? theme.palette.text.disabled
: tool.metadata.color
}
/>
</Box>

{action && (
<Box
draggable={false}
sx={{
ml: "auto",
display: "flex",
alignItems: "center",
flexShrink: 0,
zIndex: 3,
}}
>
{action}
</Box>
)}
</Box>

{/* Title */}
<Typography
variant="body2"
sx={{
color: disabled
color: gate.blocked
? theme.palette.text.disabled
: theme.palette.text.primary,
fontWeight: 500,
mb: 1,
opacity: gate.blocked ? 0.5 : 1,
overflow: "hidden",
textOverflow: "ellipsis",
display: "-webkit-box",
Expand All @@ -185,9 +245,10 @@ export default function ToolGridItem({ tool, disabled, onClick }) {
<Typography
variant="caption"
sx={{
color: disabled
color: gate.blocked
? theme.palette.text.disabled
: theme.palette.text.primary,
opacity: gate.blocked ? 0.5 : 1,
}}
>
{tool.metadata.category ?? t("common:other")}
Expand Down
Loading
Loading