diff --git a/agenta-web/cypress/support/commands/evaluations.ts b/agenta-web/cypress/support/commands/evaluations.ts index a1294709eb..ab3ea2e881 100644 --- a/agenta-web/cypress/support/commands/evaluations.ts +++ b/agenta-web/cypress/support/commands/evaluations.ts @@ -15,18 +15,7 @@ Cypress.Commands.add("createVariant", () => { cy.addingOpenaiKey() cy.visit("/apps") - // Check if there are app variants present - cy.request({ - url: `${Cypress.env().baseApiURL}/apps`, - method: "GET", - }).then((resp) => { - if (resp.body.length) { - cy.get('[data-cy="create-new-app-button"]').click() - cy.get('[data-cy="create-from-template"]').click() - } else { - cy.get('[data-cy="create-from-template"]').click() - } - }) + cy.get('[data-cy="create-from-template"]').click() const appName = randString(5) cy.task("log", `App name: ${appName}`) diff --git a/agenta-web/public/assets/rag-demo-app.webp b/agenta-web/public/assets/rag-demo-app.webp new file mode 100644 index 0000000000..77ded11bee Binary files /dev/null and b/agenta-web/public/assets/rag-demo-app.webp differ diff --git a/agenta-web/src/components/AppSelector/AppSelector.tsx b/agenta-web/src/components/AppSelector/AppSelector.tsx deleted file mode 100644 index 6282d8d8b2..0000000000 --- a/agenta-web/src/components/AppSelector/AppSelector.tsx +++ /dev/null @@ -1,398 +0,0 @@ -import {useState, useEffect, useMemo} from "react" -import {PlusOutlined} from "@ant-design/icons" -import {Modal, ConfigProvider, theme, Button, notification, Typography, Input, Divider} from "antd" -import AppCard from "./AppCard" -import {Template, GenericObject, StyleProps, JSSTheme} from "@/lib/Types" -import {useAppTheme} from "../Layout/ThemeContextProvider" -import TipsAndFeatures from "./TipsAndFeatures" -import Welcome from "./Welcome" -import {isAppNameInputValid, isDemo, redirectIfNoLLMKeys} from "@/lib/helpers/utils" -import {createAndStartTemplate, fetchAllTemplates, deleteApp} from "@/services/app-selector/api" -import {waitForAppToStart} from "@/services/api" -import AddAppFromTemplatedModal from "./modals/AddAppFromTemplateModal" -import MaxAppModal from "./modals/MaxAppModal" -import WriteOwnAppModal from "./modals/WriteOwnAppModal" -import {createUseStyles} from "react-jss" -import {useAppsData} from "@/contexts/app.context" -import {useProfileData} from "@/contexts/profile.context" -import CreateAppStatusModal from "./modals/CreateAppStatusModal" -import {usePostHogAg} from "@/hooks/usePostHogAg" -import {LlmProvider, getAllProviderLlmKeys} from "@/lib/helpers/llmProviders" -import ResultComponent from "../ResultComponent/ResultComponent" -import {dynamicContext} from "@/lib/helpers/dynamic" -import AppTemplateCard from "./AppTemplateCard" -import dayjs from "dayjs" -import NoResultsFound from "../NoResultsFound/NoResultsFound" - -const useStyles = createUseStyles((theme: JSSTheme) => ({ - container: ({themeMode}: StyleProps) => ({ - marginTop: "24px", - width: "100%", - color: themeMode === "dark" ? "#fff" : "#000", - }), - cardsList: { - width: "100%", - display: "grid", - gap: 16, - "@media (max-width: 1199px)": { - gridTemplateColumns: "repeat(2, minmax(0, 1fr))", - }, - "@media (min-width: 1200px) and (max-width: 1699px)": { - gridTemplateColumns: "repeat(3, minmax(0, 1fr))", - }, - "@media (min-width: 1700px) and (max-width: 2000px)": { - gridTemplateColumns: "repeat(4, minmax(0, 1fr))", - }, - "@media (min-width: 2001px)": { - gridTemplateColumns: "repeat(5, minmax(0, 1fr))", - }, - }, - title: { - fontSize: 16, - fontWeight: theme.fontWeightMedium, - lineHeight: "24px", - }, - modal: { - transition: "width 0.3s ease", - "& .ant-modal-content": { - overflow: "hidden", - borderRadius: 16, - "& > .ant-modal-close": { - top: 16, - }, - }, - }, - appTemplate: { - gap: 16, - display: "flex", - flexDirection: "column", - }, - headerText: { - lineHeight: theme.lineHeightLG, - fontSize: theme.fontSizeHeading4, - fontWeight: theme.fontWeightStrong, - }, -})) - -const timeout = isDemo() ? 60000 : 30000 - -const AppSelector: React.FC = () => { - const posthog = usePostHogAg() - const {appTheme} = useAppTheme() - const classes = useStyles({themeMode: appTheme} as StyleProps) - const [isCreateAppModalOpen, setIsCreateAppModalOpen] = useState(false) - const [isMaxAppModalOpen, setIsMaxAppModalOpen] = useState(false) - const [templates, setTemplates] = useState([]) - const {user} = useProfileData() - const [noTemplateMessage, setNoTemplateMessage] = useState("") - const [templateId, setTemplateId] = useState(undefined) - const [statusModalOpen, setStatusModalOpen] = useState(false) - const [fetchingTemplate, setFetchingTemplate] = useState(false) - const [newApp, setNewApp] = useState("") - const [current, setCurrent] = useState(0) - const [searchTerm, setSearchTerm] = useState("") - const {apps, error, isLoading, mutate} = useAppsData() - const [statusData, setStatusData] = useState<{status: string; details?: any; appId?: string}>({ - status: "", - details: undefined, - appId: undefined, - }) - const [useOrgData, setUseOrgData] = useState(() => () => "") - const {selectedOrg} = useOrgData() - - const hasAvailableApps = Array.isArray(apps) && apps.length > 0 - - useEffect(() => { - dynamicContext("org.context", {useOrgData}).then((context) => { - setUseOrgData(() => context.useOrgData) - }) - }, []) - - const showCreateAppModal = async () => { - setIsCreateAppModalOpen(true) - } - - const showMaxAppError = () => { - setIsMaxAppModalOpen(true) - } - - const showCreateAppFromTemplateModal = () => { - setTemplateId(undefined) - setNewApp("") - setIsCreateAppModalOpen(true) - setCurrent(hasAvailableApps ? 1 : 0) - } - - const showWriteAppModal = () => { - setIsCreateAppModalOpen(true) - setCurrent(hasAvailableApps ? 2 : 1) - } - - useEffect(() => { - if (!isLoading) mutate() - const fetchTemplates = async () => { - const data = await fetchAllTemplates() - if (typeof data == "object") { - setTemplates(data) - } else { - setNoTemplateMessage(data) - } - } - - fetchTemplates() - }, []) - - const handleTemplateCardClick = async (template_id: string) => { - setIsCreateAppModalOpen(false) - // warn the user and redirect if openAI key is not present - // TODO: must be changed for multiples LLM keys - if (redirectIfNoLLMKeys()) return - - setFetchingTemplate(true) - setStatusModalOpen(true) - - // attempt to create and start the template, notify user of the progress - const apiKeys = getAllProviderLlmKeys() - await createAndStartTemplate({ - appName: newApp, - templateId: template_id, - providerKey: isDemo() && apiKeys?.length === 0 ? [] : (apiKeys as LlmProvider[]), - timeout, - onStatusChange: async (status, details, appId) => { - setStatusData((prev) => ({status, details, appId: appId || prev.appId})) - if (["error", "bad_request", "timeout", "success"].includes(status)) - setFetchingTemplate(false) - if (status === "success") { - mutate() - posthog.capture("app_deployment", { - properties: { - app_id: appId, - environment: "UI", - deployed_by: user?.id, - }, - }) - } - }, - }) - } - - const onErrorRetry = async () => { - if (statusData.appId) { - setStatusData((prev) => ({...prev, status: "cleanup", details: undefined})) - await deleteApp(statusData.appId).catch(console.error) - mutate() - } - handleTemplateCardClick(templateId as string) - } - - const onTimeoutRetry = async () => { - if (!statusData.appId) return - setStatusData((prev) => ({...prev, status: "starting_app", details: undefined})) - try { - await waitForAppToStart({appId: statusData.appId, timeout}) - } catch (error: any) { - if (error.message === "timeout") { - setStatusData((prev) => ({...prev, status: "timeout", details: undefined})) - } else { - setStatusData((prev) => ({...prev, status: "error", details: error})) - } - } - setStatusData((prev) => ({...prev, status: "success", details: undefined})) - mutate() - } - - const appNameExist = useMemo( - () => - apps.some((app: GenericObject) => app.app_name.toLowerCase() === newApp.toLowerCase()), - [apps, newApp], - ) - - const handleCreateApp = () => { - if (appNameExist) { - notification.warning({ - message: "Template Selection", - description: "App name already exists. Please choose a different name.", - duration: 3, - }) - } else if (fetchingTemplate && newApp.length > 0 && isAppNameInputValid(newApp)) { - notification.info({ - message: "Template Selection", - description: "The template image is currently being fetched. Please wait...", - duration: 3, - }) - } else if (!fetchingTemplate && newApp.length > 0 && isAppNameInputValid(newApp)) { - handleTemplateCardClick(templateId as string) - } else { - notification.warning({ - message: "Template Selection", - description: "Please provide a valid app name to choose a template.", - duration: 3, - }) - } - } - - const filteredApps = useMemo(() => { - let filtered = apps.sort( - (a, b) => dayjs(b.updated_at).valueOf() - dayjs(a.updated_at).valueOf(), - ) - - if (searchTerm) { - filtered = apps.filter((app) => - app.app_name.toLowerCase().includes(searchTerm.toLowerCase()), - ) - } - return filtered - }, [apps, searchTerm]) - - const steps = [ - { - content: ( - { - setTemplateId(template.id) - }} - handleCreateApp={handleCreateApp} - /> - ), - }, - { - content: ( - - ), - }, - ] - - if (hasAvailableApps) { - steps.unshift({ - content: ( -
- Add new app - - -
- ), - }) - } - - return ( - -
- {!isLoading && !error && ( -
-

App Management

- {Array.isArray(apps) && apps.length ? ( - - ) : null} -
- )} - - {isLoading || (!apps && !error) ? ( -
- -
- ) : error ? ( -
- -
- ) : Array.isArray(apps) && apps.length ? ( -
-
- setSearchTerm(e.target.value)} - /> - -
- - {Array.isArray(apps) && filteredApps.length ? ( -
- {filteredApps.map((app, index: number) => ( -
- -
- ))} -
- ) : ( - - )} - - -
- ) : ( - - )} -
- - setCurrent(0)} - onCancel={() => { - setIsCreateAppModalOpen(false) - }} - footer={null} - title={null} - className={classes.modal} - width={steps.length === 3 && current == 0 ? 855 : 480} - centered - > - {steps[current]?.content} - - - { - setIsMaxAppModalOpen(false) - }} - /> - - setStatusModalOpen(false)} - statusData={statusData} - appName={newApp} - /> -
- ) -} - -export default AppSelector diff --git a/agenta-web/src/components/AppSelector/AppTemplateCard.tsx b/agenta-web/src/components/AppSelector/AppTemplateCard.tsx deleted file mode 100644 index eab31fb542..0000000000 --- a/agenta-web/src/components/AppSelector/AppTemplateCard.tsx +++ /dev/null @@ -1,128 +0,0 @@ -import React from "react" -import {createUseStyles} from "react-jss" -import {JSSTheme} from "@/lib/Types" -import {Button, Card, Typography} from "antd" -import {ArrowRight} from "@phosphor-icons/react" - -const useStyles = createUseStyles((theme: JSSTheme) => ({ - card: { - width: 392, - height: 268, - display: "flex", - cursor: "pointer", - flexDirection: "column", - justifyContent: "space-between", - transition: "all 0.025s ease-in", - boxShadow: theme.boxShadowTertiary, - "& > .ant-card-head": { - minHeight: 0, - padding: theme.paddingSM, - "& .ant-card-head-title": { - fontSize: theme.fontSizeLG, - fontWeight: theme.fontWeightMedium, - }, - }, - "& > .ant-card-body": { - padding: theme.paddingSM, - flex: 1, - }, - "& > .ant-card-actions": { - padding: "0 12px", - }, - "&:hover": { - boxShadow: theme.boxShadow, - }, - }, - button: { - width: "100%", - display: "flex", - alignItems: "center", - "& > .ant-btn-icon": { - marginTop: 4, - }, - }, -})) - -interface Props { - onWriteOwnApp: () => void - onCreateFromTemplate: () => void -} - -const AppTemplateCard: React.FC = ({onWriteOwnApp, onCreateFromTemplate}) => { - const classes = useStyles() - - const templatePoints = [ - "Experiment and compare prompts and models", - "Evaluate outputs in the web UI", - "Deploy and version prompts", - "Track all LLM calls", - ] - const complexLLM = [ - "Experiment with RAG, or agent architectures in the web UI", - "Create custom playgrounds to debug and trace calls", - "Easily integrate your LLM app code into the platform", - "Evaluate workflows end-to-end in the web UI", - ] - return ( -
- } - size="large" - > - Create a new prompt - , - ]} - > -
- Quickly create a prompt and: -
    - {templatePoints.map((item) => ( -
  • {item}
  • - ))} -
-
-
- - } - size="large" - > - Create your own app - , - ]} - > -
- - Create your own complex application using any framework. - -
    - {complexLLM.map((item) => ( -
  • {item}
  • - ))} -
-
-
-
- ) -} - -export default AppTemplateCard diff --git a/agenta-web/src/components/AppSelector/TipsAndFeatures.tsx b/agenta-web/src/components/AppSelector/TipsAndFeatures.tsx deleted file mode 100644 index 2f927182ca..0000000000 --- a/agenta-web/src/components/AppSelector/TipsAndFeatures.tsx +++ /dev/null @@ -1,138 +0,0 @@ -import React, {useEffect, useState} from "react" -import {BulbFilled} from "@ant-design/icons" -import {Space} from "antd" -import {useAppTheme} from "../Layout/ThemeContextProvider" -import {MDXProvider} from "@mdx-js/react" -import {StyleProps} from "@/lib/Types" -import Image from "next/image" - -import {createUseStyles} from "react-jss" - -const useStyles = createUseStyles({ - container: ({themeMode}: StyleProps) => ({ - backgroundColor: themeMode === "dark" ? "#000" : "rgba(0,0,0,0.03)", - borderRadius: 10, - padding: 20, - width: "100%", - margin: "30px auto", - }), - header: { - "& svg": { - fontSize: 24, - color: "rgb(255, 217, 0)", - }, - "& h1": { - margin: "8px 0", - }, - }, - dotsContainer: { - textAlign: "center", - marginBottom: "20px", - }, - dots: { - display: "inline-block", - width: 10, - height: 10, - borderRadius: "50%", - margin: "0 5px", - cursor: "pointer", - }, - mdxContainer: { - borderRadius: 10, - margin: "10px auto", - width: "100%", - lineHeight: 1.6, - }, - img: { - width: "100%", - height: "auto", - }, -}) - -const slides: any[] = [] - -const TipsAndFeatures = () => { - const {appTheme} = useAppTheme() - const [activeIndex, setActiveIndex] = useState(0) - const classes = useStyles({themeMode: appTheme} as StyleProps) - - const handleDotClick = (index: number) => { - setActiveIndex(index) - } - - const getImagePath = (img: any) => { - const theme = appTheme === "dark" ? "dark" : "light" - - const imgSrc = `/assets/tips-images/${img}-${theme}.png` - - return imgSrc - } - - useEffect(() => { - const interval = setInterval(() => { - setActiveIndex((prevIndex) => (prevIndex + 1) % slides.length) - }, 3000) - - return () => { - clearInterval(interval) - } - }, []) - - return ( - <> - {slides.length ? ( -
- - -

Highlights

-
- -
- {slides.map((_, index) => ( - handleDotClick(index)} - /> - ))} -
- -
- ( - tips-and-tricks - ), - }} - > - {slides.map((Slide, index) => ( -
- -
- ))} -
-
-
- ) : ( - "" - )} - - ) -} - -export default TipsAndFeatures diff --git a/agenta-web/src/components/AppSelector/Welcome.tsx b/agenta-web/src/components/AppSelector/Welcome.tsx deleted file mode 100644 index aaf12f0c26..0000000000 --- a/agenta-web/src/components/AppSelector/Welcome.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import React from "react" -import Image from "next/image" -import {Typography} from "antd" -import AppTemplateCard from "./AppTemplateCard" - -interface Props { - onWriteOwnApp: () => void - onCreateFromTemplate: () => void -} - -const Welcome: React.FC = ({onWriteOwnApp, onCreateFromTemplate}) => { - return ( -
-
- agenta-ai - - Start building and testing your LLM
applications with Agenta AI.{" "} -
-
- -
- -
-
- ) -} -export default Welcome diff --git a/agenta-web/src/components/AppSelector/modals/AddAppFromTemplateModal.tsx b/agenta-web/src/components/AppSelector/modals/AddAppFromTemplateModal.tsx deleted file mode 100644 index 6125448b11..0000000000 --- a/agenta-web/src/components/AppSelector/modals/AddAppFromTemplateModal.tsx +++ /dev/null @@ -1,186 +0,0 @@ -import {Typography, Input, Card, Radio, Space, Button, Flex} from "antd" -import {ArrowLeft} from "@phosphor-icons/react" -import {createUseStyles} from "react-jss" -import {JSSTheme, Template} from "@/lib/Types" -import {isAppNameInputValid} from "@/lib/helpers/utils" - -const {Text} = Typography - -const useStyles = createUseStyles((theme: JSSTheme) => ({ - modal: { - display: "flex", - flexDirection: "column", - gap: 24, - }, - modalError: { - color: theme.colorError, - marginTop: 2, - }, - headerText: { - "& .ant-typography": { - lineHeight: theme.lineHeightLG, - fontSize: theme.fontSizeHeading4, - fontWeight: theme.fontWeightStrong, - }, - }, - title: { - fontSize: theme.fontSizeLG, - fontWeight: theme.fontWeightMedium, - lineHeight: theme.lineHeightLG, - }, - label: { - fontWeight: theme.fontWeightMedium, - }, - card: { - width: 208, - height: 180, - cursor: "pointer", - transitionDuration: "0.3s", - "&:hover": { - boxShadow: theme.boxShadow, - }, - "& > .ant-card-head": { - minHeight: 0, - padding: theme.paddingSM, - - "& .ant-card-head-title": { - fontSize: theme.fontSize, - fontWeight: theme.fontWeightMedium, - lineHeight: theme.lineHeight, - }, - }, - "& > .ant-card-body": { - padding: theme.paddingSM, - "& > .ant-typography": { - color: theme.colorTextSecondary, - }, - }, - }, - inputName: { - borderColor: `${theme.colorError} !important`, - "& .ant-input-clear-icon": { - color: theme.colorError, - }, - }, -})) - -type Props = { - setCurrent: React.Dispatch> - hasAvailableApps: boolean - newApp: string - setNewApp: React.Dispatch> - templates: Template[] - noTemplateMessage: string - onCardClick: (template: Template) => void - appNameExist: boolean - handleCreateApp: () => void - templateId: string | undefined -} - -const AddAppFromTemplatedModal = ({ - setCurrent, - hasAvailableApps, - newApp, - setNewApp, - templates, - noTemplateMessage, - onCardClick, - appNameExist, - handleCreateApp, - templateId, -}: Props) => { - const classes = useStyles() - - const isError = appNameExist || (newApp.length > 0 && !isAppNameInputValid(newApp)) - - const handleEnterKeyPress = (event: React.KeyboardEvent) => { - if (event.key === "Enter" && templateId) { - handleCreateApp() - } - } - - return ( -
- - {hasAvailableApps && ( - - -
- ) -} - -export default AddAppFromTemplatedModal diff --git a/agenta-web/src/components/AppSelector/modals/DeleteAppModal.tsx b/agenta-web/src/components/AppSelector/modals/DeleteAppModal.tsx deleted file mode 100644 index 5c2095a9bc..0000000000 --- a/agenta-web/src/components/AppSelector/modals/DeleteAppModal.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import React from "react" -import {Modal} from "antd" - -type DeleteAppModalProps = { - appName: string - confirmLoading: boolean -} & React.ComponentProps - -const DeleteAppModal = ({appName, confirmLoading, ...props}: DeleteAppModalProps) => { - return ( - -

Are you sure you want to delete {appName}?

-
- ) -} - -export default DeleteAppModal diff --git a/agenta-web/src/components/AppSelector/modals/WriteOwnAppModal.tsx b/agenta-web/src/components/AppSelector/modals/WriteOwnAppModal.tsx deleted file mode 100644 index 48fca7bcf4..0000000000 --- a/agenta-web/src/components/AppSelector/modals/WriteOwnAppModal.tsx +++ /dev/null @@ -1,161 +0,0 @@ -import {Typography, Space, Button} from "antd" -import {ArrowLeft, Check, Copy, Play} from "@phosphor-icons/react" -import {createUseStyles} from "react-jss" -import {JSSTheme} from "@/lib/Types" -import {isDemo} from "@/lib/helpers/utils" -import {useRouter} from "next/router" -import {useState} from "react" - -const {Text} = Typography - -const useStyles = createUseStyles((theme: JSSTheme) => ({ - modal: { - display: "flex", - flexDirection: "column", - gap: 24, - }, - headerText: { - "& .ant-typography": { - lineHeight: theme.lineHeightLG, - fontSize: theme.fontSizeHeading4, - fontWeight: theme.fontWeightStrong, - }, - }, - label: { - fontWeight: theme.fontWeightMedium, - }, - command: { - width: "92%", - backgroundColor: "#f6f8fa", - borderRadius: theme.borderRadius, - border: "1px solid", - borderColor: theme.colorBorder, - color: theme.colorText, - cursor: "default", - padding: "3.5px 11px", - }, - copyBtn: { - width: theme.controlHeight, - height: theme.controlHeight, - "& > .ant-btn-icon": { - marginTop: 2, - marginLeft: 1, - }, - }, -})) - -type Props = { - setCurrent: React.Dispatch> - hasAvailableApps: boolean -} - -const WriteOwnAppModal = ({setCurrent, hasAvailableApps}: Props) => { - const classes = useStyles() - const router = useRouter() - const [isCopied, setIsCopied] = useState(null) - - const onCopyCode = (code: string, index: number) => { - navigator.clipboard.writeText(code) - setIsCopied(index) - - setTimeout(() => setIsCopied(null), 2000) - } - const listOfCommands = [ - ...(isDemo() - ? [ - { - title: "Add an API Key", - code: "", - }, - ] - : []), - { - title: "Install Agenta AI", - code: "pip install -U agenta", - }, - { - title: "Clone the example application", - code: "git clone https://github.com/Agenta-AI/simple_prompt && cd simple_prompt", - }, - { - title: "Set up environement variable", - code: 'echo -e "OPENAI_API_KEY=sk-xxx" > .env', - }, - { - title: "Setup Agenta (select start from blank)", - code: "agenta init", - }, - { - title: "Serve an app variant", - code: "agenta variant serve --file_name app.py", - }, - ] - return ( -
-
- - {hasAvailableApps && ( - - -
- - - Create your own complex application using any framework. To learn more about how to - write your own LLM app here,{" "} - - Click here - - - - {listOfCommands.map((item, ind) => ( -
-
- - Step {ind + 1}: {item.title} - - - {item.title.includes("API Key") ? ( - - ) : ( -
-
{item.code}
- -
- )} -
-
- ))} -
- ) -} - -export default WriteOwnAppModal diff --git a/agenta-web/src/components/Layout/Layout.tsx b/agenta-web/src/components/Layout/Layout.tsx index 6b0d299575..b1cbcc8209 100644 --- a/agenta-web/src/components/Layout/Layout.tsx +++ b/agenta-web/src/components/Layout/Layout.tsx @@ -1,5 +1,5 @@ -import React, {useEffect, useMemo} from "react" -import {Breadcrumb, Layout, Modal, Space, Typography, theme} from "antd" +import React, {useEffect, useMemo, useState} from "react" +import {Breadcrumb, ConfigProvider, Layout, Modal, Space, Typography, theme} from "antd" import Sidebar from "../Sidebar/Sidebar" import {GithubFilled, LinkedinFilled, TwitterOutlined} from "@ant-design/icons" import Link from "next/link" @@ -18,6 +18,7 @@ import {JSSTheme, StyleProps as MainStyleProps} from "@/lib/Types" import {Lightning} from "@phosphor-icons/react" import packageJsonData from "../../../package.json" import {useProjectData} from "@/contexts/project.context" +import {dynamicContext} from "@/lib/helpers/dynamic" const {Content, Footer} = Layout const {Text} = Typography @@ -75,6 +76,25 @@ const useStyles = createUseStyles((theme: JSSTheme) => ({ color: "rgba(0, 0, 0, 0.45)", }, }, + banner: { + position: "sticky", + zIndex: 10, + top: 0, + left: 0, + height: 38, + backgroundColor: "#1c2c3d", + display: "flex", + alignItems: "center", + justifyContent: "center", + gap: 6, + color: "#fff", + fontSize: 12, + lineHeight: "20px", + fontWeight: 500, + "& span": { + fontWeight: 600, + }, + }, })) type LayoutProps = { @@ -86,7 +106,7 @@ const App: React.FC = ({children}) => { const {appTheme} = useAppTheme() const {currentApp} = useAppsData() const [footerRef, {height: footerHeight}] = useElementSize() - const {isProjectId} = useProjectData() + const {project, projects} = useProjectData() const classes = useStyles({themeMode: appTheme, footerHeight} as StyleProps) const router = useRouter() const appId = router.query.app_id as string @@ -94,6 +114,15 @@ const App: React.FC = ({children}) => { const {token} = theme.useToken() const [modal, contextHolder] = Modal.useModal() + const [useOrgData, setUseOrgData] = useState(() => () => "") + const {changeSelectedOrg} = useOrgData() + + useEffect(() => { + dynamicContext("org.context", {useOrgData}).then((context) => { + setUseOrgData(() => context.useOrgData) + }) + }, []) + useEffect(() => { if (user && isDemo()) { ;(window as any).intercomSettings = { @@ -166,6 +195,13 @@ const App: React.FC = ({children}) => { const isAuthRoute = router.pathname.includes("/auth") || router.pathname.includes("/post-signup") + const handleBackToWorkspaceSwitch = () => { + const project = projects.find((p) => p.user_role === "owner") + if (project && !project.is_demo) { + changeSelectedOrg(project.organization_id) + } + } + return ( {typeof window === "undefined" ? null : ( @@ -179,60 +215,87 @@ const App: React.FC = ({children}) => { ) : ( // !isAuthRoute && isProjectId - - - -
-
- - - Apps -
- ), - }, - {title: currentApp?.app_name || ""}, - ]} - /> -
- agenta v{packageJsonData.version} +
+ {project?.is_demo && ( +
+ You are in a view-only demo workspace. To go back + to your workspace{" "} + + click here + +
+ )} + + + +
+
+ + + Apps +
+ ), + }, + {title: currentApp?.app_name || ""}, + ]} + /> +
+ agenta v{packageJsonData.version} +
+ + + + {children} + + {contextHolder} + +
- - - {children} - {contextHolder} - - -
-
- - - - - - - - - - - -
Copyright © {new Date().getFullYear()} | Agenta.
-
+
+ + + + + + + + + + + +
Copyright © {new Date().getFullYear()} | Agenta.
+
+ - +
)} )} diff --git a/agenta-web/src/components/NoResultsFound/NoResultsFound.tsx b/agenta-web/src/components/NoResultsFound/NoResultsFound.tsx index 9b173ef301..f91656fd89 100644 --- a/agenta-web/src/components/NoResultsFound/NoResultsFound.tsx +++ b/agenta-web/src/components/NoResultsFound/NoResultsFound.tsx @@ -13,10 +13,11 @@ const useStyles = createUseStyles((theme: JSSTheme) => ({ justifyContent: "center", padding: "80px 0px", gap: 16, - "& > span": { + "& > span.ant-typography": { lineHeight: theme.lineHeightHeading4, fontSize: theme.fontSizeHeading4, fontWeight: theme.fontWeightMedium, + color: theme.colorText, }, }, })) diff --git a/agenta-web/src/components/Sidebar/Sidebar.tsx b/agenta-web/src/components/Sidebar/Sidebar.tsx index e3c0c403f2..0854ac8a3d 100644 --- a/agenta-web/src/components/Sidebar/Sidebar.tsx +++ b/agenta-web/src/components/Sidebar/Sidebar.tsx @@ -16,6 +16,7 @@ import {CaretDown, Gear, SignOut} from "@phosphor-icons/react" import AlertPopup from "../AlertPopup/AlertPopup" import {dynamicContext} from "@/lib/helpers/dynamic" import Avatar from "@/components/Avatar/Avatar" +import {useProjectData} from "@/contexts/project.context" const {Sider} = Layout const {Text} = Typography @@ -285,6 +286,7 @@ const Sidebar: React.FC = () => { const menu = useSidebarConfig() const {user} = useProfileData() const {logout} = useSession() + const {project} = useProjectData() const [useOrgData, setUseOrgData] = useState(() => () => "") const {selectedOrg, orgs, changeSelectedOrg} = useOrgData() @@ -366,7 +368,7 @@ const Sidebar: React.FC = () => { ), })), {type: "divider"}, - { + !project?.is_demo && { key: "settings", label: ( { const sidebarConfig: SidebarConfig[] = [ { - key: "application-link", - title: "Applications", + key: "app-management-link", + title: "App Management", tooltip: "Create new applications or switch between your existing projects.", link: "/apps", icon: , diff --git a/agenta-web/src/components/AppSelector/AppCard.tsx b/agenta-web/src/components/pages/app-management/components/AppCard.tsx similarity index 74% rename from agenta-web/src/components/AppSelector/AppCard.tsx rename to agenta-web/src/components/pages/app-management/components/AppCard.tsx index 7b7e99c89b..da4ac943cf 100644 --- a/agenta-web/src/components/AppSelector/AppCard.tsx +++ b/agenta-web/src/components/pages/app-management/components/AppCard.tsx @@ -1,15 +1,10 @@ import {Card, Dropdown, Button, Typography, Tag} from "antd" import {MoreOutlined} from "@ant-design/icons" -import {deleteApp} from "@/services/app-selector/api" -import {useState} from "react" import {createUseStyles} from "react-jss" import {JSSTheme, ListAppsItem} from "@/lib/Types" -import {useAppsData} from "@/contexts/app.context" import {Note, PencilLine, Trash} from "@phosphor-icons/react" import {useRouter} from "next/router" import {formatDay} from "@/lib/helpers/dateTimeHelper" -import DeleteAppModal from "./modals/DeleteAppModal" -import EditAppModal from "./modals/EditAppModal" const {Text} = Typography @@ -46,35 +41,19 @@ const useStyles = createUseStyles((theme: JSSTheme) => ({ textDecoration: "none", color: theme.colorText, }, + "& .ant-typography": { + fontSize: `${theme.fontSize}px !important`, + }, }, })) const AppCard: React.FC<{ app: ListAppsItem -}> = ({app}) => { - const [visibleDelete, setVisibleDelete] = useState(false) - const [confirmLoading, setConfirmLoading] = useState(false) - const {mutate} = useAppsData() + setSelectedApp: React.Dispatch> + setIsDeleteAppModalOpen: (value: React.SetStateAction) => void + setIsEditAppModalOpen: (value: React.SetStateAction) => void +}> = ({app, setSelectedApp, setIsDeleteAppModalOpen, setIsEditAppModalOpen}) => { const router = useRouter() - const [isEditAppModalOpen, setIsEditAppModalOpen] = useState(false) - - const handleDeleteOk = async () => { - setConfirmLoading(true) - try { - await deleteApp(app.app_id) - mutate() - } catch (error) { - console.error(error) - } finally { - // remove variant tabs position index from LS - localStorage.removeItem(`tabIndex_${app.app_id}`) - setVisibleDelete(false) - setConfirmLoading(false) - } - } - const handleDeleteCancel = () => { - setVisibleDelete(false) - } const classes = useStyles() @@ -106,6 +85,7 @@ const AppCard: React.FC<{ icon: , onClick: (e: any) => { e.domEvent.stopPropagation() + setSelectedApp(app) setIsEditAppModalOpen(true) }, }, @@ -116,7 +96,8 @@ const AppCard: React.FC<{ danger: true, onClick: (e: any) => { e.domEvent.stopPropagation() - setVisibleDelete(true) + setSelectedApp(app) + setIsDeleteAppModalOpen(true) }, }, ], @@ -142,20 +123,6 @@ const AppCard: React.FC<{ - - - - setIsEditAppModalOpen(false)} - appDetails={app} - /> ) } diff --git a/agenta-web/src/components/pages/app-management/components/AppTable.tsx b/agenta-web/src/components/pages/app-management/components/AppTable.tsx new file mode 100644 index 0000000000..163a29e95e --- /dev/null +++ b/agenta-web/src/components/pages/app-management/components/AppTable.tsx @@ -0,0 +1,131 @@ +import {ListAppsItem} from "@/lib/Types" +import {MoreOutlined} from "@ant-design/icons" +import {GearSix, Note, PencilLine, Trash} from "@phosphor-icons/react" +import {Button, Dropdown, Table, Tag} from "antd" +import {ColumnsType} from "antd/es/table" +import {useRouter} from "next/router" +import React from "react" +import {formatDay} from "@/lib/helpers/dateTimeHelper" +import NoResultsFound from "@/components/NoResultsFound/NoResultsFound" + +type AppTableProps = { + filteredApps: ListAppsItem[] + setSelectedApp: React.Dispatch> + setIsDeleteAppModalOpen: (value: React.SetStateAction) => void + setIsEditAppModalOpen: (value: React.SetStateAction) => void +} + +const AppTable = ({ + filteredApps, + setIsDeleteAppModalOpen, + setIsEditAppModalOpen, + setSelectedApp, +}: AppTableProps) => { + const router = useRouter() + + const columns: ColumnsType = [ + { + title: "Name", + dataIndex: "name", + key: "name", + render: (_, record) => { + return
{record.app_name}
+ }, + }, + { + title: "Date Modified", + dataIndex: "updated_at", + key: "updated_at", + render: (_, record) => { + return
{formatDay(record.updated_at)}
+ }, + }, + { + title: "Type", + dataIndex: "app_type", + key: "app_type", + render: (_, record) => { + return {record.app_type} + }, + }, + { + title: , + key: "key", + width: 56, + fixed: "right", + align: "center", + render: (_, record) => { + return ( + , + onClick: (e: any) => { + e.domEvent.stopPropagation() + router.push(`/apps/${record.app_id}/overview`) + }, + }, + {type: "divider"}, + { + key: "rename_app", + label: "Rename", + icon: , + onClick: (e: any) => { + e.domEvent.stopPropagation() + setSelectedApp(record) + setIsEditAppModalOpen(true) + }, + }, + { + key: "delete_app", + label: "Delete", + icon: , + danger: true, + onClick: (e: any) => { + e.domEvent.stopPropagation() + setSelectedApp(record) + setIsDeleteAppModalOpen(true) + }, + }, + ], + }} + > +