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// IsHyper reports whether the currently selected large model is provided
59// by Hyper.
60func (c *Common) IsHyper() bool {
61 return largeModelProviderID(c.Workspace) == "hyper"
62}
63
64// CenterRect returns a new [Rectangle] centered within the given area with the
65// specified width and height.
66func CenterRect(area uv.Rectangle, width, height int) uv.Rectangle {
67 centerX := area.Min.X + area.Dx()/2
68 centerY := area.Min.Y + area.Dy()/2
69 minX := centerX - width/2
70 minY := centerY - height/2
71 maxX := minX + width
72 maxY := minY + height
73 return image.Rect(minX, minY, maxX, maxY)
74}
75
76// BottomLeftRect returns a new [Rectangle] positioned at the bottom-left within the given area with the
77// specified width and height.
78func BottomLeftRect(area uv.Rectangle, width, height int) uv.Rectangle {
79 minX := area.Min.X
80 maxX := minX + width
81 maxY := area.Max.Y
82 minY := maxY - height
83 return image.Rect(minX, minY, maxX, maxY)
84}
85
86// IsFileTooBig checks if the file at the given path exceeds the specified size
87// limit.
88func IsFileTooBig(filePath string, sizeLimit int64) (bool, error) {
89 fileInfo, err := os.Stat(filePath)
90 if err != nil {
91 return false, fmt.Errorf("error getting file info: %w", err)
92 }
93
94 if fileInfo.Size() > sizeLimit {
95 return true, nil
96 }
97
98 return false, nil
99}
100
101// CopyToClipboard copies the given text to the clipboard using both OSC 52
102// (terminal escape sequence) and native clipboard for maximum compatibility.
103// Returns a command that reports success to the user with the given message.
104func CopyToClipboard(text, successMessage string) tea.Cmd {
105 return CopyToClipboardWithCallback(text, successMessage, nil)
106}
107
108// CopyToClipboardWithCallback copies text to clipboard and executes a callback
109// before showing the success message.
110// This is useful when you need to perform additional actions like clearing UI state.
111func CopyToClipboardWithCallback(text, successMessage string, callback tea.Cmd) tea.Cmd {
112 return tea.Sequence(
113 tea.SetClipboard(text),
114 func() tea.Msg {
115 _ = clipboard.WriteAll(text)
116 return nil
117 },
118 callback,
119 util.ReportInfo(successMessage),
120 )
121}