mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-09-07 02:07:45 +02:00
[eric] free-trial: value-first onboarding reorder, arm-on-launch, exhaustion upsell card
This commit is contained in:
@@ -232,7 +232,16 @@ const SettingsLoader: React.FC<{ children: React.ReactNode }> = ({ children }) =
|
||||
.then((r) => {
|
||||
if (r.ok) dispatch(fetchSettings());
|
||||
})
|
||||
.catch(() => {});
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
// Arm the zero-config free trial when nothing is connected so a brand-new
|
||||
// user can run an agent immediately. The backend no-ops if a real key or
|
||||
// subscription exists, so this is safe to fire on every launch.
|
||||
fetch(`${API_BASE}/subscription/free-trial/mint`, { method: 'POST' })
|
||||
.then((r) => (r.ok ? r.json() : null))
|
||||
.then((data) => { if (data && data.armed) dispatch(fetchSettings()); })
|
||||
.catch(() => {});
|
||||
});
|
||||
}, [dispatch]);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -8,10 +8,13 @@ import { step06 } from './step06_agentControlAgents';
|
||||
import { step07 } from './step07_installSkill';
|
||||
import { step08 } from './step08_makeApp';
|
||||
|
||||
// Value-first order: launch an agent (step03) FIRST so a brand-new user sees
|
||||
// the product work on the free trial, then connect-your-own-model (step01).
|
||||
// Everything else is "learn the features", revealed after the first win.
|
||||
export const STEPS: OnboardingStep[] = [
|
||||
step03,
|
||||
step01,
|
||||
step02,
|
||||
step03,
|
||||
step04,
|
||||
step05,
|
||||
step06,
|
||||
|
||||
@@ -26,6 +26,20 @@ export function hasModelConnected(s: RootState): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
/** True while the server-funded free trial is armed (no key needed yet). */
|
||||
export function hasFreeTrialActive(s: RootState): boolean {
|
||||
const d = s.settings.data as any;
|
||||
return !!(d && d.connection_mode === 'free-trial' && d.free_trial_token);
|
||||
}
|
||||
|
||||
/** True when free runs are running out (or already armed-and-spent). Surfaces the connect-model step. */
|
||||
export function freeRunsLow(s: RootState): boolean {
|
||||
const d = s.settings.data as any;
|
||||
if (!d) return false;
|
||||
const remaining = d.free_trial_remaining;
|
||||
return typeof remaining === 'number' && remaining <= 2;
|
||||
}
|
||||
|
||||
export function hasAnyToolEnabled(s: RootState): boolean {
|
||||
const items = s.tools?.items ?? {};
|
||||
// Match Tools.tsx Switch read: enabled !== false; pre-field tools treat undefined as on.
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
import type { OnboardingStep } from './types';
|
||||
import { S } from '../selectors';
|
||||
import { hasModelConnected } from './skipPredicates';
|
||||
import { hasModelConnected, hasFreeTrialActive, freeRunsLow } from './skipPredicates';
|
||||
|
||||
export const step01: OnboardingStep = {
|
||||
id: 'connect_model',
|
||||
stage: 'get_started',
|
||||
index: 1,
|
||||
title: 'Connect an AI model',
|
||||
description: 'This is the brain behind your agents.',
|
||||
// Moved to last in "Get started": the user only meets this after they've
|
||||
// seen value, framed as "keep going". Stays suppressed while the free trial
|
||||
// is armed and runs aren't low; un-suppresses when they're about to run out.
|
||||
index: 2,
|
||||
title: 'Keep going: connect your model',
|
||||
description: 'Your free runs are limited. Add your own model to keep building.',
|
||||
videoSrc: './onboarding-videos/v2/01.mp4',
|
||||
videoDurationLabel: '0:24',
|
||||
skipIf: hasModelConnected,
|
||||
skipIf: (s) => hasModelConnected(s) || (hasFreeTrialActive(s) && !freeRunsLow(s)),
|
||||
ops: [
|
||||
{ kind: 'move_to', target: S.sidebarSettingsButton },
|
||||
{ kind: 'popup', text: 'Pop into Settings.' },
|
||||
|
||||
@@ -4,13 +4,14 @@ import { isYoutubeEnabled } from './skipPredicates';
|
||||
|
||||
export const step02: OnboardingStep = {
|
||||
id: 'enable_actions',
|
||||
stage: 'get_started',
|
||||
index: 2,
|
||||
// Demoted out of the first-run path: a feature to discover after the first win.
|
||||
stage: 'learn_features',
|
||||
index: 3,
|
||||
title: 'Enable agentic actions',
|
||||
description: 'Allow agents to work across your apps.',
|
||||
videoSrc: './onboarding-videos/v2/02.mp4',
|
||||
videoDurationLabel: '0:24',
|
||||
// Narrowed to YouTube so users with other tools still get walked; step 3 needs YouTube on.
|
||||
// Narrowed to YouTube so users with other tools still get walked.
|
||||
skipIf: isYoutubeEnabled,
|
||||
ops: [
|
||||
{ kind: 'move_to', target: S.sidebarActions },
|
||||
|
||||
@@ -11,7 +11,9 @@ const FALLBACK_PROMPT =
|
||||
export const step03: OnboardingStep = {
|
||||
id: 'launch_agent',
|
||||
stage: 'get_started',
|
||||
index: 3,
|
||||
// Value first: this is now step 1. With the free trial armed it runs with no
|
||||
// model connected and no sign-in, so the user sees an agent work immediately.
|
||||
index: 1,
|
||||
title: 'Launch your first Agent',
|
||||
description: 'Click the chat bubble to fire up a new Agent in a dashboard.',
|
||||
videoSrc: './onboarding-videos/v2/03.mp4',
|
||||
|
||||
@@ -4,7 +4,7 @@ import { hasAnyBrowserSpawned } from './skipPredicates';
|
||||
|
||||
export const step04: OnboardingStep = {
|
||||
id: 'use_browser',
|
||||
stage: 'get_started',
|
||||
stage: 'learn_features',
|
||||
index: 4,
|
||||
title: 'Use the built-in browser',
|
||||
description:
|
||||
|
||||
@@ -101,6 +101,16 @@ function parseOpenSwarmError(text: string, ctx?: OverflowContext): OpenSwarmErro
|
||||
ctaAction: 'upgrade',
|
||||
};
|
||||
}
|
||||
if (/free_trial_exhausted|used your free|free OpenSwarm runs/i.test(text)) {
|
||||
return {
|
||||
kind: 'cap',
|
||||
title: "You've used your free runs",
|
||||
detail:
|
||||
'Connect a model to keep going: your own API key, an AI subscription you already pay for, or OpenSwarm Pro.',
|
||||
ctaLabel: 'Connect a model',
|
||||
ctaAction: 'settings',
|
||||
};
|
||||
}
|
||||
if (/at capacity|Try again shortly|503|service unavailable/i.test(text)) {
|
||||
return {
|
||||
kind: 'network',
|
||||
|
||||
@@ -57,9 +57,13 @@ export interface AppSettings {
|
||||
dev_mode: boolean;
|
||||
allow_experimental_updates: boolean;
|
||||
/** Managed subscription state; surfaces only when user has subscribed via cloud. */
|
||||
connection_mode?: 'own_key' | 'openswarm-pro';
|
||||
connection_mode?: 'own_key' | 'openswarm-pro' | 'free-trial';
|
||||
openswarm_bearer_token?: string | null;
|
||||
openswarm_proxy_url?: string | null;
|
||||
/** Zero-config free trial: server-owned, set by the cloud mint. remaining drives the onboarding "runs low" nudge. */
|
||||
free_trial_token?: string | null;
|
||||
free_trial_remaining?: number | null;
|
||||
free_trial_runs_limit?: number | null;
|
||||
openswarm_subscription_plan?: string | null;
|
||||
openswarm_subscription_expires?: string | null;
|
||||
openswarm_usage_cached?: SubscriptionUsage | null;
|
||||
|
||||
Reference in New Issue
Block a user