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