Files
Fabric/internal/plugins/ai/models.go
Kayvan Sylvan 817e75853e fix: implement case-insensitive vendor and model name matching across the application
## CHANGES

- Add case-insensitive vendor lookup in VendorsManager
- Implement model name normalization in GetChatter method
- Add FilterByVendor method with case-insensitive matching
- Add FindModelNameCaseInsensitive helper for model queries
- Update group/item comparison to use case-insensitive checks
- Store vendors with lowercase keys internally
- Add comprehensive tests for case-insensitive functionality
- Fix vendor filtering for model listing command
2025-11-24 21:36:17 +08:00

85 lines
2.7 KiB
Go

package ai
import (
"fmt"
"sort"
"strings"
"github.com/danielmiessler/fabric/internal/i18n"
"github.com/danielmiessler/fabric/internal/util"
)
func NewVendorsModels() *VendorsModels {
return &VendorsModels{GroupsItemsSelectorString: util.NewGroupsItemsSelectorString(i18n.T("available_models_header"))}
}
type VendorsModels struct {
*util.GroupsItemsSelectorString
}
// FilterByVendor returns a new VendorsModels containing only the specified vendor's models.
// Vendor matching is case-insensitive (e.g., "OpenAI", "openai", and "OPENAI" all match).
// If the vendor is not found, an empty VendorsModels is returned.
func (o *VendorsModels) FilterByVendor(vendor string) *VendorsModels {
filtered := NewVendorsModels()
for _, groupItems := range o.GroupsItems {
if strings.EqualFold(groupItems.Group, vendor) {
filtered.AddGroupItems(groupItems.Group, groupItems.Items...)
break
}
}
return filtered
}
// FindModelNameCaseInsensitive returns the actual model name from available models,
// matching case-insensitively. Returns empty string if not found.
// For example, if the available models contain "gpt-4o" and user queries "GPT-4O",
// this returns "gpt-4o" (the actual model name that should be sent to the API).
func (o *VendorsModels) FindModelNameCaseInsensitive(modelQuery string) string {
for _, groupItems := range o.GroupsItems {
for _, item := range groupItems.Items {
if strings.EqualFold(item, modelQuery) {
return item
}
}
}
return ""
}
// PrintWithVendor prints models including their vendor on each line.
// When shellCompleteList is true, output is suitable for shell completion.
// Default vendor and model are highlighted with an asterisk.
func (o *VendorsModels) PrintWithVendor(shellCompleteList bool, defaultVendor, defaultModel string) {
if !shellCompleteList {
fmt.Printf("%s:\n\n", o.SelectionLabel)
}
var currentItemIndex int
sortedGroups := make([]*util.GroupItems[string], len(o.GroupsItems))
copy(sortedGroups, o.GroupsItems)
sort.SliceStable(sortedGroups, func(i, j int) bool {
return strings.ToLower(sortedGroups[i].Group) < strings.ToLower(sortedGroups[j].Group)
})
for _, groupItems := range sortedGroups {
items := make([]string, len(groupItems.Items))
copy(items, groupItems.Items)
sort.SliceStable(items, func(i, j int) bool {
return strings.ToLower(items[i]) < strings.ToLower(items[j])
})
for _, item := range items {
currentItemIndex++
if shellCompleteList {
fmt.Printf("%s|%s\n", groupItems.Group, item)
} else {
mark := " "
if strings.EqualFold(groupItems.Group, defaultVendor) && strings.EqualFold(item, defaultModel) {
mark = " *"
}
fmt.Printf("%s\t[%d]\t%s|%s\n", mark, currentItemIndex, groupItems.Group, item)
}
}
}
}