jusrhee 2 年之前
父節點
當前提交
8137f867df

+ 0 - 2
dashboard/src/components/porter/Dropdown.tsx

@@ -4,8 +4,6 @@ import styled, { keyframes } from "styled-components";
 
 import Container from "./Container";
 import Spacer from "./Spacer";
-import Tag from "./Tag";
-import Text from "./Text";
 
 type Props = {
   key: string;

+ 24 - 0
dashboard/src/components/porter/I.tsx

@@ -0,0 +1,24 @@
+import React from "react";
+import styled from "styled-components";
+
+type Props = {
+  children: React.ReactNode;
+  size?: number;
+  style?: React.CSSProperties;
+};
+
+const I: React.FC<Props> = ({ children, size, style }) => {
+  return (
+    <StyledI size={size} style={style} className="material-icons">
+      {children}
+    </StyledI>
+  );
+};
+
+export default I;
+
+const StyledI = styled.i<{
+  size?: number;
+}>`
+  font-size: ${(props) => props.size || 20}px;
+`;

+ 52 - 0
dashboard/src/components/porter/InfoSection.tsx

@@ -0,0 +1,52 @@
+import React from "react";
+import styled from "styled-components";
+
+type Props = {
+  text: string;
+};
+
+const InfoSection: React.FC<Props> = ({ text }) => {
+  return (
+    <StyledInfoSection>
+      <TopRow>
+        <InfoLabel>
+          <i className="material-icons">info</i> Info
+        </InfoLabel>
+      </TopRow>
+      <Description>{text}</Description>
+    </StyledInfoSection>
+  );
+};
+
+export default InfoSection;
+
+const TopRow = styled.div`
+  display: flex;
+  align-items: center;
+`;
+
+const Description = styled.div`
+  color: #aaaabb;
+  margin-top: 13px;
+  margin-left: 1px;
+  font-size: 13px;
+`;
+
+const InfoLabel = styled.div`
+  width: 72px;
+  height: 20px;
+  display: flex;
+  align-items: center;
+  color: #aaaabb;
+  font-size: 13px;
+  > i {
+    color: #aaaabb;
+    font-size: 18px;
+    margin-right: 5px;
+  }
+`;
+
+const StyledInfoSection = styled.div`
+  font-family: "Work Sans", sans-serif;
+  margin-left: 0px;
+`;

+ 14 - 0
dashboard/src/components/porter/Line copy.tsx

@@ -0,0 +1,14 @@
+import React from "react";
+import styled from "styled-components";
+
+const Line: React.FC = () => {
+  return <StyledLine />;
+};
+
+export default Line;
+
+const StyledLine = styled.div`
+  height: 1px;
+  background: #aaaabb55;
+  width: 100%;
+`;

+ 14 - 0
dashboard/src/components/porter/Line.tsx

@@ -0,0 +1,14 @@
+import React from "react";
+import styled from "styled-components";
+
+const Line: React.FC = () => {
+  return <StyledLine />;
+};
+
+export default Line;
+
+const StyledLine = styled.div`
+  height: 1px;
+  background: #aaaabb55;
+  width: 100%;
+`;

+ 5 - 1
dashboard/src/components/porter/Tag.tsx

@@ -2,6 +2,7 @@ import React from "react";
 import styled from "styled-components";
 
 type Props = {
+  size?: number;
   backgroundColor?: string;
   children: React.ReactNode;
   hoverable?: boolean;
@@ -13,6 +14,7 @@ type Props = {
 };
 
 const Tag: React.FC<Props> = ({
+  size,
   backgroundColor,
   hoverable = true,
   hoverColor,
@@ -23,6 +25,7 @@ const Tag: React.FC<Props> = ({
 }) => {
   return (
     <StyledTag
+      size={size}
       backgroundColor={backgroundColor ?? "#ffffff22"}
       hoverable={hoverable}
       hoverColor={hoverColor ?? "#ffffff44"}
@@ -38,6 +41,7 @@ const Tag: React.FC<Props> = ({
 export default Tag;
 
 const StyledTag = styled.div<{
+  size?: number;
   hoverable: boolean;
   backgroundColor: string;
   hoverColor: string;
@@ -46,7 +50,7 @@ const StyledTag = styled.div<{
 }>`
   display: flex;
   justify-content: center;
-  font-size: 13px;
+  font-size: ${({ size }) => size ?? 13}px;
   padding: 3px 5px;
   border-radius: ${({ borderRadiusPixels }) => borderRadiusPixels}px;
   background: ${({ backgroundColor }) => backgroundColor};

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

@@ -1,13 +1,19 @@
 import React from "react";
+import AceEditor from "react-ace";
 import { useHistory, useParams } from "react-router";
 import styled from "styled-components";
 
 import Back from "components/porter/Back";
 import Button from "components/porter/Button";
 import Container from "components/porter/Container";
+import I from "components/porter/I";
+import Image from "components/porter/Image";
+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 inferenceGrad from "assets/inference-grad.svg";
+import Tag from "components/porter/Tag";
+import Text from "components/porter/Text";
 
 import DashboardHeader from "../cluster-dashboard/DashboardHeader";
 import { models, tagColor } from "./models";
@@ -26,25 +32,63 @@ const ExpandedModelTemplate: React.FC = () => {
   return (
     <Container style={{ width: "100%" }}>
       <Back to="/inference/templates" />
-      <DashboardHeader
-        capitalize={false}
-        description={template.description}
-        disableLineBreak
+      <Container row spaced>
+        <Container row>
+          <Image size={24} src={template.icon} />
+          <Spacer inline x={1} />
+          <Text size={21}>{template.name}</Text>
+        </Container>
+        <Link to={`/inference/templates/${templateId}/new`}>
+          <Button>
+            <I size={14}>add</I>
+            <Spacer inline x={0.5} />
+            Deploy model
+          </Button>
+        </Link>
+      </Container>
+      <Spacer y={1} />
+      <InfoSection text={template.description} />
+      <Spacer y={1} />
+      <Container row>
+        {template.tags?.map((t) => (
+          <>
+            <Tag key={t} backgroundColor={tagColor[t]} hoverable={false}>
+              {t}
+            </Tag>
+            <Spacer inline x={1} />
+          </>
+        ))}
+      </Container>
+      <Spacer y={1} />
+      <Line />
+      <Spacer y={1} />
+      <Text size={16}>Example usage</Text>
+      <Spacer y={0.5} />
+      <Text color="helper">
+        After deploying this model, you will be able to use it over an
+        auto-generated endpoint from any app running on Porter.
+      </Text>
+      <Spacer y={1} />
+      <AceEditor
+        value={`curl http://my-model.default.svc.cluster.local:8000/v1/completions
+  -H "Content-Type: application/json"
+  -d '{
+      "prompt": "Long Island City is a",
+      "max_tokens": 7,
+      "temperature": 0
+  }'`}
+        theme="porter"
+        name="codeEditor"
+        readOnly={true}
+        height="120px"
+        width="100%"
+        style={{ borderRadius: "10px", color: "#aaaabb" }}
+        showPrintMargin={false}
+        showGutter={true}
+        highlightActiveLine={false}
       />
     </Container>
   );
 };
 
 export default ExpandedModelTemplate;
-
-const Wrapper = styled.div`
-  width: 100%;
-  background: red;
-`;
-
-const Idk = styled.div`
-  flex: 1;
-  height: 5px;
-  width: 100%;
-  background: red;
-`;

+ 37 - 6
dashboard/src/main/home/inference-dashboard/ModelForm.tsx

@@ -1,11 +1,16 @@
 import React from "react";
 import { useHistory, useParams } from "react-router";
+import styled from "styled-components";
 import { match } from "ts-pattern";
 
 import Back from "components/porter/Back";
 import CenterWrapper from "components/porter/CenterWrapper";
-import DashboardHeader from "components/porter/DashboardHeader";
+import Container from "components/porter/Container";
+import Image from "components/porter/Image";
+import Spacer from "components/porter/Spacer";
+import Text from "components/porter/Text";
 
+import { models } from "./models";
 import Gpt2Form from "./TemplateForms/Gpt2Form";
 
 const InferenceForm: React.FC = () => {
@@ -14,6 +19,13 @@ const InferenceForm: React.FC = () => {
   }>();
   const history = useHistory();
 
+  const template = models[templateId] || {
+    name: "",
+    icon: "",
+    description: "",
+    tags: [],
+  };
+
   const renderForm = (): React.ReactElement => {
     return match(templateId)
       .returnType<JSX.Element>()
@@ -25,16 +37,35 @@ const InferenceForm: React.FC = () => {
     <CenterWrapper>
       <Back
         onClick={() => {
-          history.push(`/inference`);
+          history.push(`/inference/templates/${templateId}`);
         }}
       />
-      <DashboardHeader
-        title={<div>Configure new {templateId} instance</div>}
-        capitalize={false}
-      />
+      <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>
   );
 };
 
 export default InferenceForm;
+
+const FloatIn = styled.div`
+  animation: floatIn 0.5s;
+  animation-fill-mode: forwards;
+  @keyframes floatIn {
+    from {
+      opacity: 0;
+      transform: translateY(20px);
+    }
+    to {
+      opacity: 1;
+      transform: translateY(0px);
+    }
+  }
+`;

+ 16 - 22
dashboard/src/main/home/inference-dashboard/ModelTemplates.tsx

@@ -5,20 +5,12 @@ 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 } from "./models";
-
-const tagColor = {
-  "text-to-text": "#3f51b5",
-  "audio-to-text": "#0E7F5D",
-  "text-to-image": "#D67400",
-  "text-to-audio": "#7020C0",
-  CPU: "#72747E",
-  A100: "#f50057",
-};
+import { models, tagColor } from "./models";
 
 const ModelTemplates: React.FC = () => {
   const history = useHistory();
@@ -49,9 +41,19 @@ const ModelTemplates: React.FC = () => {
               <Spacer y={0.25} />
               <Container row>
                 {template.tags?.map((t) => (
-                  <Tag style={{ background: tagColor[t] }} key={t}>
-                    {t}
-                  </Tag>
+                  <>
+                    <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} />
@@ -70,14 +72,6 @@ const StyledTemplateComponent = styled.div`
   height: 100%;
 `;
 
-const Tag = styled.div<{ size?: string }>`
-  font-size: 10px;
-  margin-right: 10px;
-  padding: 5px 10px;
-  border-radius: 4px;
-  opacity: 0.85;
-`;
-
 const TemplateDescription = styled.div`
   color: #ffffff66;
   text-align: center;
@@ -141,6 +135,6 @@ const TemplateListWrapper = styled.div`
 
 const Icon = styled.img`
   height: 25px;
-  margin-top: 25px;
+  margin-top: 20px;
   margin-bottom: 5px;
 `;

+ 42 - 9
dashboard/src/main/home/inference-dashboard/TemplateForms/Gpt2Form.tsx

@@ -1,14 +1,47 @@
 import React from "react";
-import { useHistory } from "react-router";
+import { Controller, useFormContext } from "react-hook-form";
 
-import Back from "components/porter/Back";
-import CenterWrapper from "components/porter/CenterWrapper";
-import DashboardHeader from "components/porter/DashboardHeader";
-import { Img } from "main/home/infrastructure-dashboard/forms/CreateClusterForm";
+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 { type Gpt2 } from "lib/models";
 
-const AddonForm: React.FC = () => {
-  const history = useHistory();
-  return <>yeah idk</>;
+const Gpt2Form: React.FC = () => {
+  return (
+    <VerticalSteps
+      currentStep={0}
+      steps={[
+        <>
+          <Text size={16}>Model name</Text>
+          <Spacer y={0.5} />
+          <Text color="helper">Lowercase letters, numbers, and "-" only.</Text>
+          <Spacer height="20px" />
+          <ControlledInput
+            type="text"
+            width="300px"
+            placeholder="ex: klara-af-b2"
+          />
+        </>,
+        <>
+          <Text size={16}>Resources</Text>
+          <Spacer y={0.5} />
+          <Text color="helper">
+            Configure compute resources for your model. You can change these
+            later.
+          </Text>
+          <Spacer y={0.5} />
+          <Text>Replicas</Text>
+          <Spacer y={0.5} />
+          <Text color="helper">
+            Configure the number of replicas for your model.
+          </Text>
+        </>,
+        <Button key={2}>Deploy model</Button>,
+      ]}
+    />
+  );
 };
 
-export default AddonForm;
+export default Gpt2Form;

+ 3 - 3
dashboard/src/main/home/inference-dashboard/models.tsx

@@ -10,7 +10,7 @@ export const models = {
     name: "Llama 3 8B Instruct",
     icon: "https://1000logos.net/wp-content/uploads/2021/10/Meta-Symbol.png",
     description:
-      "Llama 3 is an auto-regressive language model that uses an optimized transformer architecture. The tuned versions use supervised fine-tuning (SFT) and reinforcement learning with human feedback (RLHF) to align with human preferences for helpfulness and safety.",
+      "Llama 3 is an auto-regressive language model that uses an optimized transformer architecture.",
     tags: ["text-to-text", "A100"],
   },
   "mistral-7b-instruct-v0-2": {
@@ -31,14 +31,14 @@ export const models = {
     name: "Stable Diffusion 2",
     icon: "https://avatars.githubusercontent.com/u/100950301?s=200&v=4",
     description:
-      "This is a model that can be used to generate and modify images based on text prompts. It is a Latent Diffusion Model that uses a fixed, pretrained text encoder (OpenCLIP-ViT/H).",
+      "This is a model that can be used to generate and modify images based on text prompts.",
     tags: ["text-to-image", "A100"],
   },
   "musicgen-large": {
     name: "MusicGen Large",
     icon: "https://1000logos.net/wp-content/uploads/2021/10/Meta-Symbol.png",
     description:
-      "MusicGen is a text-to-music model capable of genreating high-quality music samples conditioned on text descriptions or audio prompts. It is a single stage auto-regressive Transformer model trained over a 32kHz EnCodec tokenizer with 4 codebooks sampled at 50 Hz.",
+      "MusicGen is a text-to-music model capable of genreating high-quality music samples conditioned on text descriptions or audio prompts.",
     tags: ["text-to-audio", "A100"],
   },
 };