actions.go

  1package dialog
  2
  3import (
  4	"fmt"
  5	"net/http"
  6	"os"
  7	"path/filepath"
  8
  9	tea "charm.land/bubbletea/v2"
 10	"charm.land/catwalk/pkg/catwalk"
 11	"github.com/charmbracelet/crush/internal/commands"
 12	"github.com/charmbracelet/crush/internal/config"
 13	"github.com/charmbracelet/crush/internal/message"
 14	"github.com/charmbracelet/crush/internal/oauth"
 15	"github.com/charmbracelet/crush/internal/permission"
 16	"github.com/charmbracelet/crush/internal/session"
 17	"github.com/charmbracelet/crush/internal/ui/common"
 18	"github.com/charmbracelet/crush/internal/ui/util"
 19)
 20
 21// ActionClose is a message to close the current dialog.
 22type ActionClose struct{}
 23
 24// ActionQuit is a message to quit the application.
 25type ActionQuit = tea.QuitMsg
 26
 27// ActionOpenDialog is a message to open a dialog.
 28type ActionOpenDialog struct {
 29	DialogID string
 30}
 31
 32// ActionSelectSession is a message indicating a session has been selected.
 33type ActionSelectSession struct {
 34	Session session.Session
 35}
 36
 37// ActionSelectModel is a message indicating a model has been selected.
 38type ActionSelectModel struct {
 39	Provider       catwalk.Provider
 40	Model          config.SelectedModel
 41	ModelType      config.SelectedModelType
 42	ReAuthenticate bool
 43}
 44
 45// Messages for commands
 46type (
 47	ActionNewSession                  struct{}
 48	ActionToggleHelp                  struct{}
 49	ActionToggleCompactMode           struct{}
 50	ActionToggleThinking              struct{}
 51	ActionTogglePills                 struct{}
 52	ActionExternalEditor              struct{}
 53	ActionToggleYoloMode              struct{}
 54	ActionToggleNotifications         struct{}
 55	ActionToggleTransparentBackground struct{}
 56	ActionInitializeProject           struct{}
 57	ActionSummarize                   struct {
 58		SessionID string
 59	}
 60	// ActionSelectReasoningEffort is a message indicating a reasoning effort
 61	// has been selected.
 62	ActionSelectReasoningEffort struct {
 63		Effort string
 64	}
 65	ActionPermissionResponse struct {
 66		Permission permission.PermissionRequest
 67		Action     PermissionAction
 68	}
 69	// ActionRunCustomCommand is a message to run a custom command.
 70	ActionRunCustomCommand struct {
 71		Content   string
 72		Arguments []commands.Argument
 73		Args      map[string]string // Actual argument values
 74	}
 75	// ActionRunMCPPrompt is a message to run a custom command.
 76	ActionRunMCPPrompt struct {
 77		Title       string
 78		Description string
 79		PromptID    string
 80		ClientID    string
 81		Arguments   []commands.Argument
 82		Args        map[string]string // Actual argument values
 83	}
 84	// ActionEnableDockerMCP is a message to enable Docker MCP.
 85	ActionEnableDockerMCP struct{}
 86	// ActionDisableDockerMCP is a message to disable Docker MCP.
 87	ActionDisableDockerMCP struct{}
 88)
 89
 90// Messages for API key input dialog.
 91type (
 92	ActionChangeAPIKeyState struct {
 93		State APIKeyInputState
 94	}
 95)
 96
 97// Messages for OAuth2 device flow dialog.
 98type (
 99	// ActionInitiateOAuth is sent when the device auth is initiated
100	// successfully.
101	ActionInitiateOAuth struct {
102		DeviceCode      string
103		UserCode        string
104		ExpiresIn       int
105		VerificationURL string
106		Interval        int
107	}
108
109	// ActionCompleteOAuth is sent when the device flow completes successfully.
110	ActionCompleteOAuth struct {
111		Token *oauth.Token
112	}
113
114	// ActionOAuthErrored is sent when the device flow encounters an error.
115	ActionOAuthErrored struct {
116		Error error
117	}
118)
119
120// ActionCmd represents an action that carries a [tea.Cmd] to be passed to the
121// Bubble Tea program loop.
122type ActionCmd struct {
123	Cmd tea.Cmd
124}
125
126// ActionFilePickerSelected is a message indicating a file has been selected in
127// the file picker dialog.
128type ActionFilePickerSelected struct {
129	Path string
130}
131
132// Cmd returns a command that reads the file at path and sends a
133// [message.Attachement] to the program.
134func (a ActionFilePickerSelected) Cmd() tea.Cmd {
135	path := a.Path
136	if path == "" {
137		return nil
138	}
139	return func() tea.Msg {
140		isFileLarge, err := common.IsFileTooBig(path, common.MaxAttachmentSize)
141		if err != nil {
142			return util.InfoMsg{
143				Type: util.InfoTypeError,
144				Msg:  fmt.Sprintf("unable to read the image: %v", err),
145			}
146		}
147		if isFileLarge {
148			return util.InfoMsg{
149				Type: util.InfoTypeError,
150				Msg:  "file too large, max 5MB",
151			}
152		}
153
154		content, err := os.ReadFile(path)
155		if err != nil {
156			return util.InfoMsg{
157				Type: util.InfoTypeError,
158				Msg:  fmt.Sprintf("unable to read the image: %v", err),
159			}
160		}
161
162		mimeBufferSize := min(512, len(content))
163		mimeType := http.DetectContentType(content[:mimeBufferSize])
164		fileName := filepath.Base(path)
165
166		return message.Attachment{
167			FilePath: path,
168			FileName: fileName,
169			MimeType: mimeType,
170			Content:  content,
171		}
172	}
173}