u
This commit is contained in:
198
src/components/chat/chatAttachments.js
Normal file
198
src/components/chat/chatAttachments.js
Normal file
@@ -0,0 +1,198 @@
|
||||
/**
|
||||
* @file chatAttachments.js
|
||||
* @description Helpers for handling file attachments in the AI chat window.
|
||||
* Supports: images, PDFs, CSV, plain text, XML, and HTML.
|
||||
*/
|
||||
|
||||
export const SUPPORTED_CATEGORIES = {
|
||||
IMAGE: 'image',
|
||||
PDF: 'pdf',
|
||||
CSV: 'csv',
|
||||
TEXT: 'text',
|
||||
XML: 'xml',
|
||||
HTML: 'html',
|
||||
};
|
||||
|
||||
export const ACCEPT_STRING = [
|
||||
'image/*',
|
||||
'application/pdf',
|
||||
'.pdf',
|
||||
'text/csv',
|
||||
'.csv',
|
||||
'text/plain',
|
||||
'.txt',
|
||||
'.md',
|
||||
'.log',
|
||||
'text/xml',
|
||||
'application/xml',
|
||||
'.xml',
|
||||
'text/html',
|
||||
'.html',
|
||||
'.htm',
|
||||
].join(',');
|
||||
|
||||
export const MAX_FILE_SIZE = 25 * 1024 * 1024; // 25 MB
|
||||
|
||||
/**
|
||||
* Determine the attachment category based on MIME type or file extension.
|
||||
* @param {File|{ name: string, type?: string }} file
|
||||
* @returns {'image'|'pdf'|'csv'|'text'|'xml'|'html'|'unknown'}
|
||||
*/
|
||||
export function categorizeFile(file) {
|
||||
const name = (file?.name || '').toLowerCase();
|
||||
const type = (file?.type || '').toLowerCase();
|
||||
|
||||
// Images
|
||||
if (type.startsWith('image/') || /\.(png|jpe?g|webp|gif|svg|avif|bmp|ico)$/.test(name)) {
|
||||
return SUPPORTED_CATEGORIES.IMAGE;
|
||||
}
|
||||
|
||||
// PDFs
|
||||
if (type === 'application/pdf' || name.endsWith('.pdf')) {
|
||||
return SUPPORTED_CATEGORIES.PDF;
|
||||
}
|
||||
|
||||
// CSV
|
||||
if (
|
||||
type === 'text/csv' ||
|
||||
type === 'application/csv' ||
|
||||
type === 'text/comma-separated-values' ||
|
||||
name.endsWith('.csv')
|
||||
) {
|
||||
return SUPPORTED_CATEGORIES.CSV;
|
||||
}
|
||||
|
||||
// XML
|
||||
if (type === 'text/xml' || type === 'application/xml' || name.endsWith('.xml')) {
|
||||
return SUPPORTED_CATEGORIES.XML;
|
||||
}
|
||||
|
||||
// HTML
|
||||
if (type === 'text/html' || name.endsWith('.html') || name.endsWith('.htm')) {
|
||||
return SUPPORTED_CATEGORIES.HTML;
|
||||
}
|
||||
|
||||
// Plain Text / Markdown / Log
|
||||
if (
|
||||
type.startsWith('text/') ||
|
||||
type === 'application/json' ||
|
||||
/\.(txt|md|log|json|ini|env|yaml|yml)$/.test(name)
|
||||
) {
|
||||
return SUPPORTED_CATEGORIES.TEXT;
|
||||
}
|
||||
|
||||
return 'unknown';
|
||||
}
|
||||
|
||||
/**
|
||||
* Human-readable format of file size in B, KB, or MB.
|
||||
* @param {number} bytes
|
||||
* @returns {string}
|
||||
*/
|
||||
export function formatFileSize(bytes) {
|
||||
if (!bytes || Number.isNaN(bytes) || bytes <= 0) return '0 B';
|
||||
if (bytes < 1024) return `${bytes} B`;
|
||||
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
||||
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a browser File object and convert it to a structured attachment object.
|
||||
* @param {File} file
|
||||
* @returns {Promise<object>}
|
||||
*/
|
||||
export async function readFileAttachment(file) {
|
||||
const category = categorizeFile(file);
|
||||
if (category === 'unknown') {
|
||||
throw new Error(`Das Dateiformat von "${file.name}" wird nicht unterstützt. Erlaubt sind Bilder, PDFs, CSV, Text, XML und HTML.`);
|
||||
}
|
||||
|
||||
if (file.size > MAX_FILE_SIZE) {
|
||||
throw new Error(`Die Datei "${file.name}" ist mit ${formatFileSize(file.size)} zu groß (maximal 25 MB).`);
|
||||
}
|
||||
|
||||
const id = `att-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
|
||||
|
||||
// For text-based formats, read as text and dataUrl
|
||||
const isTextBased =
|
||||
category === SUPPORTED_CATEGORIES.CSV ||
|
||||
category === SUPPORTED_CATEGORIES.TEXT ||
|
||||
category === SUPPORTED_CATEGORIES.XML ||
|
||||
category === SUPPORTED_CATEGORIES.HTML;
|
||||
|
||||
const dataUrl = await new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => resolve(reader.result);
|
||||
reader.onerror = () => reject(new Error(`Fehler beim Lesen der Datei "${file.name}"`));
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
|
||||
let textContent = '';
|
||||
let previewSnippet = '';
|
||||
|
||||
if (isTextBased) {
|
||||
try {
|
||||
textContent = await new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => resolve(reader.result);
|
||||
reader.onerror = () => reject(new Error(`Fehler beim Lesen von "${file.name}" als Text`));
|
||||
reader.readAsText(file);
|
||||
});
|
||||
// First 25 lines or 2000 chars as preview
|
||||
previewSnippet = textContent.slice(0, 2000);
|
||||
} catch {
|
||||
// Non-fatal if text reading fails
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
id,
|
||||
name: file.name,
|
||||
size: file.size,
|
||||
mimeType: file.type || getMimeTypeFallback(category, file.name),
|
||||
category,
|
||||
dataUrl,
|
||||
textContent: textContent || undefined,
|
||||
previewSnippet: previewSnippet || undefined,
|
||||
uploadedAt: new Date().toISOString(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Fallback MIME type based on extension/category.
|
||||
*/
|
||||
function getMimeTypeFallback(category, filename) {
|
||||
const ext = (filename.split('.').pop() || '').toLowerCase();
|
||||
switch (category) {
|
||||
case SUPPORTED_CATEGORIES.IMAGE:
|
||||
if (ext === 'png') return 'image/png';
|
||||
if (ext === 'webp') return 'image/webp';
|
||||
if (ext === 'svg') return 'image/svg+xml';
|
||||
if (ext === 'gif') return 'image/gif';
|
||||
return 'image/jpeg';
|
||||
case SUPPORTED_CATEGORIES.PDF:
|
||||
return 'application/pdf';
|
||||
case SUPPORTED_CATEGORIES.CSV:
|
||||
return 'text/csv';
|
||||
case SUPPORTED_CATEGORIES.XML:
|
||||
return 'application/xml';
|
||||
case SUPPORTED_CATEGORIES.HTML:
|
||||
return 'text/html';
|
||||
default:
|
||||
return 'text/plain';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger download of an attachment in the browser.
|
||||
* @param {object} attachment
|
||||
*/
|
||||
export function downloadAttachment(attachment) {
|
||||
if (!attachment) return;
|
||||
const link = document.createElement('a');
|
||||
link.href = attachment.dataUrl || (attachment.url ? attachment.url : '');
|
||||
link.download = attachment.name || 'anhang';
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
}
|
||||
Reference in New Issue
Block a user