User Request
Add LLM Providers and LLM Models as new entity sections in the platform UI. They should appear under the Settings sidebar group with Create, List, and Edit pages that follow the same layout and styles as existing entity pages.
Specification
Overview
Two new entity types backed by the gateway's LLM REST API (/llm/v1):
LLM Provider — represents an external LLM endpoint (e.g., OpenAI, Anthropic)
- Fields:
id (uuid), endpoint (uri), authMethod (enum: "bearer"), createdAt, updatedAt
- Create:
POST /providers — body: { endpoint, authMethod, token } (all required)
- Update:
PATCH /providers/{id} — body: { endpoint?, authMethod?, token? }
- List:
GET /providers?page=1&perPage=20 → { items, page, perPage, total }
- Get:
GET /providers/{id}
- Delete:
DELETE /providers/{id}
LLM Model — a named model reference linked to a provider
- Fields:
id (uuid), name, llmProviderId (uuid), remoteName, createdAt, updatedAt
- Create:
POST /models — body: { name, llmProviderId, remoteName } (all required)
- Update:
PATCH /models/{id} — body: { name?, llmProviderId?, remoteName? }
- List:
GET /models?page=1&perPage=20&providerId={uuid} → { items, page, perPage, total }
- Get:
GET /models/{id}
- Delete:
DELETE /models/{id}
1. Vite Proxy + HTTP Client
vite.config.ts — Add proxy entry for /llm:
'/llm': {
target: process.env.VITE_LLM_GATEWAY_URL || 'http://llm-gateway:8080',
changeOrigin: true,
},
src/api/http.ts — Add new gateway HTTP client:
export const llmHttp: HttpClient = wrap(createHttp('/llm/v1'));
2. API Module — src/api/modules/llmEntities.ts
Types (LLMProvider, LLMModel, CreateLLMProviderInput, UpdateLLMProviderInput, CreateLLMModelInput, UpdateLLMModelInput, PaginatedResponse<T>) and CRUD functions using llmHttp:
listProviders, getProvider, createProvider, updateProvider, deleteProvider
listModels, getModel, createModel, updateModel, deleteModel
3. TanStack Query Hooks
src/api/hooks/useLLMProviders.ts:
useLLMProviders(page, perPage) — query
useLLMProvider(id) — query (enabled when id is set)
useCreateLLMProvider() — mutation
useUpdateLLMProvider() — mutation
useDeleteLLMProvider() — mutation
src/api/hooks/useLLMModels.ts:
useLLMModels(page, perPage, providerId?) — query
useLLMModel(id) — query
useCreateLLMModel() — mutation
useUpdateLLMModel() — mutation
useDeleteLLMModel() — mutation
Query keys: ['llm', 'providers', ...] and ['llm', 'models', ...]. All mutations invalidate their respective query keys on success with notifySuccess/notifyError.
4. List Pages
src/pages/LLMProvidersListPage.tsx:
- Layout: header bar (title "LLM Providers" + description + "New provider" button) → table → pagination
- Table columns: Endpoint | Auth Method | Created | Actions (Edit/Delete)
- Server-side pagination with page state
- Empty state: "No LLM providers configured yet."
- Delete via
window.confirm() + mutation
src/pages/LLMModelsListPage.tsx:
- Same layout
- Table columns: Name | Remote Name | Provider (resolved endpoint) | Created | Actions
- Fetches providers (page=1, perPage=100) for provider lookup map
- Empty state: "No LLM models configured yet."
Both pages follow the styling from EntityListPage — border-b header, flex column overflow, sticky thead, agyn CSS variables.
5. Upsert Pages
src/pages/LLMProviderUpsertPage.tsx:
- Props:
mode: 'create' | 'edit'
- Edit mode reads
:id from route params, fetches provider
- Fields:
endpoint (Input, required, URL pattern), authMethod (SelectInput, options: bearer), token (Input type=password, required on create)
- Uses
react-hook-form + Form/FormField/FormItem/FormLabel/FormControl/FormMessage components
- Layout: white bg, header with title + Cancel/Submit buttons, form body max-w-xl
src/pages/LLMModelUpsertPage.tsx:
- Fields:
name (Input, required), llmProviderId (SelectInput populated from useLLMProviders, required), remoteName (Input, required)
- Same layout pattern
6. Navigation + Routing
src/layout/RootLayout.tsx:
- Add to
MENU_ITEM_ROUTES: llmProviders: '/settings/llm-providers', llmModels: '/settings/llm-models'
- Add to
MENU_ITEMS settings group (after existing llm):
{ id: 'llmProviders', label: 'LLM Providers', icon: <Server ...> }
{ id: 'llmModels', label: 'LLM Models', icon: <Bot ...> }
src/App.tsx:
<Route path="/settings/llm-providers" element={<LLMProvidersListPage />} />
<Route path="/settings/llm-providers/new" element={<LLMProviderUpsertPage mode="create" />} />
<Route path="/settings/llm-providers/:id/edit" element={<LLMProviderUpsertPage mode="edit" />} />
<Route path="/settings/llm-models" element={<LLMModelsListPage />} />
<Route path="/settings/llm-models/new" element={<LLMModelUpsertPage mode="create" />} />
<Route path="/settings/llm-models/:id/edit" element={<LLMModelUpsertPage mode="edit" />} />
7. Tests
src/pages/__tests__/llm-providers.test.tsx and src/pages/__tests__/llm-models.test.tsx:
- Use MSW handlers for
/llm/v1/providers and /llm/v1/models (both relative and absolute)
- Use
TestProviders + MemoryRouter from existing test utils
- Test cases: list renders rows, create navigates on submit, edit loads existing values, delete confirms and removes, validation blocks empty submit
Files Summary
| File |
Action |
packages/platform-ui/vite.config.ts |
Modify — add /llm proxy |
packages/platform-ui/src/api/http.ts |
Modify — add llmHttp |
packages/platform-ui/src/api/modules/llmEntities.ts |
New |
packages/platform-ui/src/api/hooks/useLLMProviders.ts |
New |
packages/platform-ui/src/api/hooks/useLLMModels.ts |
New |
packages/platform-ui/src/pages/LLMProvidersListPage.tsx |
New |
packages/platform-ui/src/pages/LLMModelsListPage.tsx |
New |
packages/platform-ui/src/pages/LLMProviderUpsertPage.tsx |
New |
packages/platform-ui/src/pages/LLMModelUpsertPage.tsx |
New |
packages/platform-ui/src/layout/RootLayout.tsx |
Modify — sidebar + routes |
packages/platform-ui/src/App.tsx |
Modify — add routes |
packages/platform-ui/src/pages/__tests__/llm-providers.test.tsx |
New |
packages/platform-ui/src/pages/__tests__/llm-models.test.tsx |
New |
User Request
Add LLM Providers and LLM Models as new entity sections in the platform UI. They should appear under the Settings sidebar group with Create, List, and Edit pages that follow the same layout and styles as existing entity pages.
Specification
Overview
Two new entity types backed by the gateway's LLM REST API (
/llm/v1):LLM Provider — represents an external LLM endpoint (e.g., OpenAI, Anthropic)
id(uuid),endpoint(uri),authMethod(enum: "bearer"),createdAt,updatedAtPOST /providers— body:{ endpoint, authMethod, token }(all required)PATCH /providers/{id}— body:{ endpoint?, authMethod?, token? }GET /providers?page=1&perPage=20→{ items, page, perPage, total }GET /providers/{id}DELETE /providers/{id}LLM Model — a named model reference linked to a provider
id(uuid),name,llmProviderId(uuid),remoteName,createdAt,updatedAtPOST /models— body:{ name, llmProviderId, remoteName }(all required)PATCH /models/{id}— body:{ name?, llmProviderId?, remoteName? }GET /models?page=1&perPage=20&providerId={uuid}→{ items, page, perPage, total }GET /models/{id}DELETE /models/{id}1. Vite Proxy + HTTP Client
vite.config.ts— Add proxy entry for/llm:src/api/http.ts— Add new gateway HTTP client:2. API Module —
src/api/modules/llmEntities.tsTypes (
LLMProvider,LLMModel,CreateLLMProviderInput,UpdateLLMProviderInput,CreateLLMModelInput,UpdateLLMModelInput,PaginatedResponse<T>) and CRUD functions usingllmHttp:listProviders,getProvider,createProvider,updateProvider,deleteProviderlistModels,getModel,createModel,updateModel,deleteModel3. TanStack Query Hooks
src/api/hooks/useLLMProviders.ts:useLLMProviders(page, perPage)— queryuseLLMProvider(id)— query (enabled when id is set)useCreateLLMProvider()— mutationuseUpdateLLMProvider()— mutationuseDeleteLLMProvider()— mutationsrc/api/hooks/useLLMModels.ts:useLLMModels(page, perPage, providerId?)— queryuseLLMModel(id)— queryuseCreateLLMModel()— mutationuseUpdateLLMModel()— mutationuseDeleteLLMModel()— mutationQuery keys:
['llm', 'providers', ...]and['llm', 'models', ...]. All mutations invalidate their respective query keys on success withnotifySuccess/notifyError.4. List Pages
src/pages/LLMProvidersListPage.tsx:window.confirm()+ mutationsrc/pages/LLMModelsListPage.tsx:Both pages follow the styling from
EntityListPage— border-b header, flex column overflow, sticky thead, agyn CSS variables.5. Upsert Pages
src/pages/LLMProviderUpsertPage.tsx:mode: 'create' | 'edit':idfrom route params, fetches providerendpoint(Input, required, URL pattern),authMethod(SelectInput, options: bearer),token(Input type=password, required on create)react-hook-form+ Form/FormField/FormItem/FormLabel/FormControl/FormMessage componentssrc/pages/LLMModelUpsertPage.tsx:name(Input, required),llmProviderId(SelectInput populated from useLLMProviders, required),remoteName(Input, required)6. Navigation + Routing
src/layout/RootLayout.tsx:MENU_ITEM_ROUTES:llmProviders: '/settings/llm-providers',llmModels: '/settings/llm-models'MENU_ITEMSsettings group (after existingllm):{ id: 'llmProviders', label: 'LLM Providers', icon: <Server ...> }{ id: 'llmModels', label: 'LLM Models', icon: <Bot ...> }src/App.tsx:7. Tests
src/pages/__tests__/llm-providers.test.tsxandsrc/pages/__tests__/llm-models.test.tsx:/llm/v1/providersand/llm/v1/models(both relative and absolute)TestProviders+MemoryRouterfrom existing test utilsFiles Summary
packages/platform-ui/vite.config.ts/llmproxypackages/platform-ui/src/api/http.tsllmHttppackages/platform-ui/src/api/modules/llmEntities.tspackages/platform-ui/src/api/hooks/useLLMProviders.tspackages/platform-ui/src/api/hooks/useLLMModels.tspackages/platform-ui/src/pages/LLMProvidersListPage.tsxpackages/platform-ui/src/pages/LLMModelsListPage.tsxpackages/platform-ui/src/pages/LLMProviderUpsertPage.tsxpackages/platform-ui/src/pages/LLMModelUpsertPage.tsxpackages/platform-ui/src/layout/RootLayout.tsxpackages/platform-ui/src/App.tsxpackages/platform-ui/src/pages/__tests__/llm-providers.test.tsxpackages/platform-ui/src/pages/__tests__/llm-models.test.tsx