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)
85
86// Messages for API key input dialog.
87type (
88 ActionChangeAPIKeyState struct {
89 State APIKeyInputState
90 }
91)
92
93// Messages for OAuth2 device flow dialog.
94type (
95 // ActionInitiateOAuth is sent when the device auth is initiated
96 // successfully.
97 ActionInitiateOAuth struct {
98 DeviceCode string
99 UserCode string
100 ExpiresIn int
101 VerificationURL string
102 Interval int
103 }
104
105 // ActionCompleteOAuth is sent when the device flow completes successfully.
106 ActionCompleteOAuth struct {
107 Token *oauth.Token
108 }
109
110 // ActionOAuthErrored is sent when the device flow encounters an error.
111 ActionOAuthErrored struct {
112 Error error
113 }
114)
115
116// ActionCmd represents an action that carries a [tea.Cmd] to be passed to the
117// Bubble Tea program loop.
118type ActionCmd struct {
119 Cmd tea.Cmd
120}
121
122// ActionFilePickerSelected is a message indicating a file has been selected in
123// the file picker dialog.
124type ActionFilePickerSelected struct {
125 Path string
126}
127
128// Cmd returns a command that reads the file at path and sends a
129// [message.Attachement] to the program.
130func (a ActionFilePickerSelected) Cmd() tea.Cmd {
131 path := a.Path
132 if path == "" {
133 return nil
134 }
135 return func() tea.Msg {
136 isFileLarge, err := common.IsFileTooBig(path, common.MaxAttachmentSize)
137 if err != nil {
138 return util.InfoMsg{
139 Type: util.InfoTypeError,
140 Msg: fmt.Sprintf("unable to read the image: %v", err),
141 }
142 }
143 if isFileLarge {
144 return util.InfoMsg{
145 Type: util.InfoTypeError,
146 Msg: "file too large, max 5MB",
147 }
148 }
149
150 content, err := os.ReadFile(path)
151 if err != nil {
152 return util.InfoMsg{
153 Type: util.InfoTypeError,
154 Msg: fmt.Sprintf("unable to read the image: %v", err),
155 }
156 }
157
158 mimeBufferSize := min(512, len(content))
159 mimeType := http.DetectContentType(content[:mimeBufferSize])
160 fileName := filepath.Base(path)
161
162 return message.Attachment{
163 FilePath: path,
164 FileName: fileName,
165 MimeType: mimeType,
166 Content: content,
167 }
168 }
169}