chore: support clipboard past image

Kujtim Hoxha created

Change summary

internal/tui/components/chat/editor/editor.go            | 41 ++++++++++
internal/tui/components/dialogs/filepicker/filepicker.go | 10 +
2 files changed, 47 insertions(+), 4 deletions(-)

Detailed changes

internal/tui/components/chat/editor/editor.go 🔗

@@ -2,8 +2,10 @@ package editor
 
 import (
 	"fmt"
+	"net/http"
 	"os"
 	"os/exec"
+	"path/filepath"
 	"runtime"
 	"slices"
 	"strings"
@@ -207,6 +209,45 @@ func (m *editorCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 	case OpenEditorMsg:
 		m.textarea.SetValue(msg.Text)
 		m.textarea.MoveToEnd()
+	case tea.PasteMsg:
+		path := strings.ReplaceAll(string(msg), "\\ ", " ")
+		// try to get an image
+		path, err := filepath.Abs(path)
+		if err != nil {
+			m.textarea, cmd = m.textarea.Update(msg)
+			return m, cmd
+		}
+		isAllowedType := false
+		for _, ext := range filepicker.AllowedTypes {
+			if strings.HasSuffix(path, ext) {
+				isAllowedType = true
+				break
+			}
+		}
+		if !isAllowedType {
+
+			m.textarea, cmd = m.textarea.Update(msg)
+			return m, cmd
+		}
+		tooBig, _ := filepicker.IsFileTooBig(path, filepicker.MaxAttachmentSize)
+		if tooBig {
+			m.textarea, cmd = m.textarea.Update(msg)
+			return m, cmd
+		}
+
+		content, err := os.ReadFile(path)
+		if err != nil {
+			m.textarea, cmd = m.textarea.Update(msg)
+			return m, cmd
+		}
+		mimeBufferSize := min(512, len(content))
+		mimeType := http.DetectContentType(content[:mimeBufferSize])
+		fileName := filepath.Base(path)
+		attachment := message.Attachment{FilePath: path, FileName: fileName, MimeType: mimeType, Content: content}
+		return m, util.CmdHandler(filepicker.FilePickedMsg{
+			Attachment: attachment,
+		})
+
 	case tea.KeyPressMsg:
 		cur := m.textarea.Cursor()
 		curIdx := m.textarea.Width()*cur.Y + cur.X

internal/tui/components/dialogs/filepicker/filepicker.go 🔗

@@ -21,7 +21,7 @@ import (
 )
 
 const (
-	maxAttachmentSize  = int64(5 * 1024 * 1024) // 5MB
+	MaxAttachmentSize  = int64(5 * 1024 * 1024) // 5MB
 	FilePickerID       = "filepicker"
 	fileSelectionHight = 10
 )
@@ -45,10 +45,12 @@ type model struct {
 	help            help.Model
 }
 
+var AllowedTypes = []string{".jpg", ".jpeg", ".png"}
+
 func NewFilePickerCmp(workingDir string) FilePicker {
 	t := styles.CurrentTheme()
 	fp := filepicker.New()
-	fp.AllowedTypes = []string{".jpg", ".jpeg", ".png"}
+	fp.AllowedTypes = AllowedTypes
 
 	if workingDir != "" {
 		fp.CurrentDirectory = workingDir
@@ -127,7 +129,7 @@ func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 		return m, tea.Sequence(
 			util.CmdHandler(dialogs.CloseDialogMsg{}),
 			func() tea.Msg {
-				isFileLarge, err := ValidateFileSize(path, maxAttachmentSize)
+				isFileLarge, err := IsFileTooBig(path, MaxAttachmentSize)
 				if err != nil {
 					return util.ReportError(fmt.Errorf("unable to read the image: %w", err))
 				}
@@ -222,7 +224,7 @@ func (m *model) Position() (int, int) {
 	return row, col
 }
 
-func ValidateFileSize(filePath string, sizeLimit int64) (bool, error) {
+func IsFileTooBig(filePath string, sizeLimit int64) (bool, error) {
 	fileInfo, err := os.Stat(filePath)
 	if err != nil {
 		return false, fmt.Errorf("error getting file info: %w", err)