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/skills"
18 "github.com/charmbracelet/crush/internal/ui/common"
19 "github.com/charmbracelet/crush/internal/ui/util"
20)
21
22// ActionClose is a message to close the current dialog.
23type ActionClose struct{}
24
25// ActionQuit is a message to quit the application.
26type ActionQuit = tea.QuitMsg
27
28// ActionOpenDialog is a message to open a dialog.
29type ActionOpenDialog struct {
30 DialogID string
31}
32
33// ActionSelectSession is a message indicating a session has been selected.
34type ActionSelectSession struct {
35 Session session.Session
36}
37
38// ActionSelectModel is a message indicating a model has been selected.
39type ActionSelectModel struct {
40 Provider catwalk.Provider
41 Model config.SelectedModel
42 ModelType config.SelectedModelType
43 ReAuthenticate bool
44}
45
46// Messages for commands
47type (
48 ActionNewSession struct{}
49 ActionToggleHelp struct{}
50 ActionToggleCompactMode struct{}
51 ActionToggleThinking struct{}
52 ActionTogglePills struct{}
53 ActionExternalEditor struct{}
54 ActionToggleYoloMode struct{}
55 ActionToggleNotifications struct{}
56 ActionSelectNotificationStyle struct {
57 Style string
58 }
59 ActionToggleTransparentBackground struct{}
60 ActionInitializeProject struct{}
61 ActionSummarize struct {
62 SessionID string
63 }
64 // ActionSelectReasoningEffort is a message indicating a reasoning effort
65 // has been selected.
66 ActionSelectReasoningEffort struct {
67 Effort string
68 }
69 ActionPermissionResponse struct {
70 Permission permission.PermissionRequest
71 Action PermissionAction
72 }
73 // ActionRunCustomCommand is a message to run a custom command.
74 ActionRunCustomCommand struct {
75 Content string
76 Arguments []commands.Argument
77 Args map[string]string // Actual argument values
78 Skill *skills.Skill // Set when this is a skill command
79 }
80 // ActionAttachSkill is sent when a skill is selected from the commands
81 // dialog to be attached to the conversation as a markdown attachment.
82 ActionAttachSkill struct {
83 ID string
84 Name string
85 }
86 // ActionRunMCPPrompt is a message to run a custom command.
87 ActionRunMCPPrompt struct {
88 Title string
89 Description string
90 PromptID string
91 ClientID string
92 Arguments []commands.Argument
93 Args map[string]string // Actual argument values
94 }
95 // ActionEnableDockerMCP is a message to enable Docker MCP.
96 ActionEnableDockerMCP struct{}
97 // ActionDisableDockerMCP is a message to disable Docker MCP.
98 ActionDisableDockerMCP struct{}
99)
100
101// Messages for API key input dialog.
102type (
103 ActionChangeAPIKeyState struct {
104 State APIKeyInputState
105 }
106)
107
108// Messages for OAuth2 device flow dialog.
109type (
110 // ActionInitiateOAuth is sent when the device auth is initiated
111 // successfully.
112 ActionInitiateOAuth struct {
113 DeviceCode string
114 UserCode string
115 ExpiresIn int
116 VerificationURL string
117 Interval int
118 }
119
120 // ActionCompleteOAuth is sent when the device flow completes successfully.
121 ActionCompleteOAuth struct {
122 Token *oauth.Token
123 }
124
125 // ActionOAuthErrored is sent when the device flow encounters an error.
126 ActionOAuthErrored struct {
127 Error error
128 }
129)
130
131// ActionCmd represents an action that carries a [tea.Cmd] to be passed to the
132// Bubble Tea program loop.
133type ActionCmd struct {
134 Cmd tea.Cmd
135}
136
137// ActionFilePickerSelected is a message indicating a file has been selected in
138// the file picker dialog.
139type ActionFilePickerSelected struct {
140 Path string
141}
142
143// Cmd returns a command that reads the file at path and sends a
144// [message.Attachement] to the program.
145func (a ActionFilePickerSelected) Cmd() tea.Cmd {
146 path := a.Path
147 if path == "" {
148 return nil
149 }
150 return func() tea.Msg {
151 isFileLarge, err := common.IsFileTooBig(path, common.MaxAttachmentSize)
152 if err != nil {
153 return util.InfoMsg{
154 Type: util.InfoTypeError,
155 Msg: fmt.Sprintf("unable to read the image: %v", err),
156 }
157 }
158 if isFileLarge {
159 return util.InfoMsg{
160 Type: util.InfoTypeError,
161 Msg: "file too large, max 5MB",
162 }
163 }
164
165 content, err := os.ReadFile(path)
166 if err != nil {
167 return util.InfoMsg{
168 Type: util.InfoTypeError,
169 Msg: fmt.Sprintf("unable to read the image: %v", err),
170 }
171 }
172
173 mimeBufferSize := min(512, len(content))
174 mimeType := http.DetectContentType(content[:mimeBufferSize])
175 fileName := filepath.Base(path)
176
177 return message.Attachment{
178 FilePath: path,
179 FileName: fileName,
180 MimeType: mimeType,
181 Content: content,
182 }
183 }
184}