import { Navigate, createFileRoute } from "@tanstack/react-router" import { useEffect, useRef, useState } from "react" import type { ModelOption } from "@/lib/api" import { AppShell, SettingsRow, SettingsSection } from "@/components/AppShell" import { Button } from "@/components/ui/button" import { Combobox, ComboboxContent, ComboboxEmpty, ComboboxInput, ComboboxItem, ComboboxList, } from "@/components/ui/combobox" import { Input } from "@/components/ui/input" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select" import { Skeleton } from "@/components/ui/skeleton" import { Switch } from "@/components/ui/switch" import { buildProfileUpdate, useOptions, useProfile, useRepos, useSaveProfile, } from "@/lib/profile" import { useSession } from "@/lib/session" export const Route = createFileRoute("/cloud-agents")({ component: CloudAgentsPage, }) function CloudAgentsPage() { const session = useSession() const profile = useProfile() const options = useOptions() const repos = useRepos() const save = useSaveProfile() const [modelId, setModelId] = useState("") const [effort, setEffort] = useState("") const [defaultRepo, setDefaultRepo] = useState("") const [baseBranch, setBaseBranch] = useState("") const [branchPrefix, setBranchPrefix] = useState("") const [error, setError] = useState(null) const initialized = useRef(false) const firstModel: ModelOption | undefined = options.data?.models[0] const currentModel: ModelOption | undefined = options.data?.models.find((m) => m.id === modelId) ?? firstModel useEffect(() => { if (!profile.data || initialized.current) return // For users with no saved profile, wait until the options API has loaded // so the model/effort selects can initialise to the first available option. const hasModel = !!profile.data.default_model || !!firstModel if (!hasModel) return initialized.current = true setModelId(profile.data.default_model ?? firstModel?.id ?? "") setEffort(profile.data.reasoning_effort ?? firstModel?.default_effort ?? "") setDefaultRepo(profile.data.default_repo ?? "") setBaseBranch(profile.data.base_branch ?? "") setBranchPrefix(profile.data.branch_prefix ?? "") }, [profile.data, firstModel?.id, firstModel?.default_effort, firstModel]) useEffect(() => { if (currentModel && !currentModel.efforts.includes(effort)) { setEffort(currentModel.default_effort) } }, [currentModel, effort]) if (session.isLoading) { return (
) } if (!session.data) return const fallbackModel = firstModel?.id ?? "" const fallbackEffort = firstModel?.default_effort ?? "" const persist = (patch: Parameters[1]) => { setError(null) save .mutateAsync( buildProfileUpdate(profile.data, patch, fallbackModel, fallbackEffort) ) .catch((e: Error) => setError(e.message)) } const persistDefaults = () => { persist({ default_model: modelId, reasoning_effort: effort, default_repo: defaultRepo || null, base_branch: baseBranch || null, branch_prefix: branchPrefix || null, }) } return (
v && setModelId(v)}> {options.data?.models.map((m) => ( {m.label} ))} } /> v && setEffort(v)}> {currentModel?.efforts.map((e) => ( {e} ))} } /> {repos.data && repos.data.repositories.length > 0 ? ( r.full_name)} value={defaultRepo} onValueChange={(v) => setDefaultRepo(typeof v === "string" ? v : "") } > No matches {repos.data.repositories.map((r) => ( {r.full_name} ))} ) : ( setDefaultRepo(e.target.value)} /> )}
} /> setBaseBranch(e.target.value)} /> } /> setBranchPrefix(e.target.value)} /> } />
persist({ auto_fix_ci: v })} disabled /> } /> persist({ create_prs: v })} /> } />
{error &&

{error}

}
) }