import { Dialog } from "@base-ui/react/dialog" import { useQuery } from "@tanstack/react-query" import { useEffect, useState } from "react" import { IoLogoSlack } from "react-icons/io5" import type { ModelOption } from "@/lib/api" import { Button } from "@/components/ui/button" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select" import { api, slackConnectUrl } from "@/lib/api" import { buildProfileUpdate, useOptions, useProfile, useSaveProfile, } from "@/lib/profile" import { useSession } from "@/lib/session" /** * First-run onboarding modal: pick a default agent model, then connect Slack. * * The model step shows until the user has saved a default model; the Slack step * shows (where Sign in with Slack is enabled) until their Slack account is * linked. Both steps live in the same dialog so a new user is walked through * picking a model and connecting Slack in one place. Dismissing hides it for * the session; an incomplete step reappears on the next login. */ export function OnboardingDialog() { const session = useSession() const mapping = useQuery({ queryKey: ["myMapping"], queryFn: api.myMapping }) const profile = useProfile() const options = useOptions() const save = useSaveProfile() const [dismissed, setDismissed] = useState(false) const [error, setError] = useState(null) const firstModel: ModelOption | undefined = options.data?.models[0] const defaultModel = options.data?.default_agent_model ?? firstModel?.id ?? "" const defaultEffort = options.data?.default_agent_reasoning_effort ?? firstModel?.default_effort ?? "" const [modelId, setModelId] = useState("") const [effort, setEffort] = useState("") useEffect(() => { if (!modelId && defaultModel) setModelId(defaultModel) }, [modelId, defaultModel]) const currentModel: ModelOption | undefined = options.data?.models.find((m) => m.id === modelId) ?? firstModel useEffect(() => { if (!currentModel) return if (!effort || !currentModel.efforts.includes(effort)) { setEffort(currentModel.default_effort) } }, [currentModel, effort]) const slackEnabled = session.data?.slack_oauth_enabled ?? false const slackConnected = !!mapping.data?.slack_user_id const hasDefaultModel = !!profile.data?.default_model const needsModel = !profile.isLoading && profile.data !== undefined && !hasDefaultModel const needsSlack = slackEnabled && !slackConnected && !mapping.isLoading && !mapping.isError const open = !dismissed && (needsModel || needsSlack) const step: "model" | "slack" = needsModel ? "model" : "slack" const handleSaveModel = () => { if (!modelId) return setError(null) save .mutateAsync( buildProfileUpdate( profile.data, { default_model: modelId, reasoning_effort: effort }, defaultModel, defaultEffort ) ) .catch((e: Error) => setError(e.message)) } return ( { if (!next) setDismissed(true) }} > {step === "model" ? (
Choose your default model Pick the model Open SWE uses when you don't specify one. You can change this anytime in your settings.
Default model
Reasoning effort
{error &&

{error}

}
) : (
Connect your Slack account
Connect Slack so that when you tag Open SWE, it can resolve your GitHub account. We use the email Slack verifies, which also lets Linear mentions resolve to you.
)}
) }