|
| 1 | +# Providers UI Specification |
| 2 | + |
| 3 | +> **Scope:** Frontend UX only. The domain model and wire protocol live in `provider-connections`; this spec covers the user-facing flows that consume those contracts. |
| 4 | +
|
| 5 | +## Purpose |
| 6 | + |
| 7 | +Defines the Rook dashboard flows for discovering, configuring, and managing AI provider connections. Captures the 3-screen navigation model (Catalog → Details → Connection Modal), the quota placeholder, and the `EmptyState` wrapper used across the dashboard. |
| 8 | + |
| 9 | +--- |
| 10 | + |
| 11 | +## Requirements |
| 12 | + |
| 13 | +### Requirement: Providers Catalog |
| 14 | + |
| 15 | +The system SHALL display a catalog view at `/providers` listing every supported `ProviderKind` as a card grouped by category (`API Key`, `OAuth`, `Local`). Each card SHALL show the display name, configured-connection count, a kind-level enable toggle, and a test-all action. The view SHALL support client-side category filtering and name search. |
| 16 | + |
| 17 | +#### Scenario: Empty catalog |
| 18 | +- **WHEN** the user navigates to `/providers` with no connections configured |
| 19 | +- **THEN** the catalog shows every supported kind (`openai`, `anthropic`, `ollama`, `gemini`, `groq`) with `0 Connections` |
| 20 | +- **AND** each card shows an empty-state indicator for connections |
| 21 | + |
| 22 | +#### Scenario: Catalog with connections |
| 23 | +- **WHEN** the user navigates to `/providers` with at least one connection |
| 24 | +- **THEN** each card shows its configured count (e.g., `Ollama Cloud: 3 Connections`) |
| 25 | +- **AND** the card is visually highlighted to indicate configured connections |
| 26 | + |
| 27 | +#### Scenario: Filter by category |
| 28 | +- **WHEN** the user clicks a category chip (e.g., `API Key`) |
| 29 | +- **THEN** only kinds in that category are visible |
| 30 | +- **AND** the chip shows an active state |
| 31 | +- **AND** clicking the same chip again removes the filter |
| 32 | + |
| 33 | +#### Scenario: Search the catalog |
| 34 | +- **WHEN** the user types in the search input |
| 35 | +- **THEN** only kinds whose name contains the search term are visible |
| 36 | +- **AND** the match is case-insensitive |
| 37 | + |
| 38 | +#### Scenario: Navigate to details |
| 39 | +- **WHEN** the user clicks a kind card |
| 40 | +- **THEN** the user is navigated to `/providers/:providerKind` (e.g., `/providers/ollama`) |
| 41 | + |
| 42 | +--- |
| 43 | + |
| 44 | +### Requirement: Provider Details |
| 45 | + |
| 46 | +The system SHALL display a details view at `/providers/:providerKind` listing all connections for the specified kind. The header SHALL show the provider name, total connection count, and bulk action buttons (`Test All`, `Add`). The route SHALL validate `:providerKind` against the `ProviderKind` union. |
| 47 | + |
| 48 | +#### Scenario: Details with connections |
| 49 | +- **WHEN** the user navigates to `/providers/ollama` with 3 Ollama connections |
| 50 | +- **THEN** the header shows `Ollama Cloud` and `3 Connections` |
| 51 | +- **AND** the page lists all 3 connections with name, status, model, priority, and proxy state |
| 52 | + |
| 53 | +#### Scenario: Empty details state |
| 54 | +- **WHEN** the user navigates to `/providers/openai` with 0 OpenAI connections |
| 55 | +- **THEN** the header shows `OpenAI` and `0 Connections` |
| 56 | +- **AND** the page shows an empty state with a prominent `Add your first OpenAI connection` CTA |
| 57 | + |
| 58 | +#### Scenario: Test all connections |
| 59 | +- **WHEN** the user clicks `Test All` |
| 60 | +- **THEN** all connections for the kind are tested sequentially |
| 61 | +- **AND** each connection's status indicator updates as its test completes |
| 62 | + |
| 63 | +#### Scenario: Add from details |
| 64 | +- **WHEN** the user clicks `Add` |
| 65 | +- **THEN** the connection modal opens in create mode with `providerKind` pre-filled to the current kind |
| 66 | + |
| 67 | +#### Scenario: Navigate back to catalog |
| 68 | +- **WHEN** the user clicks the back link or breadcrumb |
| 69 | +- **THEN** the user is navigated to `/providers` |
| 70 | + |
| 71 | +--- |
| 72 | + |
| 73 | +### Requirement: Connection Modal |
| 74 | + |
| 75 | +The system SHALL provide a modal dialog for creating and editing provider connections. The modal SHALL accept a `providerKind` prop and a `mode` prop (`'create' | 'edit'`) and SHALL be controlled by its parent via `v-model:open`. The modal MUST require a successful credential test before `Save` is enabled. |
| 76 | + |
| 77 | +#### Scenario: Open in create mode |
| 78 | +- **WHEN** the user clicks `Add` from the catalog or details view |
| 79 | +- **THEN** the modal opens with `providerKind` pre-filled (or selectable if opened from catalog) |
| 80 | +- **AND** the form shows fields for the selected `authType` (API Key for `apikey`, OAuth fields for `oauth`) |
| 81 | + |
| 82 | +#### Scenario: Test credentials |
| 83 | +- **WHEN** the user fills the form and clicks `Test` |
| 84 | +- **THEN** the modal calls `POST /api/providers/test-credentials` |
| 85 | +- **AND** displays the result (ok/error, latency) |
| 86 | +- **AND** `Save` is disabled until the result is `ok` |
| 87 | + |
| 88 | +#### Scenario: Save a new connection |
| 89 | +- **WHEN** the test result is `ok` and the user clicks `Save` |
| 90 | +- **THEN** the modal calls `POST /api/providers` |
| 91 | +- **AND** the modal closes |
| 92 | +- **AND** the catalog/details view refreshes to show the new connection |
| 93 | + |
| 94 | +#### Scenario: Edit existing connection |
| 95 | +- **WHEN** the modal opens in `edit` mode with a connection id |
| 96 | +- **THEN** the form is pre-populated from the existing record |
| 97 | +- **AND** saving calls `PUT /api/providers/{id}` with `expectedUpdatedAt` |
| 98 | +- **AND** the API key field is empty (stored secrets are not re-exposed) |
| 99 | + |
| 100 | +#### Scenario: Cancel without saving |
| 101 | +- **WHEN** the user clicks `Cancel` or presses `Escape` |
| 102 | +- **THEN** the modal closes without saving |
| 103 | +- **AND** no mutation API call is made |
| 104 | + |
| 105 | +--- |
| 106 | + |
| 107 | +### Requirement: Providers Quota Placeholder |
| 108 | + |
| 109 | +The system SHALL display a placeholder quota view at `/providers/quota` with mocked data and a banner explaining that real per-provider quota integration is tracked in a follow-up issue. |
| 110 | + |
| 111 | +#### Scenario: Navigate to quota |
| 112 | +- **WHEN** the user navigates to `/providers/quota` |
| 113 | +- **THEN** the page shows mocked quota data (tokens consumed, limits, cost) |
| 114 | +- **AND** a banner explains that per-provider quota integration is a follow-up |
| 115 | +- **AND** the page references the follow-up tracking issue |
| 116 | + |
| 117 | +--- |
| 118 | + |
| 119 | +### Requirement: EmptyState Component |
| 120 | + |
| 121 | +The system SHALL provide a reusable `EmptyState` component that wraps the shadcn-vue `Empty` primitive while preserving the existing public API (`title`, `description`, `icon` props). |
| 122 | + |
| 123 | +#### Scenario: Backward-compatible callsites |
| 124 | +- **WHEN** a component uses `<EmptyState title="..." description="..." :icon="..." />` |
| 125 | +- **THEN** it renders correctly via the shadcn-vue `Empty` composition underneath |
| 126 | +- **AND** no existing callsite requires changes |
| 127 | + |
| 128 | +#### Scenario: New shadcn-vue features |
| 129 | +- **WHEN** a new callsite uses shadcn-vue `Empty` slots or sub-components (e.g., `EmptyHeader`, `EmptyContent`) |
| 130 | +- **THEN** the wrapper supports them via slot passthrough or new prop names |
| 131 | + |
| 132 | +--- |
| 133 | + |
| 134 | +### Requirement: Catalog Metadata Source |
| 135 | + |
| 136 | +The catalog view SHALL derive its kind-level metadata (displayName, runtimeId, defaultBaseUrl, supportsOAuth, description, iconName) from a static `PROVIDER_KINDS` constant in `apps/rook/dashboard/src/config/providerCatalog.ts`. Configured-connection counts per kind SHALL be derived from the live list returned by `GET /api/providers`. |
| 137 | + |
| 138 | +#### Scenario: Display kind metadata |
| 139 | +- **WHEN** the catalog renders a kind card |
| 140 | +- **THEN** the card shows the kind's display name and description from `PROVIDER_KINDS` |
| 141 | +- **AND** the connection count is the length of the live connections filtered by `providerKind` |
| 142 | + |
| 143 | +#### Scenario: Adding a new kind |
| 144 | +- **WHEN** a developer adds an entry to `PROVIDER_KINDS` |
| 145 | +- **THEN** the new kind appears in the catalog without template changes |
| 146 | +- **AND** the route `/providers/:providerKind` resolves only for kinds in the backend `ProviderKind` enum |
0 commit comments