docs/plans/2026-06-10-001-feat-llm-router-admin-form-plan.md
Replace the LLM Router admin page's raw request JSON editor with a provider-aware form that builds the existing POST /api/admin/config/router body for OpenRouter, Azure, DashScope cosyvoice, StepFun, unspeech, and default aliases. Keep preview/apply as the safety gate and preserve an advanced JSON view as an escape hatch, not the default editing path.
The current LLM Router admin page exposes the backend request body directly as JSON. That matches the server contract but makes routine operator work brittle: admins must remember each slice kind's required fields, know which URLs are HTTP or WebSocket roots, and avoid mistakes around merge/reset and default aliases while handling plaintext provider keys.
The backend already owns validation, encryption, merge semantics, and redacted previews. This plan keeps that backend boundary intact and improves the admin UI so the common configuration flows are discoverable, structured, and reviewable before apply.
Form Coverage
openrouter, azure, dashscope-cosyvoice, stepfun, and unspeech.merge versus reset mode as an explicit, visible choice before preview or apply.Safety And Review
AdminRouterConfigRequest shape the server validates today; the backend remains the source of truth for encryption and final validation.Operator Experience
AdminRouterConfigRequest and call adminApi.applyRouterConfig exactly as the JSON page does. This avoids duplicating encryption, merge, validation, and invalidation behavior in the browser.@proj-airi/ui primitives, DatalistField, global .panel/.badge styling, and Vue class arrays. Do not invent a separate mini design system for this one page.flowchart TB
FORM[Provider-aware form state] --> BUILD[Request builder module]
BUILD --> ERRORS[Client validation summary]
BUILD --> PAYLOAD[AdminRouterConfigRequest]
PAYLOAD --> PREVIEW[Preview submit with dryRun true]
PAYLOAD --> APPLY[Apply submit with dryRun false]
PREVIEW --> REDACTED[Server redacted preview panel]
APPLY --> REDACTED
FORM --> EXPORT[Advanced JSON export]
IMPORT[Advanced JSON import] --> BUILD
ADVANCED[Advanced JSON submit] --> PREVIEW
The form state is the primary editing model. The request builder is the single bridge from UI concepts to the server body. Preview and apply both use the same built payload so admins do not review one shape and apply another.
POST /api/admin/config/router, its Valibot schemas, encryption behavior, configKV writes, or Redis invalidation.apps/ui-admin is the admin surface; responsive behavior still needs narrow viewport verification.AdminRouterConfigRequest projection outside the Vue page.apps/ui-admin/src/modules/api.tsapps/ui-admin/src/modules/router-config-form.tsapps/ui-admin/src/modules/router-config-form.test.tsArray<Record<string, unknown>> for router slices with a discriminated union mirroring the admin route's existing slice kinds. Add form-facing state that can represent editable drafts, provider defaults, validation errors, and the compiled request payload. Keep plaintext keys out of previews and summaries.apps/server/src/routes/admin/config/router/index.ts for required fields and URL rules; apps/server/src/services/domain/admin/router-config/index.ts for default key entry ids and provider defaults; apps/ui-admin/src/pages/VoicePackFormPage.vue for form-state normalization.openrouter slice and defaults.chatModel.intl versus cn region and upstream model.streaming; unspeech streaming draft compiles WebSocket URL, key, models, and default model.apps/ui-admin/src/pages/LlmRouterPage.vueapps/ui-admin/src/components/llm-router/RouterSliceEditor.vueapps/ui-admin/src/components/llm-router/RouterDefaultsEditor.vueapps/ui-admin/src/components/llm-router/RouterModeControl.vueapps/ui-admin/src/pages/LlmRouterPage.test.tsapps/ui-admin/src/modules/router-config-form.test.tsapps/ui-admin/src/pages/FluxPage.vue for preview/apply action flow; apps/ui-admin/src/pages/VoicePackFormPage.vue for @proj-airi/ui fields, status badges, Callout, and grouped class arrays; docs/ai/context/ui-components.md for primitive props.apps/ui-admin/src/pages/LlmRouterPage.vueapps/ui-admin/src/components/llm-router/RouterPreviewPanel.vueapps/ui-admin/src/components/llm-router/RouterAdvancedJsonPanel.vueapps/ui-admin/src/pages/LlmRouterPage.test.tsapps/ui-admin/src/modules/router-config-form.test.tsadminApi.applyRouterConfig. Render applied, invalidatedKeys, and preview as separate scan-friendly sections before the raw JSON block. Add advanced JSON export/import and an explicit advanced preview/apply path for cases the typed form cannot represent yet.formatJson panels in LlmRouterPage.vue and FluxPage.vue; server response shape in apps/server/src/services/domain/admin/router-config/index.ts.dryRun: true, stores preview result, leaves last apply untouched, and renders invalidated keys as empty for dry run.dryRun: false, updates both preview and last apply state, and renders invalidated keys from the server.apps/ui-admin/src/styles/main.cssapps/ui-admin/src/pages/LlmRouterPage.vuedocs/ai/context/ui-components.md only if implementation changes packages/ui primitivesapps/ui-admin/src/styles/main.css for admin shell primitives; VoicePackFormPage.vue for responsive form density.LLM_ROUTER_CONFIG, UNSPEECH_UPSTREAM, and default aliases.| Surface | Impact |
|---|---|
apps/ui-admin | Primary user-facing change; LLM Router page becomes form-first with typed request building. |
apps/server | No planned runtime changes; route schemas and service builders remain the contract the UI mirrors. |
| ConfigKV / Redis invalidation | No behavior change; preview/apply still goes through the existing admin endpoint. |
| Public web, Electron, mobile | No direct product UI change; only admin app responsive behavior is in scope. |
apps/ui-admin currently has only module-level Vitest coverage. Mitigate with pure request-builder tests plus manual browser verification; add component tests only if implementation introduces behavior that cannot be covered through the builder.apps/ui-admin/src/pages/LlmRouterPage.vue currently owns the raw JSON textarea, preview, and apply flow.apps/server/src/routes/admin/config/router/index.ts defines the Valibot body schema and supported slice kinds.apps/server/src/services/domain/admin/router-config/index.ts defines provider defaults, request application semantics, redacted previews, and invalidated key behavior.apps/ui-admin/src/pages/VoicePackFormPage.vue shows the current admin form pattern with @proj-airi/ui primitives and responsive class arrays.apps/ui-admin/src/pages/FluxPage.vue shows the preview-first admin mutation pattern.docs/ai/context/ui-components.md documents the UI primitives to reuse if implementation touches shared components.