32fb2cdeaf
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>
87 lines
3.1 KiB
TypeScript
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';
|
|
}
|