|
<!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"> |
|
|
|
<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> |
|
|
|
|
|
<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> |
|
|
|
|
|
<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> |
|
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6"> |
|
|
|
<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> |
|
|
|
|
|
<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> |
|
|
|
|
|
<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> |
|
|
|
|
|
<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> |
|
|
|
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'); |
|
|
|
|
|
const API_KEY = 'sk-43eabcf11a4448a388c02243cf6fbbdc'; |
|
|
|
|
|
let selectedModel = 'deepseek-chat'; |
|
|
|
|
|
modelOptions.forEach(option => { |
|
option.addEventListener('click', () => { |
|
|
|
modelOptions.forEach(opt => opt.classList.remove('selected')); |
|
|
|
|
|
option.classList.add('selected'); |
|
|
|
|
|
selectedModel = option.dataset.model; |
|
|
|
|
|
if (!debugPreview.classList.contains('hidden')) { |
|
selectedModelDisplay.textContent = selectedModel; |
|
} |
|
}); |
|
}); |
|
|
|
|
|
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'); |
|
} |
|
}); |
|
|
|
|
|
sourceLang.addEventListener('change', () => { |
|
const selectedOption = sourceLang.options[sourceLang.selectedIndex]; |
|
sourceLanguageDisplay.textContent = `Origem: ${selectedOption.text}`; |
|
}); |
|
|
|
|
|
swapLangs.addEventListener('click', () => { |
|
const tempLang = sourceLang.value; |
|
sourceLang.value = targetLang.value; |
|
targetLang.value = tempLang; |
|
|
|
|
|
const sourceOption = sourceLang.options[sourceLang.selectedIndex]; |
|
sourceLanguageDisplay.textContent = `Origem: ${sourceOption.text}`; |
|
|
|
|
|
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 = '-'; |
|
} |
|
}); |
|
|
|
|
|
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 = '-'; |
|
}); |
|
|
|
|
|
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 usePromptExample(promptText) { |
|
customPrompt.value = promptText; |
|
} |
|
|
|
|
|
function processPromptTemplate(template) { |
|
const sourceLanguage = sourceLang.options[sourceLang.selectedIndex].text; |
|
const targetLanguage = targetLang.options[targetLang.selectedIndex].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'; |
|
} |
|
|
|
|
|
return template |
|
.replace(/\$\{targetLanguage\}|\{targetLanguage\}/g, targetLanguage) |
|
.replace(/\$\{sourceLanguage\}|\{sourceLanguage\}/g, sourceLanguage) |
|
.replace(/\$\{formality\}|\{formality\}/g, formality); |
|
} |
|
|
|
|
|
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; |
|
|
|
|
|
let userPrompt = customPrompt.value.trim(); |
|
if (!userPrompt) { |
|
userPrompt = `Traduza este texto para {targetLanguage}:`; |
|
} |
|
|
|
|
|
const processedPrompt = processPromptTemplate(userPrompt); |
|
|
|
|
|
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; |
|
|
|
|
|
const cleanText = translatedText.replace(/^["']|["']$/g, '').trim(); |
|
|
|
|
|
targetText.innerHTML = `<div class="fade-in"><p class="whitespace-pre-line">${cleanText}</p></div>`; |
|
|
|
|
|
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'); |
|
} |
|
} |
|
|
|
|
|
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); |
|
}); |
|
|
|
|
|
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> |