feat(studio): in-place System AI editor (v0.12.0)

Replaces the read-only System AI block in Settings with a real
editor. Operator never has to touch ~/.fai/config.yaml again
to change provider, endpoint, model, API-key env-var, or
privacy mode.

UX choices that align with the zero-learning-curve rule:

  * Provider dropdown lists Ollama / OpenAI / LM Studio / vLLM
    / Custom by friendly name. Each selection auto-fills the
    endpoint and api_key_env defaults — but only when the
    field is still empty or matches a different preset's
    default, so manual overrides are never clobbered.

  * Each provider has a one-line description rendered under
    the dropdown ("Ollama → Local `ollama serve`. Models stay
    on your machine. No API key needed.") and a model-hint
    placeholder ("gemma3:4b · llama3.2:3b · qwen2.5-coder:7b")
    that goes away when a model is typed.

  * Privacy mode is a vertical radio group with each option's
    description in place — no doc-lookup needed.

  * "Test connection" sends an `ok`-ping and renders the
    same error-kind → fix-hint mapping the audit drill-down
    uses.

  * "Save" persists + hot-reloads the hub. No daemon restart
    required. UI status badge flips to "enabled · <privacy>"
    immediately.

`fai_dart_sdk` 0.5.0 carries the underlying `updateSystemAi` /
`testSystemAi` RPCs.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
This commit is contained in:
flemming-it 2026-05-07 13:41:11 +02:00
parent 4c4e2bb1eb
commit 15ea403c86
6 changed files with 601 additions and 5 deletions

View file

@ -135,6 +135,46 @@ class HubService {
);
}
/// Persist a new System-AI configuration. The hub writes back
/// to `~/.fai/config.yaml` and hot-reloads the in-memory copy
/// in one round-trip; the caller gets the resulting status
/// for an immediate UI refresh.
Future<SystemAiStatus> updateSystemAi({
required String provider,
required String endpoint,
required String model,
required String apiKeyEnv,
required String privacyMode,
}) async {
final r = await _client.updateSystemAi(
provider: provider,
endpoint: endpoint,
model: model,
apiKeyEnv: apiKeyEnv,
privacyMode: privacyMode,
);
return SystemAiStatus(
enabled: r.enabled,
provider: r.provider,
endpoint: r.endpoint,
model: r.model,
privacyMode: r.privacyMode,
apiKeyEnv: r.apiKeyEnv,
);
}
/// Probe the configured provider with a tiny ping. Same
/// error-kind taxonomy as [askAi]; "Test connection" buttons
/// use this to surface "is it working?".
Future<AskAiResult> testSystemAi() async {
final r = await _client.testSystemAi();
return AskAiResult(
errorKind: r.errorKind,
text: r.text,
latencyMs: r.latencyMs,
);
}
/// Active channel + per-channel daemon status snapshot.
Future<ChannelStatusSnapshot> channelStatus() async {
final r = await _client.channelStatus();