Skip to content

Commit c904b64

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 2b12875 commit c904b64

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
@@ -130,13 +130,12 @@ export default function Models() {
130130
const filterVal = params.filter !== undefined ? params.filter : filter
131131
const sortVal = params.sort !== undefined ? params.sort : sort
132132
const backendVal = params.backendFilter !== undefined ? params.backendFilter : backendFilter
133-
// Combine search text and filter into 'term' param
134-
const term = searchVal || filterVal || ''
135133
const queryParams = {
136134
page: params.page || page,
137135
items: 9,
138136
}
139-
if (term) queryParams.term = term
137+
if (filterVal) queryParams.tag = filterVal
138+
if (searchVal) queryParams.term = searchVal
140139
if (backendVal) queryParams.backend = backendVal
141140
if (sortVal) {
142141
queryParams.sort = sortVal

core/http/routes/ui_api.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,7 @@ func RegisterUIAPIRoutes(app *echo.Echo, cl *config.ModelConfigLoader, ml *model
210210
// Model Gallery APIs (admin only)
211211
app.GET("/api/models", func(c echo.Context) error {
212212
term := c.QueryParam("term")
213+
tag := c.QueryParam("tag")
213214
page := c.QueryParam("page")
214215
if page == "" {
215216
page = "1"
@@ -253,6 +254,9 @@ func RegisterUIAPIRoutes(app *echo.Echo, cl *config.ModelConfigLoader, ml *model
253254
}
254255
sort.Strings(backendNames)
255256

257+
if tag != "" {
258+
models = gallery.GalleryElements[*gallery.GalleryModel](models).FilterByTag(tag)
259+
}
256260
if term != "" {
257261
models = gallery.GalleryElements[*gallery.GalleryModel](models).Search(term)
258262
}
@@ -776,6 +780,7 @@ func RegisterUIAPIRoutes(app *echo.Echo, cl *config.ModelConfigLoader, ml *model
776780
// Backend Gallery APIs
777781
app.GET("/api/backends", func(c echo.Context) error {
778782
term := c.QueryParam("term")
783+
tag := c.QueryParam("tag")
779784
page := c.QueryParam("page")
780785
if page == "" {
781786
page = "1"
@@ -806,6 +811,9 @@ func RegisterUIAPIRoutes(app *echo.Echo, cl *config.ModelConfigLoader, ml *model
806811
}
807812
sort.Strings(tags)
808813

814+
if tag != "" {
815+
backends = gallery.GalleryElements[*gallery.GalleryBackend](backends).FilterByTag(tag)
816+
}
809817
if term != "" {
810818
backends = gallery.GalleryElements[*gallery.GalleryBackend](backends).Search(term)
811819
}

0 commit comments

Comments
 (0)