Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 59 additions & 4 deletions web/ee/src/components/PostSignupForm/OnboardingScreen.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {useState} from "react"
import {useMemo, useState} from "react"

import {ArrowLeft, Code, TreeView, Rocket} from "@phosphor-icons/react"
import {Typography, Card, Button} from "antd"
import {ArrowLeft, Code, Rocket, Sparkle, TreeView} from "@phosphor-icons/react"
import {Button, Card, message, Typography} from "antd"
import {useRouter} from "next/router"
import {createUseStyles} from "react-jss"

Expand All @@ -12,7 +12,8 @@ import {
import useURL from "@/oss/hooks/useURL"
import {usePostHogAg} from "@/oss/lib/helpers/analytics/hooks/usePostHogAg"
import {JSSTheme} from "@/oss/lib/Types"
import {waitForWorkspaceContext, buildPostLoginPath} from "@/oss/state/url/postLoginRedirect"
import {useProjectData} from "@/oss/state/project/hooks"
import {buildPostLoginPath, waitForWorkspaceContext} from "@/oss/state/url/postLoginRedirect"

import {RunEvaluationView} from "./RunEvaluationView"

Expand Down Expand Up @@ -88,6 +89,32 @@ const useStyles = createUseStyles((theme: JSSTheme) => ({
flexWrap: "wrap",
justifyContent: "center",
},
dividerContainer: {
display: "flex",
alignItems: "center",
gap: 16,
width: "100%",
maxWidth: 600,
color: theme.colorTextTertiary,
fontSize: 13,
},
dividerLine: {
flex: 1,
height: 1,
backgroundColor: theme.colorBorderSecondary,
},
demoLink: {
display: "flex",
alignItems: "center",
gap: 8,
color: theme.colorTextSecondary,
fontSize: 15,
cursor: "pointer",
transition: "color 0.2s ease",
"&:hover": {
color: theme.colorPrimary,
},
},
backButton: {
alignSelf: "flex-start",
marginBottom: 20,
Expand All @@ -112,8 +139,25 @@ export const OnboardingScreen = () => {
const [view, setView] = useState<ViewState>("selection")
const router = useRouter()
const posthog = usePostHogAg()
const {projects} = useProjectData()
const _url = useURL() // Keep hook call for potential future use

const demoProject = useMemo(() => projects.find((project) => project.is_demo), [projects])

const handleDemoSelection = async () => {
posthog?.capture?.("onboarding_selection_v1", {
selection: "demo",
})

if (!demoProject) {
message.error("Demo project is not available.")
return
}

// Navigate directly to the demo project
router.push(`/w/${demoProject.workspace_id}/p/${demoProject.project_id}/apps`)
}

const handleSelection = async (selection: "trace" | "eval" | "test_prompt") => {
posthog?.capture?.("onboarding_selection_v1", {
selection,
Expand Down Expand Up @@ -252,6 +296,17 @@ export const OnboardingScreen = () => {
</Card>
</div>

<div className={classes.dividerContainer}>
<div className={classes.dividerLine} />
<span>or</span>
<div className={classes.dividerLine} />
</div>

<div className={classes.demoLink} onClick={handleDemoSelection}>
<Sparkle size={18} />
<span>Explore demo workspace</span>
</div>

<Button type="link" onClick={() => router.push("/apps")}>
Skip
</Button>
Expand Down
113 changes: 97 additions & 16 deletions web/oss/src/components/Layout/Layout.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
import {memo, Suspense, useEffect, useMemo, useRef, type ReactNode, type RefObject} from "react"
import {
memo,
Suspense,
useCallback,
useEffect,
useMemo,
useRef,
useState,
type ReactNode,
type RefObject,
} from "react"

import {GithubFilled, LinkedinFilled, TwitterOutlined} from "@ant-design/icons"
import {ConfigProvider, Layout, Modal, Skeleton, Space, theme} from "antd"
import clsx from "clsx"
import {useAtomValue} from "jotai"
import {useAtom, useAtomValue, useSetAtom} from "jotai"
import dynamic from "next/dynamic"
import Link from "next/link"
import {ErrorBoundary} from "react-error-boundary"
Expand All @@ -12,9 +22,16 @@ import {useLocalStorage, useResizeObserver} from "usehooks-ts"
import useURL from "@/oss/hooks/useURL"
import {usePostHogAg} from "@/oss/lib/helpers/analytics/hooks/usePostHogAg"
import {currentAppAtom} from "@/oss/state/app"
import {useAppQuery, useAppState} from "@/oss/state/appState"
import {requestNavigationAtom, useAppQuery, useAppState} from "@/oss/state/appState"
import {cacheWorkspaceOrgPair} from "@/oss/state/org/selectors/org"
import {useProfileData} from "@/oss/state/profile"
import {getProjectValues, useProjectData} from "@/oss/state/project"
import {
cacheLastUsedProjectId,
demoReturnHintDismissedAtom,
demoReturnHintPendingAtom,
lastNonDemoProjectAtom,
} from "@/oss/state/project/selectors/project"

import OldAppDeprecationBanner from "../Banners/OldAppDeprecationBanner"
import CustomWorkflowBanner from "../CustomWorkflow/CustomWorkflowBanner"
Expand Down Expand Up @@ -73,27 +90,91 @@ const AppWithVariants = memo(
}, [appState.asPath, appState.pathname])

const currentApp = useAtomValue(currentAppAtom)
const {project, projects} = useProjectData()
const {project} = useProjectData()
const lastNonDemoProject = useAtomValue(lastNonDemoProjectAtom)
const [demoReturnHintPending, setDemoReturnHintPending] = useAtom(demoReturnHintPendingAtom)
const [demoReturnHintDismissed, setDemoReturnHintDismissed] = useAtom(
demoReturnHintDismissedAtom,
)
const [isDemoReturnModalOpen, setDemoReturnModalOpen] = useState(false)
const navigate = useSetAtom(requestNavigationAtom)
// const profileLoading = useAtomValue(profilePendingAtom)
// const {changeSelectedOrg} = useOrgData()

const handleBackToWorkspaceSwitch = () => {
const project = projects.find((p) => p.user_role === "owner")
if (project && !project.is_demo && project.organization_id) {
// changeSelectedOrg(project.organization_id)
useEffect(() => {
if (project?.is_demo) return
if (!demoReturnHintPending) return
if (demoReturnHintDismissed) {
setDemoReturnHintPending(false)
return
}
setDemoReturnHintPending(false)
setDemoReturnModalOpen(true)
}, [
demoReturnHintDismissed,
demoReturnHintPending,
project?.is_demo,
setDemoReturnHintPending,
])

const closeDemoReturnModal = useCallback(() => {
setDemoReturnModalOpen(false)
setDemoReturnHintDismissed(true)
}, [setDemoReturnHintDismissed])

const handleBackToWorkspaceSwitch = useCallback(() => {
if (!lastNonDemoProject?.workspaceId || !lastNonDemoProject?.projectId) {
navigate({type: "href", href: "/w", method: "push"})
return
}
}

cacheLastUsedProjectId(lastNonDemoProject.workspaceId, lastNonDemoProject.projectId)

if (lastNonDemoProject.organizationId) {
cacheWorkspaceOrgPair(
lastNonDemoProject.workspaceId,
lastNonDemoProject.organizationId,
)
}

if (!demoReturnHintDismissed) {
setDemoReturnHintPending(true)
}
const href = `/w/${encodeURIComponent(
lastNonDemoProject.workspaceId,
)}/p/${encodeURIComponent(lastNonDemoProject.projectId)}/apps`
navigate({type: "href", href, method: "push"})
}, [demoReturnHintDismissed, lastNonDemoProject, navigate, setDemoReturnHintPending])

return (
<div className={clsx([{"flex flex-col grow min-h-0": isHumanEval || isEvaluator}])}>
<Modal
title="Want to revisit the demo?"
open={isDemoReturnModalOpen}
onOk={closeDemoReturnModal}
onCancel={closeDemoReturnModal}
okText="Got it"
cancelText="Do not show again"
>
<p className="m-0">
Open the org switcher in the sidebar. Select the organization tagged demo to
return.
</p>
</Modal>
{project?.is_demo && (
<div className={classes.banner}>
You are in <span>a view-only</span> demo workspace. To go back to your
workspace{" "}
<span className="cursor-pointer" onClick={handleBackToWorkspaceSwitch}>
click here
</span>
</div>
<>
<div className="fixed top-0 left-0 right-0 z-[9999] flex items-center justify-center gap-1.5 h-[38px] bg-[#1c2c3d] text-white text-sm font-medium">
You're viewing the demo workspace.
<button
type="button"
className="bg-transparent border-none p-0 text-white text-sm font-medium underline underline-offset-2 hover:opacity-80 transition-opacity cursor-pointer"
onClick={handleBackToWorkspaceSwitch}
>
Return to your workspace
</button>
</div>
<div className="h-[38px] shrink-0" />
</>
)}
<Layout hasSider className={classes.layout}>
<SidebarIsland
Expand Down
26 changes: 23 additions & 3 deletions web/oss/src/state/project/hooks.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import {useCallback, useEffect} from "react"

import {useQueryClient} from "@tanstack/react-query"
import {useAtom, useAtomValue} from "jotai"
import {useAtom, useAtomValue, useSetAtom} from "jotai"

import {projectsQueryAtom} from "./selectors/project"
import {cacheLastUsedProjectId, projectAtom, projectsAtom, projectIdAtom} from "./selectors/project"
import {
cacheLastUsedProjectId,
lastNonDemoProjectAtom,
projectAtom,
projectIdAtom,
projectsAtom,
} from "./selectors/project"

export const useProjectData = () => {
const [{data: projects, isPending: _isPending, isLoading, refetch: _refetch}] =
Expand All @@ -13,12 +19,26 @@ export const useProjectData = () => {
const projectId = useAtomValue(projectIdAtom)
const isProjectId = !!projectId
const queryClient = useQueryClient()
const setLastNonDemoProject = useSetAtom(lastNonDemoProjectAtom)

useEffect(() => {
if (!project?.project_id) return
const workspaceKey = project.workspace_id || project.organization_id || null
cacheLastUsedProjectId(workspaceKey, project.project_id)
}, [project?.organization_id, project?.project_id, project?.workspace_id])
if (!project.is_demo && workspaceKey) {
setLastNonDemoProject({
workspaceId: workspaceKey,
projectId: project.project_id,
organizationId: project.organization_id ?? null,
})
}
}, [
project?.organization_id,
project?.project_id,
project?.workspace_id,
project?.is_demo,
setLastNonDemoProject,
])

const reset = useCallback(async () => {
return await queryClient.removeQueries({queryKey: ["projects"]})
Expand Down
25 changes: 25 additions & 0 deletions web/oss/src/state/project/selectors/project.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {atom} from "jotai"
import {atomWithStorage} from "jotai/utils"
import {atomWithQuery} from "jotai-tanstack-query"

import {queryClient} from "@/oss/lib/api/queryClient"
Expand All @@ -12,6 +13,30 @@ import {sessionExistsAtom} from "@/oss/state/session"
import {logAtom} from "@/oss/state/utils/logAtom"

const LAST_USED_PROJECTS_KEY = "lastUsedProjectsByWorkspace"
const LAST_NON_DEMO_PROJECT_KEY = "agenta:last-non-demo-project"
const DEMO_RETURN_HINT_DISMISSED_KEY = "agenta:demo-return-hint-dismissed"
const DEMO_RETURN_HINT_PENDING_KEY = "agenta:demo-return-hint-pending"

export interface LastNonDemoProject {
workspaceId: string
projectId: string
organizationId: string | null
}

export const lastNonDemoProjectAtom = atomWithStorage<LastNonDemoProject | null>(
LAST_NON_DEMO_PROJECT_KEY,
null,
)

export const demoReturnHintDismissedAtom = atomWithStorage<boolean>(
DEMO_RETURN_HINT_DISMISSED_KEY,
false,
)

export const demoReturnHintPendingAtom = atomWithStorage<boolean>(
DEMO_RETURN_HINT_PENDING_KEY,
false,
)

const readLastUsedProjectId = (workspaceId: string | null): string | null => {
if (typeof window === "undefined" || !workspaceId) return null
Expand Down