feat(frontend): panneau Settings Deployment et gateway serveur desktop (#68 F1+F2)
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>
This commit is contained in:
85
frontend/src/features/settings/SettingsView.tsx
Normal file
85
frontend/src/features/settings/SettingsView.tsx
Normal file
@ -0,0 +1,85 @@
|
||||
/**
|
||||
* `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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user