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