From 721033bd739f0e98079f73f460fc77befc69d4ce Mon Sep 17 00:00:00 2001 From: LukeParkerDev <10430890+Hona@users.noreply.github.com> Date: Thu, 17 Sep 2026 13:45:58 +1000 Subject: [PATCH] feat(app): attach any file and deliver unsupported ones by path Drag, paste, and pick no longer reject files the model cannot read. On submit each attachment is delivered inline when the selected model reads it natively; otherwise the prompt references it by path: the source path on a local server, or a copy staged under the server tmp directory (from /api/info) via fs.write on a remote one. Removes the accepted-type filters and the unsupported-attachment toast. --- .../src/composer/attachments/attachments.ts | 88 +++---------------- .../app/src/composer/attachments/deliver.ts | 70 +++++++++++++++ packages/app/src/composer/comment-note.ts | 20 +++++ .../app/src/composer/composer.stories.tsx | 2 +- packages/app/src/composer/editor/editor.tsx | 1 - packages/app/src/composer/model.ts | 7 +- packages/app/src/composer/request.test.ts | 61 ++++++------- packages/app/src/composer/request.ts | 36 +++++--- packages/app/src/composer/submit.test.ts | 10 +++ packages/app/src/composer/submit.ts | 51 ++++++----- packages/app/src/desktop.ts | 1 - packages/app/src/runtime/i18n/am.ts | 2 - packages/app/src/runtime/i18n/ar.ts | 2 - packages/app/src/runtime/i18n/az.ts | 2 - packages/app/src/runtime/i18n/bg.ts | 2 - packages/app/src/runtime/i18n/bn.ts | 2 - packages/app/src/runtime/i18n/br.ts | 2 - packages/app/src/runtime/i18n/bs.ts | 2 - packages/app/src/runtime/i18n/ca.ts | 2 - packages/app/src/runtime/i18n/cs.ts | 2 - packages/app/src/runtime/i18n/da.ts | 2 - packages/app/src/runtime/i18n/de.ts | 2 - packages/app/src/runtime/i18n/dv.ts | 3 - packages/app/src/runtime/i18n/dz.ts | 3 - packages/app/src/runtime/i18n/el.ts | 2 - packages/app/src/runtime/i18n/en.ts | 2 - packages/app/src/runtime/i18n/es.ts | 3 - packages/app/src/runtime/i18n/et.ts | 2 - packages/app/src/runtime/i18n/fa.ts | 2 - packages/app/src/runtime/i18n/fi.ts | 2 - packages/app/src/runtime/i18n/fo.ts | 2 - packages/app/src/runtime/i18n/fr.ts | 3 - packages/app/src/runtime/i18n/he.ts | 2 - packages/app/src/runtime/i18n/hi.ts | 2 - packages/app/src/runtime/i18n/hr.ts | 2 - packages/app/src/runtime/i18n/hu.ts | 2 - packages/app/src/runtime/i18n/hy.ts | 2 - packages/app/src/runtime/i18n/id.ts | 2 - packages/app/src/runtime/i18n/is.ts | 2 - packages/app/src/runtime/i18n/it.ts | 2 - packages/app/src/runtime/i18n/ja.ts | 2 - packages/app/src/runtime/i18n/ka.ts | 2 - packages/app/src/runtime/i18n/km.ts | 2 - packages/app/src/runtime/i18n/ko.ts | 2 - packages/app/src/runtime/i18n/lo.ts | 2 - packages/app/src/runtime/i18n/lt.ts | 2 - packages/app/src/runtime/i18n/lv.ts | 2 - packages/app/src/runtime/i18n/mk.ts | 2 - packages/app/src/runtime/i18n/mn.ts | 2 - packages/app/src/runtime/i18n/ms.ts | 2 - packages/app/src/runtime/i18n/my.ts | 3 - packages/app/src/runtime/i18n/ne.ts | 2 - packages/app/src/runtime/i18n/nl.ts | 3 - packages/app/src/runtime/i18n/no.ts | 2 - packages/app/src/runtime/i18n/pa.ts | 3 - packages/app/src/runtime/i18n/pl.ts | 2 - packages/app/src/runtime/i18n/ro.ts | 2 - packages/app/src/runtime/i18n/ru.ts | 2 - packages/app/src/runtime/i18n/si.ts | 2 - packages/app/src/runtime/i18n/sk.ts | 2 - packages/app/src/runtime/i18n/sl.ts | 2 - packages/app/src/runtime/i18n/sq.ts | 3 - packages/app/src/runtime/i18n/sr.ts | 2 - packages/app/src/runtime/i18n/sv.ts | 2 - packages/app/src/runtime/i18n/tg.ts | 3 - packages/app/src/runtime/i18n/th.ts | 2 - packages/app/src/runtime/i18n/tk.ts | 2 - packages/app/src/runtime/i18n/tr.ts | 2 - packages/app/src/runtime/i18n/uk.ts | 2 - packages/app/src/runtime/i18n/ur.ts | 2 - packages/app/src/runtime/i18n/uz.ts | 2 - packages/app/src/runtime/i18n/vi.ts | 2 - packages/app/src/runtime/i18n/zh.ts | 2 - packages/app/src/runtime/i18n/zht.ts | 2 - .../app/src/runtime/persistence/drafts.ts | 12 ++- .../app/src/session/composer/controller.ts | 2 + packages/app/src/session/composer/queue.ts | 17 ++-- .../src/session/timeline/message-timeline.tsx | 2 + .../src/renderer/platform/files.test.ts | 6 +- .../desktop/src/renderer/platform/files.ts | 4 +- .../desktop/src/renderer/platform/index.ts | 9 +- packages/session-ui/src/actions.ts | 7 ++ .../src/message/current-message.tsx | 6 +- .../src/message/message-content.tsx | 13 ++- .../src/timeline/session-timeline-row.tsx | 4 +- 85 files changed, 255 insertions(+), 309 deletions(-) create mode 100644 packages/app/src/composer/attachments/deliver.ts diff --git a/packages/app/src/composer/attachments/attachments.ts b/packages/app/src/composer/attachments/attachments.ts index 4f62dcb16ec0..9650c4ebfbac 100644 --- a/packages/app/src/composer/attachments/attachments.ts +++ b/packages/app/src/composer/attachments/attachments.ts @@ -4,64 +4,6 @@ import { createBlobReference } from "@/runtime/persistence/drafts" import { uuid } from "@/runtime/persistence/uuid" import type { ComposerAttachment, ComposerPrompt } from "../types" -const accepted = [ - "image/png", - "image/jpeg", - "image/gif", - "image/webp", - "application/pdf", - "text/*", - "application/json", - "application/ld+json", - "application/toml", - "application/x-toml", - "application/x-yaml", - "application/xml", - "application/yaml", - ".c", - ".cc", - ".cjs", - ".conf", - ".cpp", - ".css", - ".csv", - ".cts", - ".env", - ".go", - ".gql", - ".graphql", - ".h", - ".hh", - ".hpp", - ".htm", - ".html", - ".ini", - ".java", - ".js", - ".json", - ".jsx", - ".log", - ".md", - ".mdx", - ".mjs", - ".mts", - ".py", - ".rb", - ".rs", - ".sass", - ".scss", - ".sh", - ".sql", - ".toml", - ".ts", - ".tsx", - ".txt", - ".xml", - ".yaml", - ".yml", - ".zsh", -] - type PromptTarget = { current: () => ComposerPrompt cursor: () => number | undefined @@ -75,7 +17,6 @@ export type ComposerAttachmentConfig = { ) => Promise directory: () => string isDialogActive: () => boolean - warn: () => void duplicate: () => void onError: (error: unknown) => void readClipboardImage?: () => Promise @@ -102,13 +43,9 @@ export function createComposerAttachments( if (!editor) return return { prompt, cursor: prompt.cursor() ?? cursorPosition(editor) } } - const add = async (file: File, toast = true, target = capture(), clipboard = false) => { + const add = async (file: File, target = capture(), clipboard = false) => { if (!target) return false const mime = await attachmentMime(file) - if (!mime) { - if (toast) input.warn() - return false - } const blob = input.store ? await input.store(file) : await createBlobReference(file) const sourcePath = input.getPathForFile?.(file) || undefined // Native clipboard images arrive with a fresh timestamped filename on every paste, so identical @@ -138,13 +75,11 @@ export function createComposerAttachments( target.prompt.set([...target.prompt.current(), attachment], target.cursor) return true } - const addAttachments = async (files: File[], toast = true, target = capture()) => { - const found = await files.reduce(async (result, file) => { + const addAttachments = async (files: File[], target = capture()) => { + return files.reduce(async (result, file) => { const previous = await result - return (await add(file, false, target)) || previous + return (await add(file, target)) || previous }, Promise.resolve(false)) - if (!found && files.length > 0 && toast) input.warn() - return found } const handlePaste = async (event: ClipboardEvent) => { const clipboardData = event.clipboardData @@ -159,13 +94,13 @@ export function createComposerAttachments( return file ? [file] : [] }) if (files.length > 0) { - await addAttachments(files, true, target) + await addAttachments(files, target) return } const plainText = clipboardData.getData("text/plain") ?? "" if (input.readClipboardImage && !plainText) { const file = await input.readClipboardImage() - if (file && (await add(file, true, target, true))) return + if (file && (await add(file, target, true))) return } if (!plainText) return const text = plainText.includes("\r") ? plainText.replace(/\r\n?/g, "\n") : plainText @@ -223,9 +158,7 @@ export function createComposerAttachments( fallback() return } - void input - .picker({ defaultPath: input.directory(), multiple: true, accept: accepted }, (file) => add(file)) - .catch(input.onError) + void input.picker({ defaultPath: input.directory(), multiple: true }, (file) => add(file)).catch(input.onError) }, } } @@ -249,6 +182,8 @@ const textMimes = new Set([ "application/yaml", ]) +// Text-like files normalize to text/plain so the server inlines their content; every other +// file keeps a binary type and is delivered to the model by path or as native media. async function attachmentMime(file: File) { const type = file.type.split(";", 1)[0]?.trim().toLowerCase() ?? "" if (imageMimes.has(type) || type === "application/pdf") return type @@ -259,10 +194,11 @@ async function attachmentMime(file: File) { if (type.startsWith("text/") || textMimes.has(type) || type.endsWith("+json") || type.endsWith("+xml")) { return "text/plain" } + const binary = type || "application/octet-stream" const bytes = new Uint8Array(await file.slice(0, 4096).arrayBuffer()) - if (bytes.some((byte) => byte === 0)) return + if (bytes.some((byte) => byte === 0)) return binary const control = bytes.filter((byte) => byte < 9 || (byte > 13 && byte < 32)).length - if (bytes.length > 0 && control / bytes.length > 0.3) return + if (bytes.length > 0 && control / bytes.length > 0.3) return binary return "text/plain" } diff --git a/packages/app/src/composer/attachments/deliver.ts b/packages/app/src/composer/attachments/deliver.ts new file mode 100644 index 000000000000..a1cfca16c838 --- /dev/null +++ b/packages/app/src/composer/attachments/deliver.ts @@ -0,0 +1,70 @@ +import type { Accessor } from "solid-js" +import { blobBytes, blobDataUrl } from "@/runtime/persistence/drafts" +import { useServer } from "@/runtime/server/current" +import { useServerSDK } from "@/runtime/server/client" +import { useWorkspaceLocation } from "@/workspaces/location" +import type { ComposerControls } from "../adapter" +import type { ImageAttachmentPart } from "../state" + +// Where a prompt is headed: the model that reads it and the server that runs its tools. +export type AttachmentDestination = { + /** Input modalities the selected model reads natively. */ + input: { image: boolean; pdf: boolean } + /** The server shares the client's filesystem, so an attachment's source path resolves as-is. */ + local: boolean + /** Copies a file into the server's temporary directory and returns its absolute path there. */ + upload: (file: { name: string; data: Uint8Array }) => Promise +} + +export type DeliveredAttachment = + | { type: "inline"; attachment: ImageAttachmentPart; dataUrl: string } + | { type: "path"; attachment: ImageAttachmentPart; path: string } + +// An attachment travels inline when the model reads its bytes natively. Anything else reaches +// the model as a path on the server, which its tools can open, instead of being rejected. +export function deliverAttachments(attachments: ImageAttachmentPart[], destination: AttachmentDestination) { + return Promise.all(attachments.map((attachment) => deliver(attachment, destination))) +} + +async function deliver( + attachment: ImageAttachmentPart, + destination: AttachmentDestination, +): Promise { + if (native(attachment.mime, destination.input)) { + return { type: "inline", attachment, dataUrl: await blobDataUrl(attachment.blob, attachment.mime) } + } + if (destination.local && attachment.sourcePath) return { type: "path", attachment, path: attachment.sourcePath } + const path = await destination.upload({ name: attachment.filename, data: await blobBytes(attachment.blob) }) + return { type: "path", attachment, path } +} + +const imageMimes = new Set(["image/png", "image/jpeg", "image/gif", "image/webp"]) + +// Mirrors the attachment kinds the server forwards to the model as message content. +function native(mime: string, input: AttachmentDestination["input"]) { + if (mime === "text/plain") return true + if (imageMimes.has(mime)) return input.image + if (mime === "application/pdf") return input.pdf + return false +} + +export function useAttachmentDestination(controls: Accessor) { + const server = useServer() + const sdk = useServerSDK() + const location = useWorkspaceLocation() + return (): AttachmentDestination => ({ + input: controls().model.selection.current()?.capabilities.input ?? { image: false, pdf: false }, + local: server.isLocal, + upload: async (file) => { + const info = await sdk.api.server.info() + // One directory per upload keeps the original filename without collisions; the server + // normalizes the separators and returns the resolved path. + const written = await sdk.api.file.write({ + location: { directory: location().directory }, + path: `${info.paths.tmp}/uploads/${crypto.randomUUID()}/${file.name}`, + payload: file.data, + }) + return written.data.path + }, + }) +} diff --git a/packages/app/src/composer/comment-note.ts b/packages/app/src/composer/comment-note.ts index dc0e04e9914c..b40589195846 100644 --- a/packages/app/src/composer/comment-note.ts +++ b/packages/app/src/composer/comment-note.ts @@ -8,6 +8,13 @@ export type PromptComment = { origin?: "review" | "file" } +/** An attachment the model receives as a path on the server rather than inline bytes. */ +export type PromptAttachmentReference = { + name: string + mime: string + path: string +} + function selection(selection: unknown) { if (!selection || typeof selection !== "object") return undefined const startLine = Number((selection as FileSelection).startLine) @@ -58,8 +65,17 @@ export function readPromptPresentation(value: unknown) { const displayText = (value as { displayText?: unknown }).displayText const comments = (value as { comments?: unknown }).comments if (typeof displayText !== "string" || !Array.isArray(comments)) return + const attachments = (value as { attachments?: unknown }).attachments return { displayText, + attachments: (Array.isArray(attachments) ? attachments : []).flatMap((item): PromptAttachmentReference[] => { + if (!item || typeof item !== "object") return [] + const name = (item as { name?: unknown }).name + const mime = (item as { mime?: unknown }).mime + const path = (item as { path?: unknown }).path + if (typeof name !== "string" || typeof mime !== "string" || typeof path !== "string") return [] + return [{ name, mime, path }] + }), comments: comments.flatMap((item): PromptComment[] => { if (!item || typeof item !== "object") return [] const path = (item as { path?: unknown }).path @@ -80,6 +96,10 @@ export function readPromptPresentation(value: unknown) { } } +export function formatAttachmentReference(input: PromptAttachmentReference) { + return `Attached file: \`${input.path}\`` +} + export function formatCommentNote(input: { path: string; selection?: FileSelection; comment: string }) { const start = input.selection ? Math.min(input.selection.startLine, input.selection.endLine) : undefined const end = input.selection ? Math.max(input.selection.startLine, input.selection.endLine) : undefined diff --git a/packages/app/src/composer/composer.stories.tsx b/packages/app/src/composer/composer.stories.tsx index be246d896479..c3b72eec1dd6 100644 --- a/packages/app/src/composer/composer.stories.tsx +++ b/packages/app/src/composer/composer.stories.tsx @@ -167,7 +167,7 @@ function ComposerStory(props: { ? buildPromptRequest({ prompt: draft.prompt, context: draft.context.items, - images: [], + attachments: [], text: value, sessionDirectory: "C:/repo", }) diff --git a/packages/app/src/composer/editor/editor.tsx b/packages/app/src/composer/editor/editor.tsx index 22b91bdcb4d7..cf0b0ea258e0 100644 --- a/packages/app/src/composer/editor/editor.tsx +++ b/packages/app/src/composer/editor/editor.tsx @@ -102,7 +102,6 @@ export function ComposerEditor(props: ComposerEditorProps) { ref={props.controller.setFileInput} type="file" multiple - accept="image/png,image/jpeg,image/gif,image/webp,application/pdf,text/*,application/json,application/ld+json,application/toml,application/x-toml,application/x-yaml,application/xml,application/yaml,.c,.cc,.cjs,.conf,.cpp,.css,.csv,.cts,.env,.go,.gql,.graphql,.h,.hh,.hpp,.htm,.html,.ini,.java,.js,.json,.jsx,.log,.md,.mdx,.mjs,.mts,.py,.rb,.rs,.sass,.scss,.sh,.sql,.toml,.ts,.tsx,.txt,.xml,.yaml,.yml,.zsh" class="hidden" onChange={(event) => { const list = event.currentTarget.files diff --git a/packages/app/src/composer/model.ts b/packages/app/src/composer/model.ts index b56732920867..26bcb22dd405 100644 --- a/packages/app/src/composer/model.ts +++ b/packages/app/src/composer/model.ts @@ -22,6 +22,7 @@ import type { PromptHistoryComment } from "./history/entry" import { createComposerHistory } from "./history/store" import { composerPlaceholder } from "./placeholder" import { createComposerSubmit } from "./submit" +import { useAttachmentDestination } from "./attachments/deliver" export type ComposerModel = ComposerEditorModel & { readonly model: ComposerControls["model"] @@ -265,6 +266,7 @@ export function createComposerModel(adapter: ComposerAdapter, options?: { queue? resetHistory: () => controller.resetHistory(), setMode: (next) => controller.dispatch({ type: next === "shell" ? "mode.shell" : "mode.normal" }), closePopover: () => controller.dispatch({ type: "popover.close" }), + destination: useAttachmentDestination(adapter.controls), delivery: (alternate) => { const queue = options?.queue if (!queue) return "steer" @@ -339,11 +341,6 @@ export function createComposerModel(adapter: ComposerAdapter, options?: { queue? picker: platform.openAttachmentPickerDialog, directory: () => sdk().directory, isDialogActive: () => !!dialog.active, - warn: () => - showToast({ - title: language.t("prompt.toast.pasteUnsupported.title"), - description: language.t("prompt.toast.pasteUnsupported.description"), - }), duplicate: () => showToast({ title: language.t("prompt.toast.attachmentDuplicate.title") }), onError: (error) => showToast({ diff --git a/packages/app/src/composer/request.test.ts b/packages/app/src/composer/request.test.ts index 401653985db6..7a605647bfc3 100644 --- a/packages/app/src/composer/request.test.ts +++ b/packages/app/src/composer/request.test.ts @@ -1,8 +1,17 @@ import { describe, expect, test } from "bun:test" import { Skill } from "@opencode/schema/skill" -import type { Prompt } from "@/composer/state" +import type { ImageAttachmentPart, Prompt } from "@/composer/state" +import type { DeliveredAttachment } from "./attachments/deliver" import { buildPromptRequest } from "./request" +function inline(filename: string, mime: string, extra?: Partial): DeliveredAttachment { + return { + type: "inline", + attachment: { type: "image", id: `img_${filename}`, filename, mime, blob: { id: filename, url: "" }, ...extra }, + dataUrl: `data:${mime};base64,AAA`, + } +} + describe("buildPromptRequest", () => { test("builds text, files, and agents from the prompt", () => { const prompt: Prompt = [ @@ -21,9 +30,7 @@ describe("buildPromptRequest", () => { const result = buildPromptRequest({ prompt, context: [{ key: "ctx:1", type: "file", path: "src/bar.ts", comment: "check this" }], - images: [ - { type: "image", id: "img_1", filename: "a.png", mime: "image/png", dataUrl: "data:image/png;base64,AAA" }, - ], + attachments: [inline("a.png", "image/png")], text: "hello @src/foo.ts @planner", sessionDirectory: "/repo", }) @@ -45,16 +52,7 @@ describe("buildPromptRequest", () => { const result = buildPromptRequest({ prompt: [{ type: "text", content: "check these", start: 0, end: 11 }], context: [], - images: [ - { type: "image", id: "img_1", filename: "a.png", mime: "image/png", dataUrl: "data:image/png;base64,AAA" }, - { - type: "image", - id: "img_2", - filename: "b.pdf", - mime: "application/pdf", - dataUrl: "data:application/pdf;base64,BBB", - }, - ], + attachments: [inline("a.png", "image/png"), inline("b.pdf", "application/pdf")], text: "check these", sessionDirectory: "/repo", }) @@ -69,15 +67,10 @@ describe("buildPromptRequest", () => { const result = buildPromptRequest({ prompt: [], context: [], - images: [ - { - type: "image", - id: "img_external", - filename: "opencode.global.dat", + attachments: [ + inline("opencode.global.dat", "text/plain", { sourcePath: "C:\\Users\\Luke\\AppData\\Roaming\\ai.opencode.desktop.beta\\opencode.global.dat", - mime: "text/plain", - dataUrl: "data:text/plain;base64,AAA", - }, + }), ], text: "inspect this", sessionDirectory: "C:\\Repos\\sst\\opencode", @@ -102,7 +95,7 @@ describe("buildPromptRequest", () => { }, ], context: [], - images: [], + attachments: [], text: "@docs", sessionDirectory: "/repo/app", }) @@ -124,7 +117,7 @@ describe("buildPromptRequest", () => { { key: "ctx:dup", type: "file", path: "src/foo.ts" }, { key: "ctx:comment", type: "file", path: "src/foo.ts", comment: "focus here" }, ], - images: [], + attachments: [], text: "@src/foo.ts", sessionDirectory: "/repo", }) @@ -146,7 +139,7 @@ describe("buildPromptRequest", () => { comment: "Compare with @src/shared.ts and @src/review.ts.", }, ], - images: [], + attachments: [], text: "look", sessionDirectory: "/repo", }) @@ -162,7 +155,7 @@ describe("buildPromptRequest", () => { const result = buildPromptRequest({ prompt, context: [], - images: [], + attachments: [], text: "@src\\foo.ts", sessionDirectory: "D:\\projects\\myapp", // Windows path }) @@ -183,7 +176,7 @@ describe("buildPromptRequest", () => { const result = buildPromptRequest({ prompt, context: [], - images: [], + attachments: [], text: "@file#name.txt", sessionDirectory: "C:\\Users\\test\\Documents", // Windows path }) @@ -204,7 +197,7 @@ describe("buildPromptRequest", () => { const result = buildPromptRequest({ prompt, context: [], - images: [], + attachments: [], text: "@src/app.ts", sessionDirectory: "/home/user/project", }) @@ -218,7 +211,7 @@ describe("buildPromptRequest", () => { const result = buildPromptRequest({ prompt, context: [], - images: [], + attachments: [], text: "@README.md", sessionDirectory: "/Users/kelvin/Projects/opencode", }) @@ -233,7 +226,7 @@ describe("buildPromptRequest", () => { { key: "ctx:1", type: "file", path: "src\\utils\\helper.ts" }, { key: "ctx:2", type: "file", path: "test\\unit.test.ts", comment: "check tests" }, ], - images: [], + attachments: [], text: "test", sessionDirectory: "D:\\workspace\\app", }) @@ -255,7 +248,7 @@ describe("buildPromptRequest", () => { const result = buildPromptRequest({ prompt, context: [], - images: [], + attachments: [], text: "@D:\\other\\project\\file.ts", sessionDirectory: "C:\\current\\project", }) @@ -282,7 +275,7 @@ describe("buildPromptRequest", () => { const result = buildPromptRequest({ prompt, context: [], - images: [], + attachments: [], text: "@src\\App.tsx", sessionDirectory: "C:\\project", }) @@ -307,7 +300,7 @@ describe("buildPromptRequest", () => { const result = buildPromptRequest({ prompt, context: [], - images: [], + attachments: [], text: "@..\\..\\shared\\util.ts", sessionDirectory: "C:\\projects\\myapp\\src", }) @@ -337,7 +330,7 @@ describe("buildPromptRequest", () => { }, ], context: [], - images: [], + attachments: [], text: "@review", sessionDirectory: "/repo", }) diff --git a/packages/app/src/composer/request.ts b/packages/app/src/composer/request.ts index 0b3f4d8a22c3..555e16d45f59 100644 --- a/packages/app/src/composer/request.ts +++ b/packages/app/src/composer/request.ts @@ -1,8 +1,14 @@ import { getFilename } from "@opencode/util/path" import type { FileSelection } from "@/workspaces/files/model" import { encodeFilePath } from "@/workspaces/files/path" -import type { AgentPart, FileAttachmentPart, ImageAttachmentPart, Prompt, SkillPart } from "@/composer/state" -import { formatCommentNote, type PromptComment } from "@/composer/comment-note" +import type { AgentPart, FileAttachmentPart, Prompt, SkillPart } from "@/composer/state" +import { + formatAttachmentReference, + formatCommentNote, + type PromptAttachmentReference, + type PromptComment, +} from "@/composer/comment-note" +import type { DeliveredAttachment } from "@/composer/attachments/deliver" // Network fields feed both boundaries; display fields keep desktop-only rendering details in the local echo. type PromptRequest = { @@ -12,6 +18,7 @@ type PromptRequest = { agents: { name: string; mention?: { start: number; end: number; text: string } }[] skills: { id: string; name: string; mention?: { start: number; end: number; text: string } }[] comments: PromptComment[] + attachments: PromptAttachmentReference[] } type ContextFile = { @@ -28,7 +35,7 @@ type ContextFile = { type BuildPromptRequestInput = { prompt: Prompt context: ContextFile[] - images: (Omit & { dataUrl: string })[] + attachments: DeliveredAttachment[] text: string sessionDirectory: string } @@ -106,18 +113,27 @@ export function buildPromptRequest(input: BuildPromptRequestInput): PromptReques return [file, ...mentions] }) - const images = input.images.map((attachment) => ({ - uri: attachment.dataUrl, - mime: attachment.mime, - name: attachment.sourcePath ?? attachment.filename, - })) + const inline = input.attachments.flatMap((item) => + item.type === "inline" + ? [{ uri: item.dataUrl, mime: item.attachment.mime, name: item.attachment.sourcePath ?? item.attachment.filename }] + : [], + ) + // Like comments, path references reach the model as text and the message UI through metadata. + const attachments = input.attachments.flatMap((item) => + item.type === "path" ? [{ name: item.attachment.filename, mime: item.attachment.mime, path: item.path }] : [], + ) return { - text: [...(input.text.trim() ? [input.text] : []), ...comments.map(formatCommentNote)].join("\n"), + text: [ + ...(input.text.trim() ? [input.text] : []), + ...attachments.map(formatAttachmentReference), + ...comments.map(formatCommentNote), + ].join("\n"), displayText: input.text, - files: [...files, ...context, ...images], + files: [...files, ...context, ...inline], agents, skills, comments, + attachments, } } diff --git a/packages/app/src/composer/submit.test.ts b/packages/app/src/composer/submit.test.ts index d100291cd1f8..15d9ffefac96 100644 --- a/packages/app/src/composer/submit.test.ts +++ b/packages/app/src/composer/submit.test.ts @@ -3,6 +3,7 @@ import type { ModelSelection } from "@/providers/models/selection" import type { SessionMessageUser } from "@opencode/client/promise" import { Skill } from "@opencode/schema/skill" import type { ActiveComposerAdapter, ComposerControls, ComposerSession, NewSessionComposerAdapter } from "./adapter" +import type { AttachmentDestination } from "./attachments/deliver" import { createMemoryComposerState } from "./state" import { createComposerSubmit } from "./submit" @@ -48,6 +49,14 @@ function controls(): ComposerControls { } } +const destination: AttachmentDestination = { + input: { image: true, pdf: true }, + local: false, + upload: async () => { + throw new Error("native attachments must not upload") + }, +} + function submitInput( adapter: ActiveComposerAdapter | NewSessionComposerAdapter, notify = { missingSelection() {}, failed(_kind: "shell" | "command" | "prompt", _error: unknown) {} }, @@ -64,6 +73,7 @@ function submitInput( resetHistory() {}, setMode() {}, closePopover() {}, + destination: () => destination, notify, comments: { capture: () => [], clear() {}, restore() {} }, }) diff --git a/packages/app/src/composer/submit.ts b/packages/app/src/composer/submit.ts index 8aafcaf04f12..a7342e5415bc 100644 --- a/packages/app/src/composer/submit.ts +++ b/packages/app/src/composer/submit.ts @@ -8,7 +8,7 @@ import type { ComposerAdapter, ComposerDelivery, ComposerSelection, ComposerSess import { createComposerSubmission } from "./submission-state" import { buildPromptRequest } from "./request" import { setCursorPosition } from "./editor/dom" -import { blobDataUrl } from "@/runtime/persistence/drafts" +import { deliverAttachments, type AttachmentDestination } from "./attachments/deliver" import type { ModelSelection } from "@/providers/models/selection" const submitting = new WeakSet() @@ -34,6 +34,7 @@ type ComposerSubmitInput = { resetHistory: () => void setMode: (mode: "normal" | "shell") => void closePopover: () => void + destination: () => AttachmentDestination delivery?: (alternate: boolean) => ComposerDelivery notify: { missingSelection: () => void @@ -86,10 +87,16 @@ export function createComposerSubmit(input: ComposerSubmitInput) { const optimisticBusy = !input.adapter.working() if (optimisticBusy && input.adapter.kind === "new-session") session.data.session.setStatus(session.id, "running") - const sending = sendPrompt(session, value, input.adapter.controls().model.selection.trackSessionCommit, () => { - if (optimisticBusy && input.adapter.kind === "active-session") - session.data.session.setStatus(session.id, "running") - }).then( + const sending = sendPrompt( + session, + value, + input.destination(), + input.adapter.controls().model.selection.trackSessionCommit, + () => { + if (optimisticBusy && input.adapter.kind === "active-session") + session.data.session.setStatus(session.id, "running") + }, + ).then( () => ({ ok: true as const }), (error) => ({ ok: false as const, error }), ) @@ -122,9 +129,13 @@ export function createComposerSubmit(input: ComposerSubmitInput) { if (command) { clearSubmission(input, submission) - void sendCommand(session, value, command, input.adapter.controls().model.selection.trackSessionCommit).catch( - (error) => failSubmission(input, session, "command", error, restore, value.id), - ) + void sendCommand( + session, + value, + command, + input.destination(), + input.adapter.controls().model.selection.trackSessionCommit, + ).catch((error) => failSubmission(input, session, "command", error, restore, value.id)) return } } finally { @@ -293,9 +304,10 @@ async function sendCommand( session: ComposerSession, value: ComposerSubmission, command: { command: string; arguments: string }, + destination: AttachmentDestination, track?: ModelSelection["trackSessionCommit"], ) { - const request = await buildSubmissionRequest(session, value) + const request = await buildSubmissionRequest(session, value, destination) // Like queued prompts, queued commands must not apply the composer's selection to active work. if (value.delivery === "steer") await applySelection(session, value.selection, track) await session.api.command({ @@ -334,10 +346,11 @@ async function applySelection( async function sendPrompt( session: ComposerSession, value: ComposerSubmission, + destination: AttachmentDestination, track: ModelSelection["trackSessionCommit"] | undefined, onAdmit: () => void, ) { - const request = await buildSubmissionRequest(session, value) + const request = await buildSubmissionRequest(session, value, destination) // Switching agent or model reconfigures the session immediately, and with it // the remainder of a running turn. A steer targets that turn, so its // selection applies now; a queued follow-up must not reconfigure the turn it @@ -358,6 +371,7 @@ async function sendPrompt( metadata: { displayText: request.displayText, comments: request.comments, + attachments: request.attachments, agent: value.selection.agent, model: { ...value.selection.model, @@ -370,21 +384,18 @@ async function sendPrompt( await sending } -async function buildSubmissionRequest(session: ComposerSession, value: ComposerSubmission) { - const images = await Promise.all( - value.images.map(async (attachment) => ({ - ...attachment, - dataUrl: await blobDataUrl(attachment.blob, attachment.mime), - })), - ) - const request = buildPromptRequest({ +async function buildSubmissionRequest( + session: ComposerSession, + value: ComposerSubmission, + destination: AttachmentDestination, +) { + return buildPromptRequest({ prompt: value.prompt, context: value.context, - images, + attachments: await deliverAttachments(value.images, destination), text: value.text, sessionDirectory: session.directory, }) - return request } function failSubmission( diff --git a/packages/app/src/desktop.ts b/packages/app/src/desktop.ts index 5f2253bc99a4..1f79856633e4 100644 --- a/packages/app/src/desktop.ts +++ b/packages/app/src/desktop.ts @@ -1,5 +1,4 @@ export { AppBaseProviders, AppInterface, preloadRoute } from "./app" -export { ACCEPTED_FILE_EXTENSIONS } from "./runtime/platform/file-picker" export { useCommand } from "./shell/commands/command" export { currentRoute, type LayoutRoute, useCurrentRoute } from "./shell/state/layout" export { loadLocaleDict, normalizeLocale, type Locale, useLanguage } from "./runtime/i18n/language" diff --git a/packages/app/src/runtime/i18n/am.ts b/packages/app/src/runtime/i18n/am.ts index d1b67eaab41b..332cd2058f03 100644 --- a/packages/app/src/runtime/i18n/am.ts +++ b/packages/app/src/runtime/i18n/am.ts @@ -371,8 +371,6 @@ export const dict = { "prompt.attachment.remove": "ዓባሪን አስወግድ", "prompt.action.send": "ላክ", "prompt.action.stop": "አቁም", - "prompt.toast.pasteUnsupported.title": "የማይደገፍ ዓባሪ", - "prompt.toast.pasteUnsupported.description": "ምስሎች፣ ፒዲኤፎች ወይም የጽሑፍ ፋይሎች ብቻ እዚህ ጋር ሊጣመሩ ይችላሉ።", "prompt.toast.attachmentDuplicate.title": "ይህ ፋይል አስቀድሞ ተሰቅሏል", "prompt.toast.modelAgentRequired.title": "ወኪል እና ሞዴል ይምረጡ", "prompt.toast.modelAgentRequired.description": "ፕሮምፕት ከመላክዎ በፊት ወኪል እና ሞዴል ይምረጡ።", diff --git a/packages/app/src/runtime/i18n/ar.ts b/packages/app/src/runtime/i18n/ar.ts index 5dc0572a840e..93032cde5a36 100644 --- a/packages/app/src/runtime/i18n/ar.ts +++ b/packages/app/src/runtime/i18n/ar.ts @@ -380,9 +380,7 @@ export const dict = { "prompt.attachment.remove": "إزالة المرفق", "prompt.action.send": "إرسال", "prompt.action.stop": "إيقاف", - "prompt.toast.pasteUnsupported.title": "مرفق غير مدعوم", "prompt.toast.attachmentDuplicate.title": "تم تحميل هذا الملف بالفعل", - "prompt.toast.pasteUnsupported.description": "يمكن إرفاق الصور أو ملفات PDF أو الملفات النصية فقط هنا.", "prompt.toast.modelAgentRequired.title": "حدد وكيلاً ونموذجاً", "prompt.toast.modelAgentRequired.description": "اختر وكيلاً ونموذجاً قبل إرسال الموجه.", "prompt.toast.worktreeCreateFailed.title": "فشل إنشاء شجرة العمل", diff --git a/packages/app/src/runtime/i18n/az.ts b/packages/app/src/runtime/i18n/az.ts index 79fe09efdd3c..5e9dbe2553ad 100644 --- a/packages/app/src/runtime/i18n/az.ts +++ b/packages/app/src/runtime/i18n/az.ts @@ -378,8 +378,6 @@ export const dict = { "prompt.attachment.remove": "Əlavəni sil", "prompt.action.send": "Göndər", "prompt.action.stop": "Dayandır", - "prompt.toast.pasteUnsupported.title": "Dəstəklənməyən əlavə", - "prompt.toast.pasteUnsupported.description": "Buraya yalnız şəkillər, PDF-lər və ya mətn faylları əlavə edilə bilər.", "prompt.toast.attachmentDuplicate.title": "Bu fayl artıq yüklənib", "prompt.toast.modelAgentRequired.title": "Agent və model seçin", "prompt.toast.modelAgentRequired.description": "Prompt göndərməzdən əvvəl agent və model seçin.", diff --git a/packages/app/src/runtime/i18n/bg.ts b/packages/app/src/runtime/i18n/bg.ts index aa4c4c8160a5..7cf131961c9d 100644 --- a/packages/app/src/runtime/i18n/bg.ts +++ b/packages/app/src/runtime/i18n/bg.ts @@ -378,8 +378,6 @@ export const dict = { "prompt.attachment.remove": "Премахване на прикачения файл", "prompt.action.send": "Изпратете", "prompt.action.stop": "Спрете", - "prompt.toast.pasteUnsupported.title": "Неподдържан прикачен файл", - "prompt.toast.pasteUnsupported.description": "Тук могат да се прикачват само изображения, PDF или текстови файлове.", "prompt.toast.attachmentDuplicate.title": "Този файл вече е качен", "prompt.toast.modelAgentRequired.title": "Изберете агент и модел", "prompt.toast.modelAgentRequired.description": "Изберете агент и модел, преди да изпратите подкана.", diff --git a/packages/app/src/runtime/i18n/bn.ts b/packages/app/src/runtime/i18n/bn.ts index 17316951ed19..b0de1bd638e7 100644 --- a/packages/app/src/runtime/i18n/bn.ts +++ b/packages/app/src/runtime/i18n/bn.ts @@ -375,8 +375,6 @@ export const dict: Record = { "prompt.attachment.remove": "সংযুক্তি সরান", "prompt.action.send": "পাঠান", "prompt.action.stop": "থামো", - "prompt.toast.pasteUnsupported.title": "অসমর্থিত সংযুক্তি", - "prompt.toast.pasteUnsupported.description": "এখানে শুধুমাত্র ছবি, পিডিএফ বা টেক্সট ফাইল সংযুক্ত করা যাবে।", "prompt.toast.attachmentDuplicate.title": "এই ফাইল ইতিমধ্যে আপলোড করা হয়েছে", "prompt.toast.modelAgentRequired.title": "একটি এজেন্ট এবং মডেল নির্বাচন করুন", "prompt.toast.modelAgentRequired.description": "প্রম্পট পাঠানোর আগে একটি এজেন্ট এবং মডেল বেছে নিন।", diff --git a/packages/app/src/runtime/i18n/br.ts b/packages/app/src/runtime/i18n/br.ts index d25f469a8c64..7955236d25ba 100644 --- a/packages/app/src/runtime/i18n/br.ts +++ b/packages/app/src/runtime/i18n/br.ts @@ -382,9 +382,7 @@ export const dict = { "prompt.attachment.remove": "Remover anexo", "prompt.action.send": "Enviar", "prompt.action.stop": "Parar", - "prompt.toast.pasteUnsupported.title": "Anexo não suportado", "prompt.toast.attachmentDuplicate.title": "Este arquivo já foi enviado", - "prompt.toast.pasteUnsupported.description": "Apenas imagens, PDFs ou arquivos de texto podem ser anexados aqui.", "prompt.toast.modelAgentRequired.title": "Selecione um agente e modelo", "prompt.toast.modelAgentRequired.description": "Escolha um agente e modelo antes de enviar um prompt.", "prompt.toast.worktreeCreateFailed.title": "Falha ao criar worktree", diff --git a/packages/app/src/runtime/i18n/bs.ts b/packages/app/src/runtime/i18n/bs.ts index dc3ee600a47f..ac29f4422956 100644 --- a/packages/app/src/runtime/i18n/bs.ts +++ b/packages/app/src/runtime/i18n/bs.ts @@ -403,9 +403,7 @@ export const dict = { "prompt.action.send": "Pošalji", "prompt.action.stop": "Zaustavi", - "prompt.toast.pasteUnsupported.title": "Nepodržan prilog", "prompt.toast.attachmentDuplicate.title": "Ova datoteka je već učitana", - "prompt.toast.pasteUnsupported.description": "Ovdje se mogu priložiti samo slike, PDF-ovi ili tekstualne datoteke.", "prompt.toast.modelAgentRequired.title": "Odaberi agenta i model", "prompt.toast.modelAgentRequired.description": "Odaberi agenta i model prije slanja upita.", "prompt.toast.worktreeCreateFailed.title": "Neuspješno kreiranje worktree-a", diff --git a/packages/app/src/runtime/i18n/ca.ts b/packages/app/src/runtime/i18n/ca.ts index 2a12ca3e945e..ac67b2bfdcd6 100644 --- a/packages/app/src/runtime/i18n/ca.ts +++ b/packages/app/src/runtime/i18n/ca.ts @@ -377,8 +377,6 @@ export const dict = { "prompt.attachment.remove": "Elimina el fitxer adjunt", "prompt.action.send": "Enviar", "prompt.action.stop": "Atureu-vos", - "prompt.toast.pasteUnsupported.title": "Fitxer adjunt no compatible", - "prompt.toast.pasteUnsupported.description": "Aquí només es poden adjuntar imatges, PDFs o fitxers de text.", "prompt.toast.attachmentDuplicate.title": "Aquest fitxer ja s'ha penjat", "prompt.toast.modelAgentRequired.title": "Seleccioneu un agent i un model", "prompt.toast.modelAgentRequired.description": "Trieu un agent i un model abans d'enviar una sol·licitud.", diff --git a/packages/app/src/runtime/i18n/cs.ts b/packages/app/src/runtime/i18n/cs.ts index 3adc097cbe36..b7968cddeeee 100644 --- a/packages/app/src/runtime/i18n/cs.ts +++ b/packages/app/src/runtime/i18n/cs.ts @@ -375,8 +375,6 @@ export const dict = { "prompt.attachment.remove": "Odstraňte přílohu", "prompt.action.send": "Odeslat", "prompt.action.stop": "Přestaň", - "prompt.toast.pasteUnsupported.title": "Nepodporovaná příloha", - "prompt.toast.pasteUnsupported.description": "Zde lze připojit pouze obrázky, PDFs nebo textové soubory.", "prompt.toast.attachmentDuplicate.title": "Tento soubor již byl nahrán", "prompt.toast.modelAgentRequired.title": "Vyberte agenta a model", "prompt.toast.modelAgentRequired.description": "Před odesláním výzvy vyberte zástupce a model.", diff --git a/packages/app/src/runtime/i18n/da.ts b/packages/app/src/runtime/i18n/da.ts index 544144a577d7..fd95af0c53ab 100644 --- a/packages/app/src/runtime/i18n/da.ts +++ b/packages/app/src/runtime/i18n/da.ts @@ -300,9 +300,7 @@ export const dict = { "prompt.action.send": "Send", "prompt.action.stop": "Stop", - "prompt.toast.pasteUnsupported.title": "Ikke understøttet vedhæftning", "prompt.toast.attachmentDuplicate.title": "Denne fil er allerede uploadet", - "prompt.toast.pasteUnsupported.description": "Kun billeder, PDF'er eller tekstfiler kan vedhæftes her.", "prompt.toast.modelAgentRequired.title": "Vælg en agent og model", "prompt.toast.modelAgentRequired.description": "Vælg en agent og model før du sender en forespørgsel.", "prompt.toast.worktreeCreateFailed.title": "Kunne ikke oprette worktree", diff --git a/packages/app/src/runtime/i18n/de.ts b/packages/app/src/runtime/i18n/de.ts index e93b9e388c4c..c25e05a8190d 100644 --- a/packages/app/src/runtime/i18n/de.ts +++ b/packages/app/src/runtime/i18n/de.ts @@ -286,9 +286,7 @@ export const dict = { "prompt.attachment.remove": "Anhang entfernen", "prompt.action.send": "Senden", "prompt.action.stop": "Stoppen", - "prompt.toast.pasteUnsupported.title": "Nicht unterstützter Anhang", "prompt.toast.attachmentDuplicate.title": "Diese Datei wurde bereits hochgeladen", - "prompt.toast.pasteUnsupported.description": "Hier können nur Bilder, PDFs oder Textdateien angehängt werden.", "prompt.toast.modelAgentRequired.title": "Wählen Sie einen Agenten und ein Modell", "prompt.toast.modelAgentRequired.description": "Wählen Sie einen Agenten und ein Modell, bevor Sie eine Eingabe senden.", diff --git a/packages/app/src/runtime/i18n/dv.ts b/packages/app/src/runtime/i18n/dv.ts index 462f03e174c2..5d5f492f1b1f 100644 --- a/packages/app/src/runtime/i18n/dv.ts +++ b/packages/app/src/runtime/i18n/dv.ts @@ -380,9 +380,6 @@ export const dict = { "prompt.attachment.remove": "އެޓޭޗްމަންޓް ނަގާށެވެ", "prompt.action.send": "ފޮނުވުން", "prompt.action.stop": "ހުއްޓުން", - "prompt.toast.pasteUnsupported.title": "ސަޕޯޓް ނުކުރާ އެޓޭޗްމަންޓެވެ", - "prompt.toast.pasteUnsupported.description": - "މިތަނުގައި އެޓޭޗް ކުރެވޭނީ ހަމައެކަނި ތަސްވީރު، PDFs، ނުވަތަ ޓެކްސްޓް ފައިލްތަކެވެ.", "prompt.toast.attachmentDuplicate.title": "މި ފައިލް މިހާރު ވަނީ އަޕްލޯޑްކޮށްފައެވެ", "prompt.toast.modelAgentRequired.title": "އޭޖެންޓަކާއި މޮޑެލްއެއް ހޮވުން", "prompt.toast.modelAgentRequired.description": "ޕްރޮމްޕްޓެއް ފޮނުވުމުގެ ކުރިން އޭޖެންޓަކާއި މޮޑެލްއެއް ހޮވުން.", diff --git a/packages/app/src/runtime/i18n/dz.ts b/packages/app/src/runtime/i18n/dz.ts index 1748dfbe8efa..7276278c8dd3 100644 --- a/packages/app/src/runtime/i18n/dz.ts +++ b/packages/app/src/runtime/i18n/dz.ts @@ -379,9 +379,6 @@ export const dict: Record = { "prompt.attachment.remove": "མཉམ་སྦྲགས་རྩ་བསྐྲད་གཏང་།", "prompt.action.send": "བཏང༌ནི", "prompt.action.stop": "བཀག་པ", - "prompt.toast.pasteUnsupported.title": "རྒྱབ་སྐྱོར་མེད་པའི་མཉམ་སྦྲགས།", - "prompt.toast.pasteUnsupported.description": - "པར་རིས་དང་པི་ཌི་ཨེཕ་ ཡང་ན་ ཚིག་ཡིག་ཡིག་སྣོད་ཚུ་རྐྱངམ་ཅིག་ ནཱ་ལུ་མཉམ་སྦྲགས་འབད་བཏུབ།", "prompt.toast.attachmentDuplicate.title": "ཡིག་སྣོད་འདི་ཧེ་མ་ལས་སྐྱེལ་བཙུགས་འབད་ཡི།", "prompt.toast.modelAgentRequired.title": "ལས་ཚབ་དང་དཔེ་ཚད་ཅིག་སེལ་འཐུ་འབད།", "prompt.toast.modelAgentRequired.description": "བརྡ་སྟོན་མ་གཏང་པའི་ཧེ་མ་ ལས་ཚབ་དང་དཔེ་ཚད་གདམ་ཁ་རྐྱབས།", diff --git a/packages/app/src/runtime/i18n/el.ts b/packages/app/src/runtime/i18n/el.ts index 466ac81b941a..1f916a846add 100644 --- a/packages/app/src/runtime/i18n/el.ts +++ b/packages/app/src/runtime/i18n/el.ts @@ -376,8 +376,6 @@ export const dict = { "prompt.attachment.remove": "Κατάργηση συνημμένου", "prompt.action.send": "Αποστολή", "prompt.action.stop": "Διακοπή", - "prompt.toast.pasteUnsupported.title": "Μη υποστηριζόμενο συνημμένο", - "prompt.toast.pasteUnsupported.description": "Εδώ επισυνάπτονται μόνο εικόνες, αρχεία PDF ή αρχεία κειμένου.", "prompt.toast.attachmentDuplicate.title": "Αυτό το αρχείο έχει ήδη μεταφορτωθεί", "prompt.toast.modelAgentRequired.title": "Επιλέξτε έναν πράκτορα και μοντέλο", "prompt.toast.modelAgentRequired.description": diff --git a/packages/app/src/runtime/i18n/en.ts b/packages/app/src/runtime/i18n/en.ts index c82892415a7f..19cde8b1632b 100644 --- a/packages/app/src/runtime/i18n/en.ts +++ b/packages/app/src/runtime/i18n/en.ts @@ -365,8 +365,6 @@ export const dict = { "prompt.action.send": "Send", "prompt.action.stop": "Stop", - "prompt.toast.pasteUnsupported.title": "Unsupported attachment", - "prompt.toast.pasteUnsupported.description": "Only images, PDFs, or text files can be attached here.", "prompt.toast.attachmentDuplicate.title": "This file has already been uploaded", "prompt.toast.modelAgentRequired.title": "Select an agent and model", "prompt.toast.modelAgentRequired.description": "Choose an agent and model before sending a prompt.", diff --git a/packages/app/src/runtime/i18n/es.ts b/packages/app/src/runtime/i18n/es.ts index 874788a0828a..c9a7ae53abdb 100644 --- a/packages/app/src/runtime/i18n/es.ts +++ b/packages/app/src/runtime/i18n/es.ts @@ -403,10 +403,7 @@ export const dict = { "prompt.action.send": "Enviar", "prompt.action.stop": "Detener", - "prompt.toast.pasteUnsupported.title": "Adjunto no compatible", "prompt.toast.attachmentDuplicate.title": "Este archivo ya se ha subido", - "prompt.toast.pasteUnsupported.description": - "Aquí solo se pueden adjuntar imágenes, archivos PDF o archivos de texto.", "prompt.toast.modelAgentRequired.title": "Selecciona un agente y modelo", "prompt.toast.modelAgentRequired.description": "Elige un agente y modelo antes de enviar un prompt.", "prompt.toast.worktreeCreateFailed.title": "Fallo al crear el árbol de trabajo", diff --git a/packages/app/src/runtime/i18n/et.ts b/packages/app/src/runtime/i18n/et.ts index 12512be0cbae..2d3e6589826c 100644 --- a/packages/app/src/runtime/i18n/et.ts +++ b/packages/app/src/runtime/i18n/et.ts @@ -374,8 +374,6 @@ export const dict = { "prompt.attachment.remove": "Eemalda manus", "prompt.action.send": "Saada", "prompt.action.stop": "Peatus", - "prompt.toast.pasteUnsupported.title": "Toetamata manus", - "prompt.toast.pasteUnsupported.description": "Siia saab lisada ainult pilte, PDFs või tekstifaile.", "prompt.toast.attachmentDuplicate.title": "See fail on juba üles laaditud", "prompt.toast.modelAgentRequired.title": "Valige agent ja mudel", "prompt.toast.modelAgentRequired.description": "Enne viipa saatmist valige agent ja mudel.", diff --git a/packages/app/src/runtime/i18n/fa.ts b/packages/app/src/runtime/i18n/fa.ts index dfe777f91fbe..85272252c22e 100644 --- a/packages/app/src/runtime/i18n/fa.ts +++ b/packages/app/src/runtime/i18n/fa.ts @@ -375,8 +375,6 @@ export const dict = { "prompt.attachment.remove": "حذف پیوست", "prompt.action.send": "ارسال کنید", "prompt.action.stop": "توقف کنید", - "prompt.toast.pasteUnsupported.title": "پیوست پشتیبانی نشده است", - "prompt.toast.pasteUnsupported.description": "فقط تصاویر، PDFs، یا فایل های متنی را می توان در اینجا پیوست کرد.", "prompt.toast.attachmentDuplicate.title": "این فایل قبلا آپلود شده است", "prompt.toast.modelAgentRequired.title": "یک عامل و مدل را انتخاب کنید", "prompt.toast.modelAgentRequired.description": "قبل از ارسال درخواست، یک عامل و مدل را انتخاب کنید.", diff --git a/packages/app/src/runtime/i18n/fi.ts b/packages/app/src/runtime/i18n/fi.ts index 108391663410..0263ef34e535 100644 --- a/packages/app/src/runtime/i18n/fi.ts +++ b/packages/app/src/runtime/i18n/fi.ts @@ -282,8 +282,6 @@ export const dict = { "prompt.attachment.remove": "Poista liite", "prompt.action.send": "Lähetä", "prompt.action.stop": "Pysäytä", - "prompt.toast.pasteUnsupported.title": "Liitettä ei tueta", - "prompt.toast.pasteUnsupported.description": "Vain kuvia, PDF-tiedostoja tai tekstitiedostoja voi liittää tähän.", "prompt.toast.attachmentDuplicate.title": "Tämä tiedosto on jo ladattu", "prompt.toast.modelAgentRequired.title": "Valitse agentti ja malli", "prompt.toast.modelAgentRequired.description": "Valitse agentti ja malli ennen kehotteen lähettämistä.", diff --git a/packages/app/src/runtime/i18n/fo.ts b/packages/app/src/runtime/i18n/fo.ts index 40a8bfebd108..295b6be43022 100644 --- a/packages/app/src/runtime/i18n/fo.ts +++ b/packages/app/src/runtime/i18n/fo.ts @@ -374,8 +374,6 @@ export const dict = { "prompt.attachment.remove": "Strika viðheftið", "prompt.action.send": "Send", "prompt.action.stop": "Steðga", - "prompt.toast.pasteUnsupported.title": "Óstuðlað viðhefti", - "prompt.toast.pasteUnsupported.description": "Bert myndir, PDFs, ella tekstfílur kunnu viðheftast her.", "prompt.toast.attachmentDuplicate.title": "Hendan fílan er longu løgd upp.", "prompt.toast.modelAgentRequired.title": "Vel agent og modell", "prompt.toast.modelAgentRequired.description": "Vel agent og modell, áðrenn tú sendir ein prompt.", diff --git a/packages/app/src/runtime/i18n/fr.ts b/packages/app/src/runtime/i18n/fr.ts index 3487df519b10..401dbe4a8aa8 100644 --- a/packages/app/src/runtime/i18n/fr.ts +++ b/packages/app/src/runtime/i18n/fr.ts @@ -385,10 +385,7 @@ export const dict = { "prompt.attachment.remove": "Supprimer la pièce jointe", "prompt.action.send": "Envoyer", "prompt.action.stop": "Arrêter", - "prompt.toast.pasteUnsupported.title": "Pièce jointe non prise en charge", "prompt.toast.attachmentDuplicate.title": "Ce fichier a déjà été téléversé", - "prompt.toast.pasteUnsupported.description": - "Seules les images, les PDF ou les fichiers texte peuvent être joints ici.", "prompt.toast.modelAgentRequired.title": "Sélectionnez un agent et un modèle", "prompt.toast.modelAgentRequired.description": "Choisissez un agent et un modèle avant d'envoyer une invite.", "prompt.toast.worktreeCreateFailed.title": "Échec de la création de l'arbre de travail", diff --git a/packages/app/src/runtime/i18n/he.ts b/packages/app/src/runtime/i18n/he.ts index ef40fdb8b147..c259978574ad 100644 --- a/packages/app/src/runtime/i18n/he.ts +++ b/packages/app/src/runtime/i18n/he.ts @@ -373,8 +373,6 @@ export const dict = { "prompt.attachment.remove": "הסר את הקובץ המצורף", "prompt.action.send": "שלח", "prompt.action.stop": "עצור", - "prompt.toast.pasteUnsupported.title": "קובץ מצורף לא נתמך", - "prompt.toast.pasteUnsupported.description": "ניתן לצרף כאן רק תמונות, קובצי PDF או קבצי טקסט.", "prompt.toast.attachmentDuplicate.title": "הקובץ הזה כבר הועלה", "prompt.toast.modelAgentRequired.title": "בחר סוכן ומודל", "prompt.toast.modelAgentRequired.description": "יש לבחור סוכן ומודל לפני שליחת פרומפט.", diff --git a/packages/app/src/runtime/i18n/hi.ts b/packages/app/src/runtime/i18n/hi.ts index 4e9f5e60ae64..d4d4d631f324 100644 --- a/packages/app/src/runtime/i18n/hi.ts +++ b/packages/app/src/runtime/i18n/hi.ts @@ -382,8 +382,6 @@ export const dict = { "prompt.attachment.remove": "अनुलग्नक हटाएँ", "prompt.action.send": "भेजें", "prompt.action.stop": "रोकें", - "prompt.toast.pasteUnsupported.title": "असमर्थित अनुलग्नक", - "prompt.toast.pasteUnsupported.description": "यहां केवल छवियां, PDFs, या टेक्स्ट फ़ाइलें संलग्न की जा सकती हैं।", "prompt.toast.attachmentDuplicate.title": "यह फ़ाइल पहले ही अपलोड की जा चुकी है", "prompt.toast.modelAgentRequired.title": "एक एजेंट और मॉडल चुनें", "prompt.toast.modelAgentRequired.description": "प्रॉम्प्ट भेजने से पहले एक एजेंट और मॉडल चुनें।", diff --git a/packages/app/src/runtime/i18n/hr.ts b/packages/app/src/runtime/i18n/hr.ts index 8ffb9712d520..639fc9d89346 100644 --- a/packages/app/src/runtime/i18n/hr.ts +++ b/packages/app/src/runtime/i18n/hr.ts @@ -379,8 +379,6 @@ export const dict = { "prompt.attachment.remove": "Ukloni privitak", "prompt.action.send": "Poslati", "prompt.action.stop": "Zaustavi", - "prompt.toast.pasteUnsupported.title": "Nepodržani privitak", - "prompt.toast.pasteUnsupported.description": "Ovdje se mogu priložiti samo slike, PDF-ovi ili tekstualne datoteke.", "prompt.toast.attachmentDuplicate.title": "Ova datoteka je već učitana", "prompt.toast.modelAgentRequired.title": "Odaberite agenta i model", "prompt.toast.modelAgentRequired.description": "Odaberite agenta i model prije slanja upita.", diff --git a/packages/app/src/runtime/i18n/hu.ts b/packages/app/src/runtime/i18n/hu.ts index b641db2e2d3d..7d720dbd15f0 100644 --- a/packages/app/src/runtime/i18n/hu.ts +++ b/packages/app/src/runtime/i18n/hu.ts @@ -379,8 +379,6 @@ export const dict = { "prompt.attachment.remove": "Távolítsa el a mellékletet", "prompt.action.send": "Elküld", "prompt.action.stop": "Leállítás", - "prompt.toast.pasteUnsupported.title": "Nem támogatott melléklet", - "prompt.toast.pasteUnsupported.description": "Ide csak képeket, PDF-eket vagy szöveges fájlokat lehet csatolni.", "prompt.toast.attachmentDuplicate.title": "Ezt a fájlt már feltöltötték", "prompt.toast.modelAgentRequired.title": "Válasszon egy ügynököt és modellt", "prompt.toast.modelAgentRequired.description": "A felszólítás elküldése előtt válasszon ügynököt és modellt.", diff --git a/packages/app/src/runtime/i18n/hy.ts b/packages/app/src/runtime/i18n/hy.ts index ef06d344b1bc..7cadd4669c23 100644 --- a/packages/app/src/runtime/i18n/hy.ts +++ b/packages/app/src/runtime/i18n/hy.ts @@ -377,8 +377,6 @@ export const dict = { "prompt.attachment.remove": "Հեռացնել հավելվածը", "prompt.action.send": "Ուղարկել", "prompt.action.stop": "Կանգնեցնել", - "prompt.toast.pasteUnsupported.title": "Չաջակցվող հավելված", - "prompt.toast.pasteUnsupported.description": "Այստեղ կարող են կցվել միայն պատկերներ, PDF կամ տեքստային ֆայլեր։", "prompt.toast.attachmentDuplicate.title": "Այս ֆայլն արդեն վերբեռնվել է", "prompt.toast.modelAgentRequired.title": "Ընտրեք գործակալ և մոդել", "prompt.toast.modelAgentRequired.description": "Ընտրեք գործակալ և մոդել՝ նախքան հուշում ուղարկելը։", diff --git a/packages/app/src/runtime/i18n/id.ts b/packages/app/src/runtime/i18n/id.ts index 3bc7936a64b6..ca46feb28fb5 100644 --- a/packages/app/src/runtime/i18n/id.ts +++ b/packages/app/src/runtime/i18n/id.ts @@ -403,8 +403,6 @@ export const dict = { "prompt.action.send": "Kirim", "prompt.action.stop": "Hentikan", - "prompt.toast.pasteUnsupported.title": "Lampiran tidak didukung", - "prompt.toast.pasteUnsupported.description": "Hanya gambar, PDF, atau berkas teks yang dapat dilampirkan di sini.", "prompt.toast.attachmentDuplicate.title": "Berkas ini sudah diunggah", "prompt.toast.modelAgentRequired.title": "Pilih agen dan model", "prompt.toast.modelAgentRequired.description": "Pilih agen dan model sebelum mengirim prompt.", diff --git a/packages/app/src/runtime/i18n/is.ts b/packages/app/src/runtime/i18n/is.ts index 5e6d5feaf50e..090c854fc00a 100644 --- a/packages/app/src/runtime/i18n/is.ts +++ b/packages/app/src/runtime/i18n/is.ts @@ -379,8 +379,6 @@ export const dict = { "prompt.attachment.remove": "Fjarlægðu viðhengi", "prompt.action.send": "Senda", "prompt.action.stop": "Stöðva", - "prompt.toast.pasteUnsupported.title": "Óstudd viðhengi", - "prompt.toast.pasteUnsupported.description": "Aðeins er hægt að hengja myndir, PDF-skjöl eða textaskrár hér við.", "prompt.toast.attachmentDuplicate.title": "Þessari skrá hefur þegar verið hlaðið upp", "prompt.toast.modelAgentRequired.title": "Veldu fulltrúa og líkan", "prompt.toast.modelAgentRequired.description": "Veldu fulltrúa og líkan áður en þú sendir kvaðningu.", diff --git a/packages/app/src/runtime/i18n/it.ts b/packages/app/src/runtime/i18n/it.ts index 30ccf5f35a17..57a9ca014dcc 100644 --- a/packages/app/src/runtime/i18n/it.ts +++ b/packages/app/src/runtime/i18n/it.ts @@ -284,8 +284,6 @@ export const dict = { "prompt.attachment.remove": "Rimuovi l'allegato", "prompt.action.send": "Invia", "prompt.action.stop": "Interrompi", - "prompt.toast.pasteUnsupported.title": "Allegato non supportato", - "prompt.toast.pasteUnsupported.description": "Qui è possibile allegare solo immagini, PDF o file di testo.", "prompt.toast.attachmentDuplicate.title": "Questo file è già stato caricato", "prompt.toast.modelAgentRequired.title": "Seleziona un agente e un modello", "prompt.toast.modelAgentRequired.description": "Scegli un agente e un modello prima di inviare un prompt.", diff --git a/packages/app/src/runtime/i18n/ja.ts b/packages/app/src/runtime/i18n/ja.ts index 1e8b1c2767ce..bf20faff7001 100644 --- a/packages/app/src/runtime/i18n/ja.ts +++ b/packages/app/src/runtime/i18n/ja.ts @@ -379,9 +379,7 @@ export const dict = { "prompt.attachment.remove": "添付ファイルを削除", "prompt.action.send": "送信", "prompt.action.stop": "停止", - "prompt.toast.pasteUnsupported.title": "サポートされていない添付ファイル", "prompt.toast.attachmentDuplicate.title": "このファイルはすでにアップロードされています", - "prompt.toast.pasteUnsupported.description": "画像、PDF、またはテキストファイルのみ添付できます。", "prompt.toast.modelAgentRequired.title": "エージェントとモデルを選択", "prompt.toast.modelAgentRequired.description": "プロンプトを送信する前にエージェントとモデルを選択してください。", "prompt.toast.worktreeCreateFailed.title": "ワークツリーの作成に失敗しました", diff --git a/packages/app/src/runtime/i18n/ka.ts b/packages/app/src/runtime/i18n/ka.ts index ce9de389f4cc..670f69054103 100644 --- a/packages/app/src/runtime/i18n/ka.ts +++ b/packages/app/src/runtime/i18n/ka.ts @@ -375,8 +375,6 @@ export const dict = { "prompt.attachment.remove": "დანართის წაშლა", "prompt.action.send": "გაგზავნა", "prompt.action.stop": "შეჩერება", - "prompt.toast.pasteUnsupported.title": "მხარდაუჭერელი დანართი", - "prompt.toast.pasteUnsupported.description": "აქ შეიძლება დაერთოს მხოლოდ სურათები, PDF ან ტექსტური ფაილები.", "prompt.toast.attachmentDuplicate.title": "ეს ფაილი უკვე ატვირთულია", "prompt.toast.modelAgentRequired.title": "აირჩიეთ აგენტი და მოდელი", "prompt.toast.modelAgentRequired.description": "აირჩიეთ აგენტი და მოდელი მოთხოვნის გაგზავნამდე.", diff --git a/packages/app/src/runtime/i18n/km.ts b/packages/app/src/runtime/i18n/km.ts index 408069c04d38..61b7bc520b0b 100644 --- a/packages/app/src/runtime/i18n/km.ts +++ b/packages/app/src/runtime/i18n/km.ts @@ -374,8 +374,6 @@ export const dict = { "prompt.attachment.remove": "លុបឯកសារភ្ជាប់ចេញ", "prompt.action.send": "ផ្ញើ", "prompt.action.stop": "ឈប់", - "prompt.toast.pasteUnsupported.title": "ឯកសារភ្ជាប់ដែលមិនគាំទ្រ", - "prompt.toast.pasteUnsupported.description": "មានតែរូបភាព PDF ឬឯកសារអត្ថបទប៉ុណ្ណោះដែលអាចភ្ជាប់មកទីនេះបាន។", "prompt.toast.attachmentDuplicate.title": "ឯកសារនេះត្រូវបានផ្ទុកឡើងរួចហើយ", "prompt.toast.modelAgentRequired.title": "ជ្រើសរើសភ្នាក់ងារ និងម៉ូដែល", "prompt.toast.modelAgentRequired.description": "ជ្រើសរើសភ្នាក់ងារ និងម៉ូដែលមុនពេលផ្ញើប្រអប់បញ្ចូល។", diff --git a/packages/app/src/runtime/i18n/ko.ts b/packages/app/src/runtime/i18n/ko.ts index 3cafc4bc5b32..825adaf632cc 100644 --- a/packages/app/src/runtime/i18n/ko.ts +++ b/packages/app/src/runtime/i18n/ko.ts @@ -268,9 +268,7 @@ export const dict = { "prompt.attachment.remove": "첨부 파일 제거", "prompt.action.send": "전송", "prompt.action.stop": "중지", - "prompt.toast.pasteUnsupported.title": "지원되지 않는 첨부 파일", "prompt.toast.attachmentDuplicate.title": "이 파일은 이미 업로드되었습니다", - "prompt.toast.pasteUnsupported.description": "이미지, PDF 또는 텍스트 파일만 첨부할 수 있습니다.", "prompt.toast.modelAgentRequired.title": "에이전트 및 모델 선택", "prompt.toast.modelAgentRequired.description": "프롬프트를 보내기 전에 에이전트와 모델을 선택하세요.", "prompt.toast.worktreeCreateFailed.title": "작업 트리 생성 실패", diff --git a/packages/app/src/runtime/i18n/lo.ts b/packages/app/src/runtime/i18n/lo.ts index 0144da1e1b9c..1470cf54b9aa 100644 --- a/packages/app/src/runtime/i18n/lo.ts +++ b/packages/app/src/runtime/i18n/lo.ts @@ -374,8 +374,6 @@ export const dict = { "prompt.attachment.remove": "ເອົາໄຟລ໌ແນບອອກ", "prompt.action.send": "ສົ່ງ", "prompt.action.stop": "ຢຸດ", - "prompt.toast.pasteUnsupported.title": "ບໍ່ຮອງຮັບໄຟລ໌ແນບ", - "prompt.toast.pasteUnsupported.description": "ພຽງແຕ່ຮູບພາບ, PDFs, ຫຼືໄຟລ໌ຂໍ້ຄວາມສາມາດຕິດຢູ່ນີ້.", "prompt.toast.attachmentDuplicate.title": "ໄຟລ໌ນີ້ໄດ້ຖືກອັບໂຫລດໄປກ່ອນແລ້ວ", "prompt.toast.modelAgentRequired.title": "ເລືອກຕົວແທນ ແລະຕົວແບບ", "prompt.toast.modelAgentRequired.description": "ເລືອກຕົວແທນ ແລະຕົວແບບກ່ອນສົ່ງ prompt.", diff --git a/packages/app/src/runtime/i18n/lt.ts b/packages/app/src/runtime/i18n/lt.ts index a9741a2668ee..c93ac8d8357f 100644 --- a/packages/app/src/runtime/i18n/lt.ts +++ b/packages/app/src/runtime/i18n/lt.ts @@ -380,8 +380,6 @@ export const dict = { "prompt.attachment.remove": "Pašalinti priedą", "prompt.action.send": "Siųsti", "prompt.action.stop": "Stabdyti", - "prompt.toast.pasteUnsupported.title": "Nepalaikomas priedas", - "prompt.toast.pasteUnsupported.description": "Čia galima pridėti tik vaizdus, ​​PDF arba tekstinius failus.", "prompt.toast.attachmentDuplicate.title": "Šis failas jau buvo įkeltas", "prompt.toast.modelAgentRequired.title": "Pasirinkite agentą ir modelį", "prompt.toast.modelAgentRequired.description": "Prieš siųsdami raginimą, pasirinkite agentą ir modelį.", diff --git a/packages/app/src/runtime/i18n/lv.ts b/packages/app/src/runtime/i18n/lv.ts index 588a7249ccdd..84e25fb13db2 100644 --- a/packages/app/src/runtime/i18n/lv.ts +++ b/packages/app/src/runtime/i18n/lv.ts @@ -375,8 +375,6 @@ export const dict = { "prompt.attachment.remove": "Noņemt pielikumu", "prompt.action.send": "Sūtīt", "prompt.action.stop": "Apturēt", - "prompt.toast.pasteUnsupported.title": "Neatbalstīts pielikums", - "prompt.toast.pasteUnsupported.description": "Šeit var pievienot tikai attēlus, PDF vai teksta failus.", "prompt.toast.attachmentDuplicate.title": "Šis fails jau ir augšupielādēts", "prompt.toast.modelAgentRequired.title": "Izvēlieties aģentu un modeli", "prompt.toast.modelAgentRequired.description": "Pirms nosūtīšanas izvēlieties aģentu un modeli.", diff --git a/packages/app/src/runtime/i18n/mk.ts b/packages/app/src/runtime/i18n/mk.ts index 8e4a484cbad3..bfba54b1a3fe 100644 --- a/packages/app/src/runtime/i18n/mk.ts +++ b/packages/app/src/runtime/i18n/mk.ts @@ -376,8 +376,6 @@ export const dict = { "prompt.attachment.remove": "Отстранете го прилогот", "prompt.action.send": "Испрати", "prompt.action.stop": "Стоп", - "prompt.toast.pasteUnsupported.title": "Неподдржан прилог", - "prompt.toast.pasteUnsupported.description": "Овде може да се прикачат само слики, PDFs или текстуални датотеки.", "prompt.toast.attachmentDuplicate.title": "Оваа датотека е веќе поставена", "prompt.toast.modelAgentRequired.title": "Изберете агент и модел", "prompt.toast.modelAgentRequired.description": "Изберете агент и модел пред да испратите известување.", diff --git a/packages/app/src/runtime/i18n/mn.ts b/packages/app/src/runtime/i18n/mn.ts index eaf64a410075..69f764c4ae3a 100644 --- a/packages/app/src/runtime/i18n/mn.ts +++ b/packages/app/src/runtime/i18n/mn.ts @@ -378,8 +378,6 @@ export const dict = { "prompt.attachment.remove": "Хавсралтыг устгана уу", "prompt.action.send": "Илгээх", "prompt.action.stop": "Зогс", - "prompt.toast.pasteUnsupported.title": "Дэмжигдээгүй хавсралт", - "prompt.toast.pasteUnsupported.description": "Энд зөвхөн зураг, PDFс, эсвэл текст файлыг хавсаргах боломжтой.", "prompt.toast.attachmentDuplicate.title": "Энэ файлыг аль хэдийн байршуулсан байна", "prompt.toast.modelAgentRequired.title": "Агент болон загварыг сонгоно уу", "prompt.toast.modelAgentRequired.description": "Промпт илгээхээсээ өмнө агент болон загварыг сонгоно уу.", diff --git a/packages/app/src/runtime/i18n/ms.ts b/packages/app/src/runtime/i18n/ms.ts index 024819a41506..dd989c0cfd65 100644 --- a/packages/app/src/runtime/i18n/ms.ts +++ b/packages/app/src/runtime/i18n/ms.ts @@ -375,8 +375,6 @@ export const dict = { "prompt.attachment.remove": "Buang lampiran", "prompt.action.send": "Hantar", "prompt.action.stop": "Henti", - "prompt.toast.pasteUnsupported.title": "Lampiran tidak disokong", - "prompt.toast.pasteUnsupported.description": "Hanya imej, PDF, atau fail teks boleh dilampirkan di sini.", "prompt.toast.attachmentDuplicate.title": "Fail ini telah dimuat naik", "prompt.toast.modelAgentRequired.title": "Pilih ejen dan model", "prompt.toast.modelAgentRequired.description": "Pilih ejen dan model sebelum menghantar prompt.", diff --git a/packages/app/src/runtime/i18n/my.ts b/packages/app/src/runtime/i18n/my.ts index 7ea2d45aa7ab..3969a13c03c3 100644 --- a/packages/app/src/runtime/i18n/my.ts +++ b/packages/app/src/runtime/i18n/my.ts @@ -378,9 +378,6 @@ export const dict = { "prompt.attachment.remove": "ပူးတွဲပါဖိုင်ကို ဖယ်ရှားပါ။", "prompt.action.send": "ပို့ပါ။", "prompt.action.stop": "ရပ်ပါ။", - "prompt.toast.pasteUnsupported.title": "ပူးတွဲပါဖိုင်ကို ပံ့ပိုးမထားပါ။", - "prompt.toast.pasteUnsupported.description": - "ရုပ်ပုံများ၊ PDF များ သို့မဟုတ် စာသားဖိုင်များကိုသာ ဤနေရာတွင် ပူးတွဲနိုင်ပါသည်။", "prompt.toast.attachmentDuplicate.title": "ဤဖိုင်ကို အပ်လုဒ်လုပ်ပြီးပါပြီ။", "prompt.toast.modelAgentRequired.title": "အေးဂျင့်နှင့် မော်ဒယ်ကို ရွေးပါ။", "prompt.toast.modelAgentRequired.description": "Prompt မပို့မီ အေးဂျင့်နှင့် မော်ဒယ်ကို ရွေးပါ။", diff --git a/packages/app/src/runtime/i18n/ne.ts b/packages/app/src/runtime/i18n/ne.ts index 122c015f23fc..7a1a8a035433 100644 --- a/packages/app/src/runtime/i18n/ne.ts +++ b/packages/app/src/runtime/i18n/ne.ts @@ -376,8 +376,6 @@ export const dict: Record = { "prompt.attachment.remove": "संलग्नक हटाउनुहोस्", "prompt.action.send": "पठाउनुहोस्", "prompt.action.stop": "रोक्नुहोस्", - "prompt.toast.pasteUnsupported.title": "असमर्थित संलग्नक", - "prompt.toast.pasteUnsupported.description": "केवल छविहरू, PDF हरू, वा पाठ फाइलहरू यहाँ संलग्न गर्न सकिन्छ।", "prompt.toast.attachmentDuplicate.title": "यो फाइल पहिले नै अपलोड गरिएको छ", "prompt.toast.modelAgentRequired.title": "एक एजेन्ट र मोडेल चयन गर्नुहोस्", "prompt.toast.modelAgentRequired.description": "प्रम्प्ट पठाउनु अघि एजेन्ट र मोडेल छान्नुहोस्।", diff --git a/packages/app/src/runtime/i18n/nl.ts b/packages/app/src/runtime/i18n/nl.ts index 16a243c56531..b5289c5a7ba4 100644 --- a/packages/app/src/runtime/i18n/nl.ts +++ b/packages/app/src/runtime/i18n/nl.ts @@ -375,9 +375,6 @@ export const dict = { "prompt.attachment.remove": "Bijlage verwijderen", "prompt.action.send": "Verzenden", "prompt.action.stop": "Stop", - "prompt.toast.pasteUnsupported.title": "Niet-ondersteunde bijlage", - "prompt.toast.pasteUnsupported.description": - "Hier kunnen alleen afbeeldingen, pdf's of tekstbestanden worden bijgevoegd.", "prompt.toast.attachmentDuplicate.title": "Dit bestand is al geüpload", "prompt.toast.modelAgentRequired.title": "Selecteer een agent en model", "prompt.toast.modelAgentRequired.description": "Kies een agent en model voordat je een prompt verzendt.", diff --git a/packages/app/src/runtime/i18n/no.ts b/packages/app/src/runtime/i18n/no.ts index 83776c638fc2..33df2a852392 100644 --- a/packages/app/src/runtime/i18n/no.ts +++ b/packages/app/src/runtime/i18n/no.ts @@ -393,9 +393,7 @@ export const dict = { "prompt.action.send": "Send", "prompt.action.stop": "Stopp", - "prompt.toast.pasteUnsupported.title": "Ikke støttet vedlegg", "prompt.toast.attachmentDuplicate.title": "Denne filen er allerede lastet opp", - "prompt.toast.pasteUnsupported.description": "Kun bilder, PDF-er eller tekstfiler kan legges ved her.", "prompt.toast.modelAgentRequired.title": "Velg en agent og modell", "prompt.toast.modelAgentRequired.description": "Velg en agent og modell før du sender en forespørsel.", "prompt.toast.worktreeCreateFailed.title": "Kunne ikke opprette worktree", diff --git a/packages/app/src/runtime/i18n/pa.ts b/packages/app/src/runtime/i18n/pa.ts index 8e1e9eddfbda..25063270535f 100644 --- a/packages/app/src/runtime/i18n/pa.ts +++ b/packages/app/src/runtime/i18n/pa.ts @@ -381,9 +381,6 @@ export const dict = { "prompt.attachment.remove": "منسلکہ ہٹا دیو", "prompt.action.send": "گھلو", "prompt.action.stop": "روکو", - "prompt.toast.pasteUnsupported.title": "غیر تعاون یافتہ منسلکہ", - "prompt.toast.pasteUnsupported.description": - "ایتھے صرف تصویراں، پی ڈی ایف، یا ٹیکسٹ فائلاں منسلک کیتیاں جا سکدیاں نیں۔", "prompt.toast.attachmentDuplicate.title": "ایہہ فائل پہلے ای اپ لوڈ ہو چکی اے", "prompt.toast.modelAgentRequired.title": "اک ایجنٹ تے ماڈل چنو", "prompt.toast.modelAgentRequired.description": "پرامپٹ بھیجن توں پہلاں اک ایجنٹ تے ماڈل دا انتخاب کرو۔", diff --git a/packages/app/src/runtime/i18n/pl.ts b/packages/app/src/runtime/i18n/pl.ts index 62e6ae619479..9bb984021d33 100644 --- a/packages/app/src/runtime/i18n/pl.ts +++ b/packages/app/src/runtime/i18n/pl.ts @@ -382,9 +382,7 @@ export const dict = { "prompt.attachment.remove": "Usuń załącznik", "prompt.action.send": "Wyślij", "prompt.action.stop": "Zatrzymaj", - "prompt.toast.pasteUnsupported.title": "Nieobsługiwany załącznik", "prompt.toast.attachmentDuplicate.title": "Ten plik został już przesłany", - "prompt.toast.pasteUnsupported.description": "Można tutaj załączać tylko obrazy, pliki PDF lub pliki tekstowe.", "prompt.toast.modelAgentRequired.title": "Wybierz agenta i model", "prompt.toast.modelAgentRequired.description": "Wybierz agenta i model przed wysłaniem zapytania.", "prompt.toast.worktreeCreateFailed.title": "Nie udało się utworzyć drzewa roboczego", diff --git a/packages/app/src/runtime/i18n/ro.ts b/packages/app/src/runtime/i18n/ro.ts index 64b21287d870..7a0bf29b9998 100644 --- a/packages/app/src/runtime/i18n/ro.ts +++ b/packages/app/src/runtime/i18n/ro.ts @@ -374,8 +374,6 @@ export const dict = { "prompt.attachment.remove": "Elimină atașamentul", "prompt.action.send": "Trimite", "prompt.action.stop": "Oprește", - "prompt.toast.pasteUnsupported.title": "Atașament neacceptat", - "prompt.toast.pasteUnsupported.description": "Poți atașa doar imagini, PDF-uri sau fișiere text aici.", "prompt.toast.attachmentDuplicate.title": "Acest fișier a fost deja încărcat", "prompt.toast.modelAgentRequired.title": "Selectează un agent și un model", "prompt.toast.modelAgentRequired.description": "Alege un agent și un model înainte de a trimite un prompt.", diff --git a/packages/app/src/runtime/i18n/ru.ts b/packages/app/src/runtime/i18n/ru.ts index a667bc126cd3..fe86bda40ddc 100644 --- a/packages/app/src/runtime/i18n/ru.ts +++ b/packages/app/src/runtime/i18n/ru.ts @@ -401,9 +401,7 @@ export const dict = { "prompt.action.send": "Отправить", "prompt.action.stop": "Остановить", - "prompt.toast.pasteUnsupported.title": "Неподдерживаемое вложение", "prompt.toast.attachmentDuplicate.title": "Этот файл уже загружен", - "prompt.toast.pasteUnsupported.description": "Здесь можно прикрепить только изображения, PDF или текстовые файлы.", "prompt.toast.modelAgentRequired.title": "Выберите агента и модель", "prompt.toast.modelAgentRequired.description": "Выберите агента и модель перед отправкой запроса.", "prompt.toast.worktreeCreateFailed.title": "Не удалось создать worktree", diff --git a/packages/app/src/runtime/i18n/si.ts b/packages/app/src/runtime/i18n/si.ts index aba8428b4d4a..ff30705c08cf 100644 --- a/packages/app/src/runtime/i18n/si.ts +++ b/packages/app/src/runtime/i18n/si.ts @@ -374,8 +374,6 @@ export const dict: Record = { "prompt.attachment.remove": "ඇමුණුම ඉවත් කරන්න", "prompt.action.send": "යවන්න", "prompt.action.stop": "නවත්වන්න", - "prompt.toast.pasteUnsupported.title": "සහාය නොදක්වන ඇමුණුම", - "prompt.toast.pasteUnsupported.description": "පින්තූර, PDF හෝ පෙළ ගොනු පමණක් මෙහි ඇමිණිය හැක.", "prompt.toast.attachmentDuplicate.title": "මෙම ගොනුව දැනටමත් උඩුගත කර ඇත", "prompt.toast.modelAgentRequired.title": "නියෝජිතයෙකු සහ ආකෘතියක් තෝරන්න", "prompt.toast.modelAgentRequired.description": "ප්‍රොම්ප්ට් එකක් යැවීමට පෙර නියෝජිතයෙකු සහ ආකෘතියක් තෝරන්න.", diff --git a/packages/app/src/runtime/i18n/sk.ts b/packages/app/src/runtime/i18n/sk.ts index 601e47e2911c..9c680ecc0b4f 100644 --- a/packages/app/src/runtime/i18n/sk.ts +++ b/packages/app/src/runtime/i18n/sk.ts @@ -374,8 +374,6 @@ export const dict = { "prompt.attachment.remove": "Odstrániť prílohu", "prompt.action.send": "Odoslať", "prompt.action.stop": "Zastaviť", - "prompt.toast.pasteUnsupported.title": "Nepodporovaná príloha", - "prompt.toast.pasteUnsupported.description": "Pripojiť možno len obrázky, PDF alebo textové súbory.", "prompt.toast.attachmentDuplicate.title": "Tento súbor už bol nahraný", "prompt.toast.modelAgentRequired.title": "Vyberte agenta a model", "prompt.toast.modelAgentRequired.description": "Pred odoslaním výzvy vyberte agenta a model.", diff --git a/packages/app/src/runtime/i18n/sl.ts b/packages/app/src/runtime/i18n/sl.ts index 1977f19ba744..7bf5276a9ed5 100644 --- a/packages/app/src/runtime/i18n/sl.ts +++ b/packages/app/src/runtime/i18n/sl.ts @@ -374,8 +374,6 @@ export const dict = { "prompt.attachment.remove": "Odstrani prilogo", "prompt.action.send": "Pošlji", "prompt.action.stop": "Ustavi", - "prompt.toast.pasteUnsupported.title": "Nepodprta priloga", - "prompt.toast.pasteUnsupported.description": "Sem lahko priložite samo slike, datoteke PDF ali besedilne datoteke.", "prompt.toast.attachmentDuplicate.title": "Ta datoteka je že naložena", "prompt.toast.modelAgentRequired.title": "Izberite agenta in model", "prompt.toast.modelAgentRequired.description": "Preden pošljete poziv, izberite agenta in model.", diff --git a/packages/app/src/runtime/i18n/sq.ts b/packages/app/src/runtime/i18n/sq.ts index 7ffa5871dd7e..57c5c8364ead 100644 --- a/packages/app/src/runtime/i18n/sq.ts +++ b/packages/app/src/runtime/i18n/sq.ts @@ -375,9 +375,6 @@ export const dict = { "prompt.attachment.remove": "Hiq shtojcën", "prompt.action.send": "Dërgo", "prompt.action.stop": "Ndalo", - "prompt.toast.pasteUnsupported.title": "Bashkëngjitje e pambështetur", - "prompt.toast.pasteUnsupported.description": - "Këtu mund të bashkëngjiten vetëm imazhe, skedarë PDF ose skedarë teksti.", "prompt.toast.attachmentDuplicate.title": "Ky skedar tashmë është ngarkuar", "prompt.toast.modelAgentRequired.title": "Zgjidhni një agjent dhe model", "prompt.toast.modelAgentRequired.description": "Zgjidhni një agjent dhe model përpara se të dërgoni një kërkesë.", diff --git a/packages/app/src/runtime/i18n/sr.ts b/packages/app/src/runtime/i18n/sr.ts index 89ad4bc48b95..c81aee225500 100644 --- a/packages/app/src/runtime/i18n/sr.ts +++ b/packages/app/src/runtime/i18n/sr.ts @@ -375,8 +375,6 @@ export const dict = { "prompt.attachment.remove": "Уклоните прилог", "prompt.action.send": "Пошаљи", "prompt.action.stop": "Стоп", - "prompt.toast.pasteUnsupported.title": "Неподржани прилог", - "prompt.toast.pasteUnsupported.description": "Овде се могу приложити само слике, PDFс или текстуалне датотеке.", "prompt.toast.attachmentDuplicate.title": "Ова датотека је већ отпремљена", "prompt.toast.modelAgentRequired.title": "Изаберите агента и модел", "prompt.toast.modelAgentRequired.description": "Одаберите агента и модел пре него што пошаљете упит.", diff --git a/packages/app/src/runtime/i18n/sv.ts b/packages/app/src/runtime/i18n/sv.ts index b2ad5bafda8b..f42f9dfbe018 100644 --- a/packages/app/src/runtime/i18n/sv.ts +++ b/packages/app/src/runtime/i18n/sv.ts @@ -376,8 +376,6 @@ export const dict = { "prompt.attachment.remove": "Ta bort bilagan", "prompt.action.send": "Skicka", "prompt.action.stop": "Stoppa", - "prompt.toast.pasteUnsupported.title": "Bilaga som inte stöds", - "prompt.toast.pasteUnsupported.description": "Endast bilder, PDF-filer eller textfiler kan bifogas här.", "prompt.toast.attachmentDuplicate.title": "Den här filen har redan laddats upp", "prompt.toast.modelAgentRequired.title": "Välj en agent och modell", "prompt.toast.modelAgentRequired.description": "Välj en agent och modell innan du skickar en prompt.", diff --git a/packages/app/src/runtime/i18n/tg.ts b/packages/app/src/runtime/i18n/tg.ts index 679d56aecd0a..3ff2e4e90871 100644 --- a/packages/app/src/runtime/i18n/tg.ts +++ b/packages/app/src/runtime/i18n/tg.ts @@ -376,9 +376,6 @@ export const dict = { "prompt.attachment.remove": "Замимаро хориҷ кунед", "prompt.action.send": "Фиристодан", "prompt.action.stop": "Ист", - "prompt.toast.pasteUnsupported.title": "Замимаи дастгирӣнашаванда", - "prompt.toast.pasteUnsupported.description": - "Дар ин ҷо танҳо тасвирҳо, PDFс ё файлҳои матнӣ замима кардан мумкин аст.", "prompt.toast.attachmentDuplicate.title": "Ин файл аллакай бор карда шудааст", "prompt.toast.modelAgentRequired.title": "Агент ва моделро интихоб кунед", "prompt.toast.modelAgentRequired.description": "Пеш аз фиристодани промпт агент ва моделро интихоб кунед.", diff --git a/packages/app/src/runtime/i18n/th.ts b/packages/app/src/runtime/i18n/th.ts index c585c50027eb..b77771185fff 100644 --- a/packages/app/src/runtime/i18n/th.ts +++ b/packages/app/src/runtime/i18n/th.ts @@ -400,9 +400,7 @@ export const dict = { "prompt.action.send": "ส่ง", "prompt.action.stop": "หยุด", - "prompt.toast.pasteUnsupported.title": "ไฟล์แนบที่ไม่รองรับ", "prompt.toast.attachmentDuplicate.title": "ไฟล์นี้ถูกอัปโหลดแล้ว", - "prompt.toast.pasteUnsupported.description": "แนบได้เฉพาะรูปภาพ PDF หรือไฟล์ข้อความเท่านั้น", "prompt.toast.modelAgentRequired.title": "เลือกเอเจนต์และโมเดล", "prompt.toast.modelAgentRequired.description": "เลือกเอเจนต์และโมเดลก่อนส่งพรอมต์", "prompt.toast.worktreeCreateFailed.title": "ไม่สามารถสร้าง worktree", diff --git a/packages/app/src/runtime/i18n/tk.ts b/packages/app/src/runtime/i18n/tk.ts index 0a24412b852b..bb1472cb6a87 100644 --- a/packages/app/src/runtime/i18n/tk.ts +++ b/packages/app/src/runtime/i18n/tk.ts @@ -375,8 +375,6 @@ export const dict = { "prompt.attachment.remove": "Goşundyny aýyryň", "prompt.action.send": "Iber", "prompt.action.stop": "Dur", - "prompt.toast.pasteUnsupported.title": "Goldaw berilmeýän goşundy", - "prompt.toast.pasteUnsupported.description": "Bu ýerde diňe suratlar, PDF ýa-da tekst faýllary birikdirilip bilner.", "prompt.toast.attachmentDuplicate.title": "Bu faýl eýýäm ýüklendi", "prompt.toast.modelAgentRequired.title": "Agent we model saýlaň", "prompt.toast.modelAgentRequired.description": "Sorag ibermezden ozal agent we model saýlaň.", diff --git a/packages/app/src/runtime/i18n/tr.ts b/packages/app/src/runtime/i18n/tr.ts index 64266970a1dd..577b216f361c 100644 --- a/packages/app/src/runtime/i18n/tr.ts +++ b/packages/app/src/runtime/i18n/tr.ts @@ -407,9 +407,7 @@ export const dict = { "prompt.action.send": "Gönder", "prompt.action.stop": "Durdur", - "prompt.toast.pasteUnsupported.title": "Desteklenmeyen ek", "prompt.toast.attachmentDuplicate.title": "Bu dosya zaten yüklendi", - "prompt.toast.pasteUnsupported.description": "Buraya yalnızca resimler, PDF'ler veya metin dosyaları eklenebilir.", "prompt.toast.modelAgentRequired.title": "Bir ajan ve model seçin", "prompt.toast.modelAgentRequired.description": "İstem göndermeden önce bir ajan ve model seçin.", "prompt.toast.worktreeCreateFailed.title": "Çalışma ağacı oluşturulamadı", diff --git a/packages/app/src/runtime/i18n/uk.ts b/packages/app/src/runtime/i18n/uk.ts index 99ba7a64e5be..0d56b7190cd5 100644 --- a/packages/app/src/runtime/i18n/uk.ts +++ b/packages/app/src/runtime/i18n/uk.ts @@ -404,9 +404,7 @@ export const dict = { "prompt.action.send": "Надіслати", "prompt.action.stop": "Зупинити", - "prompt.toast.pasteUnsupported.title": "Непідтримуване вкладення", "prompt.toast.attachmentDuplicate.title": "Цей файл уже завантажено", - "prompt.toast.pasteUnsupported.description": "Сюди можна прикріплювати лише зображення, PDF або текстові файли.", "prompt.toast.modelAgentRequired.title": "Виберіть агента та модель", "prompt.toast.modelAgentRequired.description": "Виберіть агента та модель перед надсиланням запиту.", "prompt.toast.worktreeCreateFailed.title": "Не вдалося створити робоче дерево", diff --git a/packages/app/src/runtime/i18n/ur.ts b/packages/app/src/runtime/i18n/ur.ts index 3cdf0c942b44..646ff6feb170 100644 --- a/packages/app/src/runtime/i18n/ur.ts +++ b/packages/app/src/runtime/i18n/ur.ts @@ -384,8 +384,6 @@ export const dict = { "prompt.attachment.remove": "منسلکہ کو ہٹا دیں۔", "prompt.action.send": "بھیجیں۔", "prompt.action.stop": "روکیں", - "prompt.toast.pasteUnsupported.title": "غیر تعاون یافتہ منسلکہ", - "prompt.toast.pasteUnsupported.description": "یہاں صرف تصاویر، PDFs، یا ٹیکسٹ فائلیں منسلک کی جا سکتی ہیں۔", "prompt.toast.attachmentDuplicate.title": "یہ فائل پہلے ہی اپ لوڈ ہو چکی ہے", "prompt.toast.modelAgentRequired.title": "ایک ایجنٹ اور ماڈل منتخب کریں۔", "prompt.toast.modelAgentRequired.description": "پرامپٹ بھیجنے سے پہلے ایک ایجنٹ اور ماڈل کا انتخاب کریں۔", diff --git a/packages/app/src/runtime/i18n/uz.ts b/packages/app/src/runtime/i18n/uz.ts index ad101eac4a81..a169f2a4065a 100644 --- a/packages/app/src/runtime/i18n/uz.ts +++ b/packages/app/src/runtime/i18n/uz.ts @@ -377,8 +377,6 @@ export const dict = { "prompt.attachment.remove": "Qo'shimchani olib tashlang", "prompt.action.send": "Yuborish", "prompt.action.stop": "To'xtang", - "prompt.toast.pasteUnsupported.title": "Qoʻllab-quvvatlanmaydigan biriktirma", - "prompt.toast.pasteUnsupported.description": "Bu yerda faqat rasmlar, PDF yoki matnli fayllar biriktirilishi mumkin.", "prompt.toast.attachmentDuplicate.title": "Bu fayl allaqachon yuklangan", "prompt.toast.modelAgentRequired.title": "Agent va modelni tanlang", "prompt.toast.modelAgentRequired.description": "So'rov yuborishdan oldin agent va modelni tanlang.", diff --git a/packages/app/src/runtime/i18n/vi.ts b/packages/app/src/runtime/i18n/vi.ts index 221843e1e291..dc5aebf23ba0 100644 --- a/packages/app/src/runtime/i18n/vi.ts +++ b/packages/app/src/runtime/i18n/vi.ts @@ -382,8 +382,6 @@ export const dict = { "prompt.attachment.remove": "Xóa tệp đính kèm", "prompt.action.send": "Gửi", "prompt.action.stop": "Dừng", - "prompt.toast.pasteUnsupported.title": "Tệp đính kèm không được hỗ trợ", - "prompt.toast.pasteUnsupported.description": "Chỉ có thể đính kèm hình ảnh, tệp PDF hoặc tệp văn bản ở đây.", "prompt.toast.attachmentDuplicate.title": "Tệp này đã được tải lên", "prompt.toast.modelAgentRequired.title": "Chọn tác nhân và mô hình", "prompt.toast.modelAgentRequired.description": "Chọn một tác nhân và mô hình trước khi gửi lời nhắc.", diff --git a/packages/app/src/runtime/i18n/zh.ts b/packages/app/src/runtime/i18n/zh.ts index e646d0e2a222..28f5717da375 100644 --- a/packages/app/src/runtime/i18n/zh.ts +++ b/packages/app/src/runtime/i18n/zh.ts @@ -419,9 +419,7 @@ export const dict = { "prompt.attachment.remove": "移除附件", "prompt.action.send": "发送", "prompt.action.stop": "停止", - "prompt.toast.pasteUnsupported.title": "不支持的附件", "prompt.toast.attachmentDuplicate.title": "此文件已上传", - "prompt.toast.pasteUnsupported.description": "此处仅能附加图片、PDF 或文本文件。", "prompt.toast.modelAgentRequired.title": "请选择智能体和模型", "prompt.toast.modelAgentRequired.description": "发送提示前请先选择智能体和模型。", "prompt.toast.worktreeCreateFailed.title": "创建工作区失败", diff --git a/packages/app/src/runtime/i18n/zht.ts b/packages/app/src/runtime/i18n/zht.ts index 40cb629670b1..87da40dcc06e 100644 --- a/packages/app/src/runtime/i18n/zht.ts +++ b/packages/app/src/runtime/i18n/zht.ts @@ -400,9 +400,7 @@ export const dict = { "prompt.action.send": "傳送", "prompt.action.stop": "停止", - "prompt.toast.pasteUnsupported.title": "不支援的附件", "prompt.toast.attachmentDuplicate.title": "此檔案已上傳", - "prompt.toast.pasteUnsupported.description": "此處僅能附加圖片、PDF 或文字檔案。", "prompt.toast.modelAgentRequired.title": "請選擇代理程式和模型", "prompt.toast.modelAgentRequired.description": "傳送提示前請先選擇代理程式和模型。", "prompt.toast.worktreeCreateFailed.title": "建立工作樹失敗", diff --git a/packages/app/src/runtime/persistence/drafts.ts b/packages/app/src/runtime/persistence/drafts.ts index e8c4713c069d..b15cae780ccc 100644 --- a/packages/app/src/runtime/persistence/drafts.ts +++ b/packages/app/src/runtime/persistence/drafts.ts @@ -421,9 +421,17 @@ function referenced(json: string) { return ids } -export async function blobDataUrl(blob: BlobReference, mime: string) { +async function blobData(blob: BlobReference) { const kept = retained.get(aliases.get(blob.id) ?? blob.id) - const data = kept ? kept.blob : await fetch(blob.url).then((response) => response.blob()) + return kept ? kept.blob : await fetch(blob.url).then((response) => response.blob()) +} + +export async function blobBytes(blob: BlobReference) { + return new Uint8Array(await (await blobData(blob)).arrayBuffer()) +} + +export async function blobDataUrl(blob: BlobReference, mime: string) { + const data = await blobData(blob) return new Promise((resolve, reject) => { const reader = new FileReader() reader.addEventListener("error", () => reject(reader.error)) diff --git a/packages/app/src/session/composer/controller.ts b/packages/app/src/session/composer/controller.ts index 4832b00989fe..4b7028a86cd1 100644 --- a/packages/app/src/session/composer/controller.ts +++ b/packages/app/src/session/composer/controller.ts @@ -2,6 +2,7 @@ import { createEffect, createMemo, on, type Accessor } from "solid-js" import type { ComposerControls } from "@/composer/adapter" import { setCursorPosition } from "@/composer/editor/dom" import { createComposerModel } from "@/composer/model" +import { useAttachmentDestination } from "@/composer/attachments/deliver" import { useSettings } from "@/settings/model" import { createActiveComposerAdapter } from "./adapter" import { createSessionQueue } from "./queue" @@ -29,6 +30,7 @@ export function createSessionComposerController(input: { draft: adapter.state, working: adapter.working, behavior: settings.general.followUpBehavior, + destination: useAttachmentDestination(input.controls), restoreFocus: (cursor) => { const target = editor if (!target) return diff --git a/packages/app/src/session/composer/queue.ts b/packages/app/src/session/composer/queue.ts index b7fb22c5afc6..d634cbecbc88 100644 --- a/packages/app/src/session/composer/queue.ts +++ b/packages/app/src/session/composer/queue.ts @@ -8,7 +8,8 @@ import type { ComposerStateTarget } from "@/composer/submission-state" import type { ImageAttachmentPart, Prompt } from "@/composer/state" import { clonePrompt, promptLength } from "@/composer/prompt-parts" import { buildPromptRequest } from "@/composer/request" -import { blobDataUrl, createLegacyBlobReference } from "@/runtime/persistence/drafts" +import { deliverAttachments, type AttachmentDestination } from "@/composer/attachments/deliver" +import { createLegacyBlobReference } from "@/runtime/persistence/drafts" import { useData } from "@/runtime/server/current" import { useServerSDK } from "@/runtime/server/client" import { useWorkspaceLocation } from "@/workspaces/location" @@ -29,6 +30,7 @@ export function createSessionQueue(input: { draft: ComposerStateTarget working: Accessor behavior: Accessor + destination: () => AttachmentDestination restoreFocus: (cursor: number) => void }) { const data = useData() @@ -59,6 +61,7 @@ export function createSessionQueue(input: { change.item, change.prompt, change.text, + input.destination(), ) // Admit before cancelling so a failed replacement never discards the original. const admitted = await data.session.prompt({ @@ -289,13 +292,13 @@ async function editedPromptInput( item: QueuedPrompt | undefined, prompt: Prompt, text: string, + destination: AttachmentDestination, ) { - const images = await Promise.all( - prompt - .filter((part): part is ImageAttachmentPart => part.type === "image") - .map(async (part) => ({ ...part, dataUrl: await blobDataUrl(part.blob, part.mime) })), + const attachments = await deliverAttachments( + prompt.filter((part): part is ImageAttachmentPart => part.type === "image"), + destination, ) - const request = buildPromptRequest({ prompt, context: [], images, text, sessionDirectory: directory }) + const request = buildPromptRequest({ prompt, context: [], attachments, text, sessionDirectory: directory }) const payload = item?.payload const display = item ? queuedPromptText(item) : "" const notes = payload && display && payload.text.startsWith(display) ? payload.text.slice(display.length) : "" @@ -340,6 +343,6 @@ async function editedPromptInput( ], agents: agents.map((agent) => ({ name: agent.name, mention: mention(agent.mention) })), skills: skills.map((skill) => ({ id: skill.id, mention: mention(skill.mention) })), - metadata: { ...payload?.metadata, displayText: request.displayText }, + metadata: { ...payload?.metadata, displayText: request.displayText, attachments: request.attachments }, } } diff --git a/packages/app/src/session/timeline/message-timeline.tsx b/packages/app/src/session/timeline/message-timeline.tsx index be777019cc3b..b5030bf03a45 100644 --- a/packages/app/src/session/timeline/message-timeline.tsx +++ b/packages/app/src/session/timeline/message-timeline.tsx @@ -220,6 +220,7 @@ function MessageTimelineView( return ( (presentation?.displayText ?? message.text).length <= 1024 && !presentation?.comments?.length && + !presentation?.attachments.length && !parseCommentNote(message.text) ) } @@ -297,6 +298,7 @@ function MessageTimelineView( return { displayText: value?.displayText, comments: value?.comments ?? (parsed ? [parsed] : []), + references: value?.attachments, } }, actions: props.actions, diff --git a/packages/desktop/src/renderer/platform/files.test.ts b/packages/desktop/src/renderer/platform/files.test.ts index 8ac4e418cda8..dd9711e8c25e 100644 --- a/packages/desktop/src/renderer/platform/files.test.ts +++ b/packages/desktop/src/renderer/platform/files.test.ts @@ -35,7 +35,7 @@ function fileApi(events: string[]) { describe("desktop attachment files", () => { test("reads selected files sequentially and releases the token", async () => { const events: string[] = [] - const files = createDesktopFiles(fileApi(events), "windows", ["txt"]) + const files = createDesktopFiles(fileApi(events), "windows") await files.openAttachmentPickerDialog({}, async (file) => { events.push(`file:${file.name}`) @@ -52,7 +52,7 @@ describe("desktop attachment files", () => { test("releases the token when a selected file callback fails", async () => { const events: string[] = [] - const files = createDesktopFiles(fileApi(events), "windows", ["txt"]) + const files = createDesktopFiles(fileApi(events), "windows") await expect( files.openAttachmentPickerDialog({}, async () => { @@ -64,7 +64,7 @@ describe("desktop attachment files", () => { test("writes clipboard text through the native desktop API", async () => { const events: string[] = [] - const files = createDesktopFiles(fileApi(events), "windows", ["txt"]) + const files = createDesktopFiles(fileApi(events), "windows") await files.writeClipboardText("ses_123") diff --git a/packages/desktop/src/renderer/platform/files.ts b/packages/desktop/src/renderer/platform/files.ts index 401bb59bcaca..224bec84da3b 100644 --- a/packages/desktop/src/renderer/platform/files.ts +++ b/packages/desktop/src/renderer/platform/files.ts @@ -19,7 +19,7 @@ type DesktopFileAPI = Pick< | "writeClipboardText" > -export function createDesktopFiles(api: DesktopFileAPI, os: DesktopOS, acceptedExtensions: string[]) { +export function createDesktopFiles(api: DesktopFileAPI, os: DesktopOS) { const attachmentPaths = new WeakMap() const openDirectoryPickerDialog: Extract["openDirectoryPickerDialog"] = async ( options, @@ -34,7 +34,7 @@ export function createDesktopFiles(api: DesktopFileAPI, os: DesktopOS, acceptedE multiple: options?.multiple ?? false, title: options?.title, defaultPath: options?.defaultPath, - extensions: options?.extensions ?? acceptedExtensions, + extensions: options?.extensions, }) if (!result) return try { diff --git a/packages/desktop/src/renderer/platform/index.ts b/packages/desktop/src/renderer/platform/index.ts index afb4310125e5..745d6aa79184 100644 --- a/packages/desktop/src/renderer/platform/index.ts +++ b/packages/desktop/src/renderer/platform/index.ts @@ -1,9 +1,4 @@ -import { - ACCEPTED_FILE_EXTENSIONS, - ServerConnection, - type Platform, - type UpdaterPlatform, -} from "@opencode/app/desktop" +import { ServerConnection, type Platform, type UpdaterPlatform } from "@opencode/app/desktop" import type { ElectronAPI } from "../api-types" import { setPinchZoomEnabled, webviewZoom } from "../window/zoom" import { windowFullscreen } from "../window/fullscreen" @@ -29,7 +24,7 @@ export function createDesktopPlatform( os, version: windowState.version, windowID: windowState.id, - ...createDesktopFiles(api, os, ACCEPTED_FILE_EXTENSIONS), + ...createDesktopFiles(api, os), ...createDesktopStorage(api), browserPane: { register(target, onEvent) { diff --git a/packages/session-ui/src/actions.ts b/packages/session-ui/src/actions.ts index 493f967c9c75..7b7a4bfdd923 100644 --- a/packages/session-ui/src/actions.ts +++ b/packages/session-ui/src/actions.ts @@ -9,6 +9,13 @@ export type SessionUserComment = { } } +/** An attachment delivered to the model as a path on the server instead of inline bytes. */ +export type SessionUserAttachmentReference = { + name: string + mime: string + path: string +} + export type SessionUserActions = { openAttachment?: (file: PromptFileAttachment) => void revert?: (input: { sessionID: string; messageID: string }) => Promise | void diff --git a/packages/session-ui/src/message/current-message.tsx b/packages/session-ui/src/message/current-message.tsx index b061968dd68c..73552e6e47df 100644 --- a/packages/session-ui/src/message/current-message.tsx +++ b/packages/session-ui/src/message/current-message.tsx @@ -4,12 +4,12 @@ import type { SessionMessageUser, } from "@opencode/client/promise" import { Match, Switch, type ComponentProps } from "solid-js" -import type { SessionUserActions, SessionUserComment } from "../actions" +import type { SessionUserActions, SessionUserAttachmentReference, SessionUserComment } from "../actions" import { AssistantReasoningContent, AssistantTextContent, CurrentUserMessageDisplay } from "./message-content" import { CurrentContextToolGroup, CurrentFileToolGroup, ToolDisplay } from "../tools/tool-renderer" import { currentToolError, currentToolInput, currentToolMetadata, currentToolOutput } from "./current-tool-state" -export type { SessionUserActions, SessionUserComment } from "../actions" +export type { SessionUserActions, SessionUserAttachmentReference, SessionUserComment } from "../actions" export { SessionShellMessage } from "../tools/tool-renderer" export { currentContentDefaultOpen } from "./current-tool-state" @@ -18,6 +18,7 @@ export function SessionUserMessage(props: { message: SessionMessageUser displayText?: string comments?: SessionUserComment[] + references?: SessionUserAttachmentReference[] historicalAgent: string historicalModel: SessionMessageAssistant["model"] actions?: SessionUserActions @@ -28,6 +29,7 @@ export function SessionUserMessage(props: { message={props.message} text={props.displayText ?? props.message.text} comments={props.comments} + references={props.references} agent={props.historicalAgent} model={props.historicalModel} actions={props.actions} diff --git a/packages/session-ui/src/message/message-content.tsx b/packages/session-ui/src/message/message-content.tsx index ab25a8668040..82535dd083f7 100644 --- a/packages/session-ui/src/message/message-content.tsx +++ b/packages/session-ui/src/message/message-content.tsx @@ -26,7 +26,7 @@ import type { SessionMessageCompaction, SessionMessageUser, } from "@opencode/client/promise" -import type { SessionUserActions, SessionUserComment } from "../actions" +import type { SessionUserActions, SessionUserAttachmentReference, SessionUserComment } from "../actions" import { typeLabel } from "../components/message-file" export async function writeClipboard(text: string): Promise { @@ -208,12 +208,14 @@ export function CurrentUserMessageDisplay(props: { model: SessionMessageAssistant["model"] actions?: SessionUserActions comments?: SessionUserComment[] + references?: SessionUserAttachmentReference[] }) { const data = useData() const dialog = useDialog() const i18n = useI18n() const [state, setState] = createStore({ copied: false, reverting: false }) const attachments = createMemo(() => (props.message.files ?? []).filter((file) => !file.mention)) + const references = createMemo(() => props.references ?? []) const inlineFiles = createMemo(() => (props.message.files ?? []).filter((file) => !!file.mention)) const agents = createMemo(() => props.message.agents ?? []) const comments = createMemo(() => props.comments ?? []) @@ -242,8 +244,15 @@ export function CurrentUserMessageDisplay(props: { } } const renderAttachments = () => ( - 0}> + 0 || references().length > 0}>
+ + {(file) => ( + + {typeLabel(file.name, file.mime, i18n.t("ui.common.file"))} + + )} + {(file) => { const url = () => (file.source.type === "uri" ? file.source.uri : `data:${file.mime};base64,${file.data}`) diff --git a/packages/session-ui/src/timeline/session-timeline-row.tsx b/packages/session-ui/src/timeline/session-timeline-row.tsx index f006f0067e34..7478095fe17a 100644 --- a/packages/session-ui/src/timeline/session-timeline-row.tsx +++ b/packages/session-ui/src/timeline/session-timeline-row.tsx @@ -8,7 +8,7 @@ import { useI18n } from "@opencode/ui/context/i18n" import { Tooltip } from "@opencode/ui/tooltip" import { For, Show, createMemo, type Accessor, type JSX } from "solid-js" import { Dynamic } from "solid-js/web" -import type { SessionUserActions, SessionUserComment } from "../actions" +import type { SessionUserActions, SessionUserAttachmentReference, SessionUserComment } from "../actions" import { useData } from "../context" import { TimelineSeparator } from "../components/timeline-separator" import { @@ -41,6 +41,7 @@ type FramedTimelineRow = Exclude export type SessionUserPresentation = { displayText?: string comments?: SessionUserComment[] + references?: SessionUserAttachmentReference[] } export function createSessionTimelineRowRenderer(input: { @@ -596,6 +597,7 @@ export function createSessionTimelineRowRenderer(input: { message={message()} displayText={presentation()?.displayText} comments={presentation()?.comments} + references={presentation()?.references} historicalAgent={context()?.agent ?? ""} historicalModel={context()?.model ?? { id: "", providerID: "" }} actions={input.actions}