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