diff --git a/assets/js/main.js b/assets/js/main.js index f65f923..19c8209 100644 --- a/assets/js/main.js +++ b/assets/js/main.js @@ -4,8 +4,11 @@ document.addEventListener('DOMContentLoaded', function () { const fileNameDisplay = document.getElementById('file-upload-filename'); const translateBtn = document.getElementById('translate-btn'); const processingMessage = document.getElementById('processing-message'); + const previewContainer = document.getElementById('preview-container'); + const imagePreview = document.getElementById('image-preview'); + const startTranslationBtn = document.getElementById('start-translation-btn'); + let uploadedFilePath = ''; - // Trigger file input click when the custom upload area is clicked if(fileUploadLabel) { fileUploadLabel.addEventListener('click', () => { if(fileUploadInput) { @@ -14,36 +17,56 @@ document.addEventListener('DOMContentLoaded', function () { }); } - // Update UI when a file is selected if(fileUploadInput) { fileUploadInput.addEventListener('change', () => { if (fileUploadInput.files.length > 0) { - const fileName = fileUploadInput.files[0].name; + const file = fileUploadInput.files[0]; + const fileName = file.name; if(fileNameDisplay) { fileNameDisplay.textContent = `Selected file: ${fileName}`; } - if(translateBtn) { - translateBtn.disabled = false; - } - } else { - if(fileNameDisplay) { - fileNameDisplay.textContent = ''; - } - if(translateBtn) { - translateBtn.disabled = true; - } + + const formData = new FormData(); + formData.append('document', file); + + processingMessage.style.display = 'block'; + fileUploadLabel.style.display = 'none'; + + fetch('upload.php', { + method: 'POST', + body: formData + }) + .then(response => response.json()) + .then(data => { + processingMessage.style.display = 'none'; + if (data.success && data.filePath) { + uploadedFilePath = data.filePath; + imagePreview.src = uploadedFilePath; + previewContainer.style.display = 'block'; + document.getElementById('translation-form').style.display = 'none'; + startTranslationBtn.style.display = 'block'; + } else { + throw new Error(data.message || 'File upload failed.'); + } + }) + .catch(error => { + processingMessage.style.display = 'none'; + const responseMessageDiv = document.getElementById('response-message'); + responseMessageDiv.innerHTML = ``; + responseMessageDiv.style.display = 'block'; + console.error('Upload Error:', error); + }); + } }); } - - // Handle form submission - const translationForm = document.getElementById('translation-form'); - if(translationForm) { - translationForm.addEventListener('submit', function(e) { + + if(startTranslationBtn) { + startTranslationBtn.addEventListener('click', function(e) { e.preventDefault(); const formData = new FormData(); - formData.append('document', fileUploadInput.files[0]); + formData.append('file_path', uploadedFilePath); formData.append('source-lang', document.getElementById('source-lang').value); formData.append('target-lang', document.getElementById('target-lang').value); @@ -57,18 +80,15 @@ document.addEventListener('DOMContentLoaded', function () { responseMessageDiv.style.display = 'none'; } - if(translateBtn) { - translateBtn.disabled = true; - translateBtn.innerHTML = ' Translating...'; - } + startTranslationBtn.disabled = true; + startTranslationBtn.innerHTML = ' Translating...'; - fetch('upload.php', { + fetch('translate.php', { method: 'POST', body: formData }) .then(response => { if (!response.ok) { - // Try to get error message from JSON response, otherwise use status text return response.json().then(err => { throw new Error(err.message || response.statusText); }).catch(() => { @@ -105,15 +125,12 @@ document.addEventListener('DOMContentLoaded', function () { console.error('Fetch Error:', error); }) .finally(() => { - if(translateBtn) { - translateBtn.disabled = false; - translateBtn.innerHTML = ' Translate'; - } + startTranslationBtn.disabled = false; + startTranslationBtn.innerHTML = ' Translate'; }); }); } - // Handle copy and download buttons const copyBtn = document.getElementById('copy-btn'); const downloadBtn = document.getElementById('download-btn'); const translationOutput = document.getElementById('translation-output'); @@ -145,14 +162,10 @@ document.addEventListener('DOMContentLoaded', function () { try { const { jsPDF } = window.jspdf; const doc = new jsPDF(); - - // Set font that supports a wide range of characters doc.setFont('Helvetica'); doc.setFontSize(12); - const text = translationOutput.textContent; const lines = doc.splitTextToSize(text, 180); - doc.text(lines, 15, 20); doc.save('translation.pdf'); } catch (error) { @@ -164,4 +177,4 @@ document.addEventListener('DOMContentLoaded', function () { } }); } -}); \ No newline at end of file +}); diff --git a/index.php b/index.php index 1f2a902..fb0b657 100644 --- a/index.php +++ b/index.php @@ -95,10 +95,14 @@ -
- +
diff --git a/translate.php b/translate.php new file mode 100644 index 0000000..caf5319 --- /dev/null +++ b/translate.php @@ -0,0 +1,92 @@ + $status, + 'message' => $message, + 'data' => $data + ]); + exit; +} + +if ($_SERVER['REQUEST_METHOD'] !== 'POST') { + json_response('error', 'Invalid request method.'); +} + +if (!isset($_POST['file_path']) || !isset($_POST['source_lang']) || !isset($_POST['target_lang'])) { + json_response('error', 'Missing required parameters.'); +} + +$file_path = $_POST['file_path']; +$source_lang = $_POST['source_lang']; +$target_lang = $_POST['target_lang']; + +$full_path = __DIR__ . '/' . $file_path; + +if (!file_exists($full_path)) { + json_response('error', 'File not found.'); +} + +try { + $file_content = file_get_contents($full_path); + if ($file_content === false) { + json_response('error', 'Failed to read file content.'); + } + + $base64_image = base64_encode($file_content); + + if (function_exists('mime_content_type')) { + $mime_type = mime_content_type($full_path); + } else { + $mime_type = 'application/octet-stream'; + } + + $resp = LocalAIApi::createResponse([ + 'input' => [ + [ + 'role' => 'user', + 'content' => [ + [ + 'type' => 'text', + 'text' => "You are an expert document translator. Please perform the following tasks:\n" \ + . "1. **OCR Extraction:** Extract all visible text from the attached image.\n" \ + . "2. **Translation:** Translate the extracted text from `{$source_lang}` to `{$target_lang}`.\n" \ + . "3. **Output:** Return ONLY the translated text as a single block of plain text. Do not include any explanations, apologies, or introductory phrases. Just the translation." + ], + [ + 'type' => 'image', + 'source' => [ + 'type' => 'base64', + 'media_type' => $mime_type, + 'data' => $base64_image, + ], + ], + ], + ], + ], + ]); + + if (!empty($resp['success'])) { + $translated_text = LocalAIApi::extractText($resp); + if ($translated_text === '') { + $decoded = LocalAIApi::decodeJsonFromResponse($resp); + $error_details = $decoded ? json_encode($decoded, JSON_PRETTY_PRINT) : 'AI returned an empty or invalid response.'; + json_response('error', 'AI processing failed. Details: ' . $error_details); + } else { + json_response('success', 'File translated successfully.', ['translatedText' => $translated_text]); + } + } else { + $error_message = $resp['error'] ?? 'Unknown AI error.'; + json_response('error', 'Failed to get response from AI service: ' . $error_message); + } + +} catch (Exception $e) { + json_response('error', 'An exception occurred: ' . $e->getMessage()); +} +?> diff --git a/upload.php b/upload.php index cb47189..0cf7bb5 100644 --- a/upload.php +++ b/upload.php @@ -1,33 +1,29 @@ $status, - 'message' => $message, - 'data' => $data - ]); +header('Content-Type: application/json'); + +function json_response($success, $message, $filePath = null) { + $response = ['success' => $success, 'message' => $message]; + if ($filePath) { + $response['filePath'] = $filePath; + } + echo json_encode($response); exit; } if ($_SERVER['REQUEST_METHOD'] !== 'POST') { - json_response('error', 'Invalid request method.'); + json_response(false, 'Invalid request method.'); } if (!isset($_FILES['document']) || $_FILES['document']['error'] == UPLOAD_ERR_NO_FILE) { - json_response('error', 'No file was uploaded.'); + json_response(false, 'No file was uploaded.'); } $file = $_FILES['document']; -// Check for upload errors if ($file['error'] !== UPLOAD_ERR_OK) { $upload_errors = [ UPLOAD_ERR_INI_SIZE => 'The uploaded file exceeds the upload_max_filesize directive in php.ini.', @@ -39,90 +35,24 @@ if ($file['error'] !== UPLOAD_ERR_OK) { UPLOAD_ERR_EXTENSION => 'A PHP extension stopped the file upload.', ]; $error_message = $upload_errors[$file['error']] ?? 'Unknown upload error.'; - json_response('error', $error_message); + json_response(false, $error_message); } $upload_dir = __DIR__ . '/uploads/'; if (!is_dir($upload_dir)) { if (!mkdir($upload_dir, 0775, true)) { - json_response('error', 'Failed to create upload directory.'); + json_response(false, 'Failed to create upload directory.'); } } -$file_name = basename($file['name']); +$file_extension = pathinfo($file['name'], PATHINFO_EXTENSION); +$file_name = uniqid('doc_') . '.' . $file_extension; $target_path = $upload_dir . $file_name; -// Move the file to the uploads directory if (move_uploaded_file($file['tmp_name'], $target_path)) { - // AI Translation Step - try { - require_once __DIR__ . '/ai/LocalAIApi.php'; - - $source_lang = $_POST['source_lang'] ?? 'auto-detect'; - $target_lang = $_POST['target_lang'] ?? 'English'; - $file_path_for_ai = 'uploads/' . $file_name; - - // Read the file content and base64 encode it - $file_content = file_get_contents($target_path); - $base64_image = base64_encode($file_content); - - // Check if mime_content_type function exists - if (function_exists('mime_content_type')) { - $mime_type = mime_content_type($target_path); - } else { - // Fallback to a generic MIME type if the function doesn't exist - $mime_type = 'application/octet-stream'; - } - - $resp = LocalAIApi::createResponse([ - 'input' => [ - [ - 'role' => 'user', - 'content' => [ - [ - 'type' => 'text', - 'text' => "You are an expert document translator. Please perform the following tasks:\n" \ - . "1. **OCR Extraction:** Extract all visible text from the attached image.\n" \ - . "2. **Translation:** Translate the extracted text from `{$source_lang}` to `{$target_lang}`.\n" \ - . "3. **Output:** Return ONLY the translated text as a single block of plain text. Do not include any explanations, apologies, or introductory phrases. Just the translation." - ], - [ - 'type' => 'image', - 'source' => [ - 'type' => 'base64', - 'media_type' => $mime_type, - 'data' => $base64_image, - ], - ], - ], - ], - ], - ]); - - if (!empty($resp['success'])) { - $translated_text = LocalAIApi::extractText($resp); - if ($translated_text === '') { - // Handle cases where the AI returns a non-text response or empty text - $decoded = LocalAIApi::decodeJsonFromResponse($resp); - $error_details = $decoded ? json_encode($decoded, JSON_PRETTY_PRINT) : 'AI returned an empty or invalid response.'; - json_response('error', 'AI processing failed. Details: ' . $error_details); - } else { - json_response('success', 'File translated successfully.', [ - 'filePath' => $file_path_for_ai, - 'translatedText' => $translated_text - ]); - } - } else { - $error_message = $resp['error'] ?? 'Unknown AI error.'; - json_response('error', 'Failed to get response from AI service: ' . $error_message); - } - - } catch (Exception $e) { - json_response('error', 'An exception occurred during AI processing: ' . $e->getMessage()); - } - + $web_path = 'uploads/' . $file_name; + json_response(true, 'File uploaded successfully.', $web_path); } else { - json_response('error', 'Failed to move uploaded file.'); + json_response(false, 'Failed to move uploaded file.'); } - -?> \ No newline at end of file +?>