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>
83 lines
2.7 KiB
TypeScript
83 lines
2.7 KiB
TypeScript
/**
|
|
* Tauri adapter for {@link DesktopServerGateway} (ticket #68). One of the only
|
|
* places that calls `invoke()`; features reach it exclusively through the port.
|
|
*
|
|
* Commands and payload keys are camelCase, matching the backend DTO convention.
|
|
* `save_server_exposure_settings` / `preview_server_exposure_settings` take the
|
|
* settings directly under a `settings` key (no `request` wrapper).
|
|
*
|
|
* **Polling note.** The backend exposes no status *event* — `embedded_server.rs`
|
|
* emits nothing — so `onStatusChanged` is implemented here by polling
|
|
* `embedded_server_status`. That keeps the port shape push-like (and swappable
|
|
* for a real event later) while the polling schedule stays an adapter detail.
|
|
*/
|
|
|
|
import { invoke } from "@tauri-apps/api/core";
|
|
|
|
import type {
|
|
EmbeddedServerStatus,
|
|
ServerExposurePreview,
|
|
ServerExposureSettings,
|
|
Unsubscribe,
|
|
} from "@/domain";
|
|
import type { DesktopServerGateway } from "@/ports";
|
|
|
|
/** Status poll interval (ms). Cheap, in-process command; transitions are coarse. */
|
|
const STATUS_POLL_MS = 2000;
|
|
|
|
export class TauriDesktopServerGateway implements DesktopServerGateway {
|
|
constructor(private readonly pollMs: number = STATUS_POLL_MS) {}
|
|
|
|
getExposureSettings(): Promise<ServerExposureSettings> {
|
|
return invoke<ServerExposureSettings>("get_server_exposure_settings");
|
|
}
|
|
|
|
async saveExposureSettings(settings: ServerExposureSettings): Promise<void> {
|
|
await invoke("save_server_exposure_settings", { settings });
|
|
}
|
|
|
|
previewExposure(
|
|
settings: ServerExposureSettings,
|
|
): Promise<ServerExposurePreview> {
|
|
return invoke<ServerExposurePreview>("preview_server_exposure_settings", {
|
|
settings,
|
|
});
|
|
}
|
|
|
|
status(): Promise<EmbeddedServerStatus> {
|
|
return invoke<EmbeddedServerStatus>("embedded_server_status");
|
|
}
|
|
|
|
start(): Promise<EmbeddedServerStatus> {
|
|
return invoke<EmbeddedServerStatus>("embedded_server_start");
|
|
}
|
|
|
|
stop(): Promise<EmbeddedServerStatus> {
|
|
return invoke<EmbeddedServerStatus>("embedded_server_stop");
|
|
}
|
|
|
|
async onStatusChanged(
|
|
handler: (status: EmbeddedServerStatus) => void,
|
|
): Promise<Unsubscribe> {
|
|
let stopped = false;
|
|
const timer = setInterval(() => {
|
|
void this.status().then(
|
|
(status) => {
|
|
// The unsubscribe may land while a poll is in flight; drop late results
|
|
// so a torn-down consumer never gets called.
|
|
if (!stopped) handler(status);
|
|
},
|
|
() => {
|
|
// A failed poll is not a status: swallow it and let the next tick try
|
|
// again, rather than tearing the subscription down.
|
|
},
|
|
);
|
|
}, this.pollMs);
|
|
|
|
return () => {
|
|
stopped = true;
|
|
clearInterval(timer);
|
|
};
|
|
}
|
|
}
|