import { useEffect, useState, useSyncExternalStore } from "react"; import { Button, Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, Field, FieldLabel, Input, } from "@ace/ui"; import type { ProviderSettings, Settings as HostSettings } from "@ace/host/protocol"; import { host } from "./host"; import { desktop } from "./desktop"; import { HostStatus } from "./host-settings"; import { UpdateSettings } from "./updates"; const sources = { keychain: "Saved in Keychain", environment: "Using an environment key", missing: "No key added", unavailable: "Keychain access needed", }; function Provider({ provider, disabled }: { provider: ProviderSettings; disabled: boolean }) { const [value, setValue] = useState(""); const [confirm, setConfirm] = useState(false); const [action, setAction] = useState<{ busy?: boolean; error?: string; message?: string }>({}); const id = `key-${provider.id}`; async function run(op: "key-set" | "key-check" | "key-remove") { setAction({ busy: true }); try { await host.request( op === "key-set" ? { op, provider: provider.id, value } : { op, provider: provider.id }, ); if (op === "key-set") setValue(""); setConfirm(false); setAction({ message: op === "key-check" ? "Connection verified." : op === "key-remove" ? "Saved key removed." : "Key checked and saved.", }); } catch (error) { setAction({ error: (error as Error).message }); } } return (
{ event.preventDefault(); void run("key-set"); }} >

{provider.name}

{sources[provider.source]}
{provider.override && (

{provider.override}{" "} takes priority over a saved key. Remove that environment override and restart Ace Helper to use Keychain.

)} {provider.error &&

{provider.error}

} {provider.stored ? "Replace API key" : "API key"}
setValue(event.target.value)} />
{(provider.source === "keychain" || provider.source === "environment") && ( )} {provider.stored && !confirm && ( )}
{confirm && (

Remove this saved key? New model requests will need another credential.

)} {action.busy &&

Checking with {provider.name} or waiting for Keychain…

} {action.error &&

{action.error}

} {action.message &&

{action.message}

}
); } function DefaultModel({ settings, disabled }: { settings: HostSettings; disabled: boolean }) { const [action, setAction] = useState<{ busy?: boolean; error?: string }>({}); const chosen = settings.modelOverride || settings.model; const value = chosen ? `${chosen.provider}/${chosen.modelId}` : ""; const available = settings.models.map((model) => `${model.provider}/${model.modelId}`); async function save(value: string) { const split = value.indexOf("/"); const model = value ? { provider: value.slice(0, split), modelId: value.slice(split + 1) } : null; setAction({ busy: true }); try { await host.request({ op: "preferences", model }); setAction({}); } catch (error) { setAction({ error: (error as Error).message }); } } return ( Default model

{settings.modelOverride ? "ACE_MODEL overrides this preference. Remove it and restart Ace Helper to use your saved choice." : "Used for a chat's first agent run. Existing chats keep their model."}

{action.busy &&

Saving default model…

} {action.error &&

{action.error}

}
); } export function Settings( { onClose, initialSection }: { onClose: () => void; initialSection?: "updates" }, ) { const status = useSyncExternalStore(host.subscribe, () => host.status); const [section, setSection] = useState<"providers" | "host" | "updates">( initialSection || (status === "open" ? "providers" : "host"), ); const version = useSyncExternalStore(host.subscribe, () => host.settingsVersion); const [retry, setRetry] = useState(0); const [result, setResult] = useState<{ settings?: HostSettings; error?: string }>({}); useEffect(() => { const updates = () => setSection("updates"); window.addEventListener("ace:updates", updates); return () => window.removeEventListener("ace:updates", updates); }, []); useEffect(() => { if (status !== "open" || section !== "providers") return; let active = true; host.request({ op: "settings" }).then( (settings) => { if (active) setResult({ settings }); }, (error: Error) => { if (active) setResult({ error: error.message }); }, ); return () => { active = false; }; }, [status, version, retry, section]); return ( { if (!open) onClose(); }} > Settings {section === "providers" ? "Provider keys stay in your system keychain. Changes apply to the next model request." : section === "updates" ? "Keep Ace and Ace Helper up to date." : "Tools, team connectivity, and background hosting."} {section === "updates" ? : section === "host" ? : ( <> {status !== "open" &&

Waiting for Ace Helper to reconnect…

} {result.settings ? ( <> {result.settings.providers.map((provider) => ( ))} {result.settings.error && (

{result.settings.error}

)} ) : !result.error && status === "open" && (

Reading provider settings…

)} {result.error &&

{result.error}

}
)}
); }