paris-space / index.html
EGuihaire's picture
Add 2 files
562b286 verified
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Transcription Audio avec GPT-4</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://unpkg.com/dropzone@5/dist/min/dropzone.min.js"></script>
<link rel="stylesheet" href="https://unpkg.com/dropzone@5/dist/min/dropzone.min.css" type="text/css" />
<style>
.dropzone {
border: 2px dashed #3b82f6;
border-radius: 0.5rem;
background: #f8fafc;
min-height: 200px;
padding: 20px;
}
.dropzone .dz-message {
font-size: 1.25rem;
color: #64748b;
}
.progress-bar {
height: 6px;
background-color: #e2e8f0;
border-radius: 3px;
margin-top: 10px;
}
.progress-bar-fill {
height: 100%;
border-radius: 3px;
background-color: #3b82f6;
width: 0%;
transition: width 0.3s ease;
}
.file-item {
transition: all 0.3s ease;
}
.file-item:hover {
transform: translateY(-2px);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
}
.loader {
border: 3px solid #f3f3f3;
border-top: 3px solid #3b82f6;
border-radius: 50%;
width: 20px;
height: 20px;
animation: spin 1s linear infinite;
display: inline-block;
vertical-align: middle;
margin-right: 8px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>
</head>
<body class="bg-gray-50 min-h-screen">
<div class="container mx-auto px-4 py-8 max-w-4xl">
<div class="text-center mb-8">
<h1 class="text-3xl font-bold text-gray-800 mb-2">Transcription Audio avec GPT-4</h1>
<p class="text-gray-600">Transcrivez vos fichiers audio en texte avec une précision exceptionnelle</p>
</div>
<div class="bg-white rounded-xl shadow-md p-6 mb-8">
<div class="mb-6">
<label for="api-key" class="block text-sm font-medium text-gray-700 mb-1">Clé API OpenAI</label>
<div class="flex">
<input type="password" id="api-key" placeholder="sk-..."
class="flex-1 px-4 py-2 border border-gray-300 rounded-l-md focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
<button id="toggle-key" class="px-4 py-2 bg-gray-100 border border-l-0 border-gray-300 rounded-r-md hover:bg-gray-200">
👁️
</button>
</div>
<p class="mt-1 text-xs text-gray-500">Votre clé API n'est jamais envoyée à nos serveurs</p>
</div>
<div class="mb-4">
<label class="block text-sm font-medium text-gray-700 mb-2">Options de transcription</label>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label for="language" class="block text-xs text-gray-500 mb-1">Langue</label>
<select id="language" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
<option value="fr">Français</option>
<option value="en">Anglais</option>
<option value="es">Espagnol</option>
<option value="de">Allemand</option>
<option value="it">Italien</option>
</select>
</div>
<div>
<label for="model" class="block text-xs text-gray-500 mb-1">Modèle</label>
<select id="model" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
<option value="whisper-1">Whisper (optimisé pour audio)</option>
<option value="gpt-4o">GPT-4 Omni (meilleure précision)</option>
</select>
</div>
</div>
</div>
<div id="dropzone" class="dropzone">
<div class="dz-message" data-dz-message>
<div class="flex flex-col items-center justify-center">
<svg class="w-12 h-12 text-blue-500 mb-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12"></path>
</svg>
<span>Glissez-déposez vos fichiers audio ici</span>
<span class="text-sm text-gray-500 mt-1">ou cliquez pour sélectionner</span>
</div>
</div>
</div>
<div class="mt-4">
<div class="flex justify-between items-center mb-2">
<span class="text-sm font-medium text-gray-700">Fichiers en attente</span>
<button id="transcribe-all" class="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed" disabled>
Transcrire tout
</button>
</div>
<div id="file-list" class="space-y-2">
<!-- Les fichiers apparaîtront ici -->
</div>
</div>
</div>
<div id="results" class="bg-white rounded-xl shadow-md p-6 hidden">
<div class="flex justify-between items-center mb-4">
<h2 class="text-xl font-semibold text-gray-800">Résultats de transcription</h2>
<button id="copy-all" class="px-3 py-1 bg-gray-100 text-gray-700 rounded-md text-sm hover:bg-gray-200">
Copier tout
</button>
</div>
<div id="transcription-results" class="space-y-4">
<!-- Les résultats apparaîtront ici -->
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Toggle API key visibility
const apiKeyInput = document.getElementById('api-key');
const toggleKeyBtn = document.getElementById('toggle-key');
toggleKeyBtn.addEventListener('click', function() {
if (apiKeyInput.type === 'password') {
apiKeyInput.type = 'text';
toggleKeyBtn.textContent = '🔒';
} else {
apiKeyInput.type = 'password';
toggleKeyBtn.textContent = '👁️';
}
});
// Initialize Dropzone
Dropzone.autoDiscover = false;
const myDropzone = new Dropzone("#dropzone", {
url: "/fake-url", // We'll handle the upload manually
paramName: "file",
maxFilesize: 50, // MB
acceptedFiles: "audio/*,video/*,.mp3,.wav,.m4a,.mp4,.ogg",
addRemoveLinks: true,
autoProcessQueue: false,
dictDefaultMessage: "",
dictFallbackMessage: "Votre navigateur ne supporte pas le glisser-déposer de fichiers.",
dictFileTooBig: "Fichier trop volumineux ({{filesize}}MB). Taille maximale: {{maxFilesize}}MB.",
dictInvalidFileType: "Type de fichier non supporté.",
dictResponseError: "Le serveur a répondu avec le code {{statusCode}}.",
dictCancelUpload: "Annuler",
dictUploadCanceled: "Téléchargement annulé.",
dictRemoveFile: "Supprimer",
dictMaxFilesExceeded: "Vous ne pouvez pas télécharger plus de fichiers."
});
const fileList = document.getElementById('file-list');
const transcribeAllBtn = document.getElementById('transcribe-all');
const resultsSection = document.getElementById('results');
const transcriptionResults = document.getElementById('transcription-results');
const copyAllBtn = document.getElementById('copy-all');
let filesToTranscribe = [];
// Handle added files
myDropzone.on("addedfile", function(file) {
filesToTranscribe.push(file);
updateFileList();
transcribeAllBtn.disabled = filesToTranscribe.length === 0;
});
// Handle removed files
myDropzone.on("removedfile", function(file) {
filesToTranscribe = filesToTranscribe.filter(f => f !== file);
updateFileList();
transcribeAllBtn.disabled = filesToTranscribe.length === 0;
// Remove from DOM if exists
const fileElement = document.getElementById(`file-${file.upload.uuid}`);
if (fileElement) {
fileElement.remove();
}
});
// Update the file list UI
function updateFileList() {
fileList.innerHTML = '';
filesToTranscribe.forEach(file => {
const fileElement = document.createElement('div');
fileElement.id = `file-${file.upload.uuid}`;
fileElement.className = 'file-item bg-gray-50 rounded-lg p-3 flex justify-between items-center';
fileElement.innerHTML = `
<div class="flex items-center">
<svg class="w-5 h-5 text-blue-500 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19V6l12-3v13M9 19c0 1.105-1.343 2-3 2s-3-.895-3-2 1.343-2 3-2 3 .895 3 2zm12-3c0 1.105-1.343 2-3 2s-3-.895-3-2 1.343-2 3-2 3 .895 3 2zM9 10l12-3"></path>
</svg>
<span class="text-sm font-medium">${file.name}</span>
<span class="text-xs text-gray-500 ml-2">(${(file.size / (1024 * 1024)).toFixed(2)} MB)</span>
</div>
<div class="flex items-center">
<button class="transcribe-btn px-3 py-1 bg-blue-600 text-white rounded-md text-sm hover:bg-blue-700 mr-2" data-file-id="${file.upload.uuid}">
Transcrire
</button>
<button class="remove-btn text-gray-500 hover:text-red-500" data-file-id="${file.upload.uuid}">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path>
</svg>
</button>
</div>
`;
fileList.appendChild(fileElement);
// Add event listeners to the buttons
fileElement.querySelector('.transcribe-btn').addEventListener('click', () => transcribeFile(file));
fileElement.querySelector('.remove-btn').addEventListener('click', () => {
myDropzone.removeFile(file);
});
});
}
// Transcribe a single file
async function transcribeFile(file) {
const apiKey = apiKeyInput.value.trim();
if (!apiKey) {
alert('Veuillez entrer votre clé API OpenAI');
return;
}
const language = document.getElementById('language').value;
const model = document.getElementById('model').value;
const fileElement = document.getElementById(`file-${file.upload.uuid}`);
if (!fileElement) return;
const transcribeBtn = fileElement.querySelector('.transcribe-btn');
transcribeBtn.disabled = true;
transcribeBtn.innerHTML = '<span class="loader"></span> Traitement...';
try {
// In a real app, you would upload the file to your server first
// Then your server would call the OpenAI API
// Here we're simulating the process with a timeout
// Simulate upload progress
let progress = 0;
const progressInterval = setInterval(() => {
progress += Math.random() * 10;
if (progress >= 90) clearInterval(progressInterval);
transcribeBtn.innerHTML = `<span class="loader"></span> Upload ${Math.min(100, Math.round(progress))}%`;
}, 300);
// Simulate API call to OpenAI
const response = await simulateOpenAICall(apiKey, file, language, model);
clearInterval(progressInterval);
transcribeBtn.innerHTML = 'Terminé ✓';
// Show results
showTranscriptionResult(file.name, response);
// Remove file from list after a delay
setTimeout(() => {
myDropzone.removeFile(file);
}, 2000);
} catch (error) {
console.error('Error:', error);
transcribeBtn.innerHTML = 'Erreur';
alert('Une erreur est survenue lors de la transcription: ' + error.message);
}
}
// Simulate OpenAI API call (replace with actual API call in production)
function simulateOpenAICall(apiKey, file, language, model) {
return new Promise((resolve) => {
setTimeout(() => {
// This is a mock response - in a real app you would call:
// const formData = new FormData();
// formData.append('file', file);
// formData.append('model', 'whisper-1');
// formData.append('language', language);
// const response = await fetch('https://api.openai.com/v1/audio/transcriptions', {
// method: 'POST',
// headers: {
// 'Authorization': `Bearer ${apiKey}`
// },
// body: formData
// });
// const data = await response.json();
// Mock data based on file name
const mockText = `Ceci est une transcription simulée du fichier ${file.name}.
Les systèmes de transcription automatique comme Whisper et GPT-4 offrent des résultats de plus en plus précis. Cette démo montre l'interface utilisateur, mais dans une application réelle, vous appelleriez l'API OpenAI pour obtenir une vraie transcription.
La qualité de la transcription dépend de plusieurs facteurs :
- La qualité de l'enregistrement audio
- La présence de bruit de fond
- La complexité du vocabulaire utilisé
- L'accent des locuteurs
Pour une meilleure précision, utilisez des fichiers audio de haute qualité et spécifiez la langue correcte.`;
resolve({ text: mockText });
}, 3000);
});
}
// Show transcription result
function showTranscriptionResult(filename, result) {
resultsSection.classList.remove('hidden');
const resultElement = document.createElement('div');
resultElement.className = 'bg-gray-50 rounded-lg p-4';
resultElement.innerHTML = `
<div class="flex justify-between items-center mb-2">
<h3 class="font-medium text-blue-600">${filename}</h3>
<button class="copy-btn px-2 py-1 bg-gray-200 text-gray-700 rounded text-xs hover:bg-gray-300 flex items-center">
<svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 012-2h2a2 2 0 012 2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3"></path>
</svg>
Copier
</button>
</div>
<div class="text-sm text-gray-700 whitespace-pre-line transcription-text">${result.text}</div>
`;
transcriptionResults.appendChild(resultElement);
// Add copy button functionality
resultElement.querySelector('.copy-btn').addEventListener('click', () => {
const textToCopy = resultElement.querySelector('.transcription-text').textContent;
navigator.clipboard.writeText(textToCopy).then(() => {
const btn = resultElement.querySelector('.copy-btn');
btn.innerHTML = '<svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path></svg> Copié!';
setTimeout(() => {
btn.innerHTML = '<svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 012-2h2a2 2 0 012 2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3"></path></svg> Copier';
}, 2000);
});
});
}
// Transcribe all files
transcribeAllBtn.addEventListener('click', async () => {
const apiKey = apiKeyInput.value.trim();
if (!apiKey) {
alert('Veuillez entrer votre clé API OpenAI');
return;
}
transcribeAllBtn.disabled = true;
transcribeAllBtn.innerHTML = '<span class="loader"></span> Traitement...';
// Process files sequentially
for (const file of [...filesToTranscribe]) {
await transcribeFile(file);
}
transcribeAllBtn.innerHTML = 'Tout transcrire';
});
// Copy all transcriptions
copyAllBtn.addEventListener('click', () => {
const allTexts = Array.from(document.querySelectorAll('.transcription-text'))
.map(el => el.textContent)
.join('\n\n');
navigator.clipboard.writeText(allTexts).then(() => {
copyAllBtn.textContent = 'Tout copié!';
setTimeout(() => {
copyAllBtn.textContent = 'Copier tout';
}, 2000);
});
});
});
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=EGuihaire/paris-space" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>