142 lines
6.8 KiB
Markdown
142 lines
6.8 KiB
Markdown
# Settings Menu — Design
|
|
|
|
**Date:** 2026-06-28
|
|
**Status:** Approved (design), pending implementation plan
|
|
**Layers:** frontend, backend (harness involved only as data source)
|
|
|
|
## Problem
|
|
|
|
When creating a new question, the `NewSessionDialog` form currently asks the user
|
|
for four parameters inline: **workspace**, **model** (modello), **provider**, and
|
|
**thinking**. Model/provider are free-text, thinking is free-text, and these choices
|
|
are re-entered on every new question.
|
|
|
|
These four parameters should instead be **global settings**, edited from a dedicated
|
|
**"Settings"** menu item placed under "Nuova domanda" in the sidebar. The "Nuova
|
|
domanda" form is reduced to just the question text and uses the current settings.
|
|
|
|
The settings must be populated from real sources:
|
|
|
|
1. **Workspace** — from the workspace YAML files where workspaces are defined.
|
|
2. **Model & provider** — chosen among those actually available to Pi, via a real
|
|
query to Pi (not free text).
|
|
3. **Thinking** — fixed list: `low`, `medium`, `high`.
|
|
|
|
## Decisions (from brainstorming)
|
|
|
|
- **Settings scope:** GLOBAL. The "Nuova domanda" form contains only the question
|
|
text and uses the current settings. There is no per-question override.
|
|
- **Persistence:** a JSON file on the backend (survives restarts, backend can apply
|
|
it as defaults).
|
|
- **Workspace display name:** filename-derived (no new YAML field). YAGNI.
|
|
- **Settings file location:** `backend/data/settings.json`, path overridable via the
|
|
`SETTINGS_FILE` env var, gitignored.
|
|
|
|
## Sources of truth
|
|
|
|
| Setting | Source |
|
|
|-----------|------------------------------------------------------------------------|
|
|
| Workspace | `harness/workspaces/*.yaml` (already exposed via `GET /workspaces`) |
|
|
| Provider | Derived from Pi's available models (group by `model.provider`) |
|
|
| Model | Pi RPC `get_available_models` → `{ models: [{ provider, id, name, reasoning }] }` |
|
|
| Thinking | Fixed list `low` / `medium` / `high` |
|
|
|
|
### Pi model query — confirmed facts
|
|
|
|
`pi --mode rpc` accepts the stdin command `get_available_models` and replies:
|
|
|
|
```json
|
|
{ "type": "response", "id": "...", "command": "get_available_models",
|
|
"models": [ { "provider": "anthropic", "id": "claude-...", "name": "…", "reasoning": true, ... }, … ] }
|
|
```
|
|
|
|
`modelRegistry.getAvailable()` returns **only models with auth configured** (an API
|
|
key present in the backend's environment), so every returned model is actually
|
|
usable. `provider` is a field on each model; the provider list is derived by grouping.
|
|
Pi supports thinking levels `off, minimal, low, medium, high, xhigh`; we expose only
|
|
`low/medium/high`.
|
|
|
|
## Harness layer
|
|
|
|
No code change required. The harness remains:
|
|
- the **source** of workspace YAML files (read by the backend via `GET /workspaces`),
|
|
- the tool whose `tht session new` already accepts `--provider/--model/--thinking`,
|
|
- the wrapper around Pi, which answers `get_available_models`.
|
|
|
|
## Backend layer (Fastify)
|
|
|
|
### Settings store
|
|
- New module persisting `{ workspace, provider, model, thinking }` to a JSON file
|
|
(default `backend/data/settings.json`, overridable via `SETTINGS_FILE`).
|
|
- On read, if the file is absent, return effective defaults: env
|
|
`PI_PROVIDER/PI_MODEL/PI_THINKING` plus the first workspace from `GET /workspaces`.
|
|
|
|
### Endpoints
|
|
- `GET /settings` → current effective settings `{ workspace, provider, model, thinking }`.
|
|
- `PUT /settings` → persist new settings. Validates `model` against the available
|
|
models from Pi; rejects unknown models with a clear error. (If Pi returns no models —
|
|
unavailable — validation is skipped so settings can still be saved.)
|
|
- `GET /models` → completes the existing stub: ephemeral Pi spawn + `get_available_models`,
|
|
returns `{ models: [{ provider, id, name, reasoning }] }`. Result is cached in memory
|
|
(Pi startup is slow); graceful fallback to `{ models: [] }` when Pi is unavailable.
|
|
- `GET /workspaces` → unchanged.
|
|
|
|
### Ephemeral model listing
|
|
- Spawn `pi --mode rpc` with the same cwd/env as session spawns (so API keys resolve),
|
|
send `get_available_models`, read the response, kill the child.
|
|
- Cache the result in memory for the process lifetime; expose an implicit refresh by
|
|
letting the cache be re-queried (a query param or a short TTL — implementation detail
|
|
for the plan). Keep the existing injectable seam (`listModels` dep) so tests stub it.
|
|
|
|
### `POST /sessions`
|
|
- No longer requires `workspace/model/provider/thinking` from the frontend.
|
|
- Reads the settings file and applies them:
|
|
- `workspace` → selects the `-c <config>` for `tht session new`,
|
|
- `provider`/`model` → `set_model`,
|
|
- `thinking` → `set_thinking_level`.
|
|
- Request body is reduced to `{ question }` (plus optional `name`). Any settings-like
|
|
fields in the body are ignored (no per-question override).
|
|
|
|
## Frontend layer (React + base-ui + Tailwind + React Query)
|
|
|
|
### NewSessionDialog
|
|
- Remove the workspace / model / thinking / provider fields.
|
|
- Keep only the question textarea. Submit sends `{ question }` to `POST /sessions`.
|
|
|
|
### Settings menu + dialog
|
|
- Add a **"Settings"** button in the `AppShell` sidebar, directly under
|
|
"Nuova domanda", styled as a secondary (`variant="outline"`) button, matching the
|
|
existing button conventions.
|
|
- New `SettingsDialog` (same Dialog/base-ui pattern as `NewSessionDialog`) with four
|
|
controls:
|
|
- **Workspace** — `<select>` from `GET /workspaces`.
|
|
- **Provider** — `<select>` derived from the available models; changing it filters
|
|
the model list.
|
|
- **Model** — `<select>` filtered by the chosen provider (from `GET /models`).
|
|
- **Thinking** — `<select>` fixed `low/medium/high`.
|
|
- Loads current values from `GET /settings`; saves with `PUT /settings`.
|
|
- **Degradation:** if `GET /models` returns an empty list (Pi unavailable / no keys),
|
|
show a notice and fall back to free-text inputs for provider and model so settings
|
|
remain editable.
|
|
|
|
### API client
|
|
- `getSettings()` / `putSettings(s)` for `/settings`.
|
|
- `listModels()` updated to the `{ models: [{provider, id, name, reasoning}] }` shape.
|
|
- `createSession` simplified to send only `{ question }` (+ optional `name`).
|
|
|
|
## Error handling / edge cases
|
|
|
|
- **Pi unreachable or zero models:** `/models` → `[]`; `SettingsDialog` warns and
|
|
degrades to text inputs; nothing blocks.
|
|
- **Stale settings (model no longer available):** `PUT /settings` rejects unknown
|
|
models when a model list is available; at session-creation time stored values are
|
|
passed through to Pi as-is (Pi reports if invalid).
|
|
- **First run (no settings file):** `GET /settings` returns env/first-workspace
|
|
defaults; the dialog is usable immediately.
|
|
|
|
## Out of scope
|
|
|
|
- No new field in the workspace YAML schema.
|
|
- No per-question override of settings.
|
|
- No multi-user / per-user settings (single shared settings file).
|