tradutor / index.html
lufespi's picture
Add 2 files
f34f1dc verified
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>O TRADUTOR</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<script>
tailwind.config = {
theme: {
extend: {
colors: {
primary: '#3A86FF',
secondary: '#003566',
dark: '#001D3D',
light: '#F8F9FA',
accent: '#FFC300'
},
fontFamily: {
sans: ['Inter', 'sans-serif'],
},
boxShadow: {
'soft': '0 4px 24px rgba(0, 0, 0, 0.05)',
'float': '0 8px 32px rgba(58, 134, 255, 0.15)'
}
}
}
}
</script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
body {
font-family: 'Inter', sans-serif;
background-color: #F8F9FA;
color: #001D3D;
}
.text-area {
min-height: 200px;
resize: none;
transition: all 0.2s ease;
}
.language-selector {
transition: all 0.2s ease;
}
.translate-btn {
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.translate-btn:hover {
transform: translateY(-1px);
}
.translate-btn:active {
transform: translateY(1px);
}
.fade-in {
animation: fadeIn 0.3s ease forwards;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
.glass-effect {
background: rgba(255, 255, 255, 0.85);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
}
.prompt-example {
transition: all 0.2s ease;
cursor: pointer;
}
.prompt-example:hover {
background-color: rgba(58, 134, 255, 0.1);
}
.placeholder-info {
background-color: rgba(58, 134, 255, 0.05);
border-left: 3px solid #3A86FF;
}
.model-selector {
transition: all 0.2s ease;
}
.model-option {
transition: all 0.2s ease;
cursor: pointer;
}
.model-option:hover {
background-color: rgba(58, 134, 255, 0.05);
}
.model-option.selected {
background-color: rgba(58, 134, 255, 0.1);
border-color: #3A86FF;
}
</style>
</head>
<body class="min-h-screen">
<header class="sticky top-0 z-10 glass-effect shadow-sm">
<div class="container mx-auto px-4 py-5 flex justify-center">
<h1 class="text-3xl font-bold bg-gradient-to-r from-primary to-accent bg-clip-text text-transparent">O TRADUTOR</h1>
</div>
</header>
<main class="container mx-auto px-4 py-8 max-w-6xl">
<!-- Model Selection -->
<div class="bg-white rounded-2xl shadow-soft p-5 mb-6">
<h2 class="text-lg font-semibold text-gray-800 mb-4">Modelo de IA</h2>
<div class="flex flex-col sm:flex-row gap-4">
<div class="model-option selected w-full sm:w-auto border-2 border-gray-200 rounded-xl p-4 model-selector" data-model="deepseek-chat">
<h3 class="font-medium text-gray-800 mb-1 flex items-center gap-2">
<i class="fas fa-comment-alt text-primary"></i> DeepSeek Chat
</h3>
<p class="text-xs text-gray-500">Recomendado para traduções gerais e conversacionais</p>
</div>
<div class="model-option w-full sm:w-auto border-2 border-gray-200 rounded-xl p-4 model-selector" data-model="deepseek-reasoner">
<h3 class="font-medium text-gray-800 mb-1 flex items-center gap-2">
<i class="fas fa-brain text-primary"></i> DeepSeek Reasoner
</h3>
<p class="text-xs text-gray-500">Ideal para textos técnicos e lógica complexa</p>
</div>
</div>
</div>
<!-- Language Selection -->
<div class="flex flex-col md:flex-row items-center justify-center mb-8 gap-4">
<div class="w-full md:w-auto bg-white p-5 rounded-2xl shadow-soft language-selector">
<div class="flex flex-col md:flex-row items-center gap-4">
<div class="w-full md:w-48">
<label class="block text-sm font-medium text-gray-700 mb-2">Idioma de origem</label>
<select id="sourceLang" class="w-full p-3 rounded-xl border border-gray-200 focus:ring-primary focus:border-primary">
<option value="Brazilian Portuguese" selected>Português Brasileiro</option>
<option value="English">Inglês</option>
<option value="Spanish">Espanhol</option>
<option value="French">Francês</option>
<option value="German">Alemão</option>
</select>
</div>
<button id="swapLangs" class="p-3 rounded-full bg-gray-100 hover:bg-gray-200 transition text-gray-700">
<i class="fas fa-arrow-right-arrow-left"></i>
</button>
<div class="w-full md:w-48">
<label class="block text-sm font-medium text-gray-700 mb-2">Idioma de destino</label>
<select id="targetLang" class="w-full p-3 rounded-xl border border-gray-200 focus:ring-primary focus:border-primary">
<option value="English" selected>Inglês</option>
<option value="Brazilian Portuguese">Português Brasileiro</option>
<option value="Spanish">Espanhol</option>
<option value="French">Francês</option>
<option value="German">Alemão</option>
</select>
</div>
</div>
</div>
</div>
<!-- Prompt Configuration -->
<div class="bg-white rounded-2xl shadow-soft p-6 mb-6">
<h2 class="text-lg font-semibold text-gray-800 mb-4">Configuração do Prompt</h2>
<textarea id="customPrompt" class="w-full p-4 border border-gray-200 rounded-xl focus:ring-primary focus:border-primary" placeholder="Escreva seu prompt de tradução. Você pode usar {targetLanguage}, {sourceLanguage} ou {formality} como placeholders"></textarea>
<div class="placeholder-info p-4 rounded-lg mt-4">
<h3 class="text-sm font-medium text-primary mb-2 flex items-center gap-2">
<i class="fas fa-info-circle"></i> Placeholders disponíveis:
</h3>
<div class="grid grid-cols-1 md:grid-cols-3 gap-2 text-sm">
<div class="p-2 rounded bg-gray-50">
<span class="font-mono bg-gray-200 px-1 rounded">{targetLanguage}</span> → Idioma de destino
</div>
<div class="p-2 rounded bg-gray-50">
<span class="font-mono bg-gray-200 px-1 rounded">{sourceLanguage}</span> → Idioma de origem
</div>
<div class="p-2 rounded bg-gray-50">
<span class="font-mono bg-gray-200 px-1 rounded">{formality}</span> → Tom (formal/informal)
</div>
</div>
</div>
<div class="mt-4">
<h3 class="text-sm font-medium text-gray-700 mb-2">Exemplos de prompt:</h3>
<div class="grid grid-cols-1 md:grid-cols-3 gap-2">
<div class="prompt-example bg-gray-50 p-3 rounded-lg border border-gray-200" onclick="usePromptExample('Traduza este texto para {targetLanguage}, mantendo um tom profissional:')">
<p class="text-sm font-medium">Profissional</p>
<p class="text-xs text-gray-500 truncate">"{targetLanguage}" será substituído</p>
</div>
<div class="prompt-example bg-gray-50 p-3 rounded-lg border border-gray-200" onclick="usePromptExample('Converta de {sourceLanguage} para {targetLanguage} de forma casual:')">
<p class="text-sm font-medium">Conversão</p>
<p class="text-xs text-gray-500 truncate">Usa ambos os placeholders</p>
</div>
<div class="prompt-example bg-gray-50 p-3 rounded-lg border border-gray-200" onclick="usePromptExample('Traduza como se fosse um poema em {targetLanguage}, mantendo o significado:')">
<p class="text-sm font-medium">Poético</p>
<p class="text-xs text-gray-500 truncate">Estilo específico</p>
</div>
</div>
</div>
</div>
<!-- Translation Area -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6">
<!-- Source Text -->
<div class="bg-white rounded-2xl shadow-soft overflow-hidden">
<div class="p-4 border-b">
<div class="flex justify-between items-center">
<span id="sourceLanguageDisplay" class="text-sm text-gray-700">Origem: Português</span>
<button id="clearSource" class="text-gray-400 hover:text-primary transition">
<i class="fas fa-trash-alt"></i> Limpar
</button>
</div>
</div>
<textarea id="sourceText" class="w-full p-6 text-area focus:outline-none text-gray-800 placeholder-gray-400" placeholder="Digite o texto aqui..."></textarea>
<div class="p-4 border-t flex justify-between items-center">
<span class="text-xs text-gray-500"><span id="charCount">0</span>/5000</span>
<button id="translateBtn" class="translate-btn bg-primary hover:shadow-float text-white px-6 py-3 rounded-xl font-medium flex items-center gap-2">
<i class="fas fa-language"></i> Traduzir
</button>
</div>
</div>
<!-- Target Text -->
<div class="bg-white rounded-2xl shadow-soft overflow-hidden">
<div class="p-4 border-b flex justify-between items-center">
<span class="font-medium">Tradução</span>
<button id="copyTranslation" class="text-gray-400 hover:text-primary transition" title="Copiar tradução">
<i class="fas fa-copy"></i> Copiar
</button>
</div>
<div id="targetText" class="w-full p-6 min-h-[200px] text-gray-800">
<div class="flex items-center justify-center h-full text-gray-400">
<p>A tradução aparecerá aqui</p>
</div>
</div>
<div class="p-4 border-t">
<span id="translationInfo" class="text-xs text-gray-500">-</span>
</div>
</div>
</div>
<!-- Loading Indicator -->
<div id="loadingIndicator" class="hidden text-center py-8">
<div class="inline-flex flex-col items-center">
<div class="w-12 h-12 border-4 border-primary border-t-transparent rounded-full animate-spin mb-3"></div>
<p class="text-gray-600">Traduzindo...</p>
</div>
</div>
<!-- Debug Preview (hidden by default) -->
<div id="debugPreview" class="hidden bg-gray-50 rounded-lg p-4 mt-6">
<h3 class="text-sm font-medium text-gray-700 mb-2">Pré-visualização do prompt:</h3>
<pre id="processedPromptPreview" class="whitespace-pre-wrap p-3 bg-white rounded border border-gray-200 text-sm"></pre>
<div class="mt-2">
<span class="text-xs text-gray-500">Modelo selecionado: <span id="selectedModelDisplay" class="font-mono">deepseek-chat</span></span>
</div>
</div>
</main>
<footer class="bg-dark text-white py-8 mt-12">
<div class="container mx-auto px-4 text-center">
<p class="text-sm text-gray-300">© 2025 O TRADUTOR. Todos os direitos reservados.</p>
</div>
</footer>
<script>
// DOM Elements
const sourceText = document.getElementById('sourceText');
const targetText = document.getElementById('targetText');
const sourceLang = document.getElementById('sourceLang');
const targetLang = document.getElementById('targetLang');
const swapLangs = document.getElementById('swapLangs');
const translateBtn = document.getElementById('translateBtn');
const clearSource = document.getElementById('clearSource');
const copyTranslation = document.getElementById('copyTranslation');
const charCount = document.getElementById('charCount');
const sourceLanguageDisplay = document.getElementById('sourceLanguageDisplay');
const translationInfo = document.getElementById('translationInfo');
const loadingIndicator = document.getElementById('loadingIndicator');
const customPrompt = document.getElementById('customPrompt');
const debugPreview = document.getElementById('debugPreview');
const processedPromptPreview = document.getElementById('processedPromptPreview');
const selectedModelDisplay = document.getElementById('selectedModelDisplay');
const modelOptions = document.querySelectorAll('.model-option');
// API key - Note: In a real app, this should be handled server-side
const API_KEY = 'sk-43eabcf11a4448a388c02243cf6fbbdc';
// Current selected model (defaults to deepseek-chat)
let selectedModel = 'deepseek-chat';
// Model selection
modelOptions.forEach(option => {
option.addEventListener('click', () => {
// Remove selected class from all options
modelOptions.forEach(opt => opt.classList.remove('selected'));
// Add selected class to clicked option
option.classList.add('selected');
// Update selected model
selectedModel = option.dataset.model;
// Update debug display
if (!debugPreview.classList.contains('hidden')) {
selectedModelDisplay.textContent = selectedModel;
}
});
});
// Update character count
sourceText.addEventListener('input', () => {
const count = sourceText.value.length;
charCount.textContent = count;
if (count > 5000) {
charCount.classList.add('text-red-500');
} else {
charCount.classList.remove('text-red-500');
}
});
// Update source language display when changed
sourceLang.addEventListener('change', () => {
const selectedOption = sourceLang.options[sourceLang.selectedIndex];
sourceLanguageDisplay.textContent = `Origem: ${selectedOption.text}`;
});
// Swap languages
swapLangs.addEventListener('click', () => {
const tempLang = sourceLang.value;
sourceLang.value = targetLang.value;
targetLang.value = tempLang;
// Update displays
const sourceOption = sourceLang.options[sourceLang.selectedIndex];
sourceLanguageDisplay.textContent = `Origem: ${sourceOption.text}`;
// Also swap text if there's content
if (targetText.textContent.trim() !== 'A tradução aparecerá aqui' && targetText.textContent.trim() !== '') {
const tempText = sourceText.value;
sourceText.value = targetText.textContent;
targetText.innerHTML = `<div class="flex items-center justify-center h-full text-gray-400">
<p>A tradução aparecerá aqui</p>
</div>`;
translationInfo.textContent = '-';
}
});
// Clear source text
clearSource.addEventListener('click', () => {
sourceText.value = '';
charCount.textContent = '0';
targetText.innerHTML = `<div class="flex items-center justify-center h-full text-gray-400">
<p>A tradução aparecerá aqui</p>
</div>`;
translationInfo.textContent = '-';
});
// Copy translation
copyTranslation.addEventListener('click', () => {
if (targetText.textContent.trim() !== 'A tradução aparecerá aqui' && targetText.textContent.trim() !== '') {
navigator.clipboard.writeText(targetText.textContent.trim()).then(() => {
const originalText = copyTranslation.innerHTML;
copyTranslation.innerHTML = '<i class="fas fa-check"></i> Copiado!';
copyTranslation.classList.add('text-green-500');
setTimeout(() => {
copyTranslation.innerHTML = originalText;
copyTranslation.classList.remove('text-green-500');
}, 2000);
});
}
});
// Function to use prompt example
function usePromptExample(promptText) {
customPrompt.value = promptText;
}
// Function to process the prompt template
function processPromptTemplate(template) {
const sourceLanguage = sourceLang.options[sourceLang.selectedIndex].text;
const targetLanguage = targetLang.options[targetLang.selectedIndex].text;
// Detect formality from prompt text
let formality = 'neutro';
if (template.toLowerCase().includes('formal') || template.toLowerCase().includes('profissional')) {
formality = 'formal';
} else if (template.toLowerCase().includes('casual') || template.toLowerCase().includes('coloquial') || template.toLowerCase().includes('informal')) {
formality = 'informal';
}
// Replace placeholders - supports both {xxx} and ${xxx} formats
return template
.replace(/\$\{targetLanguage\}|\{targetLanguage\}/g, targetLanguage)
.replace(/\$\{sourceLanguage\}|\{sourceLanguage\}/g, sourceLanguage)
.replace(/\$\{formality\}|\{formality\}/g, formality);
}
// Translate function
async function translateText(text, sourceLanguage, targetLanguage) {
if (!text.trim()) return;
loadingIndicator.classList.remove('hidden');
targetText.innerHTML = '<p class="text-gray-400 italic">Traduzindo...</p>';
try {
const sourceLanguageName = sourceLang.options[sourceLang.selectedIndex].text;
const targetLanguageName = targetLang.options[targetLang.selectedIndex].text;
// Get the user's custom prompt or use a default one
let userPrompt = customPrompt.value.trim();
if (!userPrompt) {
userPrompt = `Traduza este texto para {targetLanguage}:`;
}
// Process the template to replace placeholders
const processedPrompt = processPromptTemplate(userPrompt);
// Show debug preview (for testing)
debugPreview.classList.remove('hidden');
processedPromptPreview.textContent = processedPrompt + '\n\n' + text;
selectedModelDisplay.textContent = selectedModel;
const response = await fetch('https://api.deepseek.com/v1/chat/completions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${API_KEY}`
},
body: JSON.stringify({
model: selectedModel,
messages: [
{
role: 'user',
content: processedPrompt + '\n\n' + text
}
],
temperature: 0.3,
max_tokens: 2000
})
});
if (!response.ok) {
throw new Error(`API request failed with status ${response.status}`);
}
const data = await response.json();
if (data.choices && data.choices.length > 0) {
let translatedText = data.choices[0].message.content;
// Remove any extra formatting or quotes that might come with the response
const cleanText = translatedText.replace(/^["']|["']$/g, '').trim();
// Create a fade-in effect for the translation
targetText.innerHTML = `<div class="fade-in"><p class="whitespace-pre-line">${cleanText}</p></div>`;
// Update translation info
translationInfo.textContent = `Traduzido de ${sourceLanguageName} para ${targetLanguageName} (usando ${selectedModel})`;
} else {
throw new Error(data.error?.message || 'Falha na tradução - sem dados de resposta');
}
} catch (error) {
console.error('Erro na tradução:', error);
targetText.innerHTML = `<p class="text-red-500">Erro: ${error.message || 'Falha ao traduzir'}</p>`;
translationInfo.textContent = 'Falha na tradução';
} finally {
loadingIndicator.classList.add('hidden');
}
}
// Translate button click handler
translateBtn.addEventListener('click', () => {
if (sourceText.value.trim() === '') return;
if (sourceText.value.length > 5000) {
alert('O texto excede o limite máximo de 5000 caracteres');
return;
}
const sourceLanguage = sourceLang.value;
const targetLanguage = targetLang.value;
translateText(sourceText.value, sourceLanguage, targetLanguage);
});
// Translate on Ctrl+Enter
sourceText.addEventListener('keydown', (e) => {
if (e.ctrlKey && e.key === 'Enter') {
e.preventDefault();
translateBtn.click();
}
});
</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=lufespi/tradutor" style="color: #fff;text-decoration: underline;" target="_blank" >🧬 Remix</a></p></body>
</html>