mirror of
https://github.com/mudler/LocalAI.git
synced 2026-11-06 04:23:15 -05:00
Settings, config fields, the failover and router editors, the agent task form, the scheduling rule sheet and the agent job form now use ModelPicker. SearchableModelSelect, ModelMultiSelect and the scheduling ModelPicker are removed; ModelSelector stays as the name add-ons use and draws the shared picker. Aliases come from the capabilities listing, so the scheduling sheet no longer builds its own hints. A broken alias is listed, marked and not selectable. The picker's style sheet now loads with the picker, so the e2e specs that read a settings group right after opening it wait for its first field. Assisted-by: Claude Code:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
47 lines
2.4 KiB
JavaScript
47 lines
2.4 KiB
JavaScript
import { test, expect } from './coverage-fixtures.js'
|
|
|
|
// The shared model picker, reached through the settings and scheduling pages
|
|
// with the model list stubbed: aliases, broken aliases and disabled models.
|
|
|
|
const MODELS = [
|
|
{ id: 'qwen3-8b', capabilities: ['FLAG_CHAT'], backend: 'llama-cpp' },
|
|
{ id: 'fast', capabilities: ['FLAG_CHAT'], backend: 'llama-cpp', alias_of: 'qwen3-8b' },
|
|
{ id: 'lost', capabilities: [], alias_of: 'gone', alias_error: 'alias "lost" points to unknown model "gone"' },
|
|
{ id: 'old', capabilities: ['FLAG_CHAT'], disabled: true },
|
|
{ id: 'pinned-one', capabilities: ['FLAG_CHAT'], pinned: true },
|
|
]
|
|
|
|
async function stub(page) {
|
|
await page.route('**/api/models/capabilities', route => route.fulfill({ json: { data: MODELS } }))
|
|
await page.route('**/api/nodes/scheduling', route => route.fulfill({ json: [] }))
|
|
await page.route('**/api/nodes', route => route.fulfill({ json: [] }))
|
|
}
|
|
|
|
test.describe('Model picker', () => {
|
|
test('lists an alias with its target and a broken alias as unusable', async ({ page }) => {
|
|
await stub(page)
|
|
await page.goto('/app/scheduling')
|
|
await page.getByRole('button', { name: 'New rule' }).click()
|
|
await page.locator('#sched-model').click()
|
|
const list = page.getByRole('listbox')
|
|
await expect(list.getByRole('option', { name: /fast.*alias of qwen3-8b/ })).toBeVisible()
|
|
await expect(list.getByRole('option', { name: /lost.*broken alias/ })).toHaveAttribute('aria-disabled', 'true')
|
|
await expect(list.getByRole('option', { name: /old.*disabled/ })).toHaveAttribute('aria-disabled', 'true')
|
|
await expect(list.getByRole('option', { name: /pinned-one.*pinned/ })).toBeVisible()
|
|
if (process.env.PICKER_SHOTS) await page.waitForTimeout(600)
|
|
if (process.env.PICKER_SHOTS) await page.screenshot({ path: `${process.env.PICKER_SHOTS}/scheduling-picker.png` })
|
|
await list.getByRole('option', { name: /fast/ }).click()
|
|
await expect(page.locator('#sched-model')).toContainText('fast')
|
|
})
|
|
|
|
test('a typed name that is not listed can be used as it is', async ({ page }) => {
|
|
await stub(page)
|
|
await page.goto('/app/scheduling')
|
|
await page.getByRole('button', { name: 'New rule' }).click()
|
|
await page.locator('#sched-model').click()
|
|
await page.getByRole('combobox', { name: 'Filter models' }).fill('later-model')
|
|
await page.getByRole('option', { name: /Use "later-model"/ }).click()
|
|
await expect(page.locator('#sched-model')).toContainText('later-model')
|
|
})
|
|
})
|