diff options
| author | adamdottv <[email protected]> | 2025-07-08 08:08:53 -0500 |
|---|---|---|
| committer | adamdottv <[email protected]> | 2025-07-08 08:09:01 -0500 |
| commit | 662d022a4859ee1c004133559ee42c5f7044dda7 (patch) | |
| tree | 304b17deceb254d0d70d29e9d46e865b1c4868cf /packages/tui/internal/components/chat | |
| parent | 9efef03919f99750277a7f15722c24d7c7958224 (diff) | |
| download | opencode-662d022a4859ee1c004133559ee42c5f7044dda7.tar.gz opencode-662d022a4859ee1c004133559ee42c5f7044dda7.zip | |
feat(tui): paste images and pdfs
Diffstat (limited to 'packages/tui/internal/components/chat')
| -rw-r--r-- | packages/tui/internal/components/chat/editor.go | 93 |
1 files changed, 76 insertions, 17 deletions
diff --git a/packages/tui/internal/components/chat/editor.go b/packages/tui/internal/components/chat/editor.go index f32e5c32a..cc31fbef2 100644 --- a/packages/tui/internal/components/chat/editor.go +++ b/packages/tui/internal/components/chat/editor.go @@ -1,9 +1,12 @@ package chat import ( + "encoding/base64" "fmt" "log/slog" + "os" "path/filepath" + "strconv" "strings" "github.com/charmbracelet/bubbles/v2/spinner" @@ -15,10 +18,10 @@ import ( "github.com/sst/opencode/internal/commands" "github.com/sst/opencode/internal/components/dialog" "github.com/sst/opencode/internal/components/textarea" - "github.com/sst/opencode/internal/image" "github.com/sst/opencode/internal/styles" "github.com/sst/opencode/internal/theme" "github.com/sst/opencode/internal/util" + "golang.design/x/clipboard" ) type EditorComponent interface { @@ -63,6 +66,57 @@ func (m *editorComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) { cmds = append(cmds, cmd) return m, tea.Batch(cmds...) } + case tea.PasteMsg: + text := string(msg) + text = strings.ReplaceAll(text, "\\", "") + text, err := strconv.Unquote(`"` + text + `"`) + if err != nil { + slog.Error("Failed to unquote text", "error", err) + m.textarea.InsertRunesFromUserInput([]rune(msg)) + return m, nil + } + if _, err := os.Stat(text); err != nil { + slog.Error("Failed to paste file", "error", err) + m.textarea.InsertRunesFromUserInput([]rune(msg)) + return m, nil + } + + filePath := text + ext := strings.ToLower(filepath.Ext(filePath)) + + mediaType := "" + switch ext { + case ".jpg": + mediaType = "image/jpeg" + case ".png", ".jpeg", ".gif", ".webp": + mediaType = "image/" + ext[1:] + case ".pdf": + mediaType = "application/pdf" + default: + mediaType = "text/plain" + } + + fileBytes, err := os.ReadFile(filePath) + if err != nil { + slog.Error("Failed to read file", "error", err) + m.textarea.InsertRunesFromUserInput([]rune(msg)) + return m, nil + } + base64EncodedFile := base64.StdEncoding.EncodeToString(fileBytes) + url := fmt.Sprintf("data:%s;base64,%s", mediaType, base64EncodedFile) + + attachment := &textarea.Attachment{ + ID: uuid.NewString(), + Display: fmt.Sprintf("<%s>", filePath), + URL: url, + Filename: filePath, + MediaType: mediaType, + } + m.textarea.InsertAttachment(attachment) + m.textarea.InsertString(" ") + case tea.ClipboardMsg: + text := string(msg) + m.textarea.InsertRunesFromUserInput([]rune(text)) case dialog.ThemeSelectedMsg: m.textarea = m.resetTextareaStyles() m.spinner = createSpinner() @@ -269,24 +323,29 @@ func (m *editorComponent) Clear() (tea.Model, tea.Cmd) { } func (m *editorComponent) Paste() (tea.Model, tea.Cmd) { - _, text, err := image.GetImageFromClipboard() - if err != nil { - slog.Error(err.Error()) + imageBytes := clipboard.Read(clipboard.FmtImage) + if imageBytes != nil { + base64EncodedFile := base64.StdEncoding.EncodeToString(imageBytes) + attachment := &textarea.Attachment{ + ID: uuid.NewString(), + Display: "<clipboard-image>", + Filename: "clipboard-image", + MediaType: "image/png", + URL: fmt.Sprintf("data:image/png;base64,%s", base64EncodedFile), + } + m.textarea.InsertAttachment(attachment) + m.textarea.InsertString(" ") return m, nil } - // if len(imageBytes) != 0 { - // attachmentName := fmt.Sprintf("clipboard-image-%d", len(m.attachments)) - // attachment := app.Attachment{ - // FilePath: attachmentName, - // FileName: attachmentName, - // Content: imageBytes, - // MimeType: "image/png", - // } - // m.attachments = append(m.attachments, attachment) - // } else { - m.textarea.InsertString(text) - // } - return m, nil + + textBytes := clipboard.Read(clipboard.FmtText) + if textBytes != nil { + m.textarea.InsertRunesFromUserInput([]rune(string(textBytes))) + return m, nil + } + + // fallback to reading the clipboard using OSC52 + return m, tea.ReadClipboard } func (m *editorComponent) Newline() (tea.Model, tea.Cmd) { |
