1package common
2
3import (
4 "fmt"
5 "image"
6 "os"
7
8 tea "charm.land/bubbletea/v2"
9 "github.com/atotto/clipboard"
10 "github.com/charmbracelet/crush/internal/config"
11 "github.com/charmbracelet/crush/internal/ui/styles"
12 "github.com/charmbracelet/crush/internal/ui/util"
13 "github.com/charmbracelet/crush/internal/workspace"
14 uv "github.com/charmbracelet/ultraviolet"
15)
16
17// MaxAttachmentSize defines the maximum allowed size for file attachments (5 MB).
18const MaxAttachmentSize = int64(5 * 1024 * 1024)
19
20// AllowedImageTypes defines the permitted image file types.
21var AllowedImageTypes = []string{".jpg", ".jpeg", ".png"}
22
23// Common defines common UI options and configurations.
24type Common struct {
25 Workspace workspace.Workspace
26 Styles *styles.Styles
27}
28
29// Config returns the pure-data configuration associated with this [Common] instance.
30func (c *Common) Config() *config.Config {
31 return c.Workspace.Config()
32}
33
34// DefaultCommon returns the default common UI configurations. When the
35// workspace has a large model selected, the theme is chosen based on its
36// provider; otherwise the default theme is used.
37func DefaultCommon(ws workspace.Workspace) *Common {
38 s := styles.ThemeForProvider(largeModelProviderID(ws))
39 return &Common{
40 Workspace: ws,
41 Styles: &s,
42 }
43}
44
45// largeModelProviderID returns the provider ID of the currently selected
46// large model, or the empty string if none is set or the workspace is nil.
47func largeModelProviderID(ws workspace.Workspace) string {
48 if ws == nil {
49 return ""
50 }
51 cfg := ws.Config()
52 if cfg == nil {
53 return ""
54 }
55 return cfg.Models[config.SelectedModelTypeLarge].Provider
56}
57
58// CenterRect returns a new [Rectangle] centered within the given area with the
59// specified width and height.
60func CenterRect(area uv.Rectangle, width, height int) uv.Rectangle {
61 centerX := area.Min.X + area.Dx()/2
62 centerY := area.Min.Y + area.Dy()/2
63 minX := centerX - width/2
64 minY := centerY - height/2
65 maxX := minX + width
66 maxY := minY + height
67 return image.Rect(minX, minY, maxX, maxY)
68}
69
70// BottomLeftRect returns a new [Rectangle] positioned at the bottom-left within the given area with the
71// specified width and height.
72func BottomLeftRect(area uv.Rectangle, width, height int) uv.Rectangle {
73 minX := area.Min.X
74 maxX := minX + width
75 maxY := area.Max.Y
76 minY := maxY - height
77 return image.Rect(minX, minY, maxX, maxY)
78}
79
80// IsFileTooBig checks if the file at the given path exceeds the specified size
81// limit.
82func IsFileTooBig(filePath string, sizeLimit int64) (bool, error) {
83 fileInfo, err := os.Stat(filePath)
84 if err != nil {
85 return false, fmt.Errorf("error getting file info: %w", err)
86 }
87
88 if fileInfo.Size() > sizeLimit {
89 return true, nil
90 }
91
92 return false, nil
93}
94
95// CopyToClipboard copies the given text to the clipboard using both OSC 52
96// (terminal escape sequence) and native clipboard for maximum compatibility.
97// Returns a command that reports success to the user with the given message.
98func CopyToClipboard(text, successMessage string) tea.Cmd {
99 return CopyToClipboardWithCallback(text, successMessage, nil)
100}
101
102// CopyToClipboardWithCallback copies text to clipboard and executes a callback
103// before showing the success message.
104// This is useful when you need to perform additional actions like clearing UI state.
105func CopyToClipboardWithCallback(text, successMessage string, callback tea.Cmd) tea.Cmd {
106 return tea.Sequence(
107 tea.SetClipboard(text),
108 func() tea.Msg {
109 _ = clipboard.WriteAll(text)
110 return nil
111 },
112 callback,
113 util.ReportInfo(successMessage),
114 )
115}