package chat import ( "fmt" "strings" tea "github.com/charmbracelet/bubbletea" "github.com/ollama/ollama/api" ) type chatThinkOption struct { value string label string description string } type chatThinkPicker struct { options []chatThinkOption cursor int } var chatThinkOptions = []chatThinkOption{ {value: "auto", label: "auto", description: "use the model default"}, {value: "on", label: "on", description: "enable thinking"}, {value: "off", label: "off", description: "disable thinking"}, {value: "low", label: "low", description: "use low thinking effort"}, {value: "medium", label: "medium", description: "use medium thinking effort"}, {value: "high", label: "high", description: "use high thinking effort"}, {value: "max", label: "max", description: "use maximum thinking effort"}, } func (m *chatModel) openThinkPicker() (tea.Model, tea.Cmd) { m.thinkPicker = newChatThinkPicker(m.opts.Think) m.status = "think" return *m, nil } func newChatThinkPicker(current *api.ThinkValue) *chatThinkPicker { picker := &chatThinkPicker{options: append([]chatThinkOption(nil), chatThinkOptions...)} currentValue := thinkValueLabel(current) for i, option := range picker.options { if option.value == currentValue { picker.cursor = i break } } return picker } func (m chatModel) updateThinkPicker(msg tea.KeyMsg) (tea.Model, tea.Cmd) { switch msg.Type { case tea.KeyCtrlC, tea.KeyEsc: m.thinkPicker = nil m.status = "ready" case tea.KeyEnter: return m.selectThinkOption() case tea.KeyUp: m.thinkPicker.move(-1) case tea.KeyDown: m.thinkPicker.move(1) } return m, nil } func (p *chatThinkPicker) move(delta int) { if p == nil || len(p.options) == 0 || delta == 0 { return } p.cursor = clamp(p.cursor+delta, 0, len(p.options)-1) } func (p *chatThinkPicker) selected() (chatThinkOption, bool) { if p == nil || len(p.options) == 0 { return chatThinkOption{}, false } return p.options[clamp(p.cursor, 0, len(p.options)-1)], true } func (m chatModel) selectThinkOption() (tea.Model, tea.Cmd) { option, ok := m.thinkPicker.selected() if !ok { return m, nil } m.thinkPicker = nil return m.applyThinkValue(option.value) } func (m *chatModel) handleThinkCommand(value string) (tea.Model, tea.Cmd) { return m.applyThinkValue(value) } func (m *chatModel) applyThinkValue(value string) (tea.Model, tea.Cmd) { think, label, err := parseThinkValue(value) if err != nil { m.entries = append(m.entries, newChatEntry(chatEntry{role: "error", content: err.Error(), err: err.Error()})) m.status = "error" return *m, nil } m.opts.Think = think m.status = "think " + label return *m, nil } func parseThinkValue(value string) (*api.ThinkValue, string, error) { switch strings.ToLower(strings.TrimSpace(value)) { case "", "auto", "default", "unset": return nil, "auto", nil case "on", "true", "think", "thinking": return &api.ThinkValue{Value: true}, "on", nil case "off", "false", "nothink", "no-think": return &api.ThinkValue{Value: false}, "off", nil case "low", "medium", "high", "max": value = strings.ToLower(strings.TrimSpace(value)) return &api.ThinkValue{Value: value}, value, nil default: return nil, "", fmt.Errorf("Usage: /think [auto|on|off|low|medium|high|max]") } } func thinkValueLabel(value *api.ThinkValue) string { if value == nil || value.Value == nil { return "auto" } switch v := value.Value.(type) { case bool: if v { return "on" } return "off" case string: return strings.ToLower(v) default: return "auto" } } func (m chatModel) renderThinkPicker(width int) string { picker := m.thinkPicker if picker == nil { return "" } var b strings.Builder b.WriteString(chatPickerTitleStyle.Render("Thinking mode")) b.WriteString("\n\n") for i, option := range picker.options { selected := i == picker.cursor if selected { b.WriteString(chatPickerSelectedStyle.Render("› " + option.label)) } else { b.WriteString(" ") b.WriteString(chatPickerTextStyle.Render(option.label)) } b.WriteByte('\n') b.WriteString(chatPickerMetaStyle.Render(" " + option.description)) b.WriteByte('\n') if i < len(picker.options)-1 { b.WriteByte('\n') } } b.WriteString("\n") b.WriteString(chatPickerMetaStyle.Render("↑/↓ navigate • enter select • esc cancel")) return b.String() }