diff --git a/apps/x/apps/renderer/public/logo-only.png b/apps/x/apps/renderer/public/logo-only.png new file mode 100644 index 00000000..e2fd6386 Binary files /dev/null and b/apps/x/apps/renderer/public/logo-only.png differ diff --git a/apps/x/apps/renderer/src/components/onboarding-modal.tsx b/apps/x/apps/renderer/src/components/onboarding-modal.tsx index 2aac98d7..ad0a7da5 100644 --- a/apps/x/apps/renderer/src/components/onboarding-modal.tsx +++ b/apps/x/apps/renderer/src/components/onboarding-modal.tsx @@ -2,7 +2,7 @@ import * as React from "react" import { useState, useEffect, useCallback } from "react" -import { Loader2, Mic, Mail, CheckCircle2, Sailboat, MessageSquare } from "lucide-react" +import { Loader2, Mic, Mail, CheckCircle2, MessageSquare } from "lucide-react" import { Dialog, @@ -38,7 +38,7 @@ interface OnboardingModalProps { onComplete: () => void } -type Step = 0 | 1 | 2 | 3 +type Step = 0 | 1 | 2 type LlmProviderFlavor = "openai" | "anthropic" | "google" | "openrouter" | "aigateway" | "ollama" | "openai-compatible" @@ -68,8 +68,6 @@ export function OnboardingModal({ open, onComplete }: OnboardingModalProps) { const [testState, setTestState] = useState<{ status: "idle" | "testing" | "success" | "error"; error?: string }>({ status: "idle", }) - const [savingLlmConfig, setSavingLlmConfig] = useState(false) - // OAuth provider states const [providers, setProviders] = useState([]) const [providersLoading, setProvidersLoading] = useState(true) @@ -268,7 +266,7 @@ export function OnboardingModal({ open, onComplete }: OnboardingModalProps) { }, [startSlackConnect]) const handleNext = () => { - if (currentStep < 3) { + if (currentStep < 2) { setCurrentStep((prev) => (prev + 1) as Step) } } @@ -277,24 +275,27 @@ export function OnboardingModal({ open, onComplete }: OnboardingModalProps) { onComplete() } - const handleTestConnection = useCallback(async () => { + const handleTestAndSaveLlmConfig = useCallback(async () => { if (!canTest) return setTestState({ status: "testing" }) try { const apiKey = activeConfig.apiKey.trim() || undefined const baseURL = activeConfig.baseURL.trim() || undefined const model = activeConfig.model.trim() - const result = await window.ipc.invoke("models:test", { + const providerConfig = { provider: { flavor: llmProvider, apiKey, baseURL, }, model, - }) + } + const result = await window.ipc.invoke("models:test", providerConfig) if (result.success) { setTestState({ status: "success" }) - toast.success("Connection successful") + // Save and continue + await window.ipc.invoke("models:saveConfig", providerConfig) + handleNext() } else { setTestState({ status: "error", error: result.error }) toast.error(result.error || "Connection test failed") @@ -304,31 +305,7 @@ export function OnboardingModal({ open, onComplete }: OnboardingModalProps) { setTestState({ status: "error", error: "Connection test failed" }) toast.error("Connection test failed") } - }, [activeConfig.apiKey, activeConfig.baseURL, activeConfig.model, canTest, llmProvider]) - - const handleSaveLlmConfig = useCallback(async () => { - if (testState.status !== "success") return - setSavingLlmConfig(true) - try { - const apiKey = activeConfig.apiKey.trim() || undefined - const baseURL = activeConfig.baseURL.trim() || undefined - const model = activeConfig.model.trim() - await window.ipc.invoke("models:saveConfig", { - provider: { - flavor: llmProvider, - apiKey, - baseURL, - }, - model, - }) - setSavingLlmConfig(false) - handleNext() - } catch (error) { - console.error("Failed to save LLM config:", error) - toast.error("Failed to save LLM settings") - setSavingLlmConfig(false) - } - }, [activeConfig.apiKey, activeConfig.baseURL, activeConfig.model, handleNext, llmProvider, testState.status]) + }, [activeConfig.apiKey, activeConfig.baseURL, activeConfig.model, canTest, llmProvider, handleNext]) // Check connection status for all providers const refreshAllStatuses = useCallback(async () => { @@ -468,7 +445,7 @@ export function OnboardingModal({ open, onComplete }: OnboardingModalProps) { // Step indicator component const StepIndicator = () => (
- {[0, 1, 2, 3].map((step) => ( + {[0, 1, 2].map((step) => (
) - // Step 0: Welcome - const WelcomeStep = () => ( -
-
- -
- - Your AI coworker, with memory - - Rowboat connects to your email, calendar, and meetings to help you stay on top of your work. - - -
-
-
1
-

Syncs with your email, calendar, and meetings

-
-
-
2
-

Remembers the people and context from your conversations

-
-
-
3
-

Helps you follow up and never miss what matters

-
-
- -
- ) - - // Step 1: LLM Setup + // Step 0: LLM Setup const LlmSetupStep = () => { - const providerOptions: Array<{ id: LlmProviderFlavor; name: string; description: string }> = [ + const [showMoreProviders, setShowMoreProviders] = useState(false) + + const primaryProviders: Array<{ id: LlmProviderFlavor; name: string; description: string }> = [ { id: "openai", name: "OpenAI", description: "Use your OpenAI API key" }, { id: "anthropic", name: "Anthropic", description: "Use your Anthropic API key" }, - { id: "google", name: "Google", description: "Use your Google AI Studio key" }, + { id: "google", name: "Gemini", description: "Use your Google AI Studio key" }, + { id: "ollama", name: "Ollama (Local)", description: "Run a local model via Ollama" }, + ] + + const moreProviders: Array<{ id: LlmProviderFlavor; name: string; description: string }> = [ { id: "openrouter", name: "OpenRouter", description: "Access multiple models with one key" }, { id: "aigateway", name: "AI Gateway (Vercel)", description: "Use Vercel's AI Gateway" }, - { id: "ollama", name: "Ollama (Local)", description: "Run a local model via Ollama" }, { id: "openai-compatible", name: "OpenAI-Compatible", description: "Local or hosted OpenAI-compatible API" }, ] + const isMoreProvider = moreProviders.some(p => p.id === llmProvider) + const modelsForProvider = modelsCatalog[llmProvider] || [] const showModelInput = isLocalProvider || modelsForProvider.length === 0 + const renderProviderCard = (provider: { id: LlmProviderFlavor; name: string; description: string }) => ( + + ) + return (
- +
+ Rowboat + Your AI coworker, with memory +
+ Choose your model - - Select your provider and model to power Rowboat’s AI. - -
+
Provider
- {providerOptions.map((provider) => ( - - ))} + {primaryProviders.map(renderProviderCard)}
+ {(showMoreProviders || isMoreProvider) ? ( +
+ {moreProviders.map(renderProviderCard)} +
+ ) : ( + + )}
@@ -750,48 +717,36 @@ export function OnboardingModal({ open, onComplete }: OnboardingModalProps) { )}
-
+ {testState.status === "error" && ( +
+ {testState.error || "Connection test failed"} +
+ )} + +
- {testState.status === "success" && ( - Connected - )} - {testState.status === "error" && ( - - {testState.error || "Test failed"} - - )} -
- -
-
) } - // Step 2: Connect Accounts + // Step 1: Connect Accounts const AccountConnectionStep = () => (
Connect Your Accounts - Connect your accounts to start syncing your data. You can always add more later. + Connect your accounts to start syncing your data locally. You can always add more later. @@ -821,13 +776,6 @@ export function OnboardingModal({ open, onComplete }: OnboardingModalProps) { {providers.includes('fireflies-ai') && renderOAuthProvider('fireflies-ai', 'Fireflies', , 'AI meeting transcripts')}
- {/* Team Communication Section */} -
-
- Team Communication -
- {renderSlackRow()} -
)}
@@ -843,7 +791,7 @@ export function OnboardingModal({ open, onComplete }: OnboardingModalProps) {
) - // Step 3: Completion + // Step 2: Completion const CompletionStep = () => { const hasConnections = connectedProviders.length > 0 || granolaEnabled || slackConnected @@ -856,7 +804,7 @@ export function OnboardingModal({ open, onComplete }: OnboardingModalProps) { You're All Set! {hasConnections ? ( - <>Your workspace will populate over the next ~30 minutes as we sync your data. + <>Give me 30 minutes to build your context graph.
I can still help with other things on your computer. ) : ( <>You can connect your accounts anytime from the sidebar to start syncing data. )} @@ -926,10 +874,9 @@ export function OnboardingModal({ open, onComplete }: OnboardingModalProps) { onEscapeKeyDown={(e) => e.preventDefault()} > - {currentStep === 0 && } - {currentStep === 1 && } - {currentStep === 2 && } - {currentStep === 3 && } + {currentStep === 0 && } + {currentStep === 1 && } + {currentStep === 2 && }