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