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	ActionToggleTransparentBackground struct{}
 57	ActionInitializeProject           struct{}
 58	ActionSummarize                   struct {
 59		SessionID string
 60	}
 61	// ActionSelectReasoningEffort is a message indicating a reasoning effort
 62	// has been selected.
 63	ActionSelectReasoningEffort struct {
 64		Effort string
 65	}
 66	ActionPermissionResponse struct {
 67		Permission permission.PermissionRequest
 68		Action     PermissionAction
 69	}
 70	// ActionRunCustomCommand is a message to run a custom command.
 71	ActionRunCustomCommand struct {
 72		Content   string
 73		Arguments []commands.Argument
 74		Args      map[string]string // Actual argument values
 75		Skill     *skills.Skill     // Set when this is a skill command
 76	}
 77	// ActionAttachSkill is sent when a skill is selected from the commands
 78	// dialog to be attached to the conversation as a markdown attachment.
 79	ActionAttachSkill struct {
 80		ID   string
 81		Name string
 82	}
 83	// ActionRunMCPPrompt is a message to run a custom command.
 84	ActionRunMCPPrompt struct {
 85		Title       string
 86		Description string
 87		PromptID    string
 88		ClientID    string
 89		Arguments   []commands.Argument
 90		Args        map[string]string // Actual argument values
 91	}
 92	// ActionEnableDockerMCP is a message to enable Docker MCP.
 93	ActionEnableDockerMCP struct{}
 94	// ActionDisableDockerMCP is a message to disable Docker MCP.
 95	ActionDisableDockerMCP struct{}
 96)
 97
 98// Messages for API key input dialog.
 99type (
100	ActionChangeAPIKeyState struct {
101		State APIKeyInputState
102	}
103)
104
105// Messages for OAuth2 device flow dialog.
106type (
107	// ActionInitiateOAuth is sent when the device auth is initiated
108	// successfully.
109	ActionInitiateOAuth struct {
110		DeviceCode      string
111		UserCode        string
112		ExpiresIn       int
113		VerificationURL string
114		Interval        int
115	}
116
117	// ActionCompleteOAuth is sent when the device flow completes successfully.
118	ActionCompleteOAuth struct {
119		Token *oauth.Token
120	}
121
122	// ActionOAuthErrored is sent when the device flow encounters an error.
123	ActionOAuthErrored struct {
124		Error error
125	}
126)
127
128// ActionCmd represents an action that carries a [tea.Cmd] to be passed to the
129// Bubble Tea program loop.
130type ActionCmd struct {
131	Cmd tea.Cmd
132}
133
134// ActionFilePickerSelected is a message indicating a file has been selected in
135// the file picker dialog.
136type ActionFilePickerSelected struct {
137	Path string
138}
139
140// Cmd returns a command that reads the file at path and sends a
141// [message.Attachement] to the program.
142func (a ActionFilePickerSelected) Cmd() tea.Cmd {
143	path := a.Path
144	if path == "" {
145		return nil
146	}
147	return func() tea.Msg {
148		isFileLarge, err := common.IsFileTooBig(path, common.MaxAttachmentSize)
149		if err != nil {
150			return util.InfoMsg{
151				Type: util.InfoTypeError,
152				Msg:  fmt.Sprintf("unable to read the image: %v", err),
153			}
154		}
155		if isFileLarge {
156			return util.InfoMsg{
157				Type: util.InfoTypeError,
158				Msg:  "file too large, max 5MB",
159			}
160		}
161
162		content, err := os.ReadFile(path)
163		if err != nil {
164			return util.InfoMsg{
165				Type: util.InfoTypeError,
166				Msg:  fmt.Sprintf("unable to read the image: %v", err),
167			}
168		}
169
170		mimeBufferSize := min(512, len(content))
171		mimeType := http.DetectContentType(content[:mimeBufferSize])
172		fileName := filepath.Base(path)
173
174		return message.Attachment{
175			FilePath: path,
176			FileName: fileName,
177			MimeType: mimeType,
178			Content:  content,
179		}
180	}
181}