summaryrefslogtreecommitdiffhomepage
path: root/packages/tui/internal/components/chat
diff options
context:
space:
mode:
authoradamdottv <[email protected]>2025-07-08 08:08:53 -0500
committeradamdottv <[email protected]>2025-07-08 08:09:01 -0500
commit662d022a4859ee1c004133559ee42c5f7044dda7 (patch)
tree304b17deceb254d0d70d29e9d46e865b1c4868cf /packages/tui/internal/components/chat
parent9efef03919f99750277a7f15722c24d7c7958224 (diff)
downloadopencode-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.go93
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) {