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