diff --git a/CHANGELOG.md b/CHANGELOG.md index 467d684..600fa0b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ The format is based on Keep a Changelog and this project follows Semantic Versio - **Onboarding prompt scope** — the Run page completion handoff now appears only for the onboarding first-run step, canceling the onboarding-launched server drawer stops setup with an explicit normal-mode notice, and the three-step welcome layout is centered. - **Built-in template reload after DB clear** — clearing the database from Settings now reloads the built-in benchmark library immediately, keeping shipped templates available without restarting the backend. - **Catalog empty server card** — the empty Servers catalog now presents a dashed first-server card with the add action instead of a centered empty-state panel. +- **Catalog model auto-selection** — opening the Models catalog without a server filter now selects the first available inference server so discovered models render immediately. - **Run empty preview** — the empty Run workspace now shows a dummy benchmark result with sample model, prompt, metrics, and audit rows instead of a generic empty panel. ## [0.9.0] - 2026-06-17 diff --git a/frontend/src/pages/Catalog.tsx b/frontend/src/pages/Catalog.tsx index 43dc2f9..6fab02c 100644 --- a/frontend/src/pages/Catalog.tsx +++ b/frontend/src/pages/Catalog.tsx @@ -613,6 +613,12 @@ export function Catalog({ const selectedQuantizations = useMemo(() => new Set(parseCsv(searchParams.get('quantization'))), [searchParams]); const selectedFormats = useMemo(() => new Set(parseCsv(searchParams.get('format'))), [searchParams]); const selectedCapabilities = useMemo(() => new Set(parseCsv(searchParams.get('capabilities'))), [searchParams]); + const autoSelectedModelServer = useMemo(() => { + return servers.find((server) => server.inference_server.active && !server.inference_server.archived) + ?? servers.find((server) => !server.inference_server.archived) + ?? servers[0] + ?? null; + }, [servers]); const maxParamCount = useMemo(() => { const v = searchParams.get('maxParams'); return v !== null ? Number(v) : null; @@ -626,6 +632,16 @@ export function Catalog({ }, { replace: true }); }, [activeTab, inspectorModelId, inspectorServerId, navigate]); + useEffect(() => { + if (activeTab !== 'models' || inspectorServerId || inspectorModelId || searchParams.has('servers') || !autoSelectedModelServer) { + return; + } + const next = new URLSearchParams(searchParams); + next.set('tab', 'models'); + next.set('servers', autoSelectedModelServer.inference_server.server_id); + setSearchParams(next, { replace: true }); + }, [activeTab, autoSelectedModelServer, inspectorModelId, inspectorServerId, searchParams, setSearchParams]); + useEffect(() => { if (searchParams.get('startOnboarding') !== '1') { return; @@ -903,7 +919,7 @@ export function Catalog({ setServerStageCollapsed={setServerStageCollapsed} onToggleServer={toggleServerSelection} onClearServers={() => updateQuery((params) => { - params.delete('servers'); + params.set('servers', ''); params.delete('family'); params.delete('quantization'); params.delete('format'); diff --git a/frontend/tests/e2e/models.spec.ts b/frontend/tests/e2e/models.spec.ts index 5eaf18b..2f9552b 100644 --- a/frontend/tests/e2e/models.spec.ts +++ b/frontend/tests/e2e/models.spec.ts @@ -64,8 +64,8 @@ async function seedModel( } } -test('models tab requires a selected server before showing model cards', async ({ page, request }) => { - const server = await createInferenceServer(request); +test('models tab auto-selects an available server before showing model cards', async ({ page, request }) => { + const server = await createInferenceServer(request, { display_name: `! Auto-select ${Date.now()}` }); await seedModel(request, server.inference_server.server_id, { model_id: '/lmstudio-community/Qwen3-Coder-30B-A3B-Instruct-MLX-6bit', display_name: 'Qwen3 Coder', @@ -73,10 +73,9 @@ test('models tab requires a selected server before showing model cards', async ( }); await page.goto('/catalog?tab=models'); - await expect(page.getByRole('heading', { name: 'Select a server to see its models' })).toBeVisible(); - - await page.locator('.server-filter-row').filter({ hasText: server.inference_server.display_name }).click(); await expect(page.locator('.catalog-model-card').filter({ hasText: 'Qwen3 Coder' })).toBeVisible(); + await expect(page.locator('.server-filter-row.is-selected').filter({ hasText: server.inference_server.display_name })).toBeVisible(); + await expect(page).toHaveURL(new RegExp(`servers=${encodeURIComponent(server.inference_server.server_id)}`)); await archiveInferenceServer(request, server.inference_server.server_id); });