[eric] OpenSwarm Pro: new managed plan alongside BYO — deep-link activation from billing checkout, onboarding + Settings card with live usage, gradient OpenSwarm Pro group in

the model picker, inline error cards for rate-limit/connection failures, debugger startup fix
This commit is contained in:
ciregenz
2026-04-15 23:05:02 -07:00
parent 14e1d02bbb
commit 1a68ccfc7b
22 changed files with 948 additions and 66 deletions
@@ -27,6 +27,13 @@ export interface CustomProvider {
models: Array<{ value: string; label: string; context_window?: number }>;
}
export interface SubscriptionUsage {
requests_in_window: number;
plan_limit: number;
window_hours: number;
window_ends_at: number; // unix ms
}
export interface AppSettings {
default_system_prompt: string | null;
default_folder: string | null;
@@ -46,6 +53,20 @@ export interface AppSettings {
expand_new_chats_in_dashboard: boolean;
auto_reveal_sub_agents: boolean;
dev_mode: boolean;
// Optional managed-subscription state (surfaces only when user has
// subscribed via the cloud). Mirrors AppSettings on the backend.
connection_mode?: 'own_key' | 'openswarm-pro';
openswarm_bearer_token?: string | null;
openswarm_proxy_url?: string | null;
openswarm_subscription_plan?: string | null;
openswarm_subscription_expires?: string | null;
openswarm_usage_cached?: SubscriptionUsage | null;
}
export interface ActivateSubscriptionPayload {
token: string;
plan?: string | null;
expires?: string | null;
}
export interface BrowseResult {
@@ -123,6 +144,36 @@ export const browseDirectories = createAsyncThunk(
}
);
// POST /api/subscription/activate — called after the desktop catches an
// openswarm://auth?token=... deep link. Validates + persists on the backend,
// then refreshes settings so the Settings UI flips to "Pro" mode.
export const activateSubscription = createAsyncThunk(
'settings/activateSubscription',
async (payload: ActivateSubscriptionPayload, { dispatch }) => {
const res = await fetch(`${API_BASE}/subscription/activate`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
});
if (!res.ok) throw new Error((await res.text()) || 'Activation failed');
// Pull the fresh settings so UI reflects connection_mode + plan.
await dispatch(fetchSettings());
return (await res.json()) as { ok: boolean; plan: string };
}
);
// POST /api/subscription/disconnect — clears bearer + reverts to own_key.
// Doesn't cancel the Stripe subscription (that's the Portal).
export const disconnectSubscription = createAsyncThunk(
'settings/disconnectSubscription',
async (_: void, { dispatch }) => {
const res = await fetch(`${API_BASE}/subscription/disconnect`, { method: 'POST' });
if (!res.ok) throw new Error('Disconnect failed');
await dispatch(fetchSettings());
return true;
}
);
const settingsSlice = createSlice({
name: 'settings',
initialState,