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}