Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b3465c50eb | ||
|
|
5c76810a10 | ||
|
|
4480b6fac8 | ||
|
|
a04ddd5be8 | ||
|
|
7ceedc82a3 | ||
|
|
d78986f2c1 | ||
|
|
bb853fb0c8 | ||
|
|
28bed0ead6 | ||
|
|
2c882d18a4 |
68
assets/css/custom.css
Normal file
@ -0,0 +1,68 @@
|
||||
body {
|
||||
font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||
background-color: #F8F9FA;
|
||||
}
|
||||
|
||||
.navbar-brand {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.hero {
|
||||
background: linear-gradient(135deg, #0D6EFD, #4D8BF2);
|
||||
color: white;
|
||||
padding: 4rem 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.hero h1 {
|
||||
font-weight: 700;
|
||||
font-size: 3rem;
|
||||
}
|
||||
|
||||
.translation-box {
|
||||
background-color: #FFFFFF;
|
||||
border-radius: 0.5rem;
|
||||
padding: 2rem;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.05);
|
||||
}
|
||||
|
||||
.form-label {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background-color: #0D6EFD;
|
||||
border-color: #0D6EFD;
|
||||
padding: 0.75rem 1.5rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background-color: #0b5ed7;
|
||||
border-color: #0a58ca;
|
||||
}
|
||||
|
||||
.custom-file-upload {
|
||||
border: 2px dashed #0D6EFD;
|
||||
border-radius: 0.375rem;
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
background-color: #f8f9fa;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.custom-file-upload:hover {
|
||||
background-color: #e9ecef;
|
||||
}
|
||||
|
||||
#file-upload-filename {
|
||||
margin-top: 1rem;
|
||||
font-style: italic;
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
#processing-message {
|
||||
display: none;
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
221
assets/js/main.js
Normal file
@ -0,0 +1,221 @@
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const fileUploadInput = document.getElementById('document-upload');
|
||||
const fileUploadLabel = document.querySelector('.custom-file-upload');
|
||||
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 = '';
|
||||
|
||||
if(fileUploadLabel) {
|
||||
fileUploadLabel.addEventListener('click', () => {
|
||||
if(fileUploadInput) {
|
||||
fileUploadInput.click();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
const debugPanel = document.getElementById('debug-panel');
|
||||
const debugLog = document.getElementById('debug-log');
|
||||
|
||||
function logToDebug(message) {
|
||||
if(debugPanel && debugLog) {
|
||||
debugPanel.style.display = 'block';
|
||||
debugLog.textContent += message + '\n';
|
||||
}
|
||||
console.log(message);
|
||||
}
|
||||
|
||||
if(fileUploadInput) {
|
||||
fileUploadInput.addEventListener('change', () => {
|
||||
if (fileUploadInput.files.length > 0) {
|
||||
const file = fileUploadInput.files[0];
|
||||
logToDebug('File selected: ' + file.name);
|
||||
|
||||
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 => {
|
||||
logToDebug('Received response from server. Status: ' + response.status);
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
processingMessage.style.display = 'none';
|
||||
|
||||
if (data.success && data.path) {
|
||||
uploadedFilePath = data.path;
|
||||
const file = fileUploadInput.files[0];
|
||||
|
||||
const fileExtension = uploadedFilePath.split('.').pop().toLowerCase();
|
||||
const imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'tiff', 'bmp', 'webp'];
|
||||
|
||||
fileUploadLabel.style.display = 'none';
|
||||
fileNameDisplay.textContent = file.name;
|
||||
|
||||
if (imageExtensions.includes(fileExtension)) {
|
||||
imagePreview.onload = function() {
|
||||
previewContainer.style.display = 'block';
|
||||
imagePreview.style.display = 'block';
|
||||
startTranslationBtn.style.display = 'block';
|
||||
};
|
||||
imagePreview.onerror = function() {
|
||||
alert('Error loading image preview.');
|
||||
fileUploadLabel.style.display = 'block';
|
||||
};
|
||||
imagePreview.src = uploadedFilePath + '?t=' + new Date().getTime();
|
||||
|
||||
} else if (fileExtension === 'pdf') {
|
||||
const pdfFilename = document.getElementById('pdf-filename');
|
||||
pdfFilename.textContent = file.name;
|
||||
previewContainer.style.display = 'block';
|
||||
document.getElementById('pdf-preview').style.display = 'block';
|
||||
startTranslationBtn.style.display = 'block';
|
||||
} else {
|
||||
alert("Unsupported file type for preview.");
|
||||
fileUploadLabel.style.display = 'block';
|
||||
}
|
||||
|
||||
} else {
|
||||
throw new Error(data.message || 'File upload failed.');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
logToDebug('An error occurred in the upload process: ' + error.message);
|
||||
processingMessage.style.display = 'none';
|
||||
const responseMessageDiv = document.getElementById('response-message');
|
||||
responseMessageDiv.innerHTML = `<div class="alert alert-danger" role="alert">An unexpected error occurred: ${error.message}</div>`;
|
||||
responseMessageDiv.style.display = 'block';
|
||||
});
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if(startTranslationBtn) {
|
||||
startTranslationBtn.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('file_path', uploadedFilePath);
|
||||
formData.append('source-lang', document.getElementById('source-lang').value);
|
||||
formData.append('target-lang', document.getElementById('target-lang').value);
|
||||
|
||||
const responseMessageDiv = document.getElementById('response-message');
|
||||
|
||||
if(processingMessage) {
|
||||
processingMessage.style.display = 'block';
|
||||
}
|
||||
if(responseMessageDiv){
|
||||
responseMessageDiv.innerHTML = '';
|
||||
responseMessageDiv.style.display = 'none';
|
||||
}
|
||||
|
||||
startTranslationBtn.disabled = true;
|
||||
startTranslationBtn.innerHTML = '<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> Translating...';
|
||||
|
||||
fetch('translate.php', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
return response.json().then(err => {
|
||||
throw new Error(err.message || response.statusText);
|
||||
}).catch(() => {
|
||||
throw new Error(response.statusText);
|
||||
});
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
processingMessage.style.display = 'none';
|
||||
|
||||
if (data.status === 'success') {
|
||||
const translatedText = data.data.translatedText;
|
||||
const outputContainer = document.getElementById('translation-output-container');
|
||||
const outputElement = document.getElementById('translation-output');
|
||||
|
||||
if (outputElement && outputContainer) {
|
||||
outputElement.textContent = translatedText;
|
||||
outputContainer.style.display = 'block';
|
||||
}
|
||||
|
||||
responseMessageDiv.innerHTML = `<div class="alert alert-success" role="alert">${data.message}</div>`;
|
||||
responseMessageDiv.style.display = 'block';
|
||||
|
||||
} else {
|
||||
responseMessageDiv.innerHTML = `<div class="alert alert-danger" role="alert"><strong>Error:</strong> ${data.message}</div>`;
|
||||
responseMessageDiv.style.display = 'block';
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
processingMessage.style.display = 'none';
|
||||
responseMessageDiv.innerHTML = `<div class="alert alert-danger" role="alert">An unexpected error occurred: ${error.message}</div>`;
|
||||
responseMessageDiv.style.display = 'block';
|
||||
console.error('Fetch Error:', error);
|
||||
})
|
||||
.finally(() => {
|
||||
startTranslationBtn.disabled = false;
|
||||
startTranslationBtn.innerHTML = '<i class="bi bi-stars"></i> Translate';
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const copyBtn = document.getElementById('copy-btn');
|
||||
const downloadBtn = document.getElementById('download-btn');
|
||||
const translationOutput = document.getElementById('translation-output');
|
||||
|
||||
if (copyBtn) {
|
||||
copyBtn.addEventListener('click', () => {
|
||||
if (translationOutput && navigator.clipboard) {
|
||||
navigator.clipboard.writeText(translationOutput.textContent).then(() => {
|
||||
const originalText = copyBtn.innerHTML;
|
||||
copyBtn.innerHTML = '<i class="bi bi-check-lg"></i> Copied!';
|
||||
copyBtn.classList.add('btn-success');
|
||||
copyBtn.classList.remove('btn-secondary');
|
||||
setTimeout(() => {
|
||||
copyBtn.innerHTML = originalText;
|
||||
copyBtn.classList.remove('btn-success');
|
||||
copyBtn.classList.add('btn-secondary');
|
||||
}, 2000);
|
||||
}).catch(err => {
|
||||
console.error('Failed to copy text: ', err);
|
||||
alert('Failed to copy text. Please try again.');
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (downloadBtn) {
|
||||
downloadBtn.addEventListener('click', () => {
|
||||
if (translationOutput && window.jspdf) {
|
||||
try {
|
||||
const { jsPDF } = window.jspdf;
|
||||
const doc = new jsPDF();
|
||||
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) {
|
||||
console.error('Failed to generate PDF:', error);
|
||||
alert('Failed to generate PDF. An error occurred.');
|
||||
}
|
||||
} else if (!window.jspdf) {
|
||||
alert('The PDF generation library is not loaded.');
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
BIN
assets/pasted-20251120-122957-7402e7e8.png
Normal file
|
After Width: | Height: | Size: 890 KiB |
BIN
assets/pasted-20251120-123736-8179929a.png
Normal file
|
After Width: | Height: | Size: 320 KiB |
BIN
assets/pasted-20251120-124515-9e03bb1c.png
Normal file
|
After Width: | Height: | Size: 357 KiB |
BIN
assets/vm-shot-2025-11-20T12-31-33-320Z.jpg
Normal file
|
After Width: | Height: | Size: 59 KiB |
280
index.php
@ -1,150 +1,154 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
@ini_set('display_errors', '1');
|
||||
@error_reporting(E_ALL);
|
||||
@date_default_timezone_set('UTC');
|
||||
|
||||
$phpVersion = PHP_VERSION;
|
||||
$now = date('Y-m-d H:i:s');
|
||||
?>
|
||||
<!doctype html>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>New Style</title>
|
||||
<?php
|
||||
// Read project preview data from environment
|
||||
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? '';
|
||||
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
||||
?>
|
||||
<?php if ($projectDescription): ?>
|
||||
<!-- Meta description -->
|
||||
<meta name="description" content='<?= htmlspecialchars($projectDescription) ?>' />
|
||||
<!-- Open Graph meta tags -->
|
||||
<meta property="og:description" content="<?= htmlspecialchars($projectDescription) ?>" />
|
||||
<!-- Twitter meta tags -->
|
||||
<meta property="twitter:description" content="<?= htmlspecialchars($projectDescription) ?>" />
|
||||
<?php endif; ?>
|
||||
<?php if ($projectImageUrl): ?>
|
||||
<!-- Open Graph image -->
|
||||
<meta property="og:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
|
||||
<!-- Twitter image -->
|
||||
<meta property="twitter:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
|
||||
<?php endif; ?>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>easyenglishtranslator</title>
|
||||
<meta name="description" content="Translate text on images and PDF scans easily. Upload your document, choose languages, and let AI provide you with an accurate translation.">
|
||||
<meta name="keywords" content="document translator, pdf translator, image translator, ocr translation, ai translation, language tool, easy english translator, Built with Flatlogic Generator">
|
||||
<meta property="og:title" content="easyenglishtranslator">
|
||||
<meta property="og:description" content="Translate text on images and PDF scans easily.">
|
||||
<meta property="og:image" content="">
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
<meta name="twitter:image" content="">
|
||||
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
:root {
|
||||
--bg-color-start: #6a11cb;
|
||||
--bg-color-end: #2575fc;
|
||||
--text-color: #ffffff;
|
||||
--card-bg-color: rgba(255, 255, 255, 0.01);
|
||||
--card-border-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: 'Inter', sans-serif;
|
||||
background: linear-gradient(45deg, var(--bg-color-start), var(--bg-color-end));
|
||||
color: var(--text-color);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 100vh;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
body::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><path d="M-10 10L110 10M10 -10L10 110" stroke-width="1" stroke="rgba(255,255,255,0.05)"/></svg>');
|
||||
animation: bg-pan 20s linear infinite;
|
||||
z-index: -1;
|
||||
}
|
||||
@keyframes bg-pan {
|
||||
0% { background-position: 0% 0%; }
|
||||
100% { background-position: 100% 100%; }
|
||||
}
|
||||
main {
|
||||
padding: 2rem;
|
||||
}
|
||||
.card {
|
||||
background: var(--card-bg-color);
|
||||
border: 1px solid var(--card-border-color);
|
||||
border-radius: 16px;
|
||||
padding: 2rem;
|
||||
backdrop-filter: blur(20px);
|
||||
-webkit-backdrop-filter: blur(20px);
|
||||
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
.loader {
|
||||
margin: 1.25rem auto 1.25rem;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border: 3px solid rgba(255, 255, 255, 0.25);
|
||||
border-top-color: #fff;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
@keyframes spin {
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
.hint {
|
||||
opacity: 0.9;
|
||||
}
|
||||
.sr-only {
|
||||
position: absolute;
|
||||
width: 1px; height: 1px;
|
||||
padding: 0; margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap; border: 0;
|
||||
}
|
||||
h1 {
|
||||
font-size: 3rem;
|
||||
font-weight: 700;
|
||||
margin: 0 0 1rem;
|
||||
letter-spacing: -1px;
|
||||
}
|
||||
p {
|
||||
margin: 0.5rem 0;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
code {
|
||||
background: rgba(0,0,0,0.2);
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||
}
|
||||
footer {
|
||||
position: absolute;
|
||||
bottom: 1rem;
|
||||
font-size: 0.8rem;
|
||||
opacity: 0.7;
|
||||
}
|
||||
</style>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<div class="card">
|
||||
<h1>Analyzing your requirements and generating your website…</h1>
|
||||
<div class="loader" role="status" aria-live="polite" aria-label="Applying initial changes">
|
||||
<span class="sr-only">Loading…</span>
|
||||
|
||||
<nav class="navbar navbar-expand-lg navbar-light bg-white shadow-sm">
|
||||
<div class="container">
|
||||
<a class="navbar-brand" href="#">
|
||||
<i class="bi bi-translate text-primary"></i>
|
||||
easyenglishtranslator
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<header class="hero">
|
||||
<div class="container">
|
||||
<h1>Document Translation Made Simple</h1>
|
||||
<p class="lead">Upload a PDF or image, select your languages, and let our AI do the rest.</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="container my-5">
|
||||
|
||||
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-8">
|
||||
<div class="translation-box">
|
||||
<form id="translation-form">
|
||||
<div class="mb-4">
|
||||
<label for="document-upload" class="form-label">1. Upload Your Document</label>
|
||||
<div class="custom-file-upload">
|
||||
<i class="bi bi-cloud-arrow-up-fill fs-1 text-primary"></i>
|
||||
<p class="mt-2 mb-0">Click to browse or drag & drop your file here</p>
|
||||
<p class="text-muted small">(PDF, JPG, PNG, TIFF)</p>
|
||||
</div>
|
||||
<input type="file" id="document-upload" class="d-none" accept=".pdf,.jpg,.jpeg,.png,.tiff">
|
||||
<div id="file-upload-filename" class="text-center"></div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-6">
|
||||
<label for="source-lang" class="form-label">2. Source Language</label>
|
||||
<select class="form-select" id="source-lang" required>
|
||||
<option selected disabled value="">Choose...</option>
|
||||
<option value="auto">Auto-detect</option>
|
||||
<option value="en">English</option>
|
||||
<option value="es">Spanish</option>
|
||||
<option value="fr">French</option>
|
||||
<option value="de">German</option>
|
||||
<option value="it">Italian</option>
|
||||
<option value="pt">Portuguese</option>
|
||||
<option value="nl">Dutch</option>
|
||||
<option value="ru">Russian</option>
|
||||
<option value="ja">Japanese</option>
|
||||
<option value="ko">Korean</option>
|
||||
<option value="zh">Chinese (Simplified)</option>
|
||||
<option value="ar">Arabic</option>
|
||||
<option value="hi">Hindi</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="target-lang" class="form-label">3. Target Language</label>
|
||||
<select class="form-select" id="target-lang" required>
|
||||
<option selected disabled value="">Choose...</option>
|
||||
<option value="en">English</option>
|
||||
<option value="es">Spanish</option>
|
||||
<option value="fr">French</option>
|
||||
<option value="de">German</option>
|
||||
<option value="it">Italian</option>
|
||||
<option value="pt">Portuguese</option>
|
||||
<option value="nl">Dutch</option>
|
||||
<option value="ru">Russian</option>
|
||||
<option value="ja">Japanese</option>
|
||||
<option value="ko">Korean</option>
|
||||
<option value="zh">Chinese (Simplified)</option>
|
||||
<option value="ar">Arabic</option>
|
||||
<option value="hi">Hindi</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="preview-container" class="text-center" style="display: none;">
|
||||
<h5 class="mt-4">Document Preview</h5>
|
||||
|
||||
<img id="image-preview" src="#" alt="Image Preview" class="img-fluid rounded border" style="max-height: 400px; display: none;"/>
|
||||
|
||||
<div id="pdf-preview" class="py-5" style="display: none;">
|
||||
<i class="bi bi-file-earmark-pdf-fill text-danger" style="font-size: 4rem;"></i>
|
||||
<p id="pdf-filename" class="mt-2 mb-0"></p>
|
||||
</div>
|
||||
|
||||
<div class="d-grid mt-3">
|
||||
<button type="button" id="start-translation-btn" class="btn btn-primary btn-lg" style="display: none;">
|
||||
<i class="bi bi-stars"></i> Translate
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<div id="response-message" class="mt-4"></div>
|
||||
<div id="processing-message" class="alert alert-info mt-4" role="alert" style="display: none;">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="spinner-border text-primary me-3" role="status">
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
<span>Processing your document... This may take a moment.</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="translation-output-container" class="card mt-4" style="display: none;">
|
||||
<div class="card-header fw-bold">
|
||||
<i class="bi bi-check-circle-fill text-success me-2"></i>Translation Result
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<pre id="translation-output" class="mb-0" style="white-space: pre-wrap; font-family: inherit; font-size: 1rem;"></pre>
|
||||
</div>
|
||||
<div class="card-footer bg-light text-end">
|
||||
<button id="copy-btn" class="btn btn-secondary btn-sm"><i class="bi bi-clipboard me-1"></i> Copy Text</button>
|
||||
<button id="download-btn" class="btn btn-primary btn-sm"><i class="bi bi-download me-1"></i> Download PDF</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="hint"><?= ($_SERVER['HTTP_HOST'] ?? '') === 'appwizzy.com' ? 'AppWizzy' : 'Flatlogic' ?> AI is collecting your requirements and applying the first changes.</p>
|
||||
<p class="hint">This page will update automatically as the plan is implemented.</p>
|
||||
<p>Runtime: PHP <code><?= htmlspecialchars($phpVersion) ?></code> — UTC <code><?= htmlspecialchars($now) ?></code></p>
|
||||
</div>
|
||||
</main>
|
||||
<footer>
|
||||
Page updated: <?= htmlspecialchars($now) ?> (UTC)
|
||||
|
||||
<footer class="text-center py-4 text-muted">
|
||||
<div class="container">
|
||||
<p>© <?php echo date("Y"); ?> easyenglishtranslator. All Rights Reserved. Built with Flatlogic.</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
|
||||
<script src="assets/js/main.js?v=<?php echo time(); ?>"></script>
|
||||
</body>
|
||||
</html>
|
||||
109
translate.php
Normal file
@ -0,0 +1,109 @@
|
||||
<?php
|
||||
ini_set('display_errors', 0);
|
||||
ini_set('log_errors', 1);
|
||||
ini_set('error_log', __DIR__ . '/error_log.txt');
|
||||
|
||||
// --- Debug Logging ---
|
||||
$log_file = __DIR__ . '/translate_log.txt';
|
||||
file_put_contents($log_file, "--- New Translation Request ---
|
||||
", FILE_APPEND);
|
||||
|
||||
function write_log($message) {
|
||||
global $log_file;
|
||||
$timestamp = date('Y-m-d H:i:s');
|
||||
file_put_contents($log_file, "[{$timestamp}] " . print_r($message, true) . "\n", FILE_APPEND);
|
||||
}
|
||||
// --- End Debug Logging ---
|
||||
|
||||
require_once __DIR__ . '/ai/LocalAIApi.php';
|
||||
|
||||
function json_response($status, $message, $data = null) {
|
||||
write_log("Sending JSON response: Status: {$status}, Message: {$message}");
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode([
|
||||
'status' => $status,
|
||||
'message' => $message,
|
||||
'data' => $data
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
write_log("Request method: " . $_SERVER['REQUEST_METHOD']);
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
json_response('error', 'Invalid request method.');
|
||||
}
|
||||
|
||||
write_log("POST data: " . json_encode($_POST));
|
||||
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'];
|
||||
|
||||
$url_path = parse_url($file_path, PHP_URL_PATH);
|
||||
$full_path = __DIR__ . $url_path;
|
||||
write_log("Full file path: {$full_path}");
|
||||
|
||||
if (!file_exists($full_path)) {
|
||||
json_response('error', 'File not found.');
|
||||
}
|
||||
|
||||
try {
|
||||
// Step 1: Perform OCR on the image
|
||||
write_log("Performing OCR on file: {$full_path}");
|
||||
// Use escapeshellarg to prevent command injection vulnerabilities
|
||||
$escaped_path = escapeshellarg($full_path);
|
||||
$ocr_command = "tesseract {$escaped_path} stdout";
|
||||
write_log("Executing OCR command: {$ocr_command}");
|
||||
|
||||
// Execute the command and capture the output
|
||||
$extracted_text = shell_exec($ocr_command);
|
||||
write_log("OCR raw output: " . $extracted_text);
|
||||
|
||||
if ($extracted_text === null || trim($extracted_text) === '') {
|
||||
json_response('error', 'OCR failed or no text was found in the image.');
|
||||
}
|
||||
|
||||
$extracted_text = trim($extracted_text);
|
||||
write_log("Extracted text (trimmed): " . $extracted_text);
|
||||
|
||||
// Step 2: Translate the extracted text
|
||||
write_log("Sending extracted text to AI for translation...");
|
||||
|
||||
$prompt = "Translate the following text from {$source_lang} to {$target_lang}:\n\n{$extracted_text}";
|
||||
|
||||
$payload = [
|
||||
'input' => [
|
||||
[
|
||||
'role' => 'user',
|
||||
'content' => $prompt
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
write_log("AI request payload: " . json_encode($payload, JSON_PRETTY_PRINT));
|
||||
$resp = LocalAIApi::createResponse($payload);
|
||||
write_log("Received response from AI API: " . json_encode($resp, JSON_PRETTY_PRINT));
|
||||
|
||||
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 translation failed. Details: ' . $error_details);
|
||||
} else {
|
||||
json_response('success', 'Translation successful!', ['translatedText' => $translated_text, 'extractedText' => $extracted_text]);
|
||||
}
|
||||
} else {
|
||||
$error_message = $resp['error'] ?? 'Unknown AI error.';
|
||||
json_response('error', 'AI translation failed: ' . $error_message);
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
write_log("An exception occurred: " . $e->getMessage());
|
||||
write_log("Stack trace: " . $e->getTraceAsString());
|
||||
json_response('error', 'An unexpected error occurred: ' . $e->getMessage());
|
||||
}
|
||||
?>
|
||||
482
translate_log.txt
Normal file
63
upload.php
Normal file
@ -0,0 +1,63 @@
|
||||
<?php
|
||||
ini_set('display_errors', 0);
|
||||
ini_set('log_errors', 1);
|
||||
ini_set('error_log', __DIR__ . '/error_log.txt');
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
function json_response($success, $data) {
|
||||
$response = ['success' => $success];
|
||||
if (is_string($data)) {
|
||||
$response['message'] = $data;
|
||||
} elseif (is_array($data)) {
|
||||
$response = array_merge($response, $data);
|
||||
}
|
||||
echo json_encode($response);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
json_response(false, 'Invalid request method.');
|
||||
}
|
||||
|
||||
if (!isset($_FILES['document']) || $_FILES['document']['error'] == UPLOAD_ERR_NO_FILE) {
|
||||
json_response(false, 'No file was uploaded.');
|
||||
}
|
||||
|
||||
$file = $_FILES['document'];
|
||||
|
||||
if ($file['error'] !== UPLOAD_ERR_OK) {
|
||||
$upload_errors = [
|
||||
UPLOAD_ERR_INI_SIZE => 'The uploaded file exceeds the upload_max_filesize directive in php.ini.',
|
||||
UPLOAD_ERR_FORM_SIZE => 'The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form.',
|
||||
UPLOAD_ERR_PARTIAL => 'The uploaded file was only partially uploaded.',
|
||||
UPLOAD_ERR_NO_FILE => 'No file was uploaded.',
|
||||
UPLOAD_ERR_NO_TMP_DIR => 'Missing a temporary folder.',
|
||||
UPLOAD_ERR_CANT_WRITE => 'Failed to write file to disk.',
|
||||
UPLOAD_ERR_EXTENSION => 'A PHP extension stopped the file upload.',
|
||||
];
|
||||
$error_message = $upload_errors[$file['error']] ?? 'Unknown upload error.';
|
||||
json_response(false, $error_message);
|
||||
}
|
||||
|
||||
$upload_dir = __DIR__ . '/uploads/';
|
||||
if (!is_dir($upload_dir)) {
|
||||
if (!mkdir($upload_dir, 0775, true)) {
|
||||
json_response(false, 'Failed to create upload directory.');
|
||||
}
|
||||
}
|
||||
|
||||
$file_extension = pathinfo($file['name'], PATHINFO_EXTENSION);
|
||||
$file_name = uniqid('doc_') . '.' . $file_extension;
|
||||
$target_path = $upload_dir . $file_name;
|
||||
|
||||
if (move_uploaded_file($file['tmp_name'], $target_path)) {
|
||||
|
||||
$protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') || $_SERVER['SERVER_PORT'] == 443 ? "https://" : "http://";
|
||||
$domain_name = $_SERVER['HTTP_HOST'];
|
||||
$web_path = $protocol . $domain_name . '/uploads/' . $file_name;
|
||||
json_response(true, ['path' => $web_path, 'message' => 'File uploaded successfully.']);
|
||||
} else {
|
||||
json_response(false, 'Failed to move uploaded file.');
|
||||
}
|
||||
?>
|
||||
BIN
uploads/20251120_150540.jpg
Normal file
|
After Width: | Height: | Size: 355 KiB |
BIN
uploads/doc_691f098f382bd.jpg
Normal file
|
After Width: | Height: | Size: 355 KiB |
BIN
uploads/doc_691f09aa1efe1.jpg
Normal file
|
After Width: | Height: | Size: 355 KiB |
BIN
uploads/doc_691f09fb5e8e6.jpg
Normal file
|
After Width: | Height: | Size: 355 KiB |
BIN
uploads/doc_691f0a1c677eb.jpg
Normal file
|
After Width: | Height: | Size: 355 KiB |
BIN
uploads/doc_691f0a88360cd.jpg
Normal file
|
After Width: | Height: | Size: 355 KiB |
BIN
uploads/doc_691f0ae04935d.jpg
Normal file
|
After Width: | Height: | Size: 355 KiB |
BIN
uploads/doc_691f0b64179e7.jpg
Normal file
|
After Width: | Height: | Size: 355 KiB |
BIN
uploads/doc_691f0bf340417.jpg
Normal file
|
After Width: | Height: | Size: 355 KiB |
BIN
uploads/doc_691f0c555fd8e.jpg
Normal file
|
After Width: | Height: | Size: 355 KiB |
BIN
uploads/doc_691f0c613af9f.jpg
Normal file
|
After Width: | Height: | Size: 355 KiB |
BIN
uploads/doc_691f0d239eafe.jpg
Normal file
|
After Width: | Height: | Size: 355 KiB |
BIN
uploads/doc_691f0dca02956.jpg
Normal file
|
After Width: | Height: | Size: 355 KiB |
BIN
uploads/doc_691f0e3c6c9b2.jpg
Normal file
|
After Width: | Height: | Size: 355 KiB |
BIN
uploads/doc_691f0f0a63bfe.jpg
Normal file
|
After Width: | Height: | Size: 355 KiB |
BIN
uploads/doc_691f0fc897201.jpg
Normal file
|
After Width: | Height: | Size: 355 KiB |
BIN
uploads/doc_691f109003148.jpg
Normal file
|
After Width: | Height: | Size: 355 KiB |
BIN
uploads/doc_691f10e1495ec.jpg
Normal file
|
After Width: | Height: | Size: 355 KiB |
BIN
uploads/doc_691f11431672c.jpg
Normal file
|
After Width: | Height: | Size: 355 KiB |
BIN
uploads/doc_691f11e4b0ca0.jpg
Normal file
|
After Width: | Height: | Size: 355 KiB |
BIN
uploads/doc_691f1291a7b29.jpg
Normal file
|
After Width: | Height: | Size: 355 KiB |
BIN
uploads/doc_691f13131bb30.jpg
Normal file
|
After Width: | Height: | Size: 355 KiB |
BIN
uploads/doc_691f13af407b9.jpg
Normal file
|
After Width: | Height: | Size: 355 KiB |
BIN
uploads/doc_691f144e9064d.jpg
Normal file
|
After Width: | Height: | Size: 355 KiB |
BIN
uploads/doc_691f14bb0f188.jpg
Normal file
|
After Width: | Height: | Size: 355 KiB |
BIN
uploads/doc_691f1543b7b27.jpg
Normal file
|
After Width: | Height: | Size: 355 KiB |
BIN
uploads/doc_691f15ab4855e.jpg
Normal file
|
After Width: | Height: | Size: 355 KiB |