diff --git a/app/packages/components/src/components/MuiButton/index.tsx b/app/packages/components/src/components/MuiButton/index.tsx index cf92d94cf5..4845a30fd0 100644 --- a/app/packages/components/src/components/MuiButton/index.tsx +++ b/app/packages/components/src/components/MuiButton/index.tsx @@ -1,11 +1,27 @@ import React from "react"; -import { ButtonProps, CircularProgress, Button, Stack } from "@mui/material"; +import { + ButtonProps, + CircularProgress, + Button, + Stack, + useTheme, +} from "@mui/material"; export default function MuiButton(props: ButtonPropsType) { const { loading, variant, ...otherProps } = props; + const theme = useTheme(); const containedStyles = variant === "contained" ? { sx: { color: "white" } } : {}; + const outlinedStyles = + variant === "outlined" + ? { + sx: { + color: theme.palette.text.secondary, + borderColor: theme.palette.text.secondary, + }, + } + : {}; return ( - + + )} + + + {typeof Icon === "string" && ( + + )} + {Boolean(Icon) && typeof Icon !== "string" && Icon} + {computedLabel} + + {computedDescription} + + + {computedCaption} + + {!IS_APP_MODE_FIFTYONE && ( + {Actions && } + )} + {IS_APP_MODE_FIFTYONE && ( + + + Book a demo + + + Try in browser + + + )} + {docLink && ( + + {docCaption && ( + + {docCaption || "Not ready to upgrade yet?"} + + )} + } + href={docLink} + target="_blank" + > + View documentation + + + )} + + + + ); +} + +function TypographyOrNode(props: TypographyProps) { + const { children, ...otherProps } = props; + + if (typeof children === "string") { + return {children}; + } + + if (React.isValidElement(children)) { + return children; + } + + return null; +} + +export type PanelCTAProps = { + Actions?: FunctionComponent; + caption?: string | React.ReactNode; + description?: string | React.ReactNode; + docCaption?: string; + docLink?: string; + icon?: string | React.ReactNode; + iconProps?: IconProps; + label: string | React.ReactNode; + mode?: "onboarding" | "default"; + name: string; + onBack: () => void; + panelProps?: any; + demoLabel?: string; + demoDescription?: string; + demoCaption?: string; +}; diff --git a/app/packages/components/src/components/index.ts b/app/packages/components/src/components/index.ts index 1db2226835..0ec5e953a1 100644 --- a/app/packages/components/src/components/index.ts +++ b/app/packages/components/src/components/index.ts @@ -38,5 +38,6 @@ export { default as TextField } from "./TextField"; export { default as ThemeProvider, useFont, useTheme } from "./ThemeProvider"; export { default as Tooltip } from "./Tooltip"; export { default as Toast } from "./Toast"; +export { default as PanelCTA } from "./PanelCTA"; export * from "./types"; diff --git a/app/packages/components/src/components/types.ts b/app/packages/components/src/components/types.ts index 5fdd9523e0..96a5ba8ffd 100644 --- a/app/packages/components/src/components/types.ts +++ b/app/packages/components/src/components/types.ts @@ -1 +1,2 @@ export { type AdaptiveMenuItemComponentPropsType } from "./AdaptiveMenu"; +export { type PanelCTAProps } from "./PanelCTA"; diff --git a/app/packages/core/src/plugins/SchemaIO/components/ContainerizedComponent.tsx b/app/packages/core/src/plugins/SchemaIO/components/ContainerizedComponent.tsx index 4af8cd3526..3d85c91c6f 100644 --- a/app/packages/core/src/plugins/SchemaIO/components/ContainerizedComponent.tsx +++ b/app/packages/core/src/plugins/SchemaIO/components/ContainerizedComponent.tsx @@ -34,7 +34,7 @@ export default function ContainerizedComponent(props: ContainerizedComponent) { sxForOverlay.zIndex = 999; } return ( - + {containerizedChildren} ); diff --git a/app/packages/core/src/plugins/SchemaIO/components/NativeModelEvaluationView/EmptyOverview.tsx b/app/packages/core/src/plugins/SchemaIO/components/NativeModelEvaluationView/EmptyOverview.tsx deleted file mode 100644 index fc64c36d7b..0000000000 --- a/app/packages/core/src/plugins/SchemaIO/components/NativeModelEvaluationView/EmptyOverview.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { Launch, SsidChart } from "@mui/icons-material"; -import { Box, Button, Card, Stack, Typography } from "@mui/material"; -import React from "react"; -import Evaluate from "./Evaluate"; - -export default function EmptyOverview(props: EmptyOverviewProps) { - const { height, onEvaluate, permissions } = props; - return ( - - - - - - Run your first model evaluation to see results. - - - - - - Learn more about evaluating models in FiftyOne - - - - - - - - ); -} - -type EmptyOverviewProps = { - height: number; - onEvaluate: () => void; - permissions: Record; -}; diff --git a/app/packages/core/src/plugins/SchemaIO/components/NativeModelEvaluationView/Evaluate.tsx b/app/packages/core/src/plugins/SchemaIO/components/NativeModelEvaluationView/Evaluate.tsx index 7d46ec9f7b..c9aed0c979 100644 --- a/app/packages/core/src/plugins/SchemaIO/components/NativeModelEvaluationView/Evaluate.tsx +++ b/app/packages/core/src/plugins/SchemaIO/components/NativeModelEvaluationView/Evaluate.tsx @@ -1,22 +1,13 @@ -import { Launch } from "@mui/icons-material"; -import { Button } from "@mui/material"; +import { MuiButton } from "@fiftyone/components"; +import { Add } from "@mui/icons-material"; import React from "react"; export default function Evaluate(props: EvaluateProps) { - const { variant } = props; - - if (variant === "empty") return null; - + const { onEvaluate } = props; return ( - + } variant="contained"> + Evaluate Model + ); } diff --git a/app/packages/core/src/plugins/SchemaIO/components/NativeModelEvaluationView/index.tsx b/app/packages/core/src/plugins/SchemaIO/components/NativeModelEvaluationView/index.tsx index 191189e567..69995ced54 100644 --- a/app/packages/core/src/plugins/SchemaIO/components/NativeModelEvaluationView/index.tsx +++ b/app/packages/core/src/plugins/SchemaIO/components/NativeModelEvaluationView/index.tsx @@ -1,9 +1,12 @@ import { Box } from "@mui/material"; -import React, { useMemo } from "react"; +import React, { useCallback, useMemo } from "react"; import EmptyOverview from "./EmptyOverview"; import Evaluation from "./Evaluation"; import Overview from "./Overview"; import { useTriggerEvent } from "./utils"; +import { PanelCTA } from "@fiftyone/components"; +import { constants } from "@fiftyone/utilities"; +import Evaluate from "./Evaluate"; export default function NativeModelEvaluationView(props) { const { data = {}, schema, onChange, layout } = props; @@ -42,9 +45,17 @@ export default function NativeModelEvaluationView(props) { const { page = "overview", key, id, compareKey } = viewState; const showEmptyOverview = computedEvaluations.length === 0; const triggerEvent = useTriggerEvent(); + const [showCTA, setShowCTA] = React.useState(false); + const onEvaluate = useCallback(() => { + if (constants.IS_APP_MODE_FIFTYONE) { + setShowCTA(true); + } else { + triggerEvent(on_evaluate_model); + } + }, [triggerEvent, on_evaluate_model]); return ( - + {page === "evaluation" && ( )} {page === "overview" && - (showEmptyOverview ? ( - { - triggerEvent(on_evaluate_model); + (showEmptyOverview || showCTA ? ( + { + return ( + + ); }} - permissions={permissions} + name="Model Evaluation" + onBack={() => { + setShowCTA(false); + }} + mode={showCTA ? "default" : "onboarding"} /> ) : ( { - triggerEvent(on_evaluate_model); - }} + onEvaluate={onEvaluate} evaluations={computedEvaluations} statuses={statuses} notes={notes} diff --git a/app/packages/embeddings/src/Embeddings.tsx b/app/packages/embeddings/src/Embeddings.tsx index c5edcad824..6cabb97638 100644 --- a/app/packages/embeddings/src/Embeddings.tsx +++ b/app/packages/embeddings/src/Embeddings.tsx @@ -1,32 +1,31 @@ -import { useRef, Fragment, useEffect } from "react"; -import { useExternalLink } from "@fiftyone/utilities"; -import { Loading, Selector, useTheme } from "@fiftyone/components"; +import { MuiButton, Selector, useTheme } from "@fiftyone/components"; +import { OperatorPlacements, types } from "@fiftyone/operators"; import { usePanelStatePartial, useSetPanelCloseEffect } from "@fiftyone/spaces"; +import { constants, useExternalLink } from "@fiftyone/utilities"; import { - HighlightAlt, + Add, + CenterFocusWeak, Close, Help, + HighlightAlt, OpenWith, Warning, - CenterFocusWeak, - Add, } from "@mui/icons-material"; -import { useBrainResultsSelector } from "./useBrainResult"; -import { useLabelSelector } from "./useLabelSelector"; +import { Fragment, useEffect, useRef, useState } from "react"; +import EmbeddingsCTA from "./EmbeddingsCTA"; +import { EmbeddingsPlot } from "./EmbeddingsPlot"; import { EmbeddingsContainer, - Selectors, PlotOption, + Selectors, } from "./styled-components"; -import { Warnings } from "./Warnings"; -import { useWarnings } from "./useWarnings"; -import { EmbeddingsPlot } from "./EmbeddingsPlot"; +import { useBrainResultsSelector } from "./useBrainResult"; +import useComputeVisualization from "./useComputeVisualization"; +import { useLabelSelector } from "./useLabelSelector"; import { usePlotSelection } from "./usePlotSelection"; import { useResetPlotZoom } from "./useResetPlotZoom"; -import { OperatorPlacements, types } from "@fiftyone/operators"; -import ComputeVisualizationButton from "./ComputeVisualizationButton"; -import EmptyEmbeddings from "./EmptyEmbeddings"; -import useComputeVisualization from "./useComputeVisualization"; +import { useWarnings } from "./useWarnings"; +import { Warnings } from "./Warnings"; const Value: React.FC<{ value: string; className: string }> = ({ value }) => { return <>{value}; @@ -46,6 +45,7 @@ export default function Embeddings({ containerHeight, dimensions }) { "lasso", true ); + const [showCTA, setShowCTA] = useState(false); const warnings = useWarnings(); const setPanelCloseEffect = useSetPanelCloseEffect(); @@ -67,7 +67,7 @@ export default function Embeddings({ containerHeight, dimensions }) { padding: "0.25rem", }; - if (canSelect) + if (canSelect && !showCTA) return ( @@ -152,6 +152,19 @@ export default function Embeddings({ containerHeight, dimensions }) { )} + } + onClick={() => { + if (constants.IS_APP_MODE_FIFTYONE) { + setShowCTA(true); + } else { + computeViz.prompt(); + } + }} + variant="contained" + > + Compute Embeddings + {showPlot && ( ); - - return ; + return ( + { + setShowCTA(false); + }} + /> + ); } diff --git a/app/packages/embeddings/src/EmbeddingsCTA.tsx b/app/packages/embeddings/src/EmbeddingsCTA.tsx new file mode 100644 index 0000000000..55f74e7c20 --- /dev/null +++ b/app/packages/embeddings/src/EmbeddingsCTA.tsx @@ -0,0 +1,37 @@ +import { PanelCTA, PanelCTAProps } from "@fiftyone/components"; +import ComputeVisualizationButton from "./ComputeVisualizationButton"; +import useComputeVisualization from "./useComputeVisualization"; + +export function Actions(props: ActionsProps) { + const computeViz = useComputeVisualization(); + const defaultHandler = () => computeViz.prompt(); + const { handler = defaultHandler } = props; + return ; +} + +export default function EmbeddingsCTA(props: EmbeddingsCTAProps) { + const { mode, onBack } = props; + return ( + + ); +} + +type EmbeddingsCTAProps = { + onBack: PanelCTAProps["onBack"]; + mode?: PanelCTAProps["mode"]; +}; + +type ActionsProps = { + handler?: () => void; +}; diff --git a/app/packages/embeddings/src/EmptyEmbeddings.tsx b/app/packages/embeddings/src/EmptyEmbeddings.tsx deleted file mode 100644 index ceec6d986c..0000000000 --- a/app/packages/embeddings/src/EmptyEmbeddings.tsx +++ /dev/null @@ -1,124 +0,0 @@ -import { Box, Button, Link, Typography, Grid } from "@mui/material"; -import { useTheme } from "@fiftyone/components"; -import WorkspacesIcon from "@mui/icons-material/Workspaces"; -import OpenInNewIcon from "@mui/icons-material/OpenInNew"; -import ComputeVisualizationButton from "./ComputeVisualizationButton"; -import useComputeVisualization from "./useComputeVisualization"; - -export default function EmptyEmbeddings() { - const theme = useTheme(); - const secondaryBodyColor = theme.text.secondary; - const computeViz = useComputeVisualization(); - const hasComputeVisualization = computeViz.isAvailable; - - return ( - - - - - - - - - Embeddings help you explore and understand your dataset - - - - - You can compute and visualize embeddings for your dataset using a - selection of pre-trained models or your own embeddings - - - - {!hasComputeVisualization && } - {hasComputeVisualization && ( - - )} - - - {hasComputeVisualization && ( - - - - )} - - ); -} - -const ViewDocsButton = ({ variant }) => { - const theme = useTheme(); - const secondaryBodyColor = theme.text.secondary; - const backgroundColor = variant === "secondary" ? "none" : theme.primary.main; - const isSecondary = variant === "secondary"; - - return ( - - ); -}; - -function OSSContent() { - return ( - - - - ); -} - -function ComputeVisContent({ computeViz }) { - const theme = useTheme(); - const secondaryBodyColor = theme.text.secondary; - return ( - <> - - computeViz.prompt()} /> - - - ); -} diff --git a/app/packages/embeddings/src/styled-components.tsx b/app/packages/embeddings/src/styled-components.tsx index dc46597f0e..aa8812fc7c 100644 --- a/app/packages/embeddings/src/styled-components.tsx +++ b/app/packages/embeddings/src/styled-components.tsx @@ -20,6 +20,8 @@ export const Selectors = styled.div` display: flex; column-gap: 1rem; } + justify-content: space-between; + width: 100%; `; export const PlotOption = styled(Link)` diff --git a/app/packages/embeddings/src/useComputeVisualization.ts b/app/packages/embeddings/src/useComputeVisualization.ts index 5d9b754c70..6fc3a6f301 100644 --- a/app/packages/embeddings/src/useComputeVisualization.ts +++ b/app/packages/embeddings/src/useComputeVisualization.ts @@ -1,27 +1,12 @@ -import { useCallback, useMemo } from "react"; -import { listLocalAndRemoteOperators } from "@fiftyone/operators/src/operators"; -import { usePanelEvent } from "@fiftyone/operators"; +import { useFirstExistingUri, usePanelEvent } from "@fiftyone/operators"; import { usePanelId } from "@fiftyone/spaces"; +import { useCallback } from "react"; const IS_OSS = true; // false in fiftyone-teams -const useFirstExistingUri = (uris: string[]) => { - const availableOperators = useMemo(() => listLocalAndRemoteOperators(), []); - return useMemo(() => { - const existingUri = uris.find((uri) => - availableOperators.allOperators.some((op) => op.uri === uri) - ); - const exists = Boolean(existingUri); - return { firstExistingUri: existingUri, exists }; - }, [availableOperators, uris]); -}; - export default function useComputeVisualization() { - let { firstExistingUri: computeVisUri, exists: hasComputeVisualization } = - useFirstExistingUri([ - "@voxel51/brain/compute_visualization", - "@voxel51/operators/compute_visualization", - ]); + const { firstExistingUri: computeVisUri, exists: hasComputeVisualization } = + useFirstExistingUri(["@voxel51/operators/compute_visualization"]); const panelId = usePanelId(); const triggerEvent = usePanelEvent(); diff --git a/app/packages/operators/src/CustomPanel.tsx b/app/packages/operators/src/CustomPanel.tsx index 78158d6853..14d3548ca0 100644 --- a/app/packages/operators/src/CustomPanel.tsx +++ b/app/packages/operators/src/CustomPanel.tsx @@ -79,7 +79,7 @@ export function CustomPanel(props: CustomPanelProps) { overflow: "auto", }} > - + listLocalAndRemoteOperators(), []); + return useMemo(() => { + const existingUri = uris.find((uri) => + availableOperators.allOperators.some((op) => op.uri === uri) + ); + const exists = Boolean(existingUri); + return { firstExistingUri: existingUri, exists }; + }, [availableOperators, uris]); +} diff --git a/app/packages/operators/src/index.ts b/app/packages/operators/src/index.ts index 2adaba5f04..36b0acb547 100644 --- a/app/packages/operators/src/index.ts +++ b/app/packages/operators/src/index.ts @@ -1,4 +1,5 @@ export { OPERATOR_PROMPT_AREAS } from "./constants"; +export { useFirstExistingUri } from "./hooks"; export { useOperators } from "./loader"; export { default as OperatorBrowser } from "./OperatorBrowser"; export { default as OperatorCore } from "./OperatorCore"; diff --git a/app/packages/utilities/src/constants.ts b/app/packages/utilities/src/constants.ts index 0f75dd60d4..1f8fa79f68 100644 --- a/app/packages/utilities/src/constants.ts +++ b/app/packages/utilities/src/constants.ts @@ -38,3 +38,7 @@ export const KEYPOINTS_FIELD = "fiftyone.core.labels.Keypoints"; export const KEYPOINTS_POINTS_FIELD = "fiftyone.core.fields.KeypointsField"; export const REGRESSION_FIELD = "fiftyone.core.labels.Regression"; export const GROUP = "fiftyone.core.groups.Group"; +export const BOOK_A_DEMO_LINK = "https://voxel51.com/book-a-demo/"; +export const TRY_IN_BROWSER_LINK = "https://voxel51.com/try-fiftyone/"; +export const APP_MODE = "fiftyone"; +export const IS_APP_MODE_FIFTYONE = APP_MODE === "fiftyone"; diff --git a/app/packages/utilities/src/index.ts b/app/packages/utilities/src/index.ts index f26c0c57cf..b23d0ad1e9 100644 --- a/app/packages/utilities/src/index.ts +++ b/app/packages/utilities/src/index.ts @@ -14,6 +14,7 @@ export * from "./schema"; export { default as sizeBytesEstimate } from "./size-bytes-estimate"; export * as styles from "./styles"; export * from "./type-check"; +export * as constants from "./constants"; interface O { [key: string]: O | any;