jusrhee 2 лет назад
Родитель
Сommit
567896c041

Разница между файлами не показана из-за своего большого размера
+ 1 - 0
dashboard/src/assets/inference-grad.svg


BIN
dashboard/src/assets/inference.png


Разница между файлами не показана из-за своего большого размера
+ 1 - 0
dashboard/src/assets/inference.svg


+ 19 - 0
dashboard/src/assets/llm.svg

@@ -0,0 +1,19 @@
+<svg width="139" height="139" viewBox="0 0 139 139" fill="none" xmlns="http://www.w3.org/2000/svg">
+<circle cx="69.5" cy="132.5" r="6.5" fill="white"/>
+<circle cx="69.5" cy="6.5" r="6.5" fill="white"/>
+<circle cx="69.5" cy="69.5" r="6.5" fill="white"/>
+<circle cx="132.5" cy="34.5" r="6.5" fill="white"/>
+<circle cx="132.5" cy="104.5" r="6.5" fill="white"/>
+<circle cx="6.5" cy="104.5" r="6.5" fill="white"/>
+<circle cx="6.5" cy="34.5" r="6.5" fill="white"/>
+<line x1="5.98465" y1="34.7155" x2="68.9846" y2="6.71547" stroke="white" stroke-width="5"/>
+<line x1="68.9847" y1="132.285" x2="5.98465" y2="104.285" stroke="white" stroke-width="5"/>
+<line x1="134.015" y1="104.285" x2="71.0153" y2="132.285" stroke="white" stroke-width="5"/>
+<line x1="69.8971" y1="131.648" x2="132.897" y2="33.6481" stroke="white" stroke-width="5"/>
+<line x1="70.9034" y1="133.362" x2="7.9034" y2="36.3617" stroke="white" stroke-width="5"/>
+<line x1="69.1029" y1="7.35189" x2="6.10295" y2="105.352" stroke="white" stroke-width="5"/>
+<line x1="68.0966" y1="5.63829" x2="131.097" y2="102.638" stroke="white" stroke-width="5"/>
+<line x1="5.78589" y1="104.815" x2="131.786" y2="34.8146" stroke="white" stroke-width="5"/>
+<line x1="8.21411" y1="34.8146" x2="134.214" y2="104.815" stroke="white" stroke-width="5"/>
+<line x1="71.0153" y1="6.71547" x2="134.015" y2="34.7155" stroke="white" stroke-width="5"/>
+</svg>

+ 17 - 0
dashboard/src/components/porter/CenterWrapper.tsx

@@ -0,0 +1,17 @@
+import React from "react";
+import styled from "styled-components";
+
+type Props = {
+  children: React.ReactNode;
+};
+
+const CenterWrapper: React.FC<Props> = ({ children }) => {
+  return <StyledCenterWrapper>{children}</StyledCenterWrapper>;
+};
+
+export default CenterWrapper;
+
+const StyledCenterWrapper = styled.div`
+  width: 100%;
+  max-width: 900px;
+`;

+ 90 - 0
dashboard/src/components/porter/DashboardHeader.tsx

@@ -0,0 +1,90 @@
+import React from "react";
+import styled from "styled-components";
+
+import TitleSection from "../TitleSection";
+import Container from "./Container";
+import Spacer from "./Spacer";
+import Tooltip from "./Tooltip";
+
+type Props = {
+  image?: string;
+  title: React.ReactNode;
+  description?: string;
+  materialIconClass?: string;
+  capitalize?: boolean;
+  prefix?: React.ReactNode;
+};
+
+const DashboardHeader: React.FC<Props> = ({
+  image,
+  title,
+  description,
+  materialIconClass,
+  capitalize,
+  prefix,
+}) => {
+  return (
+    <>
+      <Container row>
+        {prefix}
+        <TitleSection
+          capitalize={capitalize === undefined || capitalize}
+          icon={image}
+          materialIconClass={materialIconClass}
+        >
+          {title}
+        </TitleSection>
+      </Container>
+
+      {description && (
+        <>
+          <Spacer y={1} />
+          <InfoSection>
+            <TopRow>
+              <Tooltip content="TestInfo" position="bottom" hidden={true}>
+                <InfoLabel>
+                  <i className="material-icons">info</i> Info
+                </InfoLabel>
+              </Tooltip>
+            </TopRow>
+            <Description>{description}</Description>
+          </InfoSection>
+        </>
+      )}
+      <Spacer height="35px" />
+    </>
+  );
+};
+
+export default DashboardHeader;
+
+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 InfoSection = styled.div`
+  font-family: "Work Sans", sans-serif;
+  margin-left: 0px;
+`;

+ 23 - 0
dashboard/src/main/home/Home.tsx

@@ -57,6 +57,10 @@ import CreateDatastore from "./database-dashboard/forms/CreateDatastore";
 import CreateEnvGroup from "./env-dashboard/CreateEnvGroup";
 import EnvDashboard from "./env-dashboard/EnvDashboard";
 import ExpandedEnv from "./env-dashboard/ExpandedEnv";
+import ExpandedModelTemplate from "./inference-dashboard/ExpandedModelTemplate";
+import InferenceDashboard from "./inference-dashboard/InferenceDashboard";
+import ModelForm from "./inference-dashboard/ModelForm";
+import ModelTemplates from "./inference-dashboard/ModelTemplates";
 import ClusterContextProvider from "./infrastructure-dashboard/ClusterContextProvider";
 import ClusterDashboard from "./infrastructure-dashboard/ClusterDashboard";
 import ClusterView from "./infrastructure-dashboard/ClusterView";
@@ -490,6 +494,25 @@ 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>

+ 50 - 0
dashboard/src/main/home/inference-dashboard/ExpandedModelTemplate.tsx

@@ -0,0 +1,50 @@
+import React from "react";
+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 Spacer from "components/porter/Spacer";
+
+import inferenceGrad from "assets/inference-grad.svg";
+
+import DashboardHeader from "../cluster-dashboard/DashboardHeader";
+import { models, tagColor } from "./models";
+
+const ExpandedModelTemplate: React.FC = () => {
+  const { templateId } = useParams<{
+    templateId: string;
+  }>();
+
+  const template = models[templateId] || {
+    name: "",
+    description: "",
+    tags: [],
+  };
+
+  return (
+    <Container style={{ width: "100%" }}>
+      <Back to="/inference/templates" />
+      <DashboardHeader
+        capitalize={false}
+        description={template.description}
+        disableLineBreak
+      />
+    </Container>
+  );
+};
+
+export default ExpandedModelTemplate;
+
+const Wrapper = styled.div`
+  width: 100%;
+  background: red;
+`;
+
+const Idk = styled.div`
+  flex: 1;
+  height: 5px;
+  width: 100%;
+  background: red;
+`;

+ 79 - 0
dashboard/src/main/home/inference-dashboard/InferenceDashboard.tsx

@@ -0,0 +1,79 @@
+import React, { useContext, useState } from "react";
+import styled from "styled-components";
+
+import ClusterProvisioningPlaceholder from "components/ClusterProvisioningPlaceholder";
+import Button from "components/porter/Button";
+import Container from "components/porter/Container";
+import DashboardHeader from "components/porter/DashboardHeader";
+import DashboardPlaceholder from "components/porter/DashboardPlaceholder";
+import Link from "components/porter/Link";
+import Spacer from "components/porter/Spacer";
+import Text from "components/porter/Text";
+
+import { Context } from "shared/Context";
+import inferenceGrad from "assets/inference-grad.svg";
+
+const PreviewEnvs: React.FC = () => {
+  const { currentCluster } = useContext(Context);
+  const [models, setModels] = useState<any[]>([]);
+
+  const renderContents = () => {
+    if (currentCluster?.status === "UPDATING_UNAVAILABLE") {
+      return <ClusterProvisioningPlaceholder />;
+    }
+
+    if (models.length === 0) {
+      return (
+        <DashboardPlaceholder>
+          <Text size={16}>No ML models have been deployed yet</Text>
+          <Spacer y={0.5} />
+
+          <Text color={"helper"}>Get started by deploying a model.</Text>
+          <Spacer y={1} />
+          <Link to="/inference/templates">
+            <Button onClick={() => ({})} height="35px" alt>
+              Deploy a new model <Spacer inline x={1} />{" "}
+              <i className="material-icons" style={{ fontSize: "18px" }}>
+                east
+              </i>
+            </Button>
+          </Link>
+        </DashboardPlaceholder>
+      );
+    }
+  };
+
+  return (
+    <StyledAppDashboard>
+      <DashboardHeader
+        image={inferenceGrad}
+        title={
+          <Container row>
+            Inference
+            <Spacer inline x={1} />
+            <Badge>Beta</Badge>
+          </Container>
+        }
+        capitalize={false}
+        description="Run open source ML models in your own cloud."
+      />
+      {renderContents()}
+    </StyledAppDashboard>
+  );
+};
+
+export default PreviewEnvs;
+
+const Badge = styled.div`
+  background: linear-gradient(60deg, #4b366d 0%, #6475b9 100%);
+  color: white;
+  border-radius: 3px;
+  padding: 2px 5px;
+  margin-right: -5px;
+  font-size: 13px;
+`;
+
+const StyledAppDashboard = styled.div`
+  width: 100%;
+  height: 100%;
+`;

+ 40 - 0
dashboard/src/main/home/inference-dashboard/ModelForm.tsx

@@ -0,0 +1,40 @@
+import React from "react";
+import { useHistory, useParams } from "react-router";
+import { match } from "ts-pattern";
+
+import Back from "components/porter/Back";
+import CenterWrapper from "components/porter/CenterWrapper";
+import DashboardHeader from "components/porter/DashboardHeader";
+
+import Gpt2Form from "./TemplateForms/Gpt2Form";
+
+const InferenceForm: React.FC = () => {
+  const { templateId } = useParams<{
+    templateId: string;
+  }>();
+  const history = useHistory();
+
+  const renderForm = (): React.ReactElement => {
+    return match(templateId)
+      .returnType<JSX.Element>()
+      .with("gpt-2", () => <Gpt2Form />)
+      .otherwise(() => <div>Template not found</div>);
+  };
+
+  return (
+    <CenterWrapper>
+      <Back
+        onClick={() => {
+          history.push(`/inference`);
+        }}
+      />
+      <DashboardHeader
+        title={<div>Configure new {templateId} instance</div>}
+        capitalize={false}
+      />
+      {renderForm()}
+    </CenterWrapper>
+  );
+};
+
+export default InferenceForm;

+ 146 - 0
dashboard/src/main/home/inference-dashboard/ModelTemplates.tsx

@@ -0,0 +1,146 @@
+import React, { useContext, useMemo } from "react";
+import { useHistory, useLocation } from "react-router";
+import styled from "styled-components";
+
+import Back from "components/porter/Back";
+import Container from "components/porter/Container";
+import Spacer from "components/porter/Spacer";
+
+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",
+};
+
+const ModelTemplates: React.FC = () => {
+  const history = useHistory();
+
+  return (
+    <StyledTemplateComponent>
+      <Back to="/inference" />
+      <DashboardHeader
+        image={inferenceGrad}
+        title="Explore models"
+        capitalize={false}
+        description="Select a model to deploy on this project."
+        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 style={{ background: tagColor[t] }} key={t}>
+                    {t}
+                  </Tag>
+                ))}
+              </Container>
+              <Spacer y={0.5} />
+            </TemplateBlock>
+          );
+        })}
+      </TemplateListWrapper>
+    </StyledTemplateComponent>
+  );
+};
+
+export default ModelTemplates;
+
+const StyledTemplateComponent = styled.div`
+  width: 100%;
+  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;
+  font-weight: default;
+  padding: 0px 25px;
+  line-height: 1.4;
+  font-size: 12px;
+  display: -webkit-box;
+  overflow: hidden;
+  -webkit-line-clamp: 2;
+  -webkit-box-orient: vertical;
+`;
+
+const TemplateTitle = styled.div`
+  width: 80%;
+  text-align: center;
+  font-size: 14px;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+`;
+
+const TemplateBlock = styled.div`
+  align-items: center;
+  user-select: none;
+  display: flex;
+  font-size: 13px;
+  flex-direction: column;
+  align-item: center;
+  justify-content: space-between;
+  height: 180px;
+  cursor: pointer;
+  color: #ffffff;
+  position: relative;
+  border-radius: 5px;
+  background: ${(props) => props.theme.clickable.bg};
+  border: 1px solid #494b4f;
+  :hover {
+    border: 1px solid #7a7b80;
+  }
+
+  animation: fadeIn 0.3s 0s;
+  @keyframes fadeIn {
+    from {
+      opacity: 0;
+    }
+    to {
+      opacity: 1;
+    }
+  }
+`;
+
+const TemplateListWrapper = styled.div`
+  overflow: visible;
+  padding-bottom: 50px;
+  display: grid;
+  grid-column-gap: 30px;
+  grid-row-gap: 30px;
+  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
+`;
+
+const Icon = styled.img`
+  height: 25px;
+  margin-top: 25px;
+  margin-bottom: 5px;
+`;

+ 14 - 0
dashboard/src/main/home/inference-dashboard/TemplateForms/Gpt2Form.tsx

@@ -0,0 +1,14 @@
+import React from "react";
+import { useHistory } from "react-router";
+
+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";
+
+const AddonForm: React.FC = () => {
+  const history = useHistory();
+  return <>yeah idk</>;
+};
+
+export default AddonForm;

+ 53 - 0
dashboard/src/main/home/inference-dashboard/models.tsx

@@ -0,0 +1,53 @@
+export const models = {
+  "gpt-2": {
+    name: "GPT-2",
+    icon: "https://cdn-avatars.huggingface.co/v1/production/uploads/1620805164087-5ec0135ded25d76864d553f1.png",
+    description:
+      "GPT-2 is a large transformer-based language model. This is the smallest version of GPT-2, with 124M parameters.",
+    tags: ["text-to-text", "CPU"],
+  },
+  "llama-3-8b-instruct": {
+    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.",
+    tags: ["text-to-text", "A100"],
+  },
+  "mistral-7b-instruct-v0-2": {
+    name: "Mistral 7B Instruct v0.2",
+    icon: "https://mistral.ai/images/news/announcing-mistral.png",
+    description:
+      "The Mistral-7B-Instruct-v0.2 Large Language Model (LLM) is an instruct fine-tuned version of the Mistral-7B-v0.2.",
+    tags: ["text-to-text", "A100"],
+  },
+  "whisper-large-v3": {
+    name: "Whisper Large v3",
+    icon: "https://cdn-avatars.huggingface.co/v1/production/uploads/1620805164087-5ec0135ded25d76864d553f1.png",
+    description:
+      "Whisper is a pre-trained model for automatic speech recognition (ASR) and speech translation.",
+    tags: ["audio-to-text", "A100"],
+  },
+  "stable-diffusion-2": {
+    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).",
+    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.",
+    tags: ["text-to-audio", "A100"],
+  },
+};
+
+export const tagColor = {
+  "text-to-text": "#3f51b5",
+  "audio-to-text": "#0E7F5D",
+  "text-to-image": "#D67400",
+  "text-to-audio": "#7020C0",
+  CPU: "#72747E",
+  A100: "#f50057",
+};

+ 24 - 11
dashboard/src/main/home/sidebar/Sidebar.tsx

@@ -17,6 +17,7 @@ import collapseSidebar from "assets/collapse-sidebar.svg";
 import compliance from "assets/compliance.svg";
 import database from "assets/database.svg";
 import sliders from "assets/env-groups.svg";
+import inference from "assets/inference.svg";
 import integrations from "assets/integrations.svg";
 import lock from "assets/lock.svg";
 import pr_icon from "assets/pull_request_icon.svg";
@@ -307,7 +308,7 @@ class Sidebar extends Component<PropsType, StateType> {
             </NavButton>
             <NavButton
               path="/datastores"
-              active={window.location.pathname.startsWith("/apps")}
+              active={window.location.pathname.startsWith("/datastores")}
             >
               <Container row spaced style={{ width: "100%" }}>
                 <Container row>
@@ -316,6 +317,18 @@ class Sidebar extends Component<PropsType, StateType> {
                 </Container>
               </Container>
             </NavButton>
+            <NavButton
+              path="/inference"
+              active={window.location.pathname.startsWith("/inference")}
+            >
+              <Container row spaced style={{ width: "100%" }}>
+                <Container row>
+                  <Img src={inference} />
+                  Inference
+                </Container>
+                <Badge>Beta</Badge>
+              </Container>
+            </NavButton>
             {!currentProject.sandbox_enabled && (
               <NavButton
                 path="/addons"
@@ -346,6 +359,16 @@ class Sidebar extends Component<PropsType, StateType> {
               </NavButton>
             )}
 
+            <NavButton path="/preview-environments">
+              <Container row spaced style={{ width: "100%" }}>
+                <Container row>
+                  <Img src={pr_icon} />
+                  Preview apps
+                </Container>
+                {!currentProject.preview_envs_enabled && <Badge>Beta</Badge>}
+              </Container>
+            </NavButton>
+
             {!currentProject.sandbox_enabled && (
               <NavButton
                 path={
@@ -370,16 +393,6 @@ class Sidebar extends Component<PropsType, StateType> {
               </NavButton>
             )}
 
-            <NavButton path="/preview-environments">
-              <Container row spaced style={{ width: "100%" }}>
-                <Container row>
-                  <Img src={pr_icon} />
-                  Preview apps
-                </Container>
-                {!currentProject.preview_envs_enabled && <Badge>Beta</Badge>}
-              </Container>
-            </NavButton>
-
             <NavButton path="/compliance">
               <Container row spaced style={{ width: "100%" }}>
                 <Container row>

+ 2 - 0
dashboard/src/shared/routing.tsx

@@ -18,6 +18,7 @@ export type PorterUrl =
   | "addons"
   | "compliance"
   | "environment-groups"
+  | "inference"
   | "stacks";
 
 export const PorterUrls = [
@@ -39,6 +40,7 @@ export const PorterUrls = [
   "addons",
   "compliance",
   "environment-groups",
+  "inference",
   "stacks",
   "ory",
 ];

Некоторые файлы не были показаны из-за большого количества измененных файлов