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