Skip to content

Commit 2117f80

Browse files
committed
fix: use exact tag matching for model gallery tag filtering
The Search() method uses strings.Contains() on comma-joined tags, causing substring false positives (e.g., "asr" matching "image-diffusers"). Add FilterByTag() method that checks each tag with strings.EqualFold() for exact, case-insensitive matching. Add 'tag' query parameter to /api/models and /api/backends endpoints. Update the React frontend to send filter selections as 'tag' instead of 'term'. Closes #8775 Signed-off-by: majiayu000 <1835304752@qq.com>
1 parent 3eabd6d commit 2117f80

4 files changed

Lines changed: 85 additions & 3 deletions

File tree

core/gallery/gallery.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,19 @@ func (gm GalleryElements[T]) Search(term string) GalleryElements[T] {
9292
return filteredModels
9393
}
9494

95+
func (gm GalleryElements[T]) FilterByTag(tag string) GalleryElements[T] {
96+
var filtered GalleryElements[T]
97+
for _, m := range gm {
98+
for _, t := range m.GetTags() {
99+
if strings.EqualFold(t, tag) {
100+
filtered = append(filtered, m)
101+
break
102+
}
103+
}
104+
}
105+
return filtered
106+
}
107+
95108
func (gm GalleryElements[T]) SortByName(sortOrder string) GalleryElements[T] {
96109
sort.Slice(gm, func(i, j int) bool {
97110
if sortOrder == "asc" {

core/gallery/gallery_test.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,68 @@ var _ = Describe("Gallery", func() {
159159
})
160160
})
161161

162+
Describe("GalleryElements FilterByTag", func() {
163+
var elements GalleryElements[*GalleryModel]
164+
165+
BeforeEach(func() {
166+
elements = GalleryElements[*GalleryModel]{
167+
{
168+
Metadata: Metadata{
169+
Name: "whisper-asr",
170+
Tags: []string{"asr", "stt"},
171+
},
172+
},
173+
{
174+
Metadata: Metadata{
175+
Name: "image-diffusers",
176+
Tags: []string{"sd", "image"},
177+
},
178+
},
179+
{
180+
Metadata: Metadata{
181+
Name: "another-stt-model",
182+
Tags: []string{"stt", "audio"},
183+
},
184+
},
185+
{
186+
Metadata: Metadata{
187+
Name: "no-tags-model",
188+
Tags: []string{},
189+
},
190+
},
191+
}
192+
})
193+
194+
It("should return exact tag matches only", func() {
195+
results := elements.FilterByTag("asr")
196+
Expect(results).To(HaveLen(1))
197+
Expect(results[0].GetName()).To(Equal("whisper-asr"))
198+
})
199+
200+
It("should not match substrings (image-diffusers must NOT match 'asr')", func() {
201+
results := elements.FilterByTag("asr")
202+
for _, r := range results {
203+
Expect(r.GetName()).NotTo(Equal("image-diffusers"))
204+
}
205+
})
206+
207+
It("should be case insensitive", func() {
208+
results := elements.FilterByTag("ASR")
209+
Expect(results).To(HaveLen(1))
210+
Expect(results[0].GetName()).To(Equal("whisper-asr"))
211+
})
212+
213+
It("should return multiple models with the same tag", func() {
214+
results := elements.FilterByTag("stt")
215+
Expect(results).To(HaveLen(2))
216+
})
217+
218+
It("should return empty when no models have the tag", func() {
219+
results := elements.FilterByTag("nonexistent")
220+
Expect(results).To(HaveLen(0))
221+
})
222+
})
223+
162224
Describe("GalleryElements SortByName", func() {
163225
var elements GalleryElements[*GalleryModel]
164226

core/http/react-ui/src/pages/Models.jsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -148,13 +148,12 @@ export default function Models() {
148148
const searchVal = params.search !== undefined ? params.search : search
149149
const filterVal = params.filter !== undefined ? params.filter : filter
150150
const sortVal = params.sort !== undefined ? params.sort : sort
151-
// Combine search text and filter into 'term' param
152-
const term = searchVal || filterVal || ''
153151
const queryParams = {
154152
page: params.page || page,
155153
items: 9,
156154
}
157-
if (term) queryParams.term = term
155+
if (filterVal) queryParams.tag = filterVal
156+
if (searchVal) queryParams.term = searchVal
158157
if (sortVal) {
159158
queryParams.sort = sortVal
160159
queryParams.order = params.order || order

core/http/routes/ui_api.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,7 @@ func RegisterUIAPIRoutes(app *echo.Echo, cl *config.ModelConfigLoader, ml *model
209209
// Model Gallery APIs
210210
app.GET("/api/models", func(c echo.Context) error {
211211
term := c.QueryParam("term")
212+
tag := c.QueryParam("tag")
212213
page := c.QueryParam("page")
213214
if page == "" {
214215
page = "1"
@@ -239,6 +240,9 @@ func RegisterUIAPIRoutes(app *echo.Echo, cl *config.ModelConfigLoader, ml *model
239240
}
240241
sort.Strings(tags)
241242

243+
if tag != "" {
244+
models = gallery.GalleryElements[*gallery.GalleryModel](models).FilterByTag(tag)
245+
}
242246
if term != "" {
243247
models = gallery.GalleryElements[*gallery.GalleryModel](models).Search(term)
244248
}
@@ -726,6 +730,7 @@ func RegisterUIAPIRoutes(app *echo.Echo, cl *config.ModelConfigLoader, ml *model
726730
// Backend Gallery APIs
727731
app.GET("/api/backends", func(c echo.Context) error {
728732
term := c.QueryParam("term")
733+
tag := c.QueryParam("tag")
729734
page := c.QueryParam("page")
730735
if page == "" {
731736
page = "1"
@@ -756,6 +761,9 @@ func RegisterUIAPIRoutes(app *echo.Echo, cl *config.ModelConfigLoader, ml *model
756761
}
757762
sort.Strings(tags)
758763

764+
if tag != "" {
765+
backends = gallery.GalleryElements[*gallery.GalleryBackend](backends).FilterByTag(tag)
766+
}
759767
if term != "" {
760768
backends = gallery.GalleryElements[*gallery.GalleryBackend](backends).Search(term)
761769
}

0 commit comments

Comments
 (0)