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