Donne à l'utilisateur la surface pour activer le serveur depuis l'app. - `features/settings/` : `SettingsView`, `DeploymentSettings`, `useDeployment`. - `adapters/desktopServer.ts` + port `DesktopServerGateway` et DTO associés ; adapters mock et http/unsupported alignés (le mode web n'expose pas le contrôle du serveur qui l'héberge). - `ProjectsView` : le `showSettings: boolean` devient une navigation interne `AI Profiles` / `Deployment`. Le libellé alternant « Close AI Profiles » disparaît — Settings existait déjà dans cette vue, la surface évolue au lieu d'ajouter un `PanelId`. CORRECTION D'UN BRIEF FAUX, remontée spontanément par DevFrontend et qui mérite de survivre : le cadrage décrivait le mode `remoteProxyOtherMachine` avec deux champs. `validate_settings` en exige un troisième, `lanBindAddress`, et rejette loopback comme unspecified. Construit selon la spec, chaque save et chaque start en mode 3 aurait échoué — le lot serait parti vert et cassé. Le panneau expose donc un select alimenté par `candidateLanAddresses` fourni par le backend : la règle « le frontend n'invente jamais une IP » tient. QA ré-exécutée par Git avant merge : `npm run typecheck` exit 0 · `npx vitest run` 87 files / 789 passed, 0 échec. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
86 lines
2.9 KiB
TypeScript
86 lines
2.9 KiB
TypeScript
/**
|
|
* `SettingsView` — the Settings surface shell (ticket #68).
|
|
*
|
|
* Settings is a **main surface**, not a dockable project view: it deliberately
|
|
* has no `PanelId` and stays outside the `viewPlacement` model. It takes over
|
|
* the main area while the menu bar stays visible above it.
|
|
*
|
|
* Ticket #68 gives it a second section, so the section list becomes real
|
|
* navigation (a left column) instead of the old single toggle. That also kills
|
|
* the alternating "Close AI Profiles" menu label, which never scaled past one
|
|
* entry: the menu now names sections, the active one is marked, and closing is
|
|
* an explicit action inside the view.
|
|
*
|
|
* `EmbedderSettings` / `ModelServersPanel` are **not** pulled in here — that is
|
|
* a separate lateral rework. The section list is the seam they would slot into.
|
|
*/
|
|
|
|
import { Button, cn } from "@/shared";
|
|
import { ProfilesSettings } from "@/features/first-run";
|
|
import { DeploymentSettings } from "./DeploymentSettings";
|
|
|
|
/** The Settings sections, in menu/nav order. */
|
|
export type SettingsSection = "aiProfiles" | "deployment";
|
|
|
|
/** Human labels, shared by the nav column and the `Settings` menu. */
|
|
export const SETTINGS_SECTION_LABEL: Record<SettingsSection, string> = {
|
|
aiProfiles: "AI Profiles",
|
|
deployment: "Deployment",
|
|
};
|
|
|
|
/** Section order — the single source of truth for both nav and menu. */
|
|
export const SETTINGS_SECTIONS: SettingsSection[] = ["aiProfiles", "deployment"];
|
|
|
|
interface SettingsViewProps {
|
|
section: SettingsSection;
|
|
onSectionChange: (section: SettingsSection) => void;
|
|
onClose: () => void;
|
|
}
|
|
|
|
export function SettingsView({
|
|
section,
|
|
onSectionChange,
|
|
onClose,
|
|
}: SettingsViewProps) {
|
|
return (
|
|
<div className="flex min-h-0 flex-1 overflow-hidden">
|
|
<nav
|
|
aria-label="settings sections"
|
|
className="flex w-52 shrink-0 flex-col gap-1 border-r border-border p-3"
|
|
>
|
|
{SETTINGS_SECTIONS.map((id) => {
|
|
const active = id === section;
|
|
return (
|
|
<button
|
|
key={id}
|
|
type="button"
|
|
aria-current={active ? "page" : undefined}
|
|
onClick={() => onSectionChange(id)}
|
|
className={cn(
|
|
"rounded-md px-3 py-2 text-left text-sm transition-colors",
|
|
active
|
|
? "bg-raised font-semibold text-content"
|
|
: "text-muted hover:bg-raised hover:text-content",
|
|
)}
|
|
>
|
|
{SETTINGS_SECTION_LABEL[id]}
|
|
</button>
|
|
);
|
|
})}
|
|
|
|
<div className="mt-auto pt-2">
|
|
<Button size="sm" variant="ghost" className="w-full" onClick={onClose}>
|
|
Close Settings
|
|
</Button>
|
|
</div>
|
|
</nav>
|
|
|
|
<div className="flex flex-1 justify-center overflow-y-auto p-6">
|
|
<div className="w-full max-w-2xl">
|
|
{section === "aiProfiles" ? <ProfilesSettings /> : <DeploymentSettings />}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|