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// 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}