diff --git a/docs/source/recipes.md b/docs/source/recipes.md
index 7f65acf3..b0437e31 100644
--- a/docs/source/recipes.md
+++ b/docs/source/recipes.md
@@ -40,35 +40,16 @@ The properties within each model stub are defined as the following:
### Image Classification
-| Model Tag | Validation Baseline Metric |
-| ----------------------------------------------------------------------------------------------------------------- | -------------------------- |
-| cv/classification/efficientnet-b0/pytorch/sparseml/imagenet/arch-moderate?recipe_type=original | 76.5% top1 accuracy |
-| cv/classification/efficientnet-b4/pytorch/sparseml/imagenet/arch-moderate?recipe_type=original | 82.1% top1 accuracy |
-| cv/classification/inception_v3/pytorch/sparseml/imagenet/pruned-conservative?recipe_type=original | 77.4% top1 accuracy |
-| cv/classification/inception_v3/pytorch/sparseml/imagenet/pruned-moderate?recipe_type=original | 76.6% top1 accuracy |
-| cv/classification/mobilenet_v1-1.0/pytorch/sparseml/imagenet/base-none?recipe_type=original | 70.9% top1 accuracy |
-| cv/classification/mobilenet_v1-1.0/pytorch/sparseml/imagenet/pruned-conservative?recipe_type=original | 70.9% top1 accuracy |
-| cv/classification/mobilenet_v1-1.0/pytorch/sparseml/imagenet/pruned-moderate?recipe_type=original | 70.1% top1 accuracy |
-| cv/classification/mobilenet_v1-1.0/pytorch/sparseml/imagenet/pruned_quant-moderate?recipe_type=original | 70.1% top1 accuracy |
-| cv/classification/mobilenet_v1-1.0/pytorch/sparseml/imagenet/pruned_quant-moderate?recipe_type=original | 70.1% top1 accuracy |
-| cv/classification/resnet_v1-101/pytorch/sparseml/imagenet/pruned-moderate?recipe_type=original | 76.6% top1 accuracy |
-| cv/classification/resnet_v1-152/pytorch/sparseml/imagenet/pruned-moderate?recipe_type=original | 77.5% top1 accuracy |
-| cv/classification/resnet_v1-18/pytorch/sparseml/imagenet/pruned-conservative?recipe_type=original | 69.8% top1 accuracy |
-| cv/classification/resnet_v1-34/pytorch/sparseml/imagenet/pruned-conservative?recipe_type=original | 73.3% top1 accuracy |
-| cv/classification/resnet_v1-50/pytorch/sparseml/imagenet/pruned-conservative?recipe_type=original | 76.1% top1 accuracy |
-| cv/classification/resnet_v1-50/pytorch/sparseml/imagenet/pruned-moderate?recipe_type=original | 75.3% top1 accuracy |
-| cv/classification/resnet_v1-50/pytorch/sparseml/imagenet-augmented/pruned_quant-aggressive?recipe_type=original | 76.1% top1 accuracy |
-| cv/classification/resnet_v1-50/pytorch/sparseml/imagenette/pruned-conservative?recipe_type=original | 99.9% top1 accuracy |
-| cv/classification/resnet_v1-50/pytorch/torchvision/imagenette/pruned-conservative?recipe_type=original | 99.9% top1 accuracy |
-| cv/classification/vgg-11/pytorch/sparseml/imagenet/pruned-moderate?recipe_type=original | 68.3% top1 accuracy |
-| cv/classification/vgg-16/pytorch/sparseml/imagenet/pruned-conservative?recipe_type=original | 71.6% top1 accuracy |
-| cv/classification/vgg-16/pytorch/sparseml/imagenet/pruned-moderate?recipe_type=original | 70.8% top1 accuracy |
-| cv/classification/vgg-19/pytorch/sparseml/imagenet/pruned-moderate?recipe_type=original | 71.7% top1 accuracy |
+
+
+
+
+Image classification table not loading? View full table [here](https://sparsezoo.neuralmagic.com/recipes/cv/classification).
### Object Detection
-| Model Tag | Validation Baseline Metric |
-| ----------------------------------------------------------------------------------------------------------------- | -------------------------- |
-| cv/detection/ssd-resnet50_300/pytorch/sparseml/coco/pruned-moderate?recipe_type=original | 41.8 mAP@0.5 |
-| cv/detection/ssd-resnet50_300/pytorch/sparseml/voc/pruned-moderate?recipe_type=original | 51.5 mAP@0.5 |
-| cv/detection/yolo_v3-spp/pytorch/ultralytics/coco/pruned-aggressive?recipe_type=original | 62.1 mAP@0.5 |
+
+
+
+
+Object detection table not loading? View full table [here](https://sparsezoo.neuralmagic.com/recipes/cv/detection).
diff --git a/src/ui/api/index.jsx b/src/ui/api/index.jsx
index c11a6f57..4af32f55 100644
--- a/src/ui/api/index.jsx
+++ b/src/ui/api/index.jsx
@@ -16,4 +16,5 @@ limitations under the License.
export * from "./auth";
export * from "./models";
+export * from "./recipes";
export * from "./utils";
diff --git a/src/ui/api/models.jsx b/src/ui/api/models.jsx
index 07ddcb5f..4508fa94 100644
--- a/src/ui/api/models.jsx
+++ b/src/ui/api/models.jsx
@@ -33,7 +33,7 @@ import { API_ROOT, validateAPIResponseJSON } from "./utils";
* training_scheme: string,
* sparse_name: string,
* sparse_category: string,
- * sparse_target
+ * sparse_target: string
* }} requestBody.queries the additional queries for the search result
* @returns {Promise}
*/
diff --git a/src/ui/api/recipes.jsx b/src/ui/api/recipes.jsx
new file mode 100644
index 00000000..5e853497
--- /dev/null
+++ b/src/ui/api/recipes.jsx
@@ -0,0 +1,64 @@
+/*
+Copyright (c) 2021 - present / Neuralmagic, Inc. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import { API_ROOT, validateAPIResponseJSON } from "./utils";
+
+/**
+ * API action for searching for models in the model zoo
+ * @param {object} requestBody the requestBody
+ * @param {string} requestBody.domain the domain of the model search
+ * @param {string} requestBody.subdomain the subdomain of the model search
+ * @param {string} requestBody.token the token for the model search authentication
+ * @param {string} requestBody.page the page of search results to return
+ * @param {string} requestBody.page_legth the amount of search results to return
+ * @param {{
+ * architecture: string,
+ * sub_architecture: string,
+ * repo: string,
+ * framework: string,
+ * dataset: string,
+ * training_scheme: string,
+ * sparse_name: string,
+ * sparse_category: string,
+ * sparse_target: string,
+ * recipe_type: string
+ * }} requestBody.queries the additional queries for the search result
+ * @returns {Promise}
+ */
+export function requestSearchRecipes({
+ domain,
+ subdomain,
+ token,
+ page = 1,
+ page_length = 20,
+ queries = {},
+}) {
+ let url = `${API_ROOT}/recipes/search/${domain}/${subdomain}?page=${page}&page_length=${page_length}`;
+ if (queries) {
+ for (const [key, value] of Object.entries(queries)) {
+ url = `${url}&${key}=${value}`;
+ }
+ }
+ return validateAPIResponseJSON(
+ fetch(url, {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ "nm-token-header": token,
+ },
+ })
+ );
+}
diff --git a/src/ui/components/model-table/model-table.jsx b/src/ui/components/model-table/model-table.jsx
index d025bb74..65dcc141 100644
--- a/src/ui/components/model-table/model-table.jsx
+++ b/src/ui/components/model-table/model-table.jsx
@@ -40,7 +40,6 @@ function ModelTable({ domain, subdomain, includePagination, includeHeader, queri
const modelsState = useSelector(selectModelsState);
const results = useSelector(selectModelTable);
-
useEffect(() => {
const status = lodash.get(modelsState.status, `${domain}.${subdomain}`, "idle");
if (authState.token !== null && status === "idle") {
@@ -81,6 +80,7 @@ function ModelTable({ domain, subdomain, includePagination, includeHeader, queri
copy={lodash.get(results, `${domain}.${subdomain}.copy`, false)}
width={lodash.get(results, `${domain}.${subdomain}.width`)}
includePagination={includePagination}
+ loadingMessage="Loading models"
/>
);
diff --git a/src/ui/components/recipe-table/index.jsx b/src/ui/components/recipe-table/index.jsx
new file mode 100644
index 00000000..ad34e7f5
--- /dev/null
+++ b/src/ui/components/recipe-table/index.jsx
@@ -0,0 +1,17 @@
+/*
+Copyright (c) 2021 - present / Neuralmagic, Inc. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+export { default } from "./recipe-table";
diff --git a/src/ui/components/recipe-table/recipe-table-styles.jsx b/src/ui/components/recipe-table/recipe-table-styles.jsx
new file mode 100644
index 00000000..0bf053b6
--- /dev/null
+++ b/src/ui/components/recipe-table/recipe-table-styles.jsx
@@ -0,0 +1,29 @@
+/*
+Copyright (c) 2021 - present / Neuralmagic, Inc. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import { makeStyles } from "@material-ui/core/styles";
+
+export default function makeRecipeTableStyles() {
+ return makeStyles(
+ (theme) => ({
+ root: {
+ padding: theme.spacing(0.5),
+ },
+ toolbar: {},
+ }),
+ { name: "RecipeTable" }
+ );
+}
diff --git a/src/ui/components/recipe-table/recipe-table.jsx b/src/ui/components/recipe-table/recipe-table.jsx
new file mode 100644
index 00000000..70755829
--- /dev/null
+++ b/src/ui/components/recipe-table/recipe-table.jsx
@@ -0,0 +1,104 @@
+/*
+Copyright (c) 2021 - present / Neuralmagic, Inc. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import React, { useEffect } from "react";
+import { useSelector, useDispatch } from "react-redux";
+import PropTypes from "prop-types";
+
+import lodash from "lodash";
+
+import Typography from "@material-ui/core/Typography";
+
+import {
+ selectAuthState,
+ selectRecipesState,
+ searchRecipesThunk,
+ selectRecipesTable,
+} from "../../store";
+import makeStyles from "./recipe-table-styles";
+import ZooTable from "../zoo-table";
+
+function RecipeTable({ domain, subdomain, includePagination, includeHeader, queries }) {
+ const useStyles = makeStyles();
+ const classes = useStyles();
+ const dispatch = useDispatch();
+
+ const authState = useSelector(selectAuthState);
+ const recipesState = useSelector(selectRecipesState);
+ console.log(recipesState);
+ const results = useSelector(selectRecipesTable);
+
+ useEffect(() => {
+ const status = lodash.get(recipesState.status, `${domain}.${subdomain}`, "idle");
+ if (authState.token !== null && status === "idle") {
+ dispatch(
+ searchRecipesThunk({
+ domain,
+ subdomain,
+ token: authState.token,
+ queries,
+ })
+ );
+ }
+ }, [authState.token, recipesState.status, dispatch, domain, subdomain, queries]);
+
+ const rows = lodash
+ .get(results, `${domain}.${subdomain}.data`, [])
+ .map((data) => data.row);
+ const status = lodash.get(results, `${domain}.${subdomain}.status`, "idle");
+ const loaded = status !== "idle" && status !== "loading";
+ return (
+
+ {loaded && includeHeader && (
+
+ {lodash.get(
+ results,
+ `${domain}.${subdomain}.displayName`,
+ `${domain} ${subdomain}`
+ )}
+
+ )}
+
+
+ );
+}
+
+RecipeTable.propTypes = {
+ domain: PropTypes.string.isRequired,
+ subdomain: PropTypes.string.isRequired,
+ queries: PropTypes.object,
+ includePagination: PropTypes.bool,
+ includeHeader: PropTypes.bool,
+};
+
+RecipeTable.defaultProps = {
+ includePagination: false,
+ includeHeader: false,
+ queries: {},
+};
+
+export default RecipeTable;
diff --git a/src/ui/components/zoo-table/zoo-table.jsx b/src/ui/components/zoo-table/zoo-table.jsx
index ebf13286..685e53d2 100644
--- a/src/ui/components/zoo-table/zoo-table.jsx
+++ b/src/ui/components/zoo-table/zoo-table.jsx
@@ -38,6 +38,7 @@ function ZooTable({
width,
copy,
includePagination,
+ loadingMessage,
}) {
const useStyles = makeStyles();
const classes = useStyles();
@@ -68,7 +69,7 @@ function ZooTable({
loaderSize={150}
loaderChildren={
- {!error && "Loading models"}
+ {!error && loadingMessage}
}
>
@@ -135,6 +136,7 @@ ZooTable.propTypes = {
]),
copy: PropTypes.oneOfType([PropTypes.bool, PropTypes.arrayOf(PropTypes.bool)]),
includePagination: PropTypes.bool,
+ loadingMessage: PropTypes.string,
};
ZooTable.defaultProps = {
@@ -146,6 +148,7 @@ ZooTable.defaultProps = {
paginationOptions: [10, 25, 100],
includePagination: false,
copy: false,
+ loadingMessage: "Loading...",
};
export default ZooTable;
diff --git a/src/ui/routes/paths.jsx b/src/ui/routes/paths.jsx
index 1fae26ff..24d2e89d 100644
--- a/src/ui/routes/paths.jsx
+++ b/src/ui/routes/paths.jsx
@@ -16,3 +16,5 @@ limitations under the License.
export const MODEL_TABLE_ROOT_PATH = "/models";
export const MODEL_TABLE_PATH = "/models/:domain/:subdomain";
+export const RECIPE_TABLE_ROOT_PATH = "/recipes";
+export const RECIPE_TABLE_PATH = "/recipes/:domain/:subdomain";
diff --git a/src/ui/routes/recipes-root/index.jsx b/src/ui/routes/recipes-root/index.jsx
new file mode 100644
index 00000000..cd66f8e0
--- /dev/null
+++ b/src/ui/routes/recipes-root/index.jsx
@@ -0,0 +1,17 @@
+/*
+Copyright (c) 2021 - present / Neuralmagic, Inc. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+export { default } from "./recipes-root";
diff --git a/src/ui/routes/recipes-root/recipes-root-styles.jsx b/src/ui/routes/recipes-root/recipes-root-styles.jsx
new file mode 100644
index 00000000..288a41d4
--- /dev/null
+++ b/src/ui/routes/recipes-root/recipes-root-styles.jsx
@@ -0,0 +1,26 @@
+/*
+Copyright (c) 2021 - present / Neuralmagic, Inc. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import { makeStyles } from "@material-ui/core/styles";
+
+export default function makeRecipeTableRootStyles() {
+ return makeStyles(
+ (theme) => ({
+ root: {},
+ }),
+ { name: "RecipeTableRoot" }
+ );
+}
diff --git a/src/ui/routes/recipes-root/recipes-root.jsx b/src/ui/routes/recipes-root/recipes-root.jsx
new file mode 100644
index 00000000..abcab81d
--- /dev/null
+++ b/src/ui/routes/recipes-root/recipes-root.jsx
@@ -0,0 +1,53 @@
+/*
+Copyright (c) 2021 - present / Neuralmagic, Inc. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import React from "react";
+
+import makeStyles from "./recipes-root-styles";
+import RecipesTable from "../../components/recipe-table";
+
+const DOMAINS_INFO = [
+ {
+ domain: "cv",
+ subdomain: "classification",
+ },
+ {
+ domain: "cv",
+ subdomain: "detection",
+ },
+];
+
+function RecipeTableRoot() {
+ const useStyles = makeStyles();
+
+ const classes = useStyles();
+
+ return (
+
+ {DOMAINS_INFO.map(({ domain, subdomain }) => (
+
+ ))}
+
+ );
+}
+
+export default RecipeTableRoot;
diff --git a/src/ui/routes/recipes/index.jsx b/src/ui/routes/recipes/index.jsx
new file mode 100644
index 00000000..dfa48c0d
--- /dev/null
+++ b/src/ui/routes/recipes/index.jsx
@@ -0,0 +1,17 @@
+/*
+Copyright (c) 2021 - present / Neuralmagic, Inc. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+export { default } from "./recipes";
diff --git a/src/ui/routes/recipes/recipes-styles.jsx b/src/ui/routes/recipes/recipes-styles.jsx
new file mode 100644
index 00000000..8fb1e2fd
--- /dev/null
+++ b/src/ui/routes/recipes/recipes-styles.jsx
@@ -0,0 +1,26 @@
+/*
+Copyright (c) 2021 - present / Neuralmagic, Inc. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import { makeStyles } from "@material-ui/core/styles";
+
+export default function makeRecipeTableStyles() {
+ return makeStyles(
+ (theme) => ({
+ root: {},
+ }),
+ { name: "Recipes" }
+ );
+}
diff --git a/src/ui/routes/recipes/recipes.jsx b/src/ui/routes/recipes/recipes.jsx
new file mode 100644
index 00000000..8060bf73
--- /dev/null
+++ b/src/ui/routes/recipes/recipes.jsx
@@ -0,0 +1,36 @@
+/*
+Copyright (c) 2021 - present / Neuralmagic, Inc. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import React from "react";
+import RecipeTable from "../../components/recipe-table";
+import { useQuery } from "../../hooks";
+
+import makeStyles from "./recipes-styles";
+
+function Recipes(props) {
+ const { domain, subdomain } = props.match.params;
+ const queries = useQuery();
+ const useStyles = makeStyles();
+ const classes = useStyles();
+
+ return (
+
+
+
+ );
+}
+
+export default Recipes;
diff --git a/src/ui/routes/routes.jsx b/src/ui/routes/routes.jsx
index b26f346b..b62efbb4 100644
--- a/src/ui/routes/routes.jsx
+++ b/src/ui/routes/routes.jsx
@@ -16,8 +16,15 @@ limitations under the License.
import ModelsRoot from "./models-root";
import Models from "./models";
+import RecipesRoot from "./recipes-root";
+import Recipes from "./recipes";
-import { MODEL_TABLE_PATH, MODEL_TABLE_ROOT_PATH } from "./paths";
+import {
+ MODEL_TABLE_PATH,
+ MODEL_TABLE_ROOT_PATH,
+ RECIPE_TABLE_PATH,
+ RECIPE_TABLE_ROOT_PATH,
+} from "./paths";
export function makeContentRoutes() {
return [
@@ -31,5 +38,15 @@ export function makeContentRoutes() {
exact: true,
component: Models,
},
+ {
+ path: RECIPE_TABLE_PATH,
+ exact: true,
+ component: Recipes,
+ },
+ {
+ path: RECIPE_TABLE_ROOT_PATH,
+ exact: true,
+ component: RecipesRoot,
+ },
];
}
diff --git a/src/ui/store/index.jsx b/src/ui/store/index.jsx
index 5433cfdd..c2549ff6 100644
--- a/src/ui/store/index.jsx
+++ b/src/ui/store/index.jsx
@@ -18,13 +18,16 @@ import { configureStore } from "@reduxjs/toolkit";
import authReducer from "./auth-slice";
import modelsReducer from "./models-slice";
+import recipesReducer from "./recipes-slice";
export default configureStore({
reducer: {
auth: authReducer,
models: modelsReducer,
+ recipes: recipesReducer,
},
});
export * from "./auth-slice";
export * from "./models-slice";
+export * from "./recipes-slice";
diff --git a/src/ui/store/models-slice.jsx b/src/ui/store/models-slice.jsx
index c6250638..b7de8aa7 100644
--- a/src/ui/store/models-slice.jsx
+++ b/src/ui/store/models-slice.jsx
@@ -32,20 +32,22 @@ export const searchModelsThunk = createAsyncThunk(
SEARCH_MODELS_PREFIX,
async ({ domain, subdomain, token, queries }, thunkApi) => {
let page = 1;
- const models = [];
+ let models = [];
let body;
do {
body = await requestSearchModels({ domain, subdomain, token, page, queries });
- models.push(...body.models);
- page += 1;
- thunkApi.dispatch({
- type: PARTIAL_SEARCH_MODELS_TYPE,
- payload: {
- domain,
- subdomain,
- models,
- },
- });
+ if (body.models.length > 0) {
+ models = [...models, ...body.models];
+ page += 1;
+ thunkApi.dispatch({
+ type: PARTIAL_SEARCH_MODELS_TYPE,
+ payload: {
+ domain,
+ subdomain,
+ models,
+ },
+ });
+ }
} while (body.models.length > 0);
return models;
@@ -76,9 +78,7 @@ const modelsSlice = createSlice({
lodash.setWith(state.status, `${domain}.${subdomain}`, "succeeded", {});
state.error = null;
- const models = action.payload.filter(
- (model) => !model.tags.map((tag) => tag.name).includes("demo")
- );
+ const models = action.payload;
lodash.setWith(state.models, `${domain}.${subdomain}`, models, {});
},
[searchModelsThunk.rejected]: (state, action) => {
@@ -91,9 +91,6 @@ const modelsSlice = createSlice({
lodash.setWith(state.status, `${domain}.${subdomain}`, "partial", {});
state.error = null;
- models = models.filter(
- (model) => !model.tags.map((tag) => tag.name).includes("demo")
- );
lodash.setWith(state.models, `${domain}.${subdomain}`, models, {});
},
},
diff --git a/src/ui/store/recipes-slice.jsx b/src/ui/store/recipes-slice.jsx
new file mode 100644
index 00000000..be42facb
--- /dev/null
+++ b/src/ui/store/recipes-slice.jsx
@@ -0,0 +1,198 @@
+/*
+Copyright (c) 2021 - present / Neuralmagic, Inc. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import { createSlice, createAsyncThunk, createSelector } from "@reduxjs/toolkit";
+
+import lodash from "lodash";
+
+import { getRecipeStub, getFormattedData } from "../utils";
+import { requestSearchRecipes } from "../api";
+
+const SEARCH_RECIPES_PREFIX = "recipes/searchRecipes";
+const PARTIAL_SEARCH_RECIPES_TYPE = `${SEARCH_RECIPES_PREFIX}/partial`;
+/**
+ * Async thunk for making a request to search for recipes
+ *
+ * @type {AsyncThunk, {domain: string, subdomain: string, token: string, queries: {}}, {}>}
+ */
+export const searchRecipesThunk = createAsyncThunk(
+ SEARCH_RECIPES_PREFIX,
+ async ({ domain, subdomain, token, queries }, thunkApi) => {
+ let page = 1;
+ let recipes = [];
+ let body;
+ do {
+ body = await requestSearchRecipes({ domain, subdomain, token, page, queries });
+ if (body.recipes.length > 0) {
+ recipes = [...recipes, ...body.recipes];
+ page += 1;
+ thunkApi.dispatch({
+ type: PARTIAL_SEARCH_RECIPES_TYPE,
+ payload: {
+ domain,
+ subdomain,
+ recipes,
+ },
+ });
+ }
+ } while (body.recipes.length > 0);
+ return recipes;
+ }
+);
+
+/**
+ * Slice for handling the recipes state in the redux store.
+ *
+ * @type {Slice<{recipes: Object, error: null, status: Object}, {}, string>}
+ */
+const recipesSlice = createSlice({
+ name: "recipes",
+ initialState: {
+ recipes: {},
+ error: null,
+ status: {},
+ },
+ reducers: {},
+ extraReducers: {
+ [searchRecipesThunk.pending]: (state, action) => {
+ const { domain, subdomain } = action.meta.arg;
+ lodash.setWith(state.status, `${domain}.${subdomain}`, "loading", {});
+ state.error = null;
+ },
+ [searchRecipesThunk.fulfilled]: (state, action) => {
+ const { domain, subdomain } = action.meta.arg;
+ lodash.setWith(state.status, `${domain}.${subdomain}`, "succeeded", {});
+ state.error = null;
+
+ const recipes = action.payload;
+ lodash.setWith(state.recipes, `${domain}.${subdomain}`, recipes, {});
+ },
+ [searchRecipesThunk.rejected]: (state, action) => {
+ const { domain, subdomain } = action.meta.arg;
+ lodash.setWith(state.status, `${domain}.${subdomain}`, "failed", {});
+ state.error = action.error.message;
+ },
+ [PARTIAL_SEARCH_RECIPES_TYPE]: (state, action) => {
+ let { domain, subdomain, recipes } = action.payload;
+ lodash.setWith(state.status, `${domain}.${subdomain}`, "partial", {});
+ state.error = null;
+
+ lodash.setWith(state.recipes, `${domain}.${subdomain}`, recipes, {});
+ },
+ },
+});
+
+/***
+ * Available actions for recipes redux store
+ */
+export const { defaultSearchStatus } = recipesSlice.actions;
+
+/**
+ * Simple selector to get the current recipes state
+ *
+ * @param state - the redux store state
+ * @returns {Reducer | Reducer<{recipes: Object, error: null, status: Object}>}
+ */
+export const selectRecipesState = (state) => state.recipes;
+
+const DISPLAY_NAMES = {
+ cv: {
+ classification: "Image Classification",
+ detection: "Object Detection",
+ },
+};
+
+/**
+ * Formats vision recipes to a table data format
+ *
+ * @param {string} domain domain of the recipes
+ * @param {string} subdomain subdomain of the recipes
+ * @param {{
+ * files: { checkpoint: boolean, file_type: string, file_size: number}[],
+ * results: { recorded_value: number, recorded_units: string, result_category: string }
+ * }[]} recipes the recipes of specified domain/subdomain
+ * @param {string} status status of loading recipes of specified domain/subdomain
+ */
+const visionRecipesToTableData = (domain, subdomain, recipes, status) => {
+ const displayName = lodash.get(
+ DISPLAY_NAMES,
+ `${domain}.${subdomain}`,
+ `${domain} ${subdomain}`
+ );
+
+ const data = recipes.map((recipe) => {
+ const file_size = `${(recipe.file_size / 1024).toFixed(2)} KB`;
+ return {
+ recipe,
+ row: [
+ recipe.model.display_name,
+ getRecipeStub(recipe),
+ file_size,
+ recipe.downloads,
+ getFormattedData(recipe, "training"),
+ getFormattedData(recipe, "inference"),
+ ],
+ };
+ });
+
+ return {
+ domain,
+ subdomain,
+ displayName,
+ headers: [
+ "Model Name",
+ "Recipe Stub",
+ "Content Size",
+ "Downloads",
+ "Training Metric",
+ "Inference Metric",
+ ],
+ recipes,
+ data,
+ status,
+ aligns: "left",
+ copy: [false, true, false, false, false],
+ };
+};
+
+/**
+ * Selector for recipe table for the current loaded data
+ */
+export const selectRecipesTable = createSelector(
+ [selectRecipesState],
+ (recipesState) => {
+ const table = {};
+ for (let domain in recipesState.status) {
+ table[domain] = {};
+ for (let subdomain in recipesState.status[domain]) {
+ let data;
+ if (domain === "cv") {
+ data = visionRecipesToTableData(
+ domain,
+ subdomain,
+ lodash.get(recipesState.recipes, `${domain}.${subdomain}`, []),
+ lodash.get(recipesState.status, `${domain}.${subdomain}`, "idle")
+ );
+ }
+
+ table[domain][subdomain] = data;
+ }
+ }
+ return table;
+ }
+);
+
+export default recipesSlice.reducer;
diff --git a/src/ui/utils/model-utils.jsx b/src/ui/utils/model-utils.jsx
index 20490212..20cb1091 100644
--- a/src/ui/utils/model-utils.jsx
+++ b/src/ui/utils/model-utils.jsx
@@ -37,6 +37,16 @@ export const getModelStub = (model) => {
return `zoo:${model.domain}/${model.sub_domain}/${archId}/${model.framework}/${model.repo}/${trainingId}/${sparseId}`;
};
+/**
+ * Gets a SparseZoo model stub from a model object
+ * @param {Object} recipe the model information
+ * @returns {string} SparseZoo model stub
+ */
+export const getRecipeStub = (recipe) => {
+ const modelStub = getModelStub(recipe.model);
+ return `${modelStub}/${recipe.recipe_type}`;
+};
+
/**
* Formats model's result objects of a specific type
* @param {object} model the model information