actions.go

  1package dialog
  2
  3import (
  4	"fmt"
  5	"net/http"
  6	"os"
  7	"path/filepath"
  8
  9	tea "charm.land/bubbletea/v2"
 10	"github.com/charmbracelet/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/uiutil"
 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}
 43
 44// Messages for commands
 45type (
 46	ActionNewSession        struct{}
 47	ActionToggleHelp        struct{}
 48	ActionToggleCompactMode struct{}
 49	ActionToggleThinking    struct{}
 50	ActionExternalEditor    struct{}
 51	ActionToggleYoloMode    struct{}
 52	// ActionInitializeProject is a message to initialize a project.
 53	ActionInitializeProject struct{}
 54	ActionSummarize         struct {
 55		SessionID string
 56	}
 57	ActionPermissionResponse struct {
 58		Permission permission.PermissionRequest
 59		Action     PermissionAction
 60	}
 61	// ActionRunCustomCommand is a message to run a custom command.
 62	ActionRunCustomCommand struct {
 63		Content   string
 64		Arguments []commands.Argument
 65		Args      map[string]string // Actual argument values
 66	}
 67	// ActionRunMCPPrompt is a message to run a custom command.
 68	ActionRunMCPPrompt struct {
 69		Title       string
 70		Description string
 71		PromptID    string
 72		ClientID    string
 73		Arguments   []commands.Argument
 74		Args        map[string]string // Actual argument values
 75	}
 76)
 77
 78// Messages for API key input dialog.
 79type (
 80	ActionChangeAPIKeyState struct {
 81		State APIKeyInputState
 82	}
 83)
 84
 85// Messages for OAuth2 device flow dialog.
 86type (
 87	// ActionInitiateOAuth is sent when the device auth is initiated
 88	// successfully.
 89	ActionInitiateOAuth struct {
 90		DeviceCode      string
 91		UserCode        string
 92		ExpiresIn       int
 93		VerificationURL string
 94		Interval        int
 95	}
 96
 97	// ActionCompleteOAuth is sent when the device flow completes successfully.
 98	ActionCompleteOAuth struct {
 99		Token *oauth.Token
100	}
101
102	// ActionOAuthErrored is sent when the device flow encounters an error.
103	ActionOAuthErrored struct {
104		Error error
105	}
106)
107
108// ActionCmd represents an action that carries a [tea.Cmd] to be passed to the
109// Bubble Tea program loop.
110type ActionCmd struct {
111	Cmd tea.Cmd
112}
113
114// ActionFilePickerSelected is a message indicating a file has been selected in
115// the file picker dialog.
116type ActionFilePickerSelected struct {
117	Path string
118}
119
120// Cmd returns a command that reads the file at path and sends a
121// [message.Attachement] to the program.
122func (a ActionFilePickerSelected) Cmd() tea.Cmd {
123	path := a.Path
124	if path == "" {
125		return nil
126	}
127	return func() tea.Msg {
128		isFileLarge, err := common.IsFileTooBig(path, common.MaxAttachmentSize)
129		if err != nil {
130			return uiutil.InfoMsg{
131				Type: uiutil.InfoTypeError,
132				Msg:  fmt.Sprintf("unable to read the image: %v", err),
133			}
134		}
135		if isFileLarge {
136			return uiutil.InfoMsg{
137				Type: uiutil.InfoTypeError,
138				Msg:  "file too large, max 5MB",
139			}
140		}
141
142		content, err := os.ReadFile(path)
143		if err != nil {
144			return uiutil.InfoMsg{
145				Type: uiutil.InfoTypeError,
146				Msg:  fmt.Sprintf("unable to read the image: %v", err),
147			}
148		}
149
150		mimeBufferSize := min(512, len(content))
151		mimeType := http.DetectContentType(content[:mimeBufferSize])
152		fileName := filepath.Base(path)
153
154		return message.Attachment{
155			FilePath: path,
156			FileName: fileName,
157			MimeType: mimeType,
158			Content:  content,
159		}
160	}
161}