diff --git a/CHANGELOG.md b/CHANGELOG.md index 24d3cf99..b2b84408 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,8 +7,12 @@ Write each change in both `### English` and `### 中文` under `## Unreleased`. ### English +- Replace native Logs and Models filter dropdowns with compact HeroUI Select controls that match the 32px toolbar + ### 中文 +- Logs 和模型页的筛选下拉改用紧凑 HeroUI Select,高度对齐 32px 工具栏 + ## 0.2.20 - 2026-08-29 ### English diff --git a/frontend/src/components/ui/FilterSelect.tsx b/frontend/src/components/ui/FilterSelect.tsx new file mode 100644 index 00000000..a43858d5 --- /dev/null +++ b/frontend/src/components/ui/FilterSelect.tsx @@ -0,0 +1,61 @@ +import { Label, ListBox, Select } from '@heroui/react' + +const ALL_VALUE = '__all__' + +export type FilterSelectOption = { + id: string + label: string +} + +export function FilterSelect({ + value, + onChange, + ariaLabel, + options, + className, +}: { + value: string + onChange: (value: string) => void + ariaLabel: string + options: FilterSelectOption[] + className?: string +}) { + const items = options.map((option) => ({ + id: option.id || ALL_VALUE, + label: option.label, + })) + const selectedId = value || ALL_VALUE + const selected = items.find((item) => item.id === selectedId) + + return ( + + ) +} diff --git a/frontend/src/pages/LogsPage.tsx b/frontend/src/pages/LogsPage.tsx index 0b2cd27d..520fbf57 100644 --- a/frontend/src/pages/LogsPage.tsx +++ b/frontend/src/pages/LogsPage.tsx @@ -34,6 +34,7 @@ import { type RequestLog, type RuntimeLogEntry, } from '@/api/logs' +import { FilterSelect } from '@/components/ui/FilterSelect' import { ListPager, type PageSize } from '@/components/ui/ListPager' import { LogsRequestListSkeleton, LogsRuntimeListSkeleton } from '@/components/ui/PageSkeletons' import { useI18n } from '@/hooks/useI18n' @@ -47,8 +48,6 @@ type StreamFilter = 'all' | 'stream' | 'sync' type TimeRange = 'all' | '1h' | '24h' | '7d' | 'custom' type ErrorKindFilter = 'all' | 'quota' | 'rate_limit' | 'auth' | 'not_ready' | 'unavailable' | 'invalid_request' | 'model_not_available' -const FILTER_SELECT_CLASS = 'h-8 min-w-36 rounded-lg border border-[var(--app-line-strong)] bg-[var(--app-surface-solid)] px-2.5 text-xs text-[var(--app-ink)]' - type DateRangeValue = { start: DateValue; end: DateValue } function statusColor(status?: string): 'success' | 'warning' | 'danger' | 'default' { @@ -407,28 +406,24 @@ export function LogsPage() {
- - + onChange={setModelFilter} + options={[ + { id: '', label: t('logsFilterModelAll') }, + ...(models || []).map((model) => ({ id: model.id, label: model.display_name || model.id })), + ]} + /> {([ ['all', t('logsFilterStreamAll')], @@ -445,21 +440,21 @@ export function LogsPage() { ))} - + setErrorKind((next || 'all') as ErrorKindFilter)} + options={[ + { id: '', label: t('logsFilterKindAll') }, + { id: 'quota', label: t('logsKindQuota') }, + { id: 'rate_limit', label: t('logsKindRateLimit') }, + { id: 'auth', label: t('logsKindAuth') }, + { id: 'not_ready', label: t('logsKindNotReady') }, + { id: 'unavailable', label: t('logsKindUnavailable') }, + { id: 'invalid_request', label: t('logsKindInvalidRequest') }, + { id: 'model_not_available', label: t('logsKindModelNotAvailable') }, + ]} + />
@@ -680,17 +675,15 @@ export function LogsPage() { placeholder={t('logsSearchRuntime')} aria-label={t('logsSearchRuntime')} /> - + onChange={setRuntimeAccount} + options={[ + { id: '', label: t('logsFilterAccountAll') }, + ...(accounts || []).map((account) => ({ id: account.id, label: account.name || account.id })), + ]} + /> {([ ['all', t('logsLevelAll')], diff --git a/frontend/src/pages/ProvidersPage.tsx b/frontend/src/pages/ProvidersPage.tsx index 14dd5af5..78e2d82d 100644 --- a/frontend/src/pages/ProvidersPage.tsx +++ b/frontend/src/pages/ProvidersPage.tsx @@ -8,13 +8,12 @@ import type { Overview } from '@/api/types' import { ProviderMark } from '@/components/ProviderMark' import { ModelDetailsModal, formatTokens } from '@/components/ModelDetailsModal' import { CompactSwitch } from '@/components/ui/CompactSwitch' +import { FilterSelect } from '@/components/ui/FilterSelect' import { ListPager, type PageSize } from '@/components/ui/ListPager' import { ProvidersPageSkeleton } from '@/components/ui/PageSkeletons' type ModelInfo = NonNullable[number] -const FILTER_SELECT_CLASS = 'h-8 min-w-36 rounded-lg border border-[var(--app-line-strong)] bg-[var(--app-surface-solid)] px-2.5 text-xs text-[var(--app-ink)]' - function modelSettingsKey(model: ModelInfo) { return model.settings_key || model.id } @@ -205,17 +204,15 @@ export function ProvidersPage() {
setFilter(event.target.value)} placeholder={t('filterPh')} aria-label={t('filter')} /> - + onChange={setProviderFilter} + options={[ + { id: '', label: t('providerFilterAll') }, + ...providers.map((provider) => ({ id: provider, label: provider })), + ]} + />