Files
memind/src/utils/chatAttachment.ts
T
john 32fb2cdeaf feat: chat uploads, vision turn isolation, and MindSpace agent improvements
Add chat file/image upload UX, attachment proxying, vision thumbnails, and per-turn image scoping so agents only use the current upload. Extend MindSpace asset context, billing token state, OA/scenario verify scripts, and related runtime config.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-11 00:23:01 +08:00

87 lines
3.1 KiB
TypeScript

import type { ChatFileAttachment } from '../types';
export type { ChatFileAttachment };
export const CHAT_FILE_UPLOAD_EXTENSIONS = [
'.pdf',
'.doc',
'.docx',
'.xls',
'.xlsx',
'.ppt',
'.pptx',
'.txt',
'.md',
'.csv',
'.html',
'.htm',
] as const;
export const CHAT_FILE_UPLOAD_ACCEPT = CHAT_FILE_UPLOAD_EXTENSIONS.join(',');
export const CHAT_UPLOAD_ACCEPT = `image/*,${CHAT_FILE_UPLOAD_ACCEPT}`;
export const CHAT_FILE_UPLOAD_MAX_COUNT = 5;
export const CHAT_FILE_UPLOAD_MAX_INPUT_BYTES = 30 * 1024 * 1024;
export const CHAT_FILE_UPLOAD_MAX_TOTAL_BYTES = 60 * 1024 * 1024;
export const CHAT_FILE_UPLOAD_TYPE_LABEL = 'Word、Excel、PDF、PPT、Markdown、CSV、TXT 和 HTML';
const EXTENSION_MIME = new Map<string, string>([
['.txt', 'text/plain'],
['.md', 'text/markdown'],
['.csv', 'text/csv'],
['.pdf', 'application/pdf'],
['.doc', 'application/msword'],
['.docx', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'],
['.xls', 'application/vnd.ms-excel'],
['.xlsx', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'],
['.ppt', 'application/vnd.ms-powerpoint'],
['.pptx', 'application/vnd.openxmlformats-officedocument.presentationml.presentation'],
['.html', 'text/html'],
['.htm', 'text/html'],
]);
export function isChatImageFile(file: File): boolean {
if (file.type.startsWith('image/')) return true;
return /\.(png|jpe?g|webp|gif)$/i.test(file.name);
}
export function isChatFileAttachment(file: File): boolean {
if (isChatImageFile(file)) return false;
const extension = file.name.slice(file.name.lastIndexOf('.')).toLowerCase();
return CHAT_FILE_UPLOAD_EXTENSIONS.includes(extension as (typeof CHAT_FILE_UPLOAD_EXTENSIONS)[number]);
}
export type ChatUploadKind = 'image' | 'attachment' | 'unsupported';
/** 统一上传入口:图片优先走图片通道,其余合法文档走附件通道。 */
export function classifyChatUploadFile(file: File): ChatUploadKind {
if (isChatImageFile(file)) return 'image';
if (isChatFileAttachment(file)) return 'attachment';
return 'unsupported';
}
const IMAGE_EXTENSION_MIME = new Map<string, string>([
['.png', 'image/png'],
['.jpg', 'image/jpeg'],
['.jpeg', 'image/jpeg'],
['.webp', 'image/webp'],
['.gif', 'image/gif'],
['.heic', 'image/heic'],
['.heif', 'image/heif'],
]);
/** 仅用于 UI 入队:补齐 MIME,不改动 uploadChatImage 本身。 */
export function prepareChatImageUploadFile(file: File): File | null {
if (!isChatImageFile(file)) return null;
if (file.type.startsWith('image/')) return file;
const extension = file.name.slice(file.name.lastIndexOf('.')).toLowerCase();
const mimeType = IMAGE_EXTENSION_MIME.get(extension);
if (!mimeType) return null;
return new File([file], file.name, { type: mimeType, lastModified: file.lastModified });
}
export function resolveChatFileMimeType(file: File): string {
if (file.type && file.type !== 'application/octet-stream') return file.type;
const extension = file.name.slice(file.name.lastIndexOf('.')).toLowerCase();
return EXTENSION_MIME.get(extension) ?? 'application/octet-stream';
}