Pārlūkot izejas kodu

wip: fe changes for inference models

Yosef Mihretie 2 gadi atpakaļ
vecāks
revīzija
0fb5fb88c3

+ 11 - 14
dashboard/package-lock.json

@@ -100,7 +100,7 @@
         "@babel/preset-typescript": "^7.15.0",
         "@ianvs/prettier-plugin-sort-imports": "^4.1.1",
         "@pmmmwh/react-refresh-webpack-plugin": "^0.4.3",
-        "@porter-dev/api-contracts": "^0.2.155",
+        "@porter-dev/api-contracts": "file:../../api-contracts/generated/js",
         "@testing-library/jest-dom": "^4.2.4",
         "@testing-library/react": "^9.3.2",
         "@testing-library/user-event": "^7.1.2",
@@ -173,6 +173,14 @@
         "npm": "9.7.2"
       }
     },
+    "../../api-contracts/generated/js": {
+      "version": "0.2.7",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@bufbuild/protobuf": "^1.1.0"
+      }
+    },
     "node_modules/@aashutoshrathi/word-wrap": {
       "version": "1.2.6",
       "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz",
@@ -2076,12 +2084,6 @@
         "node": ">=6.9.0"
       }
     },
-    "node_modules/@bufbuild/protobuf": {
-      "version": "1.8.0",
-      "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-1.8.0.tgz",
-      "integrity": "sha512-qR9FwI8QKIveDnUYutvfzbC21UZJJryYrLuZGjeZ/VGz+vXelUkK+xgkOHsvPEdYEdxtgUUq4313N8QtOehJ1Q==",
-      "dev": true
-    },
     "node_modules/@discoveryjs/json-ext": {
       "version": "0.5.7",
       "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz",
@@ -2786,13 +2788,8 @@
       }
     },
     "node_modules/@porter-dev/api-contracts": {
-      "version": "0.2.155",
-      "resolved": "https://registry.npmjs.org/@porter-dev/api-contracts/-/api-contracts-0.2.155.tgz",
-      "integrity": "sha512-Tar/IsKoUSmz8Q8Fw9ozflrAI+yAGzOIdx5WmZ5iCSCkvudSLnDp7xQ0po/traPzYLUldZjaNsw0KKXnOb1myQ==",
-      "dev": true,
-      "dependencies": {
-        "@bufbuild/protobuf": "^1.1.0"
-      }
+      "resolved": "../../api-contracts/generated/js",
+      "link": true
     },
     "node_modules/@react-spring/animated": {
       "version": "9.6.1",

+ 1 - 1
dashboard/package.json

@@ -107,7 +107,7 @@
     "@babel/preset-typescript": "^7.15.0",
     "@ianvs/prettier-plugin-sort-imports": "^4.1.1",
     "@pmmmwh/react-refresh-webpack-plugin": "^0.4.3",
-    "@porter-dev/api-contracts": "^0.2.155",
+    "@porter-dev/api-contracts": "file:../../api-contracts/generated/js",
     "@testing-library/jest-dom": "^4.2.4",
     "@testing-library/react": "^9.3.2",
     "@testing-library/user-event": "^7.1.2",

+ 34 - 0
dashboard/src/lib/addons/index.ts

@@ -3,6 +3,7 @@ import {
   Addon,
   AddonType,
   Datadog,
+  Deepgram,
   Metabase,
   Mezmo,
   Newrelic,
@@ -22,6 +23,7 @@ import { newrelicConfigValidator } from "./newrelic";
 import { defaultPostgresAddon, postgresConfigValidator } from "./postgres";
 import { redisConfigValidator } from "./redis";
 import { tailscaleConfigValidator } from "./tailscale";
+import { deepgramConfigValidator } from "lib/models/deepgram";
 import {
   ADDON_TEMPLATE_DATADOG,
   ADDON_TEMPLATE_METABASE,
@@ -32,6 +34,7 @@ import {
   ADDON_TEMPLATE_TAILSCALE,
   type AddonTemplate,
 } from "./template";
+import { ADDON_TEMPLATE_DEEPGRAM } from "lib/models/template";
 
 export const clientAddonValidator = z.object({
   expanded: z.boolean().default(false),
@@ -55,6 +58,7 @@ export const clientAddonValidator = z.object({
     metabaseConfigValidator,
     newrelicConfigValidator,
     tailscaleConfigValidator,
+    deepgramConfigValidator,
   ]),
 });
 export type ClientAddonType = z.infer<
@@ -153,6 +157,16 @@ export function defaultClientAddon(
       }),
       template: ADDON_TEMPLATE_TAILSCALE,
     }))
+    .with("deepgram", () => ({
+      ...clientAddonValidator.parse({
+        expanded: true,
+        name: { readOnly: false, value: "deepgram" },
+        config: deepgramConfigValidator.parse({
+          type: "deepgram",
+        }),
+      }),
+      template: ADDON_TEMPLATE_DEEPGRAM,
+    }))
     .exhaustive();
 }
 
@@ -165,6 +179,7 @@ function addonTypeEnumProto(type: ClientAddon["config"]["type"]): AddonType {
     .with("metabase", () => AddonType.METABASE)
     .with("newrelic", () => AddonType.NEWRELIC)
     .with("tailscale", () => AddonType.TAILSCALE)
+    .with("deepgram", () => AddonType.DEEPGRAM)
     .exhaustive();
 }
 
@@ -254,6 +269,16 @@ export function clientAddonToProto(
       }),
       case: "tailscale" as const,
     }))
+    .with({ type: "deepgram" }, (data) => ({
+      value: new Deepgram({
+        apiKey: data.deepgramAPIKey,
+        ecrUsername: data.quayUsername,
+        ecrPassword: data.quaySecret,
+        ecrEmail: data.quayEmail,
+        instanceType: data.instanceType,
+      }),
+      case: "deepgram" as const,
+    }))
     .exhaustive();
 
   const proto = new Addon({
@@ -365,6 +390,14 @@ export function clientAddonFromProto({
       authKey: data.value.authKey ?? "",
       subnetRoutes: data.value.subnetRoutes.map((r) => ({ route: r })),
     }))
+    .with({ case: "deepgram" }, (data) => ({
+      type: "deepgram" as const,
+      deepgramAPIKey: data.value.apiKey ?? "",
+      quayUsername: data.value.ecrUsername ?? "",
+      quaySecret: data.value.ecrPassword ?? "",
+      quayEmail: data.value.ecrEmail ?? "",
+      instanceType: "g4dn.xlarge" as const,
+    }))
     .exhaustive();
 
   const template = match(addon.config)
@@ -375,6 +408,7 @@ export function clientAddonFromProto({
     .with({ case: "metabase" }, () => ADDON_TEMPLATE_METABASE)
     .with({ case: "newrelic" }, () => ADDON_TEMPLATE_NEWRELIC)
     .with({ case: "tailscale" }, () => ADDON_TEMPLATE_TAILSCALE)
+    .with({ case: "deepgram" }, () => ADDON_TEMPLATE_DEEPGRAM)
     .exhaustive();
 
   const clientAddon = {

+ 12 - 0
dashboard/src/lib/models/deepgram.tsx

@@ -0,0 +1,12 @@
+import { z } from "zod";
+
+export const deepgramConfigValidator = z.object({
+    type: z.literal("deepgram"),
+    deepgramAPIKey: z.string().nonempty().default("*********"),
+    quayUsername: z.string().nonempty().default("username"),
+    quaySecret: z.string().nonempty().default("secret"),
+    quayEmail: z.string(),
+    instanceType: z.literal("g4dn.xlarge"),
+});
+
+export type DeepgramConfigValidator = z.infer<typeof deepgramConfigValidator>;

+ 30 - 0
dashboard/src/lib/models/template.ts

@@ -0,0 +1,30 @@
+import { ClientAddonType } from "lib/addons";
+import { AddonTemplate } from "lib/addons/template";
+import DeepgramForm from "main/home/inference-dashboard/TemplateForms/DeepgramForm";
+
+export const ADDON_TEMPLATE_DEEPGRAM: AddonTemplate<"deepgram"> = {
+    type: "deepgram",
+    displayName: "Deepgram",
+    description: "A popular speech-to-text service.",
+    icon: "https://play-lh.googleusercontent.com/wczDL05-AOb39FcL58L32h6j_TrzzGTXDLlOrOmJ-aNsnoGsT1Gkk2vU4qyTb7tGxRw=w240-h480-rw",
+    tags: ["Networking"],
+    tabs: [
+      {
+        name: "configuration",
+        displayName: "Configuration",
+        component: DeepgramForm,
+      },
+    ],
+    defaultValues: {
+      type: "deepgram",
+      deepgramAPIKey: "",
+      quayUsername: "",
+      quaySecret: "",
+      quayEmail: "", 
+      instanceType: "g4dn.xlarge",
+    },
+  };
+
+export const SUPPORTED_MODEL_ADDON_TEMPLATES: Array<AddonTemplate<ClientAddonType>> = [
+    ADDON_TEMPLATE_DEEPGRAM,
+]

+ 20 - 19
dashboard/src/main/home/Home.tsx

@@ -73,6 +73,8 @@ import { NewProjectFC } from "./new-project/NewProject";
 import Onboarding from "./onboarding/Onboarding";
 import ProjectSettings from "./project-settings/ProjectSettings";
 import Sidebar from "./sidebar/Sidebar";
+import AddonFormContextProvider from "./add-on-dashboard/AddonFormContextProvider";
+import AddonForm from "./add-on-dashboard/AddonForm";
 
 dayjs.extend(relativeTime);
 
@@ -494,25 +496,6 @@ const Home: React.FC<Props> = (props) => {
                 <Apps />
               </Route>
 
-              <Route path="/inference/templates/:templateId/new">
-                <ModelForm />
-              </Route>
-              <Route path="/inference/templates/:templateId">
-                <ExpandedModelTemplate />
-              </Route>
-              <Route path="/inference/templates">
-                <ModelTemplates />
-              </Route>
-              <Route path="/inference/:modelName/:tab">
-                <AppView />
-              </Route>
-              <Route path="/inference/:modelName">
-                <AppView />
-              </Route>
-              <Route path="/inference">
-                <InferenceDashboard />
-              </Route>
-
               <Route path="/environment-groups/new">
                 <CreateEnvGroup />
               </Route>
@@ -566,6 +549,24 @@ const Home: React.FC<Props> = (props) => {
                   <LegacyAddOnDashboard />
                 )}
               </Route>
+              <Route path="/inference/models">
+                <ModelTemplates />
+              </Route>
+              <Route path="/inference/expanded/:modelType">
+                <ExpandedModelTemplate />
+              </Route>
+              <Route path="/inference/new/:modelType">
+                <ModelForm />
+              </Route>
+              <Route path="/inference/:modelType">
+                <AddonView />
+              </Route>
+              <Route path="/inference/:modelType/:tab">
+                <AddonView />
+              </Route>
+              <Route path="/inference">
+                <InferenceDashboard />
+              </Route>
               <Route
                 path="/new-project"
                 render={() => {

+ 1 - 1
dashboard/src/main/home/add-on-dashboard/AddonTemplates.tsx

@@ -89,7 +89,7 @@ const StyledTemplateComponent = styled.div`
   height: 100%;
 `;
 
-const Tag = styled.div<{ size?: string; bottom?: string; left?: string }>`
+export const Tag = styled.div<{ size?: string; bottom?: string; left?: string }>`
   position: absolute;
   bottom: ${(props) => props.bottom || "auto"};
   left: ${(props) => props.left || "auto"};

+ 2 - 0
dashboard/src/main/home/add-on-dashboard/common/Configuration.tsx

@@ -8,6 +8,7 @@ import MetabaseForm from "../metabase/MetabaseForm";
 import MezmoForm from "../mezmo/MezmoForm";
 import NewRelicForm from "../newrelic/NewRelicForm";
 import TailscaleForm from "../tailscale/TailscaleForm";
+import DeepgramForm from "main/home/inference-dashboard/TemplateForms/DeepgramForm";
 
 type Props = {
   type: ClientAddon["config"]["type"];
@@ -20,6 +21,7 @@ const Configuration: React.FC<Props> = ({ type }) => {
     .with("metabase", () => <MetabaseForm />)
     .with("newrelic", () => <NewRelicForm />)
     .with("tailscale", () => <TailscaleForm />)
+    .with("deepgram", () => <DeepgramForm />)
     .otherwise(() => null);
 };
 

+ 33 - 18
dashboard/src/main/home/inference-dashboard/ExpandedModelTemplate.tsx

@@ -1,4 +1,4 @@
-import React from "react";
+import React, { useContext, useMemo } from "react";
 import AceEditor from "react-ace";
 import { useHistory, useParams } from "react-router";
 import styled from "styled-components";
@@ -12,33 +12,41 @@ import InfoSection from "components/porter/InfoSection";
 import Line from "components/porter/Line";
 import Link from "components/porter/Link";
 import Spacer from "components/porter/Spacer";
-import Tag from "components/porter/Tag";
-import Text from "components/porter/Text";
+import Text from "components/porter/Text"
+
+import { Tag } from "../add-on-dashboard/AddonTemplates"
 
 import DashboardHeader from "../cluster-dashboard/DashboardHeader";
 import { models, tagColor } from "./models";
+import { Context } from "shared/Context";
+import { SUPPORTED_MODEL_ADDON_TEMPLATES } from "lib/models/template";
+import AddonFormContextProvider from "../add-on-dashboard/AddonFormContextProvider";
+import { AddonTemplateTagColor } from "lib/addons/template";
 
 const ExpandedModelTemplate: React.FC = () => {
-  const { templateId } = useParams<{
-    templateId: string;
+  const { modelType } = useParams<{
+    modelType: string;
   }>();
 
-  const template = models[templateId] || {
-    name: "",
-    description: "",
-    tags: [],
-  };
+  const { currentProject } = useContext(Context);
+  const history = useHistory();
+
+  const templateMatch = SUPPORTED_MODEL_ADDON_TEMPLATES.find((t) => t.type === modelType);
+
+  if (templateMatch === undefined) {
+    return null;
+  }
 
   return (
     <Container style={{ width: "100%" }}>
-      <Back to="/inference/templates" />
+      <Back to="/inference/models" />
       <Container row spaced>
         <Container row>
-          <Image size={24} src={template.icon} />
+          <Image size={24} src={templateMatch.icon} />
           <Spacer inline x={1} />
-          <Text size={21}>{template.name}</Text>
+          <Text size={21}>{templateMatch.displayName}</Text>
         </Container>
-        <Link to={`/inference/templates/${templateId}/new`}>
+        <Link to={`/inference/new/${modelType}`}>
           <Button>
             <I size={14}>add</I>
             <Spacer inline x={0.5} />
@@ -47,15 +55,22 @@ const ExpandedModelTemplate: React.FC = () => {
         </Link>
       </Container>
       <Spacer y={1} />
-      <InfoSection text={template.description} />
+      <InfoSection text={templateMatch.description} />
       <Spacer y={1} />
       <Container row>
-        {template.tags?.map((t) => (
+        {templateMatch.tags?.map((t) => (
           <>
-            <Tag key={t} backgroundColor={tagColor[t]} hoverable={false}>
+            <Tag
+              bottom="10px"
+              left="12px"
+              style={{ background: AddonTemplateTagColor[t] }}
+              key={t}
+            >
               {t}
             </Tag>
-            <Spacer inline x={1} />
+            {templateMatch.tags.indexOf(t) !== templateMatch.tags.length - 1 && (
+              <Spacer inline x={0.5} />
+            )}
           </>
         ))}
       </Container>

+ 1 - 1
dashboard/src/main/home/inference-dashboard/InferenceDashboard.tsx

@@ -30,7 +30,7 @@ const PreviewEnvs: React.FC = () => {
 
           <Text color={"helper"}>Get started by deploying a model.</Text>
           <Spacer y={1} />
-          <Link to="/inference/templates">
+          <Link to="/inference/models">
             <Button onClick={() => ({})} height="35px" alt>
               Deploy a new model <Spacer inline x={1} />{" "}
               <i className="material-icons" style={{ fontSize: "18px" }}>

+ 16 - 31
dashboard/src/main/home/inference-dashboard/ModelForm.tsx

@@ -1,4 +1,4 @@
-import React from "react";
+import React, { useContext } from "react";
 import { useHistory, useParams } from "react-router";
 import styled from "styled-components";
 import { match } from "ts-pattern";
@@ -12,44 +12,29 @@ import Text from "components/porter/Text";
 
 import { models } from "./models";
 import Gpt2Form from "./TemplateForms/Gpt2Form";
+import { Context } from "shared/Context";
+import { SUPPORTED_MODEL_ADDON_TEMPLATES } from "lib/models/template";
+import AddonForm from "../add-on-dashboard/AddonForm";
+import AddonFormContextProvider from "../add-on-dashboard/AddonFormContextProvider";
 
 const InferenceForm: React.FC = () => {
-  const { templateId } = useParams<{
-    templateId: string;
+  const { modelType } = useParams<{
+    modelType: string;
   }>();
+
+  const { currentProject } = useContext(Context);
   const history = useHistory();
 
-  const template = models[templateId] || {
-    name: "",
-    icon: "",
-    description: "",
-    tags: [],
-  };
+  const templateMatch = SUPPORTED_MODEL_ADDON_TEMPLATES.find((t) => t.type === modelType);
 
-  const renderForm = (): React.ReactElement => {
-    return match(templateId)
-      .returnType<JSX.Element>()
-      .with("gpt-2", () => <Gpt2Form />)
-      .otherwise(() => <div>Template not found</div>);
-  };
+  if (templateMatch === undefined) {
+    return null;
+  }
 
   return (
-    <CenterWrapper>
-      <Back
-        onClick={() => {
-          history.push(`/inference/templates/${templateId}`);
-        }}
-      />
-      <Container row>
-        <FloatIn>
-          <Image size={24} src={template.icon} />
-        </FloatIn>
-        <Spacer inline x={1} />
-        <Text size={21}>Configure new {template.name} instance</Text>
-      </Container>
-      <Spacer y={1} />
-      {renderForm()}
-    </CenterWrapper>
+    <AddonFormContextProvider projectId={currentProject?.id} redirectOnSubmit>
+      <AddonForm template={templateMatch} />
+    </AddonFormContextProvider>
   );
 };
 

+ 75 - 36
dashboard/src/main/home/inference-dashboard/ModelTemplates.tsx

@@ -5,16 +5,36 @@ import styled from "styled-components";
 import Back from "components/porter/Back";
 import Container from "components/porter/Container";
 import Spacer from "components/porter/Spacer";
-import Tag from "components/porter/Tag";
 
 import inferenceGrad from "assets/inference-grad.svg";
 
 import DashboardHeader from "../cluster-dashboard/DashboardHeader";
-import { models, tagColor } from "./models";
+import { Context } from "shared/Context";
+import { SUPPORTED_MODEL_ADDON_TEMPLATES } from "lib/models/template";
+import AddonFormContextProvider from "../add-on-dashboard/AddonFormContextProvider";
+import AddonForm from "../add-on-dashboard/AddonForm";
+import { AddonTemplate, AddonTemplateTagColor } from "lib/addons/template";
+import { ClientAddonType } from "lib/addons";
 
 const ModelTemplates: React.FC = () => {
+  const { currentProject } = useContext(Context);
+  const { search } = useLocation();
+  const queryParams = new URLSearchParams(search);
   const history = useHistory();
 
+  const templateMatch = useMemo(() => {
+    const addonName = queryParams.get("addon_name");
+    return SUPPORTED_MODEL_ADDON_TEMPLATES.find((t) => t.type === addonName);
+  }, [queryParams]);
+
+  if (templateMatch) {
+    return (
+      <AddonFormContextProvider projectId={currentProject?.id} redirectOnSubmit>
+        <AddonForm template={templateMatch} />
+      </AddonFormContextProvider>
+    );
+  }
+
   return (
     <StyledTemplateComponent>
       <Back to="/inference" />
@@ -26,40 +46,42 @@ const ModelTemplates: React.FC = () => {
         disableLineBreak
       />
       <TemplateListWrapper>
-        {Array.from(Object.keys(models)).map((id: string) => {
-          const template = models[id];
-          return (
-            <TemplateBlock
-              key={id}
-              onClick={() => {
-                history.push(`/inference/templates/${id}`);
-              }}
-            >
-              <Icon src={template.icon} />
-              <TemplateTitle>{template.name}</TemplateTitle>
-              <TemplateDescription>{template.description}</TemplateDescription>
-              <Spacer y={0.25} />
-              <Container row>
-                {template.tags?.map((t) => (
-                  <>
-                    <Tag
-                      backgroundColor={tagColor[t]}
-                      key={t}
-                      size={11}
-                      hoverable={false}
-                    >
-                      {t}
-                    </Tag>
-                    {template.tags.indexOf(t) !== template.tags.length - 1 && (
-                      <Spacer inline x={0.5} />
-                    )}
-                  </>
-                ))}
-              </Container>
-              <Spacer y={0.5} />
-            </TemplateBlock>
-          );
-        })}
+        {
+          SUPPORTED_MODEL_ADDON_TEMPLATES.map(
+            (template: AddonTemplate<ClientAddonType>) => {
+              return (
+                <TemplateBlock
+                  key={template.type}
+                  onClick={() => {
+                    history.push(`/inference/expanded/${template.type}`);
+                  }}
+                >
+                  <Icon src={template.icon} />
+                  <TemplateTitle>{template.displayName}</TemplateTitle>
+                  <TemplateDescription>{template.description}</TemplateDescription>
+                  <Spacer y={0.25} />
+                  <Container row>
+                    {template.tags?.map((t) => (
+                      <>
+                        <Tag
+                          bottom="10px"
+                          left="12px"
+                          style={{ background: AddonTemplateTagColor[t] }}
+                          key={t}
+                        >
+                          {t}
+                        </Tag>
+                        {template.tags.indexOf(t) !== template.tags.length - 1 && (
+                          <Spacer inline x={0.5} />
+                        )}
+                      </>
+                    ))}
+                  </Container>
+                  <Spacer y={0.5} />
+                </TemplateBlock>
+              );
+            }
+          )}
       </TemplateListWrapper>
     </StyledTemplateComponent>
   );
@@ -138,3 +160,20 @@ const Icon = styled.img`
   margin-top: 20px;
   margin-bottom: 5px;
 `;
+
+
+const Tag = styled.div<{ size?: string; bottom?: string; left?: string }>`
+  position: absolute;
+  bottom: ${(props) => props.bottom || "auto"};
+  left: ${(props) => props.left || "auto"};
+  font-size: 10px;
+  background: linear-gradient(
+    45deg,
+    rgba(88, 24, 219, 1) 0%,
+    rgba(72, 12, 168, 1) 100%
+  ); // added gradient for shiny effect
+  padding: 10px;
+  border-radius: 4px;
+  opacity: 0.85;
+  box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.1);
+`;

+ 96 - 0
dashboard/src/main/home/inference-dashboard/TemplateForms/DeepgramForm.tsx

@@ -0,0 +1,96 @@
+import React from "react";
+
+import Button from "components/porter/Button";
+import { ControlledInput } from "components/porter/ControlledInput";
+import Spacer from "components/porter/Spacer";
+import Text from "components/porter/Text";
+import VerticalSteps from "components/porter/VerticalSteps";
+import { useFormContext } from "react-hook-form";
+import { ClientAddon } from "lib/addons";
+import AddonSaveButton from "main/home/add-on-dashboard/AddonSaveButton";
+
+const DeepgramForm: React.FC = () => {
+    const {
+        register,
+        control,
+        formState: { errors },
+    } = useFormContext<ClientAddon>();
+    return (
+        <div>
+            <Text size={16} > Instance type </Text>
+            <Spacer y={0.5} />
+            <Text color="helper"> The instance type to run the model on. Deepgram runs only on T4 GPUs and we have prefilled a preferred instance type for now. Make sure the AWS quota is properly set. </Text>
+            <Spacer height="20px" />
+            <ControlledInput
+                type="text"
+                width="300px"
+                {...register("config.instanceType")}
+                placeholder="g4dn-xlarge"
+                error={errors.config?.instanceType?.message}
+            />
+            <Spacer y={0.5} />
+            <Text size={16} > Release tag </Text>
+            <Spacer y={0.5} />
+            <Text color="helper"> The release tag for the specific deepgram model you would like to deploy</Text>
+            <Spacer height="20px" />
+            <ControlledInput
+                type="text"
+                width="300px"
+                {...register("config.releaseTag")}
+                placeholder="ex: release-240426"
+                error={errors.config?.releaseTag?.message}
+            />
+            <Spacer y={0.5} />
+            <Text size={16} > Deepgram API Key </Text>
+            <Spacer y={0.5} />
+            <Text color="helper"> The API Key provided to you by Deepgram </Text>
+            <Spacer height="20px" />
+            <ControlledInput
+                type="password"
+                width="300px"
+                {...register("config.deepgramApiKey")}
+                placeholder="deepgram-api-key"
+                error={errors.config?.deepgramApiKey?.message}
+            />
+            <Spacer y={0.5} />
+            <Text size={16} > Quay.io Username </Text>
+            <Spacer y={0.5} />
+            <Text color="helper"> The username to the container registry provided to you by Deepgram </Text>
+            <Spacer height="20px" />
+            <ControlledInput
+                type="text"
+                width="300px"
+                {...register("config.quayUsername")}
+                placeholder="ex: release-240426"
+                error={errors.config?.quayUsername?.message}
+            />
+            <Spacer y={0.5} />
+            <Text size={16} > Quay.io secret </Text>
+            <Spacer y={0.5} />
+            <Text color="helper"> The username to the container registry provided to you by Deepgram </Text>
+            <Spacer height="20px" />
+            <ControlledInput
+                type="password"
+                width="300px"
+                {...register("config.quaySecret")}
+                placeholder="quay.io-secret"
+                error={errors.config?.quaySecret?.message}
+            />
+            <Text size={16} > Quay.io email </Text>
+            <Spacer y={0.5} />
+            <Text color="helper"> The username to the container registry provided to you by Deepgram </Text>
+            <Spacer height="20px" />
+            <ControlledInput
+                type="text"
+                width="300px"
+                {...register("config.quayEmail")}
+                placeholder="x@y.com"
+                error={errors.config?.quayEmail?.message}
+            />
+            <Spacer y={1} />
+            <AddonSaveButton />
+        </div>
+    );
+};
+
+export default DeepgramForm;

+ 2 - 0
go.mod

@@ -387,3 +387,5 @@ require (
 	sigs.k8s.io/kustomize/kyaml v0.13.9 // indirect
 	sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
 )
+
+replace github.com/porter-dev/api-contracts => ../api-contracts

+ 0 - 2
go.sum

@@ -1563,8 +1563,6 @@ github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/polyfloyd/go-errorlint v0.0.0-20210722154253-910bb7978349/go.mod h1:wi9BfjxjF/bwiZ701TzmfKu6UKC357IOAtNr0Td0Lvw=
-github.com/porter-dev/api-contracts v0.2.158 h1:928I9vELiqntau4Yp8cVuX7FcLgo95Lv2uBVYj84is8=
-github.com/porter-dev/api-contracts v0.2.158/go.mod h1:VV5BzXd02ZdbWIPLVP+PX3GKawJSGQnxorVT2sUZALU=
 github.com/porter-dev/switchboard v0.0.3 h1:dBuYkiVLa5Ce7059d6qTe9a1C2XEORFEanhbtV92R+M=
 github.com/porter-dev/switchboard v0.0.3/go.mod h1:xSPzqSFMQ6OSbp42fhCi4AbGbQbsm6nRvOkrblFeXU4=
 github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=