summaryrefslogtreecommitdiffhomepage
path: root/packages
diff options
context:
space:
mode:
authorgitpush-gitpaid <[email protected]>2026-04-07 00:39:59 -0400
committerGitHub <[email protected]>2026-04-07 04:39:59 +0000
commit3c96bf84688fa5e56977a1ff95a0b920f1749983 (patch)
tree5eab0e198d776f609a46744da60d6780dd11156b /packages
parent3ea641340761818f1ed0394635681bd0e20cc3a5 (diff)
downloadopencode-3c96bf84688fa5e56977a1ff95a0b920f1749983.tar.gz
opencode-3c96bf84688fa5e56977a1ff95a0b920f1749983.zip
feat(opencode): Add PDF attachment Drag and Drop (#16926)
Co-authored-by: Aiden Cline <[email protected]> Co-authored-by: Aiden Cline <[email protected]>
Diffstat (limited to 'packages')
-rw-r--r--packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx36
-rw-r--r--packages/opencode/src/cli/cmd/tui/feature-plugins/home/tips-view.tsx2
2 files changed, 26 insertions, 12 deletions
diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx
index 087742a97..2fef184f5 100644
--- a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx
+++ b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx
@@ -2,6 +2,7 @@ import { BoxRenderable, TextareaRenderable, MouseEvent, PasteEvent, decodePasteB
import { createEffect, createMemo, onMount, createSignal, onCleanup, on, Show, Switch, Match } from "solid-js"
import "opentui-spinner/solid"
import path from "path"
+import { fileURLToPath } from "url"
import { Filesystem } from "@/util/filesystem"
import { useLocal } from "@tui/context/local"
import { useTheme } from "@tui/context/theme"
@@ -248,7 +249,7 @@ export function Prompt(props: PromptProps) {
onSelect: async () => {
const content = await Clipboard.read()
if (content?.mime.startsWith("image/")) {
- await pasteImage({
+ await pasteAttachment({
filename: "clipboard",
mime: content.mime,
content: content.data,
@@ -771,11 +772,16 @@ export function Prompt(props: PromptProps) {
)
}
- async function pasteImage(file: { filename?: string; content: string; mime: string }) {
+ async function pasteAttachment(file: { filename?: string; filepath?: string; content: string; mime: string }) {
const currentOffset = input.visualCursor.offset
const extmarkStart = currentOffset
- const count = store.prompt.parts.filter((x) => x.type === "file" && x.mime.startsWith("image/")).length
- const virtualText = `[Image ${count + 1}]`
+ const pdf = file.mime === "application/pdf"
+ const count = store.prompt.parts.filter((x) => {
+ if (x.type !== "file") return false
+ if (pdf) return x.mime === "application/pdf"
+ return x.mime.startsWith("image/")
+ }).length
+ const virtualText = pdf ? `[PDF ${count + 1}]` : `[Image ${count + 1}]`
const extmarkEnd = extmarkStart + virtualText.length
const textToInsert = virtualText + " "
@@ -796,7 +802,7 @@ export function Prompt(props: PromptProps) {
url: `data:${file.mime};base64,${file.content}`,
source: {
type: "file",
- path: file.filename ?? "",
+ path: file.filepath ?? file.filename ?? "",
text: {
start: extmarkStart,
end: extmarkEnd,
@@ -926,7 +932,7 @@ export function Prompt(props: PromptProps) {
const content = await Clipboard.read()
if (content?.mime.startsWith("image/")) {
e.preventDefault()
- await pasteImage({
+ await pasteAttachment({
filename: "clipboard",
mime: content.mime,
content: content.data,
@@ -1012,9 +1018,16 @@ export function Prompt(props: PromptProps) {
return
}
- // trim ' from the beginning and end of the pasted content. just
- // ' and nothing else
- const filepath = pastedContent.replace(/^'+|'+$/g, "").replace(/\\ /g, " ")
+ const filepath = iife(() => {
+ const raw = pastedContent.replace(/^['"]+|['"]+$/g, "")
+ if (raw.startsWith("file://")) {
+ try {
+ return fileURLToPath(raw)
+ } catch {}
+ }
+ if (process.platform === "win32") return raw
+ return raw.replace(/\\(.)/g, "$1")
+ })
const isUrl = /^(https?):\/\//.test(filepath)
if (!isUrl) {
try {
@@ -1029,14 +1042,15 @@ export function Prompt(props: PromptProps) {
return
}
}
- if (mime.startsWith("image/")) {
+ if (mime.startsWith("image/") || mime === "application/pdf") {
event.preventDefault()
const content = await Filesystem.readArrayBuffer(filepath)
.then((buffer) => Buffer.from(buffer).toString("base64"))
.catch(() => {})
if (content) {
- await pasteImage({
+ await pasteAttachment({
filename,
+ filepath,
mime,
content,
})
diff --git a/packages/opencode/src/cli/cmd/tui/feature-plugins/home/tips-view.tsx b/packages/opencode/src/cli/cmd/tui/feature-plugins/home/tips-view.tsx
index a87e4ed2b..1a9d907bb 100644
--- a/packages/opencode/src/cli/cmd/tui/feature-plugins/home/tips-view.tsx
+++ b/packages/opencode/src/cli/cmd/tui/feature-plugins/home/tips-view.tsx
@@ -55,7 +55,7 @@ const TIPS = [
"Use {highlight}/undo{/highlight} to revert the last message and file changes",
"Use {highlight}/redo{/highlight} to restore previously undone messages and file changes",
"Run {highlight}/share{/highlight} to create a public link to your conversation at opencode.ai",
- "Drag and drop images into the terminal to add them as context",
+ "Drag and drop images or PDFs into the terminal to add them as context",
"Press {highlight}Ctrl+V{/highlight} to paste images from your clipboard into the prompt",
"Press {highlight}Ctrl+X E{/highlight} or {highlight}/editor{/highlight} to compose messages in your external editor",
"Run {highlight}/init{/highlight} to auto-generate project rules based on your codebase",