mirror of
https://github.com/awizemann/scarf.git
synced 2026-05-10 18:44:45 +00:00
feat(model-picker): add search filter to Nous overlay model list
Nous returned 402 models in the recent perf capture (~496 KB of JSON). The picker's existing top-bar search field already filters the catalog list (`filteredModels`) but the Nous overlay path showed all 402 unfiltered, making it nearly unusable. Add `filteredNousModels` mirroring the `filteredModels` shape: filters `nousModels` by case-insensitive substring match against both `id` and `owned_by`. Updates the empty-state overlay so "no matches" surfaces a different message from "no models loaded" — the user knows the catalog is fine, the search just didn't match. User feedback: "we need a search in the model picker, some of these lists are large and unorganized." Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -344,7 +344,7 @@ struct ModelPickerSheet: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
List(selection: $overlayModelID) {
|
List(selection: $overlayModelID) {
|
||||||
ForEach(nousModels) { model in
|
ForEach(filteredNousModels) { model in
|
||||||
VStack(alignment: .leading, spacing: 2) {
|
VStack(alignment: .leading, spacing: 2) {
|
||||||
Text(model.id)
|
Text(model.id)
|
||||||
.font(.system(.body, design: .monospaced))
|
.font(.system(.body, design: .monospaced))
|
||||||
@@ -360,12 +360,23 @@ struct ModelPickerSheet: View {
|
|||||||
.listStyle(.inset)
|
.listStyle(.inset)
|
||||||
.frame(minHeight: 220)
|
.frame(minHeight: 220)
|
||||||
.overlay {
|
.overlay {
|
||||||
if nousModels.isEmpty && !nousIsRefreshing {
|
if filteredNousModels.isEmpty && !nousIsRefreshing {
|
||||||
ContentUnavailableView(
|
if nousModels.isEmpty {
|
||||||
"No models loaded",
|
ContentUnavailableView(
|
||||||
systemImage: "cpu",
|
"No models loaded",
|
||||||
description: Text("Sign in to Nous Portal to load the catalog, or enter a model ID manually.")
|
systemImage: "cpu",
|
||||||
)
|
description: Text("Sign in to Nous Portal to load the catalog, or enter a model ID manually.")
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
// Models loaded but the search filtered them all
|
||||||
|
// out. Different message so the user knows the
|
||||||
|
// catalog is fine, just their query didn't match.
|
||||||
|
ContentUnavailableView(
|
||||||
|
"No matches",
|
||||||
|
systemImage: "magnifyingglass",
|
||||||
|
description: Text("No models match \"\(searchText)\".")
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if nousFetchedAt == nil && !nousModels.isEmpty {
|
if nousFetchedAt == nil && !nousModels.isEmpty {
|
||||||
@@ -577,6 +588,20 @@ struct ModelPickerSheet: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Same shape as `filteredModels` but for the Nous overlay path
|
||||||
|
/// (`nousModels` is `[NousModel]`, not `[HermesModelInfo]`).
|
||||||
|
/// Nous returned 402 models in the user's capture; without a
|
||||||
|
/// filter the picker is a flat unsearchable list. Reuses the
|
||||||
|
/// same `searchText` field so the user types once and both
|
||||||
|
/// paths respond.
|
||||||
|
private var filteredNousModels: [NousModel] {
|
||||||
|
guard !searchText.isEmpty else { return nousModels }
|
||||||
|
let q = searchText.lowercased()
|
||||||
|
return nousModels.filter {
|
||||||
|
$0.id.lowercased().contains(q) || ($0.owned_by ?? "").lowercased().contains(q)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private var isSelectedProviderOverlay: Bool {
|
private var isSelectedProviderOverlay: Bool {
|
||||||
providers.first(where: { $0.providerID == selectedProviderID })?.isOverlay ?? false
|
providers.first(where: { $0.providerID == selectedProviderID })?.isOverlay ?? false
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user