constructor.go

v0.6.0
Doc Versions Source
1
package tui
2
3
import (
4
	"sourcecraft.dev/bigbes/claudio/cache"
5
	"sourcecraft.dev/bigbes/claudio/config"
6
	"sourcecraft.dev/bigbes/claudio/copilot"
7
	"sourcecraft.dev/bigbes/claudio/provider"
8
	"github.com/charmbracelet/bubbles/textinput"
9
)
10
11
func buildEntries(cfg *config.Config, showHidden bool) []entry {
12
	seen := make(map[string]bool)
13
	var entries []entry
14
	for _, key := range provider.Order {
15
		if !showHidden && cfg.Providers[key].Hidden {
16
			seen[key] = true
17
			continue
18
		}
19
		p := provider.Registry[key]
20
		// Merge ollama and ollama-cloud into a single entry with toggle.
21
		if key == "ollama" {
22
			entries = append(entries, entry{Key: key, Name: p.Name, Description: p.Description, AltKey: "ollama-cloud"})
23
			seen[key] = true
24
			seen["ollama-cloud"] = true
25
			continue
26
		}
27
		// Skip ollama-cloud as it's handled with ollama.
28
		if key == "ollama-cloud" {
29
			continue
30
		}
31
		// Merge zai and zai-coding into a single entry with toggle.
32
		if key == "zai" {
33
			entries = append(entries, entry{Key: key, Name: p.Name, Description: p.Description, AltKey: "zai-coding"})
34
			seen[key] = true
35
			seen["zai-coding"] = true
36
			continue
37
		}
38
		// Skip zai-coding as it's handled with zai.
39
		if key == "zai-coding" {
40
			continue
41
		}
42
		// Merge kimi variants into a single entry with toggle.
43
		if key == "kimi" {
44
			entries = append(entries, entry{
45
				Key:         key,
46
				Name:        p.Name,
47
				Description: "Kimi K2.5 / K2 via Moonshot",
48
				AltKeys:     []string{"kimi-api-cn", "kimi-api-intl"},
49
			})
50
			seen[key] = true
51
			seen["kimi-api-cn"] = true
52
			seen["kimi-api-intl"] = true
53
			continue
54
		}
55
		// Skip kimi-api-cn and kimi-api-intl as they're handled with kimi.
56
		if key == "kimi-api-cn" || key == "kimi-api-intl" {
57
			continue
58
		}
59
		entries = append(entries, entry{Key: key, Name: p.Name, Description: p.Description})
60
		seen[key] = true
61
	}
62
	for key, pc := range cfg.Providers {
63
		if seen[key] || (!showHidden && pc.Hidden) {
64
			continue
65
		}
66
		name := pc.Name
67
		if name == "" {
68
			name = key
69
		}
70
		desc := pc.Description
71
		if desc == "" && pc.Compat == "openai" {
72
			desc = "Custom OpenAI-compatible provider"
73
		}
74
		entries = append(entries, entry{Key: key, Name: name, Description: desc})
75
	}
76
	return entries
77
}
78
79
func New(cfg *config.Config, cdb *cache.DB) Model {
80
	ti := textinput.New()
81
	ti.Placeholder = "sk-..."
82
	ti.CharLimit = 256
83
	ti.Width = 60
84
	ti.EchoMode = textinput.EchoPassword
85
	ti.EchoCharacter = '•'
86
87
	entries := buildEntries(cfg, false)
88
89
	cursor := 0
90
	// Default to cloud mode. If ActiveProvider is "ollama", switch to local mode.
91
	// This way the last chosen variant is persisted via ActiveProvider.
92
	ollamaCloud := true
93
	// Default to Coding mode. If ActiveProvider is "zai", switch to API mode.
94
	// This way the last chosen variant is persisted via ActiveProvider.
95
	zaiCoding := true
96
	// Default to Coding (0). Other variants: 1 = api-cn, 2 = api-intl
97
	kimiVariant := 0
98
	for i, e := range entries {
99
		if e.Key == cfg.ActiveProvider {
100
			cursor = i
101
			// If the active provider is the local ollama, set cloud mode to false
102
			if e.Key == "ollama" && e.AltKey == "ollama-cloud" {
103
				ollamaCloud = false
104
			}
105
			// If the active provider is zai (API), set coding mode to false
106
			if e.Key == "zai" && e.AltKey == "zai-coding" {
107
				zaiCoding = false
108
			}
109
			// If the active provider is a kimi variant, set the appropriate index
110
			if e.Key == "kimi" && len(e.AltKeys) == 2 {
111
				if cfg.ActiveProvider == "kimi-api-cn" {
112
					kimiVariant = 1
113
				} else if cfg.ActiveProvider == "kimi-api-intl" {
114
					kimiVariant = 2
115
				} else {
116
					kimiVariant = 0
117
				}
118
			}
119
			break
120
		}
121
		// If active provider is ollama-cloud, find the merged ollama entry.
122
		if e.Key == "ollama" && e.AltKey == "ollama-cloud" && cfg.ActiveProvider == "ollama-cloud" {
123
			cursor = i
124
			ollamaCloud = true
125
			break
126
		}
127
		// If active provider is zai-coding, find the merged zai entry.
128
		if e.Key == "zai" && e.AltKey == "zai-coding" && cfg.ActiveProvider == "zai-coding" {
129
			cursor = i
130
			zaiCoding = true
131
			break
132
		}
133
		// If active provider is a kimi variant, find the merged kimi entry.
134
		if e.Key == "kimi" && len(e.AltKeys) == 2 {
135
			if cfg.ActiveProvider == "kimi-api-cn" {
136
				cursor = i
137
				kimiVariant = 1
138
				break
139
			}
140
			if cfg.ActiveProvider == "kimi-api-intl" {
141
				cursor = i
142
				kimiVariant = 2
143
				break
144
			}
145
		}
146
	}
147
148
	fi := textinput.New()
149
	fi.Placeholder = "filter..."
150
	fi.CharLimit = 64
151
	fi.Width = 30
152
153
	return Model{
154
		cfg:         cfg,
155
		cache:       cdb,
156
		entries:     entries,
157
		phase:       phaseSelect,
158
		cursor:      cursor,
159
		ollamaCloud: ollamaCloud,
160
		zaiCoding:   zaiCoding,
161
		kimiVariant: kimiVariant,
162
		input:       ti,
163
		filterInput: fi,
164
	}
165
}
166
167
// NewModelPicker creates a Model that goes directly to the copilot model selection phase.
168
func NewModelPicker(cfg *config.Config, cdb *cache.DB, models []copilot.CopilotModel) Model {
169
	families, grouped := copilot.VendorFamilies(models)
170
	items := make(map[string][]PickerItem)
171
	for fam, cms := range grouped {
172
		for _, cm := range cms {
173
			items[fam] = append(items[fam], PickerItem{ID: cm.ID, Name: cm.DisplayName(), ContextLength: cm.ContextWindow})
174
		}
175
	}
176
	m := Model{
177
		cfg:            cfg,
178
		cache:          cdb,
179
		phase:          phaseSelectModel,
180
		pickerProvider: "copilot",
181
		families:       families,
182
		familyModels:   items,
183
	}
184
	m.preSelectModel()
185
	return m
186
}
187

Source Files