added file preview
This commit is contained in:
parent
8341c2e860
commit
bf2c2f982c
11 changed files with 368 additions and 17 deletions
|
|
@ -305,6 +305,9 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
word-wrap: break-word;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.jsonTableValue {
|
.jsonTableValue {
|
||||||
|
|
@ -325,6 +328,9 @@
|
||||||
background: transparent;
|
background: transparent;
|
||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
|
word-wrap: break-word;
|
||||||
|
white-space: -wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.jsonValue {
|
.jsonValue {
|
||||||
|
|
@ -347,7 +353,8 @@
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-family: 'Fira Code', 'Monaco', 'Cascadia Code', 'Roboto Mono', 'Courier New', monospace;
|
font-family: 'Fira Code', 'Monaco', 'Cascadia Code', 'Roboto Mono', 'Courier New', monospace;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
white-space: nowrap !important;
|
word-wrap: break-word;
|
||||||
|
white-space: pre-wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.jsonValueNumber {
|
.jsonValueNumber {
|
||||||
|
|
@ -750,3 +757,85 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Text Container Styles */
|
||||||
|
.textContainer {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
background: var(--color-background);
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.textHeader {
|
||||||
|
padding: 1rem;
|
||||||
|
border-bottom: 1px solid var(--color-border);
|
||||||
|
background: var(--color-background);
|
||||||
|
}
|
||||||
|
|
||||||
|
.textTitle {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--color-text);
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.warningMessage {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
padding: 0.75rem;
|
||||||
|
background: var(--color-background);
|
||||||
|
border: 1px solid var(--color-secondary);
|
||||||
|
border-radius: 6px;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.warningIcon {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
color: var(--color-secondary);
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-top: 0.1rem;
|
||||||
|
text-align: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.warningText {
|
||||||
|
color: var(--color-text);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
line-height: 1.4;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.textPreview {
|
||||||
|
flex: 1;
|
||||||
|
padding: 1rem;
|
||||||
|
margin: 0;
|
||||||
|
background: var(--color-background);
|
||||||
|
overflow: auto;
|
||||||
|
font-family: 'Fira Code', 'Monaco', 'Cascadia Code', 'Roboto Mono', 'Courier New', monospace;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.4;
|
||||||
|
color: var(--color-text);
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.textCode {
|
||||||
|
background: transparent;
|
||||||
|
color: var(--color-text);
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: inherit;
|
||||||
|
line-height: inherit;
|
||||||
|
white-space: inherit;
|
||||||
|
word-wrap: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ import { useState, useEffect } from 'react';
|
||||||
|
|
||||||
import { IoIosDownload, IoIosCopy } from 'react-icons/io';
|
import { IoIosDownload, IoIosCopy } from 'react-icons/io';
|
||||||
|
|
||||||
|
|
||||||
import { Popup, PopupAction } from '../Popup/Popup';
|
import { Popup, PopupAction } from '../Popup/Popup';
|
||||||
import { useLanguage } from '../../contexts/LanguageContext';
|
import { useLanguage } from '../../contexts/LanguageContext';
|
||||||
import { useFileOperations } from '../../hooks/useFiles';
|
import { useFileOperations } from '../../hooks/useFiles';
|
||||||
|
|
@ -68,13 +67,16 @@ export function FilePreview({
|
||||||
try {
|
try {
|
||||||
setError(null);
|
setError(null);
|
||||||
setPreviewContent(null);
|
setPreviewContent(null);
|
||||||
const result = await handleFilePreview(fileId, fileName);
|
const result = await handleFilePreview(fileId, fileName, mimeType);
|
||||||
|
|
||||||
if (result.success && result.previewUrl) {
|
if (result.success) {
|
||||||
setPreviewUrl(result.previewUrl);
|
if (result.previewUrl) {
|
||||||
|
setPreviewUrl(result.previewUrl);
|
||||||
|
}
|
||||||
if (result.decodedContent) {
|
if (result.decodedContent) {
|
||||||
setPreviewContent(result.decodedContent);
|
setPreviewContent(result.decodedContent);
|
||||||
}
|
}
|
||||||
|
// If it's text content but MIME type says PDF, we'll handle it in renderPreview
|
||||||
} else {
|
} else {
|
||||||
setError(result.error || 'Failed to load preview');
|
setError(result.error || 'Failed to load preview');
|
||||||
}
|
}
|
||||||
|
|
@ -115,9 +117,12 @@ export function FilePreview({
|
||||||
const isPreviewing = previewingFiles.has(fileId);
|
const isPreviewing = previewingFiles.has(fileId);
|
||||||
const hasError = error || previewError;
|
const hasError = error || previewError;
|
||||||
|
|
||||||
|
// Check if this is a corrupted PDF (text content instead of PDF)
|
||||||
|
const isCorruptedPdf = mimeType === 'application/pdf' && previewContent && !previewUrl;
|
||||||
|
|
||||||
// Create action buttons for the popup header
|
// Create action buttons for the popup header
|
||||||
const actions: PopupAction[] = [
|
const actions: PopupAction[] = [
|
||||||
// Copy Content Button - only show for text-based files (exclude PDFs)
|
// Copy Content Button - only show for text-based files (exclude PDFs) or corrupted PDFs
|
||||||
...(mimeType !== 'application/pdf' && (mimeType?.startsWith('text/') || mimeType === 'application/json' || previewContent) ? [{
|
...(mimeType !== 'application/pdf' && (mimeType?.startsWith('text/') || mimeType === 'application/json' || previewContent) ? [{
|
||||||
label: copySuccess ? t('files.preview.copied', 'Copied!') : t(''),
|
label: copySuccess ? t('files.preview.copied', 'Copied!') : t(''),
|
||||||
icon: copySuccess ? '✓' : <IoIosCopy />,
|
icon: copySuccess ? '✓' : <IoIosCopy />,
|
||||||
|
|
@ -126,18 +131,31 @@ export function FilePreview({
|
||||||
variant: 'primary' as const
|
variant: 'primary' as const
|
||||||
}] : []),
|
}] : []),
|
||||||
|
|
||||||
// Download Button
|
// Download Button - hide for corrupted PDFs
|
||||||
{
|
...(isCorruptedPdf ? [] : [{
|
||||||
label: String(''),
|
label: String(''),
|
||||||
icon: downloadingFiles.has(fileId) ? undefined : <IoIosDownload />,
|
icon: downloadingFiles.has(fileId) ? undefined : <IoIosDownload />,
|
||||||
onClick: handleDownloadFile,
|
onClick: handleDownloadFile,
|
||||||
disabled: downloadingFiles.has(fileId),
|
disabled: downloadingFiles.has(fileId),
|
||||||
loading: downloadingFiles.has(fileId),
|
loading: downloadingFiles.has(fileId),
|
||||||
variant: 'success' as const
|
variant: 'success' as const
|
||||||
}
|
}])
|
||||||
];
|
];
|
||||||
|
|
||||||
const renderPreview = () => {
|
const renderPreview = () => {
|
||||||
|
// Handle text content in PDF files (corrupted files) - check this first
|
||||||
|
if (previewContent && !previewUrl && mimeType === 'application/pdf') {
|
||||||
|
console.log('🔍 FilePreview: Rendering corrupted PDF with text content');
|
||||||
|
return (
|
||||||
|
<PdfRenderer
|
||||||
|
previewUrl={undefined}
|
||||||
|
previewContent={previewContent}
|
||||||
|
fileName={fileName}
|
||||||
|
onError={() => setError('Failed to load PDF preview')}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (!previewUrl) {
|
if (!previewUrl) {
|
||||||
if (isPreviewing) {
|
if (isPreviewing) {
|
||||||
return <LoadingRenderer />;
|
return <LoadingRenderer />;
|
||||||
|
|
@ -200,9 +218,16 @@ export function FilePreview({
|
||||||
|
|
||||||
case 'application':
|
case 'application':
|
||||||
if (mimeType === 'application/pdf') {
|
if (mimeType === 'application/pdf') {
|
||||||
|
console.log('🔍 FilePreview passing normal PDF to PdfRenderer:', {
|
||||||
|
previewUrl,
|
||||||
|
previewContent: previewContent ? `${previewContent.substring(0, 50)}...` : null,
|
||||||
|
fileName,
|
||||||
|
mimeType
|
||||||
|
});
|
||||||
return (
|
return (
|
||||||
<PdfRenderer
|
<PdfRenderer
|
||||||
previewUrl={previewUrl}
|
previewUrl={previewUrl}
|
||||||
|
previewContent={previewContent || undefined}
|
||||||
fileName={fileName}
|
fileName={fileName}
|
||||||
onError={() => setError('Failed to load PDF preview')}
|
onError={() => setError('Failed to load PDF preview')}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,53 @@
|
||||||
|
import { IoIosWarning } from 'react-icons/io';
|
||||||
|
import { useLanguage } from '../../../contexts/LanguageContext';
|
||||||
import styles from '../FilePreview.module.css';
|
import styles from '../FilePreview.module.css';
|
||||||
|
|
||||||
interface PdfRendererProps {
|
interface PdfRendererProps {
|
||||||
previewUrl: string;
|
previewUrl?: string;
|
||||||
|
previewContent?: string;
|
||||||
fileName: string;
|
fileName: string;
|
||||||
onError: () => void;
|
onError: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function PdfRenderer({ previewUrl, fileName, onError }: PdfRendererProps) {
|
export function PdfRenderer({ previewUrl, previewContent, fileName, onError }: PdfRendererProps) {
|
||||||
|
const { t } = useLanguage();
|
||||||
|
|
||||||
|
|
||||||
|
const handleLoad = () => {
|
||||||
|
console.log('📄 PDF iframe loaded successfully:', { previewUrl, fileName });
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleError = (event: React.SyntheticEvent<HTMLIFrameElement, Event>) => {
|
||||||
|
console.error('❌ PDF iframe failed to load:', { previewUrl, fileName, event });
|
||||||
|
onError();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle corrupted PDF files (text content instead of PDF)
|
||||||
|
if (previewContent && !previewUrl) {
|
||||||
|
console.log('📄 Rendering corrupted PDF warning');
|
||||||
|
return (
|
||||||
|
<div className={styles.textContainer}>
|
||||||
|
<div className={styles.textHeader}>
|
||||||
|
<div className={styles.warningMessage}>
|
||||||
|
<span className={styles.warningIcon}><IoIosWarning /></span>
|
||||||
|
<span className={styles.warningText}>
|
||||||
|
{t('files.preview.pdfFileCorrupted', 'This file appears to be corrupted. It has a PDF extension but contains text content. Please re-upload the file if possible.')}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normal PDF rendering
|
||||||
return (
|
return (
|
||||||
<iframe
|
<iframe
|
||||||
src={previewUrl}
|
src={previewUrl}
|
||||||
className={styles.previewIframe}
|
className={styles.previewIframe}
|
||||||
title={`Preview of ${fileName}`}
|
title={`Preview of ${fileName}`}
|
||||||
data-mime-type="application/pdf"
|
data-mime-type="application/pdf"
|
||||||
onError={onError}
|
onLoad={handleLoad}
|
||||||
|
onError={handleError}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,33 @@
|
||||||
import styles from '../FilePreview.module.css';
|
import styles from '../FilePreview.module.css';
|
||||||
|
|
||||||
|
// Updated to handle both previewUrl and previewContent
|
||||||
|
|
||||||
interface TextRendererProps {
|
interface TextRendererProps {
|
||||||
previewUrl: string;
|
previewUrl?: string;
|
||||||
|
previewContent?: string;
|
||||||
fileName: string;
|
fileName: string;
|
||||||
mimeType?: string;
|
mimeType?: string;
|
||||||
onError: () => void;
|
onError: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function TextRenderer({ previewUrl, fileName, mimeType, onError }: TextRendererProps) {
|
export function TextRenderer({ previewUrl, previewContent, fileName, mimeType, onError }: TextRendererProps) {
|
||||||
|
// If we have previewContent directly, display it as text
|
||||||
|
if (previewContent && !previewUrl) {
|
||||||
|
return (
|
||||||
|
<div className={styles.textContainer}>
|
||||||
|
<div className={styles.textHeader}>
|
||||||
|
<span className={styles.textTitle}>Text Preview</span>
|
||||||
|
</div>
|
||||||
|
<pre className={styles.textPreview}>
|
||||||
|
<code className={styles.textCode}>
|
||||||
|
{previewContent}
|
||||||
|
</code>
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, use iframe with previewUrl
|
||||||
return (
|
return (
|
||||||
<iframe
|
<iframe
|
||||||
src={previewUrl}
|
src={previewUrl}
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,12 @@ export function useApiRequest<RequestData = any, ResponseData = any>() {
|
||||||
params,
|
params,
|
||||||
...additionalConfig
|
...additionalConfig
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// For blob responses, return the blob data directly
|
||||||
|
if (additionalConfig.responseType === 'blob') {
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
return response.data;
|
return response.data;
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
const errorMessage = formatApiError(error, `Fehler bei ${method.toUpperCase()} ${url}`);
|
const errorMessage = formatApiError(error, `Fehler bei ${method.toUpperCase()} ${url}`);
|
||||||
|
|
|
||||||
|
|
@ -418,14 +418,185 @@ export function useFileOperations() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleFilePreview = async (fileId: string, fileName: string) => {
|
const handleFilePreview = async (fileId: string, fileName: string, mimeType?: string) => {
|
||||||
setPreviewError(null);
|
setPreviewError(null);
|
||||||
setPreviewingFiles(prev => new Set(prev).add(fileId));
|
setPreviewingFiles(prev => new Set(prev).add(fileId));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
console.log(`👁️ Starting preview for file: ${fileName} (ID: ${fileId})`);
|
console.log(`👁️ Starting preview for file: ${fileName} (ID: ${fileId})`, { mimeType });
|
||||||
|
|
||||||
// First try to get JSON response (for text-based files)
|
// For PDF files, try JSON response first (API returns base64-encoded PDF)
|
||||||
|
if (mimeType === 'application/pdf') {
|
||||||
|
console.log('📄 PDF file detected, trying JSON response with base64 content');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const jsonResponse = await request({
|
||||||
|
url: `/api/files/${fileId}/preview`,
|
||||||
|
method: 'get',
|
||||||
|
additionalConfig: {
|
||||||
|
responseType: 'json',
|
||||||
|
validateStatus: function (status: number) {
|
||||||
|
return status >= 200 && status < 300;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('📄 PDF JSON response received:', {
|
||||||
|
hasContent: 'content' in jsonResponse,
|
||||||
|
hasMimeType: 'mimeType' in jsonResponse,
|
||||||
|
contentLength: jsonResponse.content?.length,
|
||||||
|
mimeType: jsonResponse.mimeType,
|
||||||
|
contentType: typeof jsonResponse.content,
|
||||||
|
contentStartsWith: jsonResponse.content?.substring(0, 10),
|
||||||
|
fullResponse: jsonResponse
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check if response has base64-encoded PDF content
|
||||||
|
if (jsonResponse && typeof jsonResponse === 'object' && 'content' in jsonResponse) {
|
||||||
|
let content = jsonResponse.content;
|
||||||
|
const responseMimeType = jsonResponse.mimeType || 'application/pdf';
|
||||||
|
|
||||||
|
// The content field contains base64-encoded JSON, so decode it first
|
||||||
|
if (typeof content === 'string' && /^[A-Za-z0-9+/=]+$/.test(content)) {
|
||||||
|
console.log('📄 Content appears to be base64-encoded, decoding first...');
|
||||||
|
try {
|
||||||
|
const decodedJsonString = atob(content);
|
||||||
|
console.log('📄 Decoded JSON string:', {
|
||||||
|
length: decodedJsonString.length,
|
||||||
|
startsWith: decodedJsonString.substring(0, 20),
|
||||||
|
isJson: decodedJsonString.startsWith('{')
|
||||||
|
});
|
||||||
|
|
||||||
|
// Parse the decoded JSON string
|
||||||
|
const nestedJson = JSON.parse(decodedJsonString);
|
||||||
|
console.log('📄 Parsed nested JSON:', {
|
||||||
|
hasContent: 'content' in nestedJson,
|
||||||
|
hasDocumentCount: 'documentCount' in nestedJson,
|
||||||
|
keys: Object.keys(nestedJson)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (nestedJson && typeof nestedJson === 'object' && 'content' in nestedJson) {
|
||||||
|
const innerContent = nestedJson.content;
|
||||||
|
const isBase64 = /^[A-Za-z0-9+/=]+$/.test(innerContent);
|
||||||
|
|
||||||
|
console.log('📄 Extracted inner content:', {
|
||||||
|
innerContentLength: innerContent?.length,
|
||||||
|
innerContentPreview: innerContent?.substring(0, 100) + '...',
|
||||||
|
isBase64: isBase64
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isBase64) {
|
||||||
|
// It's base64-encoded PDF content
|
||||||
|
content = innerContent;
|
||||||
|
} else {
|
||||||
|
// It's plain text content, not a PDF
|
||||||
|
console.log('📄 Inner content is plain text, not PDF. This appears to be a text file with PDF extension.');
|
||||||
|
// Return the text content for the FilePreview to handle as text
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
previewUrl: null,
|
||||||
|
blob: null,
|
||||||
|
isJsonContent: true,
|
||||||
|
decodedContent: innerContent,
|
||||||
|
isTextContent: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (decodeError) {
|
||||||
|
console.warn('⚠️ Failed to decode base64 content or parse JSON:', decodeError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('📄 Processing base64 PDF content:', {
|
||||||
|
contentLength: content?.length,
|
||||||
|
mimeType: responseMimeType,
|
||||||
|
contentPreview: content?.substring(0, 100) + '...',
|
||||||
|
firstChars: content?.substring(0, 20),
|
||||||
|
lastChars: content?.substring(content.length - 20),
|
||||||
|
isBase64: /^[A-Za-z0-9+/=]+$/.test(content)
|
||||||
|
});
|
||||||
|
|
||||||
|
// Decode base64 content
|
||||||
|
let decodedContent;
|
||||||
|
try {
|
||||||
|
decodedContent = atob(content);
|
||||||
|
console.log('📄 Base64 decode successful:', {
|
||||||
|
originalLength: content.length,
|
||||||
|
decodedLength: decodedContent.length,
|
||||||
|
firstBytes: decodedContent.substring(0, 10),
|
||||||
|
firstBytesHex: Array.from(decodedContent.substring(0, 10)).map(c => c.charCodeAt(0).toString(16).padStart(2, '0')).join(' ')
|
||||||
|
});
|
||||||
|
|
||||||
|
// Verify it's actually a PDF
|
||||||
|
const isPDF = decodedContent.startsWith('%PDF');
|
||||||
|
console.log('📄 PDF header verification:', {
|
||||||
|
isPDF: isPDF,
|
||||||
|
firstBytes: decodedContent.substring(0, 4),
|
||||||
|
firstBytesHex: Array.from(decodedContent.substring(0, 4)).map(c => c.charCodeAt(0).toString(16).padStart(2, '0')).join(' '),
|
||||||
|
first20Chars: decodedContent.substring(0, 20)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!isPDF) {
|
||||||
|
console.warn('⚠️ Decoded content does not appear to be a valid PDF');
|
||||||
|
console.log('📄 Full decoded content preview:', decodedContent.substring(0, 200));
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (decodeError) {
|
||||||
|
console.error('❌ Failed to decode base64 PDF content:', decodeError);
|
||||||
|
throw new Error('Failed to decode PDF content');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a blob from the decoded PDF content
|
||||||
|
// Convert string to Uint8Array for proper binary handling
|
||||||
|
const uint8Array = new Uint8Array(decodedContent.length);
|
||||||
|
for (let i = 0; i < decodedContent.length; i++) {
|
||||||
|
uint8Array[i] = decodedContent.charCodeAt(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
const blob = new Blob([uint8Array], { type: 'application/pdf' });
|
||||||
|
const url = window.URL.createObjectURL(blob);
|
||||||
|
|
||||||
|
console.log('🔗 Created PDF blob URL from base64:', url, {
|
||||||
|
blobSize: blob.size,
|
||||||
|
blobType: blob.type,
|
||||||
|
url: url,
|
||||||
|
uint8ArrayLength: uint8Array.length,
|
||||||
|
firstBytes: Array.from(uint8Array.slice(0, 4)).map(b => String.fromCharCode(b)).join(''),
|
||||||
|
firstBytesHex: Array.from(uint8Array.slice(0, 4)).map(b => b.toString(16).padStart(2, '0')).join(' ')
|
||||||
|
});
|
||||||
|
|
||||||
|
return { success: true, previewUrl: url, blob: blob, isJsonContent: true, decodedContent: decodedContent };
|
||||||
|
} else {
|
||||||
|
throw new Error('No content field in PDF response');
|
||||||
|
}
|
||||||
|
} catch (jsonError) {
|
||||||
|
console.log('📄 JSON PDF response failed, trying blob response...', jsonError);
|
||||||
|
|
||||||
|
// Fallback to blob response
|
||||||
|
const previewData = await request({
|
||||||
|
url: `/api/files/${fileId}/preview`,
|
||||||
|
method: 'get',
|
||||||
|
additionalConfig: {
|
||||||
|
responseType: 'blob',
|
||||||
|
validateStatus: function (status: number) {
|
||||||
|
return status >= 200 && status < 300;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`✅ PDF blob preview successful for: ${fileName}`, {
|
||||||
|
size: previewData.size,
|
||||||
|
type: previewData.type,
|
||||||
|
expectedType: 'application/pdf'
|
||||||
|
});
|
||||||
|
|
||||||
|
const url = window.URL.createObjectURL(previewData);
|
||||||
|
|
||||||
|
return { success: true, previewUrl: url, blob: previewData, isJsonContent: false };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// For other files, first try to get JSON response (for text-based files)
|
||||||
try {
|
try {
|
||||||
const jsonResponse = await request({
|
const jsonResponse = await request({
|
||||||
url: `/api/files/${fileId}/preview`,
|
url: `/api/files/${fileId}/preview`,
|
||||||
|
|
|
||||||
|
|
@ -375,6 +375,8 @@ export default {
|
||||||
'files.preview.loading': 'Vorschau wird geladen...',
|
'files.preview.loading': 'Vorschau wird geladen...',
|
||||||
'files.preview.unsupported': 'Vorschau für diesen Dateityp nicht verfügbar',
|
'files.preview.unsupported': 'Vorschau für diesen Dateityp nicht verfügbar',
|
||||||
'files.preview.error': 'Fehler beim Laden der Vorschau',
|
'files.preview.error': 'Fehler beim Laden der Vorschau',
|
||||||
|
'files.preview.textInPdfFile': 'Textvorschau',
|
||||||
|
'files.preview.pdfFileCorrupted': 'Diese Datei scheint beschädigt zu sein. Sie hat eine PDF-Erweiterung, enthält aber Textinhalte. Bitte laden Sie die Datei erneut hoch, falls möglich.',
|
||||||
|
|
||||||
// Workflows Page
|
// Workflows Page
|
||||||
'workflows.title': 'Workflows',
|
'workflows.title': 'Workflows',
|
||||||
|
|
|
||||||
|
|
@ -378,6 +378,8 @@ export default {
|
||||||
'files.preview.loading': 'Loading preview...',
|
'files.preview.loading': 'Loading preview...',
|
||||||
'files.preview.unsupported': 'Preview not available for this file type',
|
'files.preview.unsupported': 'Preview not available for this file type',
|
||||||
'files.preview.error': 'Error loading preview',
|
'files.preview.error': 'Error loading preview',
|
||||||
|
'files.preview.textInPdfFile': 'Text Preview',
|
||||||
|
'files.preview.pdfFileCorrupted': 'This file appears to be corrupted. It has a PDF extension but contains text content. Please re-upload the file if possible.',
|
||||||
|
|
||||||
// Workflows Page
|
// Workflows Page
|
||||||
'workflows.title': 'Workflows',
|
'workflows.title': 'Workflows',
|
||||||
|
|
|
||||||
|
|
@ -378,6 +378,8 @@ export default {
|
||||||
'files.preview.loading': 'Chargement de l\'aperçu...',
|
'files.preview.loading': 'Chargement de l\'aperçu...',
|
||||||
'files.preview.unsupported': 'Aperçu non disponible pour ce type de fichier',
|
'files.preview.unsupported': 'Aperçu non disponible pour ce type de fichier',
|
||||||
'files.preview.error': 'Erreur lors du chargement de l\'aperçu',
|
'files.preview.error': 'Erreur lors du chargement de l\'aperçu',
|
||||||
|
'files.preview.textInPdfFile': 'Aperçu du texte',
|
||||||
|
'files.preview.pdfFileCorrupted': 'Ce fichier semble être corrompu. Il a une extension PDF mais contient du contenu texte. Veuillez le télécharger à nouveau si possible.',
|
||||||
|
|
||||||
// Workflows Page
|
// Workflows Page
|
||||||
'workflows.title': 'Workflows',
|
'workflows.title': 'Workflows',
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue