mirror of
https://github.com/mudler/LocalAI.git
synced 2026-06-06 15:56:06 -04:00
fix(react-ui): polish 'Fits in my GPU' filter to use design-system Toggle (#10030)
* fix(react-ui): polish 'Fits in my GPU' filter to use design-system Toggle The recently added VRAM-fit filter in the Models page used a raw <input type="checkbox"> next to the themed range slider, breaking the visual language of the rest of the row. Swap it for the shared <Toggle> component (already used by Backends, Settings, Traces, AgentCreate), adopt the filter-bar-group__toggle class to drop the duplicated inline styles, add a fa-microchip icon to mirror the per-row fit indicator, and add a subtle left divider so the filter reads as separate from the context-size slider on its left. Assisted-by: Claude:claude-opus-4-7 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * fix(react-ui): move 'Fits in GPU' filter to filter row and unify copy Two follow-ups on the previous polish pass: 1. Move the toggle from the context-slider row into the filter-button row above. The toggle is a filter on the result set, not a config for VRAM estimation, so it belongs with the type chips and backend select. The context slider stays its own thing. 2. Unify the label copy. The same locale file had "Fits in my GPU" for the filter and "Fits in GPU" for the per-row indicator; pick the shorter, possessive-free variant everywhere (en/de/es/it/zh-CN). Update e2e selectors to match. Assisted-by: Claude:claude-opus-4-7 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> --------- Signed-off-by: Ettore Di Giacinto <mudler@localai.io> Co-authored-by: Ettore Di Giacinto <mudler@localai.io>
This commit is contained in:
@@ -282,14 +282,14 @@ test.describe('Models Gallery - Fits In GPU Filter', () => {
|
||||
await expect(page.locator('th', { hasText: 'Backend' })).toBeVisible({ timeout: 10_000 })
|
||||
})
|
||||
|
||||
test('fits checkbox is visible when GPU resources are available', async ({ page }) => {
|
||||
await expect(page.getByText('Fits in my GPU')).toBeVisible()
|
||||
test('fits toggle is visible when GPU resources are available', async ({ page }) => {
|
||||
await expect(page.getByText('Fits in GPU')).toBeVisible()
|
||||
})
|
||||
|
||||
test('enabling fits filter hides models that exceed available VRAM', async ({ page }) => {
|
||||
await expect(page.locator('tr', { hasText: 'stablediffusion-model' })).toBeVisible()
|
||||
|
||||
await page.getByLabel('Fits in my GPU').check()
|
||||
await page.getByLabel('Fits in GPU').check()
|
||||
|
||||
await expect(page.locator('tr', { hasText: 'stablediffusion-model' })).toHaveCount(0)
|
||||
await expect(page.locator('tr', { hasText: 'llama-model' })).toBeVisible()
|
||||
@@ -298,8 +298,8 @@ test.describe('Models Gallery - Fits In GPU Filter', () => {
|
||||
})
|
||||
|
||||
test('fits filter state persists after reload', async ({ page }) => {
|
||||
await page.getByLabel('Fits in my GPU').check()
|
||||
await page.getByLabel('Fits in GPU').check()
|
||||
await page.reload()
|
||||
await expect(page.getByLabel('Fits in my GPU')).toBeChecked()
|
||||
await expect(page.getByLabel('Fits in GPU')).toBeChecked()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
"diarization": "Diarisierung",
|
||||
"embedding": "Embedding",
|
||||
"rerank": "Rerank",
|
||||
"fitsGpu": "Passt in meine GPU",
|
||||
"fitsGpu": "Passt in die GPU",
|
||||
"allBackends": "Alle Backends",
|
||||
"searchBackends": "Backends suchen..."
|
||||
},
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
"rerank": "Rerank",
|
||||
"detection": "Detection",
|
||||
"vad": "VAD",
|
||||
"fitsGpu": "Fits in my GPU",
|
||||
"fitsGpu": "Fits in GPU",
|
||||
"allBackends": "All Backends",
|
||||
"searchBackends": "Search backends..."
|
||||
},
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
"diarization": "Diarización",
|
||||
"embedding": "Embedding",
|
||||
"rerank": "Rerank",
|
||||
"fitsGpu": "Cabe en mi GPU",
|
||||
"fitsGpu": "Cabe en la GPU",
|
||||
"allBackends": "Todos los backends",
|
||||
"searchBackends": "Buscar backends..."
|
||||
},
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
"diarization": "Diarizzazione",
|
||||
"embedding": "Embedding",
|
||||
"rerank": "Rerank",
|
||||
"fitsGpu": "Entra nella mia GPU",
|
||||
"fitsGpu": "Entra nella GPU",
|
||||
"allBackends": "Tutti i backend",
|
||||
"searchBackends": "Cerca backend..."
|
||||
},
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
"diarization": "说话人分离",
|
||||
"embedding": "嵌入",
|
||||
"rerank": "重排",
|
||||
"fitsGpu": "适合我的 GPU",
|
||||
"fitsGpu": "适合 GPU",
|
||||
"allBackends": "所有后端",
|
||||
"searchBackends": "搜索后端..."
|
||||
},
|
||||
|
||||
@@ -9,6 +9,7 @@ import { useResources } from '../hooks/useResources'
|
||||
import SearchableSelect from '../components/SearchableSelect'
|
||||
import ConfirmDialog from '../components/ConfirmDialog'
|
||||
import GalleryLoader from '../components/GalleryLoader'
|
||||
import Toggle from '../components/Toggle'
|
||||
import React from 'react'
|
||||
|
||||
|
||||
@@ -325,6 +326,13 @@ export default function Models() {
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
{totalGpuMemory > 0 && (
|
||||
<label className="filter-bar-group__toggle" style={{ marginLeft: 'auto' }}>
|
||||
<Toggle checked={fitsFilter} onChange={setFitsFilter} />
|
||||
<i className="fas fa-microchip" />
|
||||
<span>{t('filters.fitsGpu')}</span>
|
||||
</label>
|
||||
)}
|
||||
{allBackends.length > 0 && (
|
||||
<SearchableSelect
|
||||
value={backendFilter}
|
||||
@@ -333,7 +341,7 @@ export default function Models() {
|
||||
placeholder={t('filters.allBackends')}
|
||||
allOption={t('filters.allBackends')}
|
||||
searchPlaceholder={t('filters.searchBackends')}
|
||||
style={{ marginLeft: 'auto' }}
|
||||
style={totalGpuMemory > 0 ? undefined : { marginLeft: 'auto' }}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
@@ -355,16 +363,6 @@ export default function Models() {
|
||||
<span style={{ fontWeight: 600, minWidth: '3em' }}>
|
||||
{CONTEXT_LABELS[CONTEXT_SIZES.indexOf(contextSize)]}
|
||||
</span>
|
||||
{totalGpuMemory > 0 && (
|
||||
<label style={{ marginLeft: 'auto', display: 'inline-flex', alignItems: 'center', gap: 'var(--spacing-xs)', color: 'var(--color-text-secondary)', cursor: 'pointer' }}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={fitsFilter}
|
||||
onChange={(e) => setFitsFilter(e.target.checked)}
|
||||
/>
|
||||
<span>{t('filters.fitsGpu')}</span>
|
||||
</label>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Table */}
|
||||
|
||||
Reference in New Issue
Block a user