308 lines
No EOL
14 KiB
PHP
Executable file
308 lines
No EOL
14 KiB
PHP
Executable file
<?php
|
|
require_once 'config.php';
|
|
|
|
use League\OAuth2\Client\Provider\GenericProvider;
|
|
|
|
// HANDLE OAUTH CALLBACK FROM AZURE AD
|
|
// Azure redirects to root URL with ?code= and ?state= parameters
|
|
if (isset($_GET['code']) && isset($_GET['state'])) {
|
|
// Start session to access PKCE verifier and state
|
|
if (session_status() === PHP_SESSION_NONE) {
|
|
session_start();
|
|
}
|
|
|
|
// Verify state to prevent CSRF attacks
|
|
if (empty($_GET['state']) || (isset($_SESSION['oauth2state']) && $_GET['state'] !== $_SESSION['oauth2state'])) {
|
|
unset($_SESSION['oauth2state']);
|
|
unset($_SESSION['oauth2_code_verifier']);
|
|
die('Invalid state. Possible CSRF attack.');
|
|
}
|
|
|
|
try {
|
|
// Retrieve code verifier from session
|
|
if (!isset($_SESSION['oauth2_code_verifier'])) {
|
|
die('Code verifier not found in session.');
|
|
}
|
|
|
|
$codeVerifier = $_SESSION['oauth2_code_verifier'];
|
|
|
|
// Configure Azure AD OAuth2 Provider
|
|
$provider = new GenericProvider([
|
|
'clientId' => AZURE_CLIENT_ID,
|
|
'redirectUri' => AZURE_REDIRECT_URI,
|
|
'urlAuthorize' => AZURE_AUTHORITY . '/oauth2/v2.0/authorize',
|
|
'urlAccessToken' => AZURE_AUTHORITY . '/oauth2/v2.0/token',
|
|
'urlResourceOwnerDetails' => 'https://graph.microsoft.com/v1.0/me',
|
|
'scopes' => 'openid profile email User.Read'
|
|
]);
|
|
|
|
// Exchange authorization code for access token with PKCE
|
|
$accessToken = $provider->getAccessToken('authorization_code', [
|
|
'code' => $_GET['code'],
|
|
'code_verifier' => $codeVerifier
|
|
]);
|
|
|
|
// Get user information from Microsoft Graph API
|
|
$request = $provider->getAuthenticatedRequest(
|
|
'GET',
|
|
'https://graph.microsoft.com/v1.0/me',
|
|
$accessToken->getToken()
|
|
);
|
|
|
|
$client = new \GuzzleHttp\Client();
|
|
$response = $client->send($request);
|
|
$userData = json_decode($response->getBody(), true);
|
|
|
|
// Store user information in session
|
|
$_SESSION['authenticated'] = true;
|
|
$_SESSION['user_id'] = $userData['id'];
|
|
$_SESSION['user_name'] = $userData['displayName'] ?? $userData['userPrincipalName'];
|
|
$_SESSION['user_email'] = $userData['userPrincipalName'] ?? $userData['mail'];
|
|
$_SESSION['access_token'] = $accessToken->getToken();
|
|
$_SESSION['last_activity'] = time();
|
|
|
|
// Initialize user files array for tracking uploads
|
|
$_SESSION['user_files'] = [];
|
|
|
|
// Clean up temporary session variables
|
|
unset($_SESSION['oauth2state']);
|
|
unset($_SESSION['oauth2_code_verifier']);
|
|
|
|
// Regenerate session ID for security
|
|
session_regenerate_id(true);
|
|
|
|
// Redirect to main application (clean URL, no query parameters)
|
|
header('Location: index.php');
|
|
exit;
|
|
|
|
} catch (\League\OAuth2\Client\Provider\Exception\IdentityProviderException $e) {
|
|
// Handle authentication errors
|
|
die('Authentication failed: ' . htmlspecialchars($e->getMessage()));
|
|
} catch (\Exception $e) {
|
|
// Handle other errors
|
|
die('An error occurred: ' . htmlspecialchars($e->getMessage()));
|
|
}
|
|
}
|
|
|
|
// Normal flow - require authentication
|
|
requireAuth();
|
|
|
|
// Get current user info
|
|
$user = getCurrentUser();
|
|
?>
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Voice to Text</title>
|
|
<link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;700&display=swap" rel="stylesheet">
|
|
<link rel="stylesheet" href="style.css">
|
|
<script src="https://cdn.jsdelivr.net/npm/dompurify@2.3.3/dist/purify.min.js"></script>
|
|
</head>
|
|
<body>
|
|
<div class="app-container">
|
|
<?php if (DEV_MODE): ?>
|
|
<div class="dev-mode-banner">
|
|
🔧 DEV MODE ACTIVE - Authentication Bypassed
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<div class="user-header">
|
|
<div class="user-info">
|
|
<span class="user-name"><?php echo htmlspecialchars($user['name']); ?></span>
|
|
<span class="user-email"><?php echo htmlspecialchars($user['email']); ?></span>
|
|
</div>
|
|
<a href="logout.php" class="logout-btn">Logout</a>
|
|
</div>
|
|
|
|
<img src="V2T.svg" alt="Voice to Text" class="logo">
|
|
|
|
<div id="initialInstruction" class="initial-instruction">
|
|
Before we start, select output format and upload the Voice File (Max 350 Megabytes in size)
|
|
</div>
|
|
|
|
<div class="format-selection">
|
|
<label for="outputFormat">Output Format:</label>
|
|
<select id="outputFormat" name="outputFormat">
|
|
<option value="txt">Text Document</option>
|
|
<option value="vtt">VTT (WebVTT)</option>
|
|
<option value="srt">SRT (SubRip)</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="translation-section">
|
|
<div class="translation-toggle">
|
|
<label class="toggle-label">
|
|
<input type="checkbox" id="enableTranslation" name="enableTranslation">
|
|
<span class="toggle-text">Translate with DeepL</span>
|
|
</label>
|
|
</div>
|
|
|
|
<div id="languageSelector" class="language-selector" style="display: none;">
|
|
<label for="targetLanguage">Translate to:</label>
|
|
<select id="targetLanguage" name="targetLanguage">
|
|
<option value="BG">Bulgarian</option>
|
|
<option value="CS">Czech</option>
|
|
<option value="DA">Danish</option>
|
|
<option value="DE">German</option>
|
|
<option value="EL">Greek</option>
|
|
<option value="EN-GB">English (British)</option>
|
|
<option value="EN-US" selected>English (American)</option>
|
|
<option value="ES">Spanish</option>
|
|
<option value="ET">Estonian</option>
|
|
<option value="FI">Finnish</option>
|
|
<option value="FR">French</option>
|
|
<option value="HU">Hungarian</option>
|
|
<option value="ID">Indonesian</option>
|
|
<option value="IT">Italian</option>
|
|
<option value="JA">Japanese</option>
|
|
<option value="KO">Korean</option>
|
|
<option value="LT">Lithuanian</option>
|
|
<option value="LV">Latvian</option>
|
|
<option value="NB">Norwegian (Bokmål)</option>
|
|
<option value="NL">Dutch</option>
|
|
<option value="PL">Polish</option>
|
|
<option value="PT-BR">Portuguese (Brazilian)</option>
|
|
<option value="PT-PT">Portuguese (European)</option>
|
|
<option value="RO">Romanian</option>
|
|
<option value="RU">Russian</option>
|
|
<option value="SK">Slovak</option>
|
|
<option value="SL">Slovenian</option>
|
|
<option value="SV">Swedish</option>
|
|
<option value="TR">Turkish</option>
|
|
<option value="UK">Ukrainian</option>
|
|
<option value="ZH">Chinese (simplified)</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="file-upload-container">
|
|
<label for="fileUpload" class="file-upload-label">Upload Voice File</label>
|
|
<input type="file" id="fileUpload" name="voiceFile" hidden>
|
|
</div>
|
|
|
|
<div id="chatArea" class="chat-area"></div>
|
|
|
|
<button id="downloadButton" style="display: none;">Download Response</button>
|
|
</div>
|
|
|
|
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
|
<script>
|
|
$(document).ready(function() {
|
|
// Toggle language selector when translation is enabled/disabled
|
|
$('#enableTranslation').on('change', function() {
|
|
if ($(this).is(':checked')) {
|
|
$('#languageSelector').slideDown(300);
|
|
} else {
|
|
$('#languageSelector').slideUp(300);
|
|
}
|
|
});
|
|
|
|
$('#fileUpload').on('change', function() {
|
|
var file = this.files[0];
|
|
if (file) {
|
|
var formData = new FormData();
|
|
formData.append('voiceFile', file);
|
|
formData.append('outputFormat', $('#outputFormat').val());
|
|
formData.append('enableTranslation', $('#enableTranslation').is(':checked') ? '1' : '0');
|
|
formData.append('targetLanguage', $('#targetLanguage').val());
|
|
|
|
$('#chatArea').html('<div class="processing-container"><div class="processing-text">Processing audio file...</div><div class="progress-bar"><div class="progress-bar-fill"></div></div></div>');
|
|
|
|
$.ajax({
|
|
url: 'process.php',
|
|
type: 'POST',
|
|
data: formData,
|
|
processData: false,
|
|
contentType: false,
|
|
success: function(response) {
|
|
console.log('Server response:', response);
|
|
var data = JSON.parse(response);
|
|
console.log('Parsed data:', data);
|
|
|
|
if (data.success) {
|
|
var message = '<div class="message bot-message">';
|
|
|
|
// Show original transcription (limit size for display)
|
|
if (data.response) {
|
|
message += '<div class="transcription-section">';
|
|
message += '<h3 style="color: #FFC407; margin-bottom: 10px;">Original Transcription:</h3>';
|
|
// Truncate if too long for display
|
|
var truncatedResponse = data.response;
|
|
if (truncatedResponse.length > 10000) {
|
|
truncatedResponse = data.response.substring(0, 10000) + '<div style="color: #FFC407; font-style: italic; margin-top: 10px;">... (content truncated for display, download file for full content)</div>';
|
|
}
|
|
message += truncatedResponse;
|
|
message += '</div>';
|
|
}
|
|
|
|
// Show download link for original
|
|
if (data.fileUrl) {
|
|
message += '<div style="margin-top: 15px; margin-bottom: 15px;">';
|
|
message += '<a href="' + data.fileUrl + '" class="download-link" target="_blank">📥 Download Original ' + data.format.toUpperCase() + ' File</a>';
|
|
message += '</div>';
|
|
}
|
|
|
|
// Show translated transcription (limit size for display)
|
|
if (data.translatedResponse) {
|
|
message += '<div class="transcription-section" style="margin-top: 25px; padding-top: 20px; border-top: 1px solid #333;">';
|
|
message += '<h3 style="color: #FFC407; margin-bottom: 10px;">Translated Transcription:</h3>';
|
|
// Truncate if too long for display
|
|
var truncatedTranslated = data.translatedResponse;
|
|
if (truncatedTranslated.length > 10000) {
|
|
truncatedTranslated = data.translatedResponse.substring(0, 10000) + '<div style="color: #FFC407; font-style: italic; margin-top: 10px;">... (content truncated for display, download file for full content)</div>';
|
|
}
|
|
message += truncatedTranslated;
|
|
message += '</div>';
|
|
}
|
|
|
|
// Show download link for translated
|
|
if (data.translatedFileUrl) {
|
|
message += '<div style="margin-top: 15px; margin-bottom: 15px;">';
|
|
message += '<a href="' + data.translatedFileUrl + '" class="download-link" target="_blank">📥 Download Translated ' + data.format.toUpperCase() + ' File</a>';
|
|
message += '</div>';
|
|
}
|
|
|
|
message += '</div>';
|
|
$('#chatArea').html(message);
|
|
} else {
|
|
$('#chatArea').html('<div class="message error-message">' + data.error + '</div>');
|
|
}
|
|
},
|
|
error: function(xhr, status, error) {
|
|
console.error('AJAX Error:', status, error);
|
|
console.error('Response:', xhr.responseText);
|
|
var errorMsg = '<div class="message error-message">';
|
|
errorMsg += '<strong>Error processing file:</strong><br>';
|
|
if (xhr.status === 0) {
|
|
errorMsg += 'Cannot connect to server. Make sure the Python API is running on port 5010.<br>';
|
|
errorMsg += 'Run: <code>./start_api.sh</code>';
|
|
} else {
|
|
errorMsg += 'Status: ' + xhr.status + '<br>';
|
|
errorMsg += 'Error: ' + error;
|
|
}
|
|
errorMsg += '</div>';
|
|
$('#chatArea').html(errorMsg);
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
$('#downloadButton').on('click', function() {
|
|
var responseText = $('.bot-message').text();
|
|
var blob = new Blob([responseText], { type: 'text/plain' });
|
|
var url = URL.createObjectURL(blob);
|
|
|
|
var a = document.createElement('a');
|
|
a.href = url;
|
|
a.download = 'voice_to_text_response.txt';
|
|
document.body.appendChild(a);
|
|
a.click();
|
|
document.body.removeChild(a);
|
|
URL.revokeObjectURL(url);
|
|
});
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|