diff --git a/frontend/src/features/first-run/FirstRunWizard.tsx b/frontend/src/features/first-run/FirstRunWizard.tsx index 0c95f8a..edfcb7b 100644 --- a/frontend/src/features/first-run/FirstRunWizard.tsx +++ b/frontend/src/features/first-run/FirstRunWizard.tsx @@ -15,85 +15,34 @@ * `./profile`. */ -import { useCallback, useEffect, useState } from "react"; - import type { AgentProfile, - GatewayError, HttpChatConfig, LocalModelServerConfig, - OpenCodeConfig, - OpenCodeProviderCatalogEntry, } from "@/domain"; -import { useGateways } from "@/app/di"; import { Button, IconButton, Input, Panel, Toolbar, cn } from "@/shared"; import { ModelServersPanel, - ModelServerSelect, useModelServers, } from "@/features/model-servers"; import { useFirstRun, type WizardEntry } from "./useFirstRun"; import { defaultHttpChatConfig, - defaultOpenCodeConfig, parseArgs, validateProfile, type ProfileErrors, } from "./profile"; +import { + OpenCodeModeFields, + useOpenCodeProviderCatalog, + type OpenCodeProviderCatalog, +} from "./OpenCodeModeFields"; /** A small caption above a control. */ function Caption({ children }: { children: React.ReactNode }) { return {children}; } -function describeError(e: unknown): string { - if (e && typeof e === "object" && "message" in e) { - return String((e as GatewayError).message); - } - return String(e); -} - -/** View-model for the OpenCode cloud-provider catalogue (ticket #92). */ -interface OpenCodeProviderCatalog { - providers: OpenCodeProviderCatalogEntry[] | null; - loading: boolean; - error: string | null; - reload: () => void; -} - -/** - * Loads the static OpenCode cloud-provider catalogue once for the whole - * wizard (every Cloud row shares it), so the provider/model pickers can be - * populated. Exposes a `reload` for the blocking "Réessayer" state. - */ -function useOpenCodeProviderCatalog(): OpenCodeProviderCatalog { - const { profile } = useGateways(); - const [providers, setProviders] = useState( - null, - ); - const [loading, setLoading] = useState(false); - const [error, setError] = useState(null); - - const load = useCallback(async () => { - setLoading(true); - setError(null); - try { - setProviders(await profile.listOpenCodeProviders()); - } catch (e) { - setProviders(null); - setError(describeError(e)); - } finally { - setLoading(false); - } - }, [profile]); - - useEffect(() => { - void load(); - }, [load]); - - return { providers, loading, error, reload: () => void load() }; -} - /** * Renders the wizard when it is the first run. Calls `onDone` once the user * finishes (so the host can drop the wizard and show the normal UI). Returns @@ -339,467 +288,6 @@ function ProfileRow({ ); } -/** - * Segmented control (F — ticket #92) choosing whether an OpenCode profile runs - * against the local `llama.cpp` endpoint or a cloud provider from the OpenCode - * registry, and renders the matching sub-form. The two sub-forms never overlap; - * switching segments keeps the inactive one's draft in memory (component-local - * state) without touching `profile` until it is actually submitted. - */ -function OpenCodeModeFields({ - profile, - errors, - servers, - providerCatalog, - onChange, -}: { - profile: AgentProfile; - errors: ProfileErrors; - servers: LocalModelServerConfig[]; - providerCatalog: OpenCodeProviderCatalog; - onChange: (p: AgentProfile) => void; -}) { - const [mode, setMode] = useState<"local" | "cloud">( - profile.opencodeProvider ? "cloud" : "local", - ); - - return ( -
-
- {( - [ - { id: "local", label: "Local (llama.cpp)" }, - { id: "cloud", label: "Provider cloud" }, - ] as const - ).map((seg) => { - const active = mode === seg.id; - return ( - - ); - })} -
- - {mode === "local" && ( - - )} - - {mode === "cloud" && ( - - )} -
- ); -} - -/** Field-keyed validation errors for the Cloud sub-form, surfaced on submit. */ -interface CloudFieldErrors { - providerId?: string; - model?: string; - apiKey?: string; - npm?: string; - baseUrl?: string; -} - -/** Sentinel `