From d0a57305efcf03f4fd69ca180d97ea85e6cb2f1d Mon Sep 17 00:00:00 2001 From: Brendan Allan Date: Fri, 20 Mar 2026 23:02:07 +0800 Subject: app: file type filter on desktop + multiple files (#18403) --- packages/app/src/components/prompt-input.tsx | 9 ++- packages/app/src/components/prompt-input/files.ts | 59 +-------------- packages/app/src/constants/file-picker.ts | 89 +++++++++++++++++++++++ packages/app/src/context/platform.tsx | 2 +- packages/app/src/index.ts | 1 + 5 files changed, 101 insertions(+), 59 deletions(-) create mode 100644 packages/app/src/constants/file-picker.ts (limited to 'packages/app') diff --git a/packages/app/src/components/prompt-input.tsx b/packages/app/src/components/prompt-input.tsx index 55cfaa490..f3d3e135d 100644 --- a/packages/app/src/components/prompt-input.tsx +++ b/packages/app/src/components/prompt-input.tsx @@ -1383,11 +1383,16 @@ export const PromptInput: Component = (props) => { { - const file = e.currentTarget.files?.[0] - if (file) void addAttachment(file) + const list = e.currentTarget.files + if (list) { + for (const file of Array.from(list)) { + void addAttachment(file) + } + } e.currentTarget.value = "" }} /> diff --git a/packages/app/src/components/prompt-input/files.ts b/packages/app/src/components/prompt-input/files.ts index 594991d07..eae8af03d 100644 --- a/packages/app/src/components/prompt-input/files.ts +++ b/packages/app/src/components/prompt-input/files.ts @@ -1,4 +1,6 @@ -export const ACCEPTED_IMAGE_TYPES = ["image/png", "image/jpeg", "image/gif", "image/webp"] +import { ACCEPTED_FILE_TYPES, ACCEPTED_IMAGE_TYPES } from "@/constants/file-picker" + +export { ACCEPTED_FILE_TYPES } const IMAGE_MIMES = new Set(ACCEPTED_IMAGE_TYPES) const IMAGE_EXTS = new Map([ @@ -18,61 +20,6 @@ const TEXT_MIMES = new Set([ "application/yaml", ]) -export const ACCEPTED_FILE_TYPES = [ - ...ACCEPTED_IMAGE_TYPES, - "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", -] - const SAMPLE = 4096 function kind(type: string) { diff --git a/packages/app/src/constants/file-picker.ts b/packages/app/src/constants/file-picker.ts new file mode 100644 index 000000000..c661bc8f3 --- /dev/null +++ b/packages/app/src/constants/file-picker.ts @@ -0,0 +1,89 @@ +export const ACCEPTED_IMAGE_TYPES = ["image/png", "image/jpeg", "image/gif", "image/webp"] + +export const ACCEPTED_FILE_TYPES = [ + ...ACCEPTED_IMAGE_TYPES, + "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", +] + +const MIME_EXT = new Map([ + ["image/png", "png"], + ["image/jpeg", "jpg"], + ["image/gif", "gif"], + ["image/webp", "webp"], + ["application/pdf", "pdf"], + ["application/json", "json"], + ["application/ld+json", "jsonld"], + ["application/toml", "toml"], + ["application/x-toml", "toml"], + ["application/x-yaml", "yaml"], + ["application/xml", "xml"], + ["application/yaml", "yaml"], +]) + +const TEXT_EXT = ["txt", "text", "md", "markdown", "log", "csv"] + +export const ACCEPTED_FILE_EXTENSIONS = Array.from( + new Set( + ACCEPTED_FILE_TYPES.flatMap((item) => { + if (item.startsWith(".")) return [item.slice(1)] + if (item === "text/*") return TEXT_EXT + const out = MIME_EXT.get(item) + return out ? [out] : [] + }), + ), +).sort() + +export function filePickerFilters(ext?: string[]) { + if (!ext || ext.length === 0) return undefined + return [{ name: "Files", extensions: ext }] +} diff --git a/packages/app/src/context/platform.tsx b/packages/app/src/context/platform.tsx index b8ed58e34..3bdc46391 100644 --- a/packages/app/src/context/platform.tsx +++ b/packages/app/src/context/platform.tsx @@ -5,7 +5,7 @@ import { ServerConnection } from "./server" type PickerPaths = string | string[] | null type OpenDirectoryPickerOptions = { title?: string; multiple?: boolean } -type OpenFilePickerOptions = { title?: string; multiple?: boolean } +type OpenFilePickerOptions = { title?: string; multiple?: boolean; accept?: string[]; extensions?: string[] } type SaveFilePickerOptions = { title?: string; defaultPath?: string } type UpdateInfo = { updateAvailable: boolean; version?: string } diff --git a/packages/app/src/index.ts b/packages/app/src/index.ts index 6c870dfa4..53063f48f 100644 --- a/packages/app/src/index.ts +++ b/packages/app/src/index.ts @@ -1,4 +1,5 @@ export { AppBaseProviders, AppInterface } from "./app" +export { ACCEPTED_FILE_EXTENSIONS, ACCEPTED_FILE_TYPES, filePickerFilters } from "./constants/file-picker" export { useCommand } from "./context/command" export { type DisplayBackend, type Platform, PlatformProvider } from "./context/platform" export { ServerConnection } from "./context/server" -- cgit v1.2.3