common.go

  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}