diff --git a/web/oss/src/components/EvalRunDetails/components/views/SingleScenarioViewerPOC/ScenarioAnnotationPanel/AnnotationInputs.tsx b/web/oss/src/components/EvalRunDetails/components/views/SingleScenarioViewerPOC/ScenarioAnnotationPanel/AnnotationInputs.tsx index 70744a88c3..eaa10d44c7 100644 --- a/web/oss/src/components/EvalRunDetails/components/views/SingleScenarioViewerPOC/ScenarioAnnotationPanel/AnnotationInputs.tsx +++ b/web/oss/src/components/EvalRunDetails/components/views/SingleScenarioViewerPOC/ScenarioAnnotationPanel/AnnotationInputs.tsx @@ -42,6 +42,7 @@ export const BooleanGroupTab = memo(function BooleanGroupTab({ // Check if value is set (not null/undefined) const hasValue = value !== null && value !== undefined + const isRatingField = typeof label === "string" && /rating|thumb/i.test(label) return (
@@ -56,6 +57,7 @@ export const BooleanGroupTab = memo(function BooleanGroupTab({ onChange={(e) => handleChange(e.target.value)} value={value} disabled={disabled} + data-tour={isRatingField ? "annotation-rating" : undefined} > {options?.map((option) => ( diff --git a/web/oss/src/components/EvalRunDetails/test.tsx b/web/oss/src/components/EvalRunDetails/test.tsx index b80f3ab2f7..9c71b4e627 100644 --- a/web/oss/src/components/EvalRunDetails/test.tsx +++ b/web/oss/src/components/EvalRunDetails/test.tsx @@ -31,7 +31,7 @@ const EvalRunTestPage = ({type = "auto"}: {type?: EvalRunKind}) => { } return ( -
+
{ if (tourName === EXPLORE_PLAYGROUND_TOUR_ID) { recordWidgetEvent("playground_explored") } + if (tourName === DEPLOY_PROMPT_TOUR_ID) { + recordWidgetEvent("variant_deployed") + } + if (tourName === ANNOTATE_TRACES_TOUR_ID) { + recordWidgetEvent("trace_annotated") + } } setActiveTourId(null) }, @@ -57,10 +65,19 @@ const OnboardingInner = ({children}: {children: React.ReactNode}) => { (_step: number, tourName: string | null) => { if (tourName) { markTourSeen(tourName) + if (tourName === EXPLORE_PLAYGROUND_TOUR_ID) { + recordWidgetEvent("playground_explored") + } + if (tourName === DEPLOY_PROMPT_TOUR_ID) { + recordWidgetEvent("variant_deployed") + } + if (tourName === ANNOTATE_TRACES_TOUR_ID) { + recordWidgetEvent("trace_annotated") + } } setActiveTourId(null) }, - [markTourSeen, setActiveTourId], + [markTourSeen, recordWidgetEvent, setActiveTourId], ) return ( diff --git a/web/oss/src/components/Onboarding/Widget/OnboardingWidget.tsx b/web/oss/src/components/Onboarding/Widget/OnboardingWidget.tsx index 830e9f409e..a3a4247099 100644 --- a/web/oss/src/components/Onboarding/Widget/OnboardingWidget.tsx +++ b/web/oss/src/components/Onboarding/Widget/OnboardingWidget.tsx @@ -1,15 +1,18 @@ "use client" -import {useCallback, useEffect, useMemo, useRef} from "react" +import {useCallback, useEffect, useMemo, useRef, useState} from "react" import {useNextStep} from "@agentaai/nextstepjs" import {CaretDown, CaretUp, RocketLaunch, X} from "@phosphor-icons/react" -import {Button, Typography} from "antd" +import {Button, Typography, message} from "antd" import clsx from "clsx" import {useAtomValue, useSetAtom} from "jotai" import {useRouter} from "next/router" +import {openDeploymentsDrawerAtom} from "@/oss/components/DeploymentsDashboard/modals/store/deploymentDrawerStore" +import {usePlaygroundNavigation} from "@/oss/hooks/usePlaygroundNavigation" import {useSession} from "@/oss/hooks/useSession" +import useURL from "@/oss/hooks/useURL" import { activeTourIdAtom, hasSeenCloseTooltipAtom, @@ -21,12 +24,20 @@ import { onboardingWidgetStatusAtom, onboardingWidgetUIStateAtom, recordWidgetEventAtom, + setOnboardingWidgetActivationAtom, setOnboardingWidgetConfigAtom, setWidgetSectionExpandedAtom, tourRegistry, type OnboardingWidgetItem, } from "@/oss/lib/onboarding" +import {traceCountAtom, tracesQueryAtom} from "@/oss/state/newObservability/atoms/queries" +import {ANNOTATE_TRACES_TOUR_ID, registerAnnotateTracesTour} from "../tours/annotateTracesTour" +import {DEPLOY_PROMPT_TOUR_ID, registerDeployPromptTour} from "../tours/deployPromptTour" +import { + registerTestsetFromTracesTour, + TESTSET_FROM_TRACES_TOUR_ID, +} from "../tours/testsetFromTracesTour" import {registerWidgetClosedTour} from "../tours/widgetClosedTour" import { @@ -44,23 +55,42 @@ const {Text} = Typography const OnboardingWidget = () => { const router = useRouter() const {doesSessionExist} = useSession() + const {appURL, recentlyVisitedAppURL, baseAppURL} = useURL() const config = useAtomValue(onboardingWidgetConfigAtom) const widgetStatus = useAtomValue(onboardingWidgetStatusAtom) const setWidgetStatus = useSetAtom(onboardingWidgetStatusAtom) const widgetUIState = useAtomValue(onboardingWidgetUIStateAtom) const setWidgetConfig = useSetAtom(setOnboardingWidgetConfigAtom) const setWidgetUIState = useSetAtom(onboardingWidgetUIStateAtom) + const setWidgetActivation = useSetAtom(setOnboardingWidgetActivationAtom) const expandedSections = useAtomValue(onboardingWidgetExpandedSectionsAtom) const setSectionExpanded = useSetAtom(setWidgetSectionExpandedAtom) const completionMap = useAtomValue(onboardingWidgetCompletionAtom) const widgetEvents = useAtomValue(onboardingWidgetEventsAtom) const recordWidgetEvent = useSetAtom(recordWidgetEventAtom) + const openDeploymentsDrawer = useSetAtom(openDeploymentsDrawerAtom) const isNewUser = useAtomValue(isNewUserAtom) const hasSeenCloseTooltip = useAtomValue(hasSeenCloseTooltipAtom) const setHasSeenCloseTooltip = useSetAtom(hasSeenCloseTooltipAtom) const activeTourId = useAtomValue(activeTourIdAtom) const setActiveTourId = useSetAtom(activeTourIdAtom) const {startNextStep, isNextStepVisible} = useNextStep() + const {goToPlayground} = usePlaygroundNavigation() + const traceCount = useAtomValue(traceCountAtom) + const tracesQuery = useAtomValue(tracesQueryAtom) + const [pendingTraceTourId, setPendingTraceTourId] = useState(null) + + const registryUrl = useMemo(() => { + const base = appURL || recentlyVisitedAppURL || baseAppURL + if (!base) return null + return `${base}/variants` + }, [appURL, recentlyVisitedAppURL, baseAppURL]) + const observabilityUrl = useMemo(() => { + const base = appURL || recentlyVisitedAppURL + if (!base) return null + return `${base}/traces` + }, [appURL, recentlyVisitedAppURL]) + const isOnTracesRoute = useMemo(() => router.asPath.includes("/traces"), [router.asPath]) const allItems = useMemo( () => config.sections.flatMap((section) => section.items), @@ -74,20 +104,14 @@ const OnboardingWidget = () => { const completedEventCount = useMemo(() => Object.keys(widgetEvents).length, [widgetEvents]) - // Widget only renders for authenticated new users who haven't dismissed it + // Widget renders for authenticated users when opened and not dismissed const shouldRender = - doesSessionExist && - isNewUser && - widgetStatus !== "dismissed" && - widgetUIState.isOpen && - totalTasks > 0 - - const hasTrackedOpenRef = useRef(false) + doesSessionExist && widgetStatus !== "dismissed" && widgetUIState.isOpen && totalTasks > 0 const startTour = useCallback( (tourId: string) => { - if (!tourRegistry.has(tourId)) { - console.warn(`[Onboarding] Tour "${tourId}" not found in registry`) + if (!tourRegistry.get(tourId)) { + console.warn(`[Onboarding] Tour "${tourId}" not found or disabled`) return } @@ -102,6 +126,33 @@ const OnboardingWidget = () => { [activeTourId, isNextStepVisible, setActiveTourId, startNextStep], ) + useEffect(() => { + if (!pendingTraceTourId || !router.isReady || !isOnTracesRoute) return + if (tracesQuery.isPending || tracesQuery.isLoading || tracesQuery.isFetching) return + + if (traceCount > 0) { + startTour(pendingTraceTourId) + setPendingTraceTourId(null) + return + } + + message.info( + "No traces yet. Set up tracing first, then return here to start the walkthrough.", + ) + setPendingTraceTourId(null) + }, [ + isOnTracesRoute, + pendingTraceTourId, + router.isReady, + startTour, + traceCount, + tracesQuery.isFetching, + tracesQuery.isPending, + tracesQuery.isLoading, + ]) + + const hasTrackedOpenRef = useRef(false) + const handleItemClick = useCallback( async (item: OnboardingWidgetItem) => { if (item.disabled) return @@ -110,22 +161,96 @@ const OnboardingWidget = () => { if (item.activationHint) { recordWidgetEvent(`activation:${item.activationHint}`) + setWidgetActivation(item.activationHint) } - if (item.href) { + if (item.activationHint === "open-create-prompt" && baseAppURL) { + try { + await router.push(baseAppURL) + } catch (error) { + console.error("Failed to navigate to onboarding target", error) + return + } + } else if (item.activationHint === "open-registry" && registryUrl) { + try { + await router.push(registryUrl) + recordWidgetEvent("registry_page_viewed") + } catch (error) { + console.error("Failed to navigate to onboarding target", error) + return + } + } else if (item.activationHint === "integration-snippet") { + try { + if (registryUrl) { + await router.push(registryUrl) + } + openDeploymentsDrawer({initialWidth: 1200, mode: "variant"}) + recordWidgetEvent("integration_snippet_viewed") + } catch (error) { + console.error("Failed to open integration snippet", error) + return + } + } else if (item.activationHint === "deploy-variant") { + startTour(item.tourId || DEPLOY_PROMPT_TOUR_ID) + return + } else if (item.activationHint === "tracing-snippet" && baseAppURL) { + try { + await router.push(baseAppURL) + } catch (error) { + console.error("Failed to navigate to tracing setup", error) + return + } + } else if (item.activationHint === "trace-annotations") { + try { + if (observabilityUrl) { + await router.push(observabilityUrl) + } + setPendingTraceTourId(item.tourId || ANNOTATE_TRACES_TOUR_ID) + return + } catch (error) { + console.error("Failed to navigate to observability", error) + return + } + } else if (item.activationHint === "trace-to-testset") { + try { + if (observabilityUrl) { + await router.push(observabilityUrl) + } + setPendingTraceTourId(item.tourId || TESTSET_FROM_TRACES_TOUR_ID) + return + } catch (error) { + console.error("Failed to navigate to observability", error) + return + } + } else if (item.activationHint === "run-first-evaluation") { + goToPlayground() + } else if (item.href) { try { await router.push(item.href) } catch (error) { console.error("Failed to navigate to onboarding target", error) return } + } else if (item.activationHint === "playground-walkthrough") { + goToPlayground() } if (item.tourId) { startTour(item.tourId) } }, - [recordWidgetEvent, router, startTour], + [ + recordWidgetEvent, + baseAppURL, + observabilityUrl, + registryUrl, + router, + startTour, + openDeploymentsDrawer, + setWidgetActivation, + goToPlayground, + setPendingTraceTourId, + ], ) const toggleSection = useCallback( @@ -172,6 +297,9 @@ const OnboardingWidget = () => { // Register the widget closed tour useEffect(() => { registerWidgetClosedTour() + registerDeployPromptTour() + registerAnnotateTracesTour() + registerTestsetFromTracesTour() }, []) useEffect(() => { @@ -201,10 +329,10 @@ const OnboardingWidget = () => { useEffect(() => { const shouldComplete = totalTasks > 0 && completedTasks >= totalTasks - if (!shouldComplete) return + if (!shouldComplete || widgetStatus === "completed") return setWidgetStatus("completed") trackWidgetTaskCompleted({totalTasks, completedTasks}) - }, [totalTasks, completedTasks, setWidgetStatus]) + }, [totalTasks, completedTasks, widgetStatus, setWidgetStatus]) if (!shouldRender) { return null diff --git a/web/oss/src/components/Onboarding/hooks/useOnboardingTour.ts b/web/oss/src/components/Onboarding/hooks/useOnboardingTour.ts index 7ad98d6476..202568bf12 100644 --- a/web/oss/src/components/Onboarding/hooks/useOnboardingTour.ts +++ b/web/oss/src/components/Onboarding/hooks/useOnboardingTour.ts @@ -53,14 +53,15 @@ export function useOnboardingTour({ const hasBeenSeen = Boolean(seenTours[tourId]) const isActive = activeTourId === tourId && isNextStepVisible - const canAutoStart = isNewUser && !hasBeenSeen && autoStartCondition && tourRegistry.has(tourId) + const isTourAvailable = Boolean(tourRegistry.get(tourId)) + const canAutoStart = isNewUser && !hasBeenSeen && autoStartCondition && isTourAvailable // Manual start function const startTour = useCallback( (options?: TriggerTourOptions) => { const {force = false} = options ?? {} - if (!tourRegistry.has(tourId)) { + if (!tourRegistry.get(tourId)) { console.warn(`[Onboarding] Tour "${tourId}" not found in registry`) dispatch({type: "CHECK_FAILURE", error: "Tour not found"}) return diff --git a/web/oss/src/components/Onboarding/index.ts b/web/oss/src/components/Onboarding/index.ts index 4341783f20..23d52ebfbd 100644 --- a/web/oss/src/components/Onboarding/index.ts +++ b/web/oss/src/components/Onboarding/index.ts @@ -14,3 +14,13 @@ export { unregisterExplorePlaygroundTour, EXPLORE_PLAYGROUND_TOUR_ID, } from "./tours/explorePlaygroundTour" +export { + registerDeployPromptTour, + unregisterDeployPromptTour, + DEPLOY_PROMPT_TOUR_ID, +} from "./tours/deployPromptTour" +export { + registerFirstEvaluationTour, + unregisterFirstEvaluationTour, + FIRST_EVALUATION_TOUR_ID, +} from "./tours/firstEvaluationTour" diff --git a/web/oss/src/components/Onboarding/tours/annotateTracesTour.ts b/web/oss/src/components/Onboarding/tours/annotateTracesTour.ts new file mode 100644 index 0000000000..88693bda14 --- /dev/null +++ b/web/oss/src/components/Onboarding/tours/annotateTracesTour.ts @@ -0,0 +1,88 @@ +import {tourRegistry} from "@/oss/lib/onboarding" +import type {OnboardingTour} from "@/oss/lib/onboarding" + +/** + * Annotate Traces Tour + * + * Guides users through annotating traces for human evaluation. + */ +export const ANNOTATE_TRACES_TOUR_ID = "annotate-traces" + +const annotateTracesTour: OnboardingTour = { + id: ANNOTATE_TRACES_TOUR_ID, + steps: [ + { + icon: "🏷️", + title: "Annotate Your Traces", + content: + "You can add human feedback to traces. This helps you build evaluation datasets and track quality.", + selector: undefined, + side: "bottom", + showControls: true, + showSkip: true, + }, + { + icon: "📍", + title: "Open a Trace", + content: "Click on a trace to see its details.", + selector: '[data-tour="trace-row"]', + side: "bottom", + showControls: true, + showSkip: true, + selectorRetryAttempts: 10, + selectorRetryDelay: 200, + }, + { + icon: "✏️", + title: "Click Annotate", + content: "Click the Annotate button to add your feedback.", + selector: '[data-tour="annotate-button"]', + side: "bottom", + showControls: true, + showSkip: true, + selectorRetryAttempts: 10, + selectorRetryDelay: 200, + }, + { + icon: "👍", + title: "Choose a Rating", + content: "Select thumbs up or thumbs down. You can also add comments.", + selector: '[data-tour="annotation-rating"]', + side: "bottom", + showControls: true, + showSkip: true, + selectorRetryAttempts: 10, + selectorRetryDelay: 200, + }, + { + icon: "✅", + title: "Submit", + content: "Click Submit to save your annotation.", + selector: '[data-tour="annotation-submit"]', + side: "bottom", + showControls: true, + showSkip: true, + selectorRetryAttempts: 10, + selectorRetryDelay: 200, + }, + ], +} + +/** + * Register the tour + * + * This function should be called once to register the tour. + * It's safe to call multiple times - duplicate registrations are ignored. + */ +export function registerAnnotateTracesTour(): void { + tourRegistry.register(annotateTracesTour) +} + +/** + * Unregister the tour (for cleanup/testing) + */ +export function unregisterAnnotateTracesTour(): void { + tourRegistry.unregister(ANNOTATE_TRACES_TOUR_ID) +} + +export default annotateTracesTour diff --git a/web/oss/src/components/Onboarding/tours/deployPromptTour.ts b/web/oss/src/components/Onboarding/tours/deployPromptTour.ts new file mode 100644 index 0000000000..b233337ca9 --- /dev/null +++ b/web/oss/src/components/Onboarding/tours/deployPromptTour.ts @@ -0,0 +1,120 @@ +import {getDefaultStore} from "jotai" + +import {openDeploymentsDrawerAtom} from "@/oss/components/DeploymentsDashboard/modals/store/deploymentDrawerStore" +import {recordWidgetEventAtom, tourRegistry} from "@/oss/lib/onboarding" +import type {OnboardingTour} from "@/oss/lib/onboarding" +import {variantTableSelectionAtomFamily} from "@/oss/state/variant/atoms/selection" + +/** + * Deploy Prompt Tour + * + * Guides users through deploying a prompt version and accessing the API snippet. + */ +export const DEPLOY_PROMPT_TOUR_ID = "deploy-prompt" + +const SELECTION_SCOPE = "variants/dashboard" + +const deployPromptTour: OnboardingTour = { + id: DEPLOY_PROMPT_TOUR_ID, + steps: [ + { + icon: "🚀", + title: "Deploy Your Prompt", + content: + "Once you have a prompt version you like, you can deploy it to an environment. Let me show you how.", + selector: undefined, + side: "bottom", + showControls: true, + showSkip: true, + }, + { + icon: "📋", + title: "Open the Registry", + content: + "The registry shows all your committed prompt versions. Click here to open it.", + selector: '[data-tour="registry-nav"]', + side: "right", + showControls: true, + showSkip: true, + selectorRetryAttempts: 10, + selectorRetryDelay: 200, + onEnter: () => { + if (typeof window === "undefined") return + const registryNav = document.querySelector( + '[data-tour="registry-nav"]', + ) as HTMLElement | null + if (registryNav) { + registryNav.click() + } + }, + }, + { + icon: "📄", + title: "Select a Version", + content: "Click on a version to see its details.", + selector: '[data-tour="version-row"]', + side: "bottom", + showControls: true, + showSkip: true, + selectorRetryAttempts: 10, + selectorRetryDelay: 200, + }, + { + icon: "🌐", + title: "Deploy to an Environment", + content: + "Click Deploy to make this version available via API. You can deploy to staging or production.", + selector: '[data-tour="deploy-button"]', + side: "bottom", + showControls: true, + showSkip: true, + selectorRetryAttempts: 10, + selectorRetryDelay: 200, + }, + { + icon: "🔗", + title: "View the API Code", + content: + "Click here to see the code snippet for calling your deployed prompt. Copy this into your application.", + selector: '[data-tour="api-code-button"]', + side: "bottom", + showControls: true, + showSkip: true, + selectorRetryAttempts: 10, + selectorRetryDelay: 200, + onEnter: () => { + if (typeof window === "undefined") return + const store = getDefaultStore() + const selected = store.get(variantTableSelectionAtomFamily(SELECTION_SCOPE)) || [] + const revisionId = selected[0] ? String(selected[0]) : undefined + store.set(openDeploymentsDrawerAtom, { + initialWidth: 1200, + revisionId, + mode: "variant", + }) + store.set(recordWidgetEventAtom, "integration_snippet_viewed") + }, + }, + ], +} + +/** + * Register the tour + * + * This function should be called once to register the tour. + * It's safe to call multiple times - duplicate registrations are ignored. + */ +export function registerDeployPromptTour(): void { + tourRegistry.register(deployPromptTour, { + condition: () => true, + }) +} + +/** + * Unregister the tour (for cleanup/testing) + */ +export function unregisterDeployPromptTour(): void { + tourRegistry.unregister(DEPLOY_PROMPT_TOUR_ID) +} + +export default deployPromptTour diff --git a/web/oss/src/components/Onboarding/tours/explorePlaygroundTour.ts b/web/oss/src/components/Onboarding/tours/explorePlaygroundTour.ts index 0c76419a74..eae09d70e6 100644 --- a/web/oss/src/components/Onboarding/tours/explorePlaygroundTour.ts +++ b/web/oss/src/components/Onboarding/tours/explorePlaygroundTour.ts @@ -1,4 +1,3 @@ -import {getEnv} from "@/oss/lib/helpers/dynamicEnv" import {tourRegistry} from "@/oss/lib/onboarding" import type {OnboardingTour} from "@/oss/lib/onboarding" @@ -71,8 +70,6 @@ const explorePlaygroundTour: OnboardingTour = { ], } -const isWalkthroughsEnabled = () => getEnv("NEXT_PUBLIC_ENABLE_WALKTHROUGHS") === "true" - /** * Register the tour * @@ -81,7 +78,7 @@ const isWalkthroughsEnabled = () => getEnv("NEXT_PUBLIC_ENABLE_WALKTHROUGHS") == */ export function registerExplorePlaygroundTour(): void { tourRegistry.register(explorePlaygroundTour, { - condition: isWalkthroughsEnabled, + condition: () => true, }) } diff --git a/web/oss/src/components/Onboarding/tours/firstEvaluationTour.ts b/web/oss/src/components/Onboarding/tours/firstEvaluationTour.ts new file mode 100644 index 0000000000..431acce407 --- /dev/null +++ b/web/oss/src/components/Onboarding/tours/firstEvaluationTour.ts @@ -0,0 +1,108 @@ +import {getDefaultStore} from "jotai" + +import {recordWidgetEventAtom, tourRegistry} from "@/oss/lib/onboarding" +import type {OnboardingTour} from "@/oss/lib/onboarding" + +/** + * Run First Evaluation Tour + * + * Guides users through running their first evaluation from the Playground. + */ +export const FIRST_EVALUATION_TOUR_ID = "first-evaluation" + +const firstEvaluationTour: OnboardingTour = { + id: FIRST_EVALUATION_TOUR_ID, + steps: [ + { + icon: "🎯", + title: "Run Your First Evaluation", + content: + "Evaluations help you measure how well your prompts perform. Let's run one together.", + selector: undefined, + side: "bottom", + showControls: true, + showSkip: true, + }, + { + icon: "▶️", + title: "Open the Evaluation Modal", + content: + 'Click "Run Evaluation" to start. If you do not have a prompt yet, create one first.', + selector: '[data-tour="run-evaluation-button"]', + side: "bottom", + showControls: true, + showSkip: true, + selectorRetryAttempts: 10, + selectorRetryDelay: 200, + }, + { + icon: "📂", + title: "Select a Test Set", + content: "Choose a test set. We have created one for you to get started.", + selector: '[data-tour="testset-select"]', + side: "right", + showControls: true, + showSkip: true, + selectorRetryAttempts: 10, + selectorRetryDelay: 200, + }, + { + icon: "✏️", + title: "Choose an Evaluator", + content: + 'Select "Exact Match" to compare outputs against expected answers. You can create custom evaluators later.', + selector: '[data-tour="evaluator-select"]', + side: "right", + showControls: true, + showSkip: true, + selectorRetryAttempts: 10, + selectorRetryDelay: 200, + }, + { + icon: "🚀", + title: "Run the Evaluation", + content: "Click Run to start the evaluation.", + selector: '[data-tour="run-eval-confirm"]', + side: "top", + showControls: true, + showSkip: true, + selectorRetryAttempts: 10, + selectorRetryDelay: 200, + }, + { + icon: "📊", + title: "View Your Results", + content: + "Here are your results. You can see how each test case performed and the overall score.", + selector: '[data-tour="eval-results"]', + side: "top", + showControls: true, + showSkip: true, + selectorRetryAttempts: 10, + selectorRetryDelay: 200, + onNext: () => { + const store = getDefaultStore() + store.set(recordWidgetEventAtom, "evaluation_ran") + }, + }, + ], +} + +/** + * Register the tour + * + * This function should be called once to register the tour. + * It's safe to call multiple times - duplicate registrations are ignored. + */ +export function registerFirstEvaluationTour(): void { + tourRegistry.register(firstEvaluationTour) +} + +/** + * Unregister the tour (for cleanup/testing) + */ +export function unregisterFirstEvaluationTour(): void { + tourRegistry.unregister(FIRST_EVALUATION_TOUR_ID) +} + +export default firstEvaluationTour diff --git a/web/oss/src/components/Onboarding/tours/testsetFromTracesTour.ts b/web/oss/src/components/Onboarding/tours/testsetFromTracesTour.ts new file mode 100644 index 0000000000..bb21d72864 --- /dev/null +++ b/web/oss/src/components/Onboarding/tours/testsetFromTracesTour.ts @@ -0,0 +1,94 @@ +import {getDefaultStore} from "jotai" + +import {recordWidgetEventAtom, tourRegistry} from "@/oss/lib/onboarding" +import type {OnboardingTour} from "@/oss/lib/onboarding" + +/** + * Create Test Set from Traces Tour + * + * Guides users through turning traces into a test set. + */ +export const TESTSET_FROM_TRACES_TOUR_ID = "testset-from-traces" + +const testsetFromTracesTour: OnboardingTour = { + id: TESTSET_FROM_TRACES_TOUR_ID, + steps: [ + { + icon: "📦", + title: "Create a Test Set from Traces", + content: + "You can turn real production data into test cases. This helps you evaluate against realistic inputs.", + selector: undefined, + side: "bottom", + showControls: true, + showSkip: true, + }, + { + icon: "☑️", + title: "Select Traces", + content: "Check the boxes next to the traces you want to include.", + selector: '[data-tour="trace-checkbox"]', + side: "bottom", + showControls: true, + showSkip: true, + selectorRetryAttempts: 10, + selectorRetryDelay: 200, + }, + { + icon: "➕", + title: "Click Create Test Set", + content: "Click this button to create a test set from your selection.", + selector: '[data-tour="create-testset-button"]', + side: "bottom", + showControls: true, + showSkip: true, + selectorRetryAttempts: 10, + selectorRetryDelay: 200, + }, + { + icon: "📝", + title: "Name Your Test Set", + content: "Give your test set a descriptive name.", + selector: '[data-tour="testset-name-input"]', + side: "bottom", + showControls: true, + showSkip: true, + selectorRetryAttempts: 10, + selectorRetryDelay: 200, + }, + { + icon: "✅", + title: "Confirm", + content: "Click Create to save your test set. You can now use it in evaluations.", + selector: '[data-tour="testset-confirm"]', + side: "bottom", + showControls: true, + showSkip: true, + selectorRetryAttempts: 10, + selectorRetryDelay: 200, + onNext: () => { + const store = getDefaultStore() + store.set(recordWidgetEventAtom, "testset_created_from_traces") + }, + }, + ], +} + +/** + * Register the tour + * + * This function should be called once to register the tour. + * It's safe to call multiple times - duplicate registrations are ignored. + */ +export function registerTestsetFromTracesTour(): void { + tourRegistry.register(testsetFromTracesTour) +} + +/** + * Unregister the tour (for cleanup/testing) + */ +export function unregisterTestsetFromTracesTour(): void { + tourRegistry.unregister(TESTSET_FROM_TRACES_TOUR_ID) +} + +export default testsetFromTracesTour diff --git a/web/oss/src/components/Playground/Components/Menus/SelectVariant/index.tsx b/web/oss/src/components/Playground/Components/Menus/SelectVariant/index.tsx index 7e6d05bdd5..2224cdaf54 100644 --- a/web/oss/src/components/Playground/Components/Menus/SelectVariant/index.tsx +++ b/web/oss/src/components/Playground/Components/Menus/SelectVariant/index.tsx @@ -3,10 +3,11 @@ import {useCallback, useMemo, useState} from "react" import {ArrowsLeftRight} from "@phosphor-icons/react" import {TreeSelect, Typography} from "antd" import clsx from "clsx" -import {useAtomValue} from "jotai" +import {useAtomValue, useSetAtom} from "jotai" import VariantDetailsWithStatus from "@/oss/components/VariantDetailsWithStatus" import EnvironmentStatus from "@/oss/components/VariantDetailsWithStatus/components/EnvironmentStatus" +import {recordWidgetEventAtom} from "@/oss/lib/onboarding" import AddButton from "../../../assets/AddButton" import {variantOptionsAtomFamily} from "../../../state/atoms/optionsSelectors" @@ -66,6 +67,7 @@ const SelectVariant = ({ const [isOpenCompareSelect, setIsOpenCompareSelect] = useState(false) const [isOpenSelect, setIsOpenSelect] = useState(false) + const recordWidgetEvent = useSetAtom(recordWidgetEventAtom) const handleClose = useCallback(() => { setIsOpenSelect(false) @@ -129,7 +131,10 @@ const SelectVariant = ({ icon={} label="Compare" className="absolute top-0 left-0 z-10" - onClick={() => setIsOpenCompareSelect((prev) => !prev)} + onClick={() => { + recordWidgetEvent("playground_compared_side_by_side") + setIsOpenCompareSelect((prev) => !prev) + }} size="small" data-tour="compare-toggle" /> diff --git a/web/oss/src/components/Playground/Components/Modals/CommitVariantChangesModal/assets/CommitVariantChangesButton/index.tsx b/web/oss/src/components/Playground/Components/Modals/CommitVariantChangesModal/assets/CommitVariantChangesButton/index.tsx index 897e8a9132..47553e0469 100644 --- a/web/oss/src/components/Playground/Components/Modals/CommitVariantChangesModal/assets/CommitVariantChangesButton/index.tsx +++ b/web/oss/src/components/Playground/Components/Modals/CommitVariantChangesModal/assets/CommitVariantChangesButton/index.tsx @@ -2,10 +2,11 @@ import {cloneElement, isValidElement, useState} from "react" import {FloppyDiskBack} from "@phosphor-icons/react" import {Button} from "antd" -import {useAtomValue} from "jotai" +import {useAtomValue, useSetAtom} from "jotai" import dynamic from "next/dynamic" import {variantIsDirtyAtomFamily} from "@/oss/components/Playground/state/atoms" +import {recordWidgetEventAtom} from "@/oss/lib/onboarding" import {CommitVariantChangesButtonProps} from "../types" const CommitVariantChangesModal = dynamic(() => import("../.."), {ssr: false}) @@ -21,6 +22,11 @@ const CommitVariantChangesButton = ({ }: CommitVariantChangesButtonProps) => { const [isDeployModalOpen, setIsDeployModalOpen] = useState(false) const disabled = !useAtomValue(variantIsDirtyAtomFamily(variantId || "")) + const recordWidgetEvent = useSetAtom(recordWidgetEventAtom) + const handleSuccess = () => { + recordWidgetEvent("playground_committed_change") + onSuccess?.() + } return ( <> @@ -51,7 +57,7 @@ const CommitVariantChangesButton = ({ open={isDeployModalOpen} onCancel={() => setIsDeployModalOpen(false)} variantId={variantId} - onSuccess={onSuccess} + onSuccess={handleSuccess} commitType={commitType} /> diff --git a/web/oss/src/components/Playground/Components/Modals/DeployVariantModal/index.tsx b/web/oss/src/components/Playground/Components/Modals/DeployVariantModal/index.tsx index b420d947fe..fc2b9c876b 100644 --- a/web/oss/src/components/Playground/Components/Modals/DeployVariantModal/index.tsx +++ b/web/oss/src/components/Playground/Components/Modals/DeployVariantModal/index.tsx @@ -8,6 +8,7 @@ import router from "next/router" import {message} from "@/oss/components/AppMessageContext" import EnhancedModal from "@/oss/components/EnhancedUIs/Modal" import {usePostHogAg} from "@/oss/lib/helpers/analytics/hooks/usePostHogAg" +import {recordWidgetEventAtom} from "@/oss/lib/onboarding" import {publishMutationAtom} from "@/oss/state/deployment/atoms/publish" import { @@ -37,6 +38,7 @@ const DeployVariantModal = ({ const resetDeploy = useSetAtom(deployResetAtom) const submitDeploy = useSetAtom(deploySubmitAtom) const setModalState = useSetAtom(deployVariantModalAtom) + const recordWidgetEvent = useSetAtom(recordWidgetEventAtom) const {isPending: isLoading} = useAtomValue(publishMutationAtom) const appId = router.query.app_id as string @@ -82,7 +84,8 @@ const DeployVariantModal = ({ onClose() message.success(`Published ${variantName} to ${env}`) posthog?.capture?.("app_deployed", {app_id: appId, environment: env}) - }, [submitDeploy, onClose, variantName, appId, posthog]) + recordWidgetEvent("variant_deployed") + }, [submitDeploy, onClose, variantName, appId, posthog, recordWidgetEvent]) return ( { const loadTestsetData = useSetAtom(loadTestsetNormalizedMutationAtom) + const recordWidgetEvent = useSetAtom(recordWidgetEventAtom) const isChat = useAtomValue(appChatModeAtom) ?? false const [isTestsetModalOpen, setIsTestsetModalOpen] = useState(false) @@ -36,11 +38,12 @@ const LoadTestsetButton = ({ isChatVariant: isChat, regenerateVariableIds: true, }) + recordWidgetEvent("playground_loaded_testset") } setTestsetData(payload) }, - [loadTestsetData, isChat], + [loadTestsetData, isChat, recordWidgetEvent], ) return ( diff --git a/web/oss/src/components/Playground/Components/PlaygroundHeader/RunEvaluationButton.tsx b/web/oss/src/components/Playground/Components/PlaygroundHeader/RunEvaluationButton.tsx index 55220803b8..f20de52e3e 100644 --- a/web/oss/src/components/Playground/Components/PlaygroundHeader/RunEvaluationButton.tsx +++ b/web/oss/src/components/Playground/Components/PlaygroundHeader/RunEvaluationButton.tsx @@ -39,6 +39,7 @@ const RunEvaluationButton: React.FC = ({className}) => icon={} className={clsx("self-start", className)} disabled={!hasVariants} + data-tour="run-evaluation-button" onClick={() => setIsModalOpen(true)} size="small" > diff --git a/web/oss/src/components/Playground/PlaygroundOnboarding.tsx b/web/oss/src/components/Playground/PlaygroundOnboarding.tsx index bef4ef296c..8b23dcc1a6 100644 --- a/web/oss/src/components/Playground/PlaygroundOnboarding.tsx +++ b/web/oss/src/components/Playground/PlaygroundOnboarding.tsx @@ -1,23 +1,52 @@ "use client" +import {useEffect} from "react" + +import {useAtomValue, useSetAtom} from "jotai" + import {useOnboardingTour} from "@/oss/components/Onboarding" +import {registerDeployPromptTour} from "@/oss/components/Onboarding/tours/deployPromptTour" import { EXPLORE_PLAYGROUND_TOUR_ID, registerExplorePlaygroundTour, } from "@/oss/components/Onboarding/tours/explorePlaygroundTour" -import {getEnv} from "@/oss/lib/helpers/dynamicEnv" +import { + FIRST_EVALUATION_TOUR_ID, + registerFirstEvaluationTour, +} from "@/oss/components/Onboarding/tours/firstEvaluationTour" +import { + onboardingWidgetActivationAtom, + setOnboardingWidgetActivationAtom, +} from "@/oss/lib/onboarding" registerExplorePlaygroundTour() - -const isWalkthroughsEnabled = () => getEnv("NEXT_PUBLIC_ENABLE_WALKTHROUGHS") === "true" +registerDeployPromptTour() +registerFirstEvaluationTour() export const PlaygroundOnboarding = () => { - useOnboardingTour({ + const activationHint = useAtomValue(onboardingWidgetActivationAtom) + const setActivationHint = useSetAtom(setOnboardingWidgetActivationAtom) + const {startTour} = useOnboardingTour({ tourId: EXPLORE_PLAYGROUND_TOUR_ID, - autoStart: true, - autoStartCondition: isWalkthroughsEnabled(), + autoStart: false, + }) + const {startTour: startFirstEvaluationTour} = useOnboardingTour({ + tourId: FIRST_EVALUATION_TOUR_ID, + autoStart: false, }) + useEffect(() => { + if (activationHint !== "playground-walkthrough") return + startTour({force: true}) + setActivationHint(null) + }, [activationHint, setActivationHint, startTour]) + + useEffect(() => { + if (activationHint !== "run-first-evaluation") return + startFirstEvaluationTour({force: true}) + setActivationHint(null) + }, [activationHint, setActivationHint, startFirstEvaluationTour]) + return null } diff --git a/web/oss/src/components/Playground/assets/RunButton.tsx b/web/oss/src/components/Playground/assets/RunButton.tsx index be3c25a9ad..5e189618e3 100644 --- a/web/oss/src/components/Playground/assets/RunButton.tsx +++ b/web/oss/src/components/Playground/assets/RunButton.tsx @@ -1,5 +1,10 @@ +import type {MouseEvent} from "react" + import {PlayIcon, XCircleIcon} from "@phosphor-icons/react" import {Button, type ButtonProps} from "antd" +import {useSetAtom} from "jotai" + +import {recordWidgetEventAtom} from "@/oss/lib/onboarding" interface AddButtonProps extends ButtonProps { isRerun?: boolean @@ -15,13 +20,23 @@ const RunButton = ({ label, ...props }: AddButtonProps) => { + const {onClick, ...restProps} = props + const recordWidgetEvent = useSetAtom(recordWidgetEventAtom) + const handleClick = (event: MouseEvent) => { + if (!isCancel) { + recordWidgetEvent("playground_ran_prompt") + } + onClick?.(event) + } + return ( diff --git a/web/oss/src/components/SharedDrawers/AddToTestsetDrawer/TestsetDrawer.tsx b/web/oss/src/components/SharedDrawers/AddToTestsetDrawer/TestsetDrawer.tsx index af71ea2cbd..2ace4823cd 100644 --- a/web/oss/src/components/SharedDrawers/AddToTestsetDrawer/TestsetDrawer.tsx +++ b/web/oss/src/components/SharedDrawers/AddToTestsetDrawer/TestsetDrawer.tsx @@ -134,6 +134,7 @@ const TestsetDrawer = ({open, spanIds, onClose, initialPath = "ag.data"}: Testse !drawer.isMapColumnExist || drawer.hasDuplicateColumns } + data-tour="testset-confirm" > {drawer.isNewTestset ? "Create" : "Commit"} diff --git a/web/oss/src/components/SharedDrawers/AddToTestsetDrawer/components/TestsetSelector.tsx b/web/oss/src/components/SharedDrawers/AddToTestsetDrawer/components/TestsetSelector.tsx index 627adef35d..2f58640d25 100644 --- a/web/oss/src/components/SharedDrawers/AddToTestsetDrawer/components/TestsetSelector.tsx +++ b/web/oss/src/components/SharedDrawers/AddToTestsetDrawer/components/TestsetSelector.tsx @@ -75,6 +75,7 @@ export function TestsetSelector({ value={newTestsetName} onChange={(e) => setNewTestsetName(e.target.value)} placeholder="Testset name" + data-tour="testset-name-input" />
diff --git a/web/oss/src/components/SharedDrawers/AnnotateDrawer/assets/AnnotateDrawerTitle/index.tsx b/web/oss/src/components/SharedDrawers/AnnotateDrawer/assets/AnnotateDrawerTitle/index.tsx index 77269cff6b..1b9eeeae76 100644 --- a/web/oss/src/components/SharedDrawers/AnnotateDrawer/assets/AnnotateDrawerTitle/index.tsx +++ b/web/oss/src/components/SharedDrawers/AnnotateDrawer/assets/AnnotateDrawerTitle/index.tsx @@ -266,6 +266,7 @@ const AnnotateDrawerTitle = ({ onClick={onSaveChanges} loading={isSaving} disabled={isChangedMetricData && isChangedSelectedEvalMetrics} + data-tour="annotation-submit" > Save diff --git a/web/oss/src/components/SharedDrawers/TraceDrawer/components/TraceContent/components/TraceTypeHeader/index.tsx b/web/oss/src/components/SharedDrawers/TraceDrawer/components/TraceContent/components/TraceTypeHeader/index.tsx index 576136c440..93bbcbd87b 100644 --- a/web/oss/src/components/SharedDrawers/TraceDrawer/components/TraceContent/components/TraceTypeHeader/index.tsx +++ b/web/oss/src/components/SharedDrawers/TraceDrawer/components/TraceContent/components/TraceTypeHeader/index.tsx @@ -76,6 +76,7 @@ const TraceTypeHeader = ({ spanId: activeTrace?.span_id, }} queryKey="trace-drawer-annotations" + data-tour="annotate-button" /> diff --git a/web/oss/src/components/pages/app-management/index.tsx b/web/oss/src/components/pages/app-management/index.tsx index 4ccd65dc01..16610a9738 100644 --- a/web/oss/src/components/pages/app-management/index.tsx +++ b/web/oss/src/components/pages/app-management/index.tsx @@ -4,7 +4,6 @@ import {Typography} from "antd" import dayjs from "dayjs" import {useAtomValue, useSetAtom} from "jotai" import dynamic from "next/dynamic" -import {useRouter} from "next/router" import {useAppTheme} from "@/oss/components/Layout/ThemeContextProvider" import ResultComponent from "@/oss/components/ResultComponent/ResultComponent" @@ -12,6 +11,11 @@ import {useVaultSecret} from "@/oss/hooks/useVaultSecret" import {usePostHogAg} from "@/oss/lib/helpers/analytics/hooks/usePostHogAg" import {type LlmProvider} from "@/oss/lib/helpers/llmProviders" import {isDemo} from "@/oss/lib/helpers/utils" +import { + onboardingWidgetActivationAtom, + recordWidgetEventAtom, + setOnboardingWidgetActivationAtom, +} from "@/oss/lib/onboarding" import {Template, GenericObject, StyleProps} from "@/oss/lib/Types" import {waitForAppToStart} from "@/oss/services/api" import {createAndStartTemplate, deleteApp, ServiceType} from "@/oss/services/app-selector/api" @@ -62,6 +66,9 @@ const AppManagement: React.FC = () => { setFetchingTemplate: setFetchingCustomWorkflow, appId: "", }) + const onboardingWidgetActivation = useAtomValue(onboardingWidgetActivationAtom) + const recordWidgetEvent = useSetAtom(recordWidgetEventAtom) + const setOnboardingWidgetActivation = useSetAtom(setOnboardingWidgetActivationAtom) const posthog = usePostHogAg() const {appTheme} = useAppTheme() const classes = useStyles({themeMode: appTheme} as StyleProps) @@ -101,6 +108,7 @@ const AppManagement: React.FC = () => { deployed_by: user?.id, }, }) + recordWidgetEvent("prompt_created") } setStatusData((prev) => ({...prev, status, details, appId: appId || prev.appId})) @@ -108,14 +116,17 @@ const AppManagement: React.FC = () => { }) } - const {query: routerQuery, isReady} = useRouter() + useEffect(() => { + if (onboardingWidgetActivation !== "open-create-prompt") return + setIsAddAppFromTemplatedModal(true) + setOnboardingWidgetActivation(null) + }, [onboardingWidgetActivation, setOnboardingWidgetActivation]) useEffect(() => { - if (!isReady) return - if (routerQuery.create_prompt === "true") { - setIsAddAppFromTemplatedModal(true) - } - }, [isReady, routerQuery.create_prompt]) + if (onboardingWidgetActivation !== "tracing-snippet") return + setIsSetupTracingModal(true) + setOnboardingWidgetActivation(null) + }, [onboardingWidgetActivation, setOnboardingWidgetActivation]) const onErrorRetry = async () => { if (statusData.appId) { diff --git a/web/oss/src/components/pages/evaluations/NewEvaluation/Components/SelectEvaluatorSection/SelectEvaluatorSection.tsx b/web/oss/src/components/pages/evaluations/NewEvaluation/Components/SelectEvaluatorSection/SelectEvaluatorSection.tsx index 3545f0b98a..b0ef20b362 100644 --- a/web/oss/src/components/pages/evaluations/NewEvaluation/Components/SelectEvaluatorSection/SelectEvaluatorSection.tsx +++ b/web/oss/src/components/pages/evaluations/NewEvaluation/Components/SelectEvaluatorSection/SelectEvaluatorSection.tsx @@ -336,7 +336,7 @@ const SelectEvaluatorSection = ({ return ( <> -
+
{hasEvaluatorConfigs && (
{expectedVariables.join(", ")}
)} -
+
({ width={1200} className={classes.modalContainer} confirmLoading={submitLoading} + okButtonProps={{"data-tour": "run-eval-confirm"}} styles={{ container: { height: 700, diff --git a/web/oss/src/components/pages/observability/components/ObservabilityHeader/index.tsx b/web/oss/src/components/pages/observability/components/ObservabilityHeader/index.tsx index 94dfc107fd..49f83afabf 100644 --- a/web/oss/src/components/pages/observability/components/ObservabilityHeader/index.tsx +++ b/web/oss/src/components/pages/observability/components/ObservabilityHeader/index.tsx @@ -416,6 +416,7 @@ const ObservabilityHeader = ({ icon={} disabled={traces.length === 0 || selectedRowKeys.length === 0} tooltipProps={{title: "Add to testset"}} + data-tour="create-testset-button" /> ) : null} @@ -480,6 +481,7 @@ const ObservabilityHeader = ({ onClick={() => getTestsetTraceData()} icon={} disabled={traces.length === 0 || selectedRowKeys.length === 0} + data-tour="create-testset-button" > Add to testset diff --git a/web/oss/src/components/pages/observability/components/ObservabilityTable/index.tsx b/web/oss/src/components/pages/observability/components/ObservabilityTable/index.tsx index cfd41c9cc1..0aa5e0d2b8 100644 --- a/web/oss/src/components/pages/observability/components/ObservabilityTable/index.tsx +++ b/web/oss/src/components/pages/observability/components/ObservabilityTable/index.tsx @@ -173,6 +173,9 @@ const ObservabilityTable = () => { setSelectedRowKeys(keys) }, columnWidth: 48, + getCheckboxProps: () => ({ + "data-tour": "trace-checkbox", + }), } const showTableLoading = isLoading && traces.length === 0 @@ -262,6 +265,7 @@ const ObservabilityTable = () => { setSpanParam(undefined) } }, + "data-tour": "trace-row", })} components={{ header: { diff --git a/web/oss/src/hooks/usePlaygroundNavigation.ts b/web/oss/src/hooks/usePlaygroundNavigation.ts index 2ece0a4bdc..d5ac351e5d 100644 --- a/web/oss/src/hooks/usePlaygroundNavigation.ts +++ b/web/oss/src/hooks/usePlaygroundNavigation.ts @@ -1,10 +1,11 @@ import {useCallback} from "react" -import {useSetAtom} from "jotai" +import {message} from "antd" +import {useAtomValue, useSetAtom} from "jotai" import {useAppId} from "@/oss/hooks/useAppId" import useURL from "@/oss/hooks/useURL" -import {recentAppIdAtom} from "@/oss/state/app/atoms/fetcher" +import {appsQueryAtom, recentAppIdAtom} from "@/oss/state/app/atoms/fetcher" import {useAppNavigation} from "@/oss/state/appState" interface VariantLike { @@ -54,11 +55,23 @@ export const usePlaygroundNavigation = () => { const appId = useAppId() const {push} = useAppNavigation() const {baseAppURL} = useURL() + const appsQuery = useAtomValue(appsQueryAtom) + const recentAppId = useAtomValue(recentAppIdAtom) const setRecentAppId = useSetAtom(recentAppIdAtom) const goToPlayground = useCallback( (target?: PlaygroundTarget, options?: GoToPlaygroundOptions) => { - const resolvedAppId = options?.appId ?? appId + let resolvedAppId = options?.appId ?? appId ?? recentAppId ?? null + const apps = appsQuery?.data ?? [] + + if (!resolvedAppId && appsQuery?.isSuccess) { + if (apps.length === 0) { + message.info("Create an application to explore the playground.") + return + } + resolvedAppId = apps[0]?.app_id ?? null + } + if (!resolvedAppId) return const selectedKeys = Array.from( new Set( @@ -69,6 +82,8 @@ export const usePlaygroundNavigation = () => { ) if (options?.appId) { setRecentAppId(options.appId) + } else if (!appId && resolvedAppId && resolvedAppId !== recentAppId) { + setRecentAppId(resolvedAppId) } const querySuffix = selectedKeys.length > 0 @@ -77,7 +92,7 @@ export const usePlaygroundNavigation = () => { push(`${baseAppURL}/${resolvedAppId}/playground${querySuffix}`) }, - [appId, baseAppURL, push, setRecentAppId], + [appId, appsQuery, baseAppURL, push, recentAppId, setRecentAppId], ) return {goToPlayground} diff --git a/web/oss/src/lib/helpers/dynamicEnv.ts b/web/oss/src/lib/helpers/dynamicEnv.ts index f26a912f2c..461bf3ea47 100644 --- a/web/oss/src/lib/helpers/dynamicEnv.ts +++ b/web/oss/src/lib/helpers/dynamicEnv.ts @@ -34,7 +34,6 @@ export const processEnv = { NEXT_PUBLIC_AGENTA_AUTH_EMAIL_ENABLED: process.env.NEXT_PUBLIC_AGENTA_AUTH_EMAIL_ENABLED, NEXT_PUBLIC_AGENTA_AUTH_OIDC_ENABLED: process.env.NEXT_PUBLIC_AGENTA_AUTH_OIDC_ENABLED, NEXT_PUBLIC_AGENTA_SENDGRID_ENABLED: process.env.NEXT_PUBLIC_AGENTA_SENDGRID_ENABLED, - NEXT_PUBLIC_ENABLE_WALKTHROUGHS: process.env.NEXT_PUBLIC_ENABLE_WALKTHROUGHS, NEXT_PUBLIC_LOG_APP_ATOMS: "true", // process.env.NEXT_PUBLIC_LOG_APP_ATOMS, NEXT_PUBLIC_ENABLE_ATOM_LOGS: "true", diff --git a/web/oss/src/lib/onboarding/index.ts b/web/oss/src/lib/onboarding/index.ts index f3fb881048..72937fa339 100644 --- a/web/oss/src/lib/onboarding/index.ts +++ b/web/oss/src/lib/onboarding/index.ts @@ -19,8 +19,10 @@ export { onboardingWidgetExpandedSectionsAtom, onboardingWidgetStatusAtom, onboardingWidgetUIStateAtom, + onboardingWidgetActivationAtom, recordWidgetEventAtom, setOnboardingWidgetConfigAtom, + setOnboardingWidgetActivationAtom, setWidgetSectionExpandedAtom, hasSeenCloseTooltipAtom, openWidgetAtom, diff --git a/web/oss/src/lib/onboarding/widget/config.ts b/web/oss/src/lib/onboarding/widget/config.ts index 97ccacaceb..ae52c67f3b 100644 --- a/web/oss/src/lib/onboarding/widget/config.ts +++ b/web/oss/src/lib/onboarding/widget/config.ts @@ -21,12 +21,13 @@ export const defaultWidgetConfig: OnboardingWidgetConfig = { description: "Run, commit, load a test set, and compare variants.", activationHint: "playground-walkthrough", completionEventIds: [ + "playground_explored", "playground_ran_prompt", "playground_committed_change", "playground_loaded_testset", "playground_compared_side_by_side", ], - completionMode: "all", + completionMode: "any", }, ], }, @@ -86,6 +87,7 @@ export const defaultWidgetConfig: OnboardingWidgetConfig = { id: "deploy-environment", title: "Deploy to an environment", description: "Open the deploy UI in registry.", + tourId: "deploy-prompt", activationHint: "deploy-variant", completionEventIds: ["variant_deployed"], completionMode: "any", @@ -118,6 +120,7 @@ export const defaultWidgetConfig: OnboardingWidgetConfig = { title: "Annotate traces", description: "Navigate to traces and annotate.", activationHint: "trace-annotations", + tourId: "annotate-traces", completionEventIds: ["trace_annotated"], completionMode: "any", }, @@ -126,6 +129,7 @@ export const defaultWidgetConfig: OnboardingWidgetConfig = { title: "Create a test set from traces", description: "Create a test set from trace data.", activationHint: "trace-to-testset", + tourId: "testset-from-traces", completionEventIds: ["testset_created_from_traces"], completionMode: "any", }, diff --git a/web/oss/src/lib/onboarding/widget/index.ts b/web/oss/src/lib/onboarding/widget/index.ts index 1c81ae9598..06b6643341 100644 --- a/web/oss/src/lib/onboarding/widget/index.ts +++ b/web/oss/src/lib/onboarding/widget/index.ts @@ -8,11 +8,13 @@ export type { export { onboardingWidgetCompletionAtom, onboardingWidgetConfigAtom, + onboardingWidgetActivationAtom, onboardingWidgetEventsAtom, onboardingWidgetExpandedSectionsAtom, onboardingWidgetStatusAtom, onboardingWidgetUIStateAtom, recordWidgetEventAtom, + setOnboardingWidgetActivationAtom, setOnboardingWidgetConfigAtom, setWidgetSectionExpandedAtom, hasSeenCloseTooltipAtom, diff --git a/web/oss/src/lib/onboarding/widget/store.ts b/web/oss/src/lib/onboarding/widget/store.ts index 9036d55eed..24cd146437 100644 --- a/web/oss/src/lib/onboarding/widget/store.ts +++ b/web/oss/src/lib/onboarding/widget/store.ts @@ -24,7 +24,7 @@ export const onboardingWidgetStatusAtom = atomWithStorage( STORAGE_KEYS.WIDGET_UI, { - isOpen: true, + isOpen: false, isMinimized: false, }, ) @@ -45,6 +45,15 @@ export const onboardingWidgetConfigAtom = atom({ sections: [], }) +export const onboardingWidgetActivationAtom = atom(null) + +export const setOnboardingWidgetActivationAtom = atom( + null, + (_get, set, activationHint: string | null) => { + set(onboardingWidgetActivationAtom, activationHint) + }, +) + export const onboardingWidgetEventsAtom = atomWithStorage>( STORAGE_KEYS.COMPLETED_TASKS, {},