Files
NetMesh/application/state/useFileUpload.ts
zhaolei 3c72efcb7f
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
[Init] Initial commit - NetMesh terminal manager
2026-09-13 18:24:01 +08:00

148 lines
4.1 KiB
TypeScript

/**
* File upload conversion helpers for AI draft attachments.
*
* Supports images, PDFs, and other document types.
* Ported from 1code's use-agents-file-upload.ts
*/
import type { UploadedFile } from '../../infrastructure/ai/types';
import { getPathForFile } from '../../lib/sftpFileUtils';
export type { UploadedFile } from '../../infrastructure/ai/types';
/** Reject only known binary blobs that AI models can't process */
const REJECTED_MIME_PREFIXES = ['video/', 'audio/'];
/**
* Infer MIME type from file extension when the browser/Electron doesn't
* provide one (common for .yaml, .sh, .toml, and other code/text files).
*/
const EXTENSION_MIME_TYPES: Record<string, string> = {
// Code & Scripts — all use text/plain for maximum provider compatibility
js: 'text/plain',
mjs: 'text/plain',
cjs: 'text/plain',
jsx: 'text/plain',
ts: 'text/plain',
tsx: 'text/plain',
py: 'text/plain',
rb: 'text/plain',
rs: 'text/plain',
go: 'text/plain',
java: 'text/plain',
c: 'text/plain',
h: 'text/plain',
cpp: 'text/plain',
hpp: 'text/plain',
cs: 'text/plain',
swift: 'text/plain',
kt: 'text/plain',
scala: 'text/plain',
php: 'text/plain',
pl: 'text/plain',
sh: 'text/plain',
bash: 'text/plain',
zsh: 'text/plain',
fish: 'text/plain',
ps1: 'text/plain',
bat: 'text/plain',
cmd: 'text/plain',
sql: 'text/plain',
r: 'text/plain',
lua: 'text/plain',
dart: 'text/plain',
// Web
html: 'text/html',
htm: 'text/html',
css: 'text/css',
scss: 'text/plain',
sass: 'text/plain',
less: 'text/plain',
vue: 'text/plain',
svelte: 'text/plain',
// Config / Data
yaml: 'text/plain',
yml: 'text/plain',
json: 'application/json',
jsonc: 'application/json',
jsonl: 'application/jsonl',
xml: 'application/xml',
toml: 'application/toml',
csv: 'text/csv',
tsv: 'text/tab-separated-values',
ini: 'text/plain',
cfg: 'text/plain',
conf: 'text/plain',
env: 'text/plain',
// Docs
md: 'text/markdown',
markdown: 'text/markdown',
txt: 'text/plain',
tex: 'text/x-tex',
rst: 'text/x-rst',
log: 'text/plain',
// Other typed files
pdf: 'application/pdf',
dockerfile: 'text/plain',
};
function getExtension(fileName: string): string {
const dot = fileName.lastIndexOf('.');
if (dot === -1) return fileName.toLowerCase(); // e.g. "Dockerfile", "Makefile"
return fileName.slice(dot + 1).toLowerCase();
}
function inferMediaType(fileName: string, fileType: string): string {
if (fileType) return fileType;
const ext = getExtension(fileName);
return EXTENSION_MIME_TYPES[ext] || 'application/octet-stream';
}
function isSupportedFile(file: File): boolean {
// Allow files with empty MIME (common in Electron for .sh, .yaml, etc.)
if (!file.type) return true;
return !REJECTED_MIME_PREFIXES.some(prefix => file.type.startsWith(prefix));
}
async function fileToDataUrl(file: File): Promise<{ dataUrl: string; base64: string }> {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onloadend = () => {
const dataUrl = reader.result as string;
const base64 = dataUrl.split(',')[1] || '';
resolve({ dataUrl, base64 });
};
reader.onerror = reject;
reader.readAsDataURL(file);
});
}
export async function convertFilesToUploads(inputFiles: File[]): Promise<UploadedFile[]> {
const supported = inputFiles.filter(isSupportedFile);
if (supported.length === 0) return [];
const uploads: Array<UploadedFile | null> = await Promise.all(
supported.map(async (file) => {
const id = crypto.randomUUID();
const filename = file.name || `file-${Date.now()}`;
const mediaType = inferMediaType(filename, file.type);
try {
const result = await fileToDataUrl(file);
const filePath = getPathForFile(file);
return {
id,
filename,
dataUrl: result.dataUrl,
base64Data: result.base64,
mediaType,
filePath,
};
} catch (err) {
console.error('[useFileUpload] Failed to convert:', err);
return null;
}
}),
);
return uploads.filter((upload): upload is UploadedFile => upload !== null);
}