1package dialog
2
3import (
4 "charm.land/catwalk/pkg/catwalk"
5 "charm.land/lipgloss/v2"
6 "git.secluded.site/crush/internal/config"
7 "git.secluded.site/crush/internal/ui/common"
8 "git.secluded.site/crush/internal/ui/list"
9 "git.secluded.site/crush/internal/ui/styles"
10 "github.com/charmbracelet/x/ansi"
11 "github.com/sahilm/fuzzy"
12)
13
14// ModelGroup represents a group of model items.
15type ModelGroup struct {
16 *list.Versioned
17 Title string
18 Items []*ModelItem
19 configured bool
20 t *styles.Styles
21}
22
23// NewModelGroup creates a new ModelGroup.
24func NewModelGroup(t *styles.Styles, title string, configured bool, items ...*ModelItem) ModelGroup {
25 return ModelGroup{
26 Versioned: list.NewVersioned(),
27 Title: title,
28 Items: items,
29 configured: configured,
30 t: t,
31 }
32}
33
34// Finished implements list.Item. Model groups are immutable headers.
35func (m *ModelGroup) Finished() bool {
36 return true
37}
38
39// AppendItems appends [ModelItem]s to the group.
40func (m *ModelGroup) AppendItems(items ...*ModelItem) {
41 m.Items = append(m.Items, items...)
42}
43
44// Render implements [list.Item].
45func (m *ModelGroup) Render(width int) string {
46 var configured string
47 if m.configured {
48 configuredIcon := m.t.ToolCallSuccess.Render()
49 configuredText := m.t.Dialog.Models.ConfiguredText.Render("Configured")
50 configured = configuredIcon + " " + configuredText
51 }
52
53 title := " " + m.Title + " "
54 title = ansi.Truncate(title, max(0, width-lipgloss.Width(configured)-1), "…")
55
56 return common.Section(m.t, title, width, configured)
57}
58
59// ModelItem represents a list item for a model type.
60type ModelItem struct {
61 *list.Versioned
62
63 prov catwalk.Provider
64 model catwalk.Model
65 modelType ModelType
66
67 cache map[int]string
68 t *styles.Styles
69 m fuzzy.Match
70 focused bool
71 showProvider bool
72}
73
74// Finished implements list.Item. Model items are render-stable
75// outside of explicit SetFocused / SetMatch.
76func (m *ModelItem) Finished() bool {
77 return true
78}
79
80// SelectedModel returns this model item as a [config.SelectedModel] instance.
81func (m *ModelItem) SelectedModel() config.SelectedModel {
82 return config.SelectedModel{
83 Model: m.model.ID,
84 Provider: string(m.prov.ID),
85 ReasoningEffort: m.model.DefaultReasoningEffort,
86 MaxTokens: m.model.DefaultMaxTokens,
87 }
88}
89
90// SelectedModelType returns the type of model represented by this item.
91func (m *ModelItem) SelectedModelType() config.SelectedModelType {
92 return m.modelType.Config()
93}
94
95var _ ListItem = &ModelItem{}
96
97// NewModelItem creates a new ModelItem.
98func NewModelItem(t *styles.Styles, prov catwalk.Provider, model catwalk.Model, typ ModelType, showProvider bool) *ModelItem {
99 return &ModelItem{
100 Versioned: list.NewVersioned(),
101 prov: prov,
102 model: model,
103 modelType: typ,
104 t: t,
105 cache: make(map[int]string),
106 showProvider: showProvider,
107 }
108}
109
110// Filter implements ListItem.
111func (m *ModelItem) Filter() string {
112 return m.model.Name
113}
114
115// ID implements ListItem.
116func (m *ModelItem) ID() string {
117 return modelKey(string(m.prov.ID), m.model.ID)
118}
119
120// Render implements ListItem.
121func (m *ModelItem) Render(width int) string {
122 var providerInfo string
123 if m.showProvider {
124 providerInfo = string(m.prov.Name)
125 }
126 styles := ListItemStyles{
127 ItemBlurred: m.t.Dialog.NormalItem,
128 ItemFocused: m.t.Dialog.SelectedItem,
129 InfoTextBlurred: m.t.Dialog.ListItem.InfoBlurred,
130 InfoTextFocused: m.t.Dialog.ListItem.InfoFocused,
131 }
132 return renderItem(styles, m.model.Name, providerInfo, m.focused, width, m.cache, &m.m)
133}
134
135// SetFocused implements ListItem.
136func (m *ModelItem) SetFocused(focused bool) {
137 if m.focused == focused {
138 return
139 }
140 m.cache = nil
141 m.focused = focused
142 if m.Versioned != nil {
143 m.Bump()
144 }
145}
146
147// SetMatch implements ListItem.
148func (m *ModelItem) SetMatch(fm fuzzy.Match) {
149 if sameFuzzyMatch(m.m, fm) {
150 return
151 }
152 m.cache = nil
153 m.m = fm
154 if m.Versioned != nil {
155 m.Bump()
156 }
157}