feat(phase-3): Complete AI content generation system

- Add /api/generate endpoints for AI content generation
- Integrate DeepSeek AI into dashboard compose page
- Add content templates for tips, products, services, engagement
- Implement batch generation for weekly/monthly calendars
- Add cost estimation endpoint

New endpoints:
- POST /api/generate/tip - Generate tech tips
- POST /api/generate/product - Generate product posts
- POST /api/generate/service - Generate service posts
- POST /api/generate/thread - Generate educational threads
- POST /api/generate/response - Generate response suggestions
- POST /api/generate/adapt - Adapt content to platform
- POST /api/generate/improve - Improve existing content
- POST /api/generate/batch - Batch generate for calendar
- GET /api/generate/batch/estimate - Estimate batch costs
- GET /api/generate/status - Check AI connection

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-28 01:49:49 +00:00
parent 3caf2a67fb
commit 964e38564a
6 changed files with 1376 additions and 15 deletions

View File

@@ -107,23 +107,23 @@
<!-- AI Assist -->
<div class="card p-6">
<h3 class="font-semibold mb-4">Asistente IA</h3>
<h3 class="font-semibold mb-4">Asistente IA (DeepSeek)</h3>
<div class="flex gap-2 flex-wrap">
<button type="button" onclick="generateTip()"
class="btn-secondary px-4 py-2 rounded-lg text-sm">
Generar Tip Tech
class="ai-btn btn-secondary px-4 py-2 rounded-lg text-sm flex items-center gap-2">
<span>Generar Tip Tech</span>
</button>
<button type="button" onclick="improveContent()"
class="btn-secondary px-4 py-2 rounded-lg text-sm">
Mejorar Texto
class="ai-btn btn-secondary px-4 py-2 rounded-lg text-sm flex items-center gap-2">
<span>Mejorar Texto</span>
</button>
<button type="button" onclick="adaptContent()"
class="btn-secondary px-4 py-2 rounded-lg text-sm">
Adaptar por Plataforma
class="ai-btn btn-secondary px-4 py-2 rounded-lg text-sm flex items-center gap-2">
<span>Adaptar por Plataforma</span>
</button>
</div>
<p class="text-gray-500 text-sm mt-2">
Usa IA para generar o mejorar el contenido
<p id="ai-status" class="text-gray-500 text-sm mt-2">
Verificando estado de IA...
</p>
</div>
@@ -402,17 +402,183 @@
}
});
// AI functions (placeholder - will connect to backend)
// AI functions
let aiLoading = false;
function setAiLoading(loading) {
aiLoading = loading;
document.querySelectorAll('.ai-btn').forEach(btn => {
btn.disabled = loading;
if (loading) {
btn.classList.add('opacity-50', 'cursor-wait');
} else {
btn.classList.remove('opacity-50', 'cursor-wait');
}
});
}
async function checkAiStatus() {
try {
const response = await fetch('/api/generate/status');
const data = await response.json();
return data.configured && data.status === 'connected';
} catch {
return false;
}
}
async function generateTip() {
alert('Función de generación con IA - Requiere configurar DEEPSEEK_API_KEY');
if (aiLoading) return;
// Show category selector
const categories = [
'productividad', 'seguridad', 'ia', 'programacion',
'hardware', 'redes', 'cloud', 'automatizacion', 'impresion3d', 'general'
];
const category = prompt(
'Categoría del tip:\n\n' + categories.join(', ') + '\n\n(Enter para "general")'
) || 'general';
const platform = selectedPlatforms[0] || 'x';
setAiLoading(true);
try {
const response = await fetch('/api/generate/tip', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ category, platform })
});
const data = await response.json();
if (data.success) {
document.getElementById('content').value = data.content;
updateCharCount();
} else {
alert('Error: ' + (data.error || 'No se pudo generar'));
}
} catch (error) {
alert('Error de conexión: ' + error.message);
} finally {
setAiLoading(false);
}
}
async function improveContent() {
alert('Función de mejora con IA - Requiere configurar DEEPSEEK_API_KEY');
if (aiLoading) return;
const content = document.getElementById('content').value;
if (!content.trim()) {
alert('Primero escribe algo de contenido para mejorar');
return;
}
const styles = ['engaging', 'professional', 'casual', 'educational'];
const style = prompt(
'Estilo de mejora:\n\n' +
'- engaging: Más atractivo\n' +
'- professional: Más formal\n' +
'- casual: Más cercano\n' +
'- educational: Más didáctico\n\n' +
'(Enter para "engaging")'
) || 'engaging';
const platform = selectedPlatforms[0] || 'x';
setAiLoading(true);
try {
const response = await fetch('/api/generate/improve', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ content, platform, style })
});
const data = await response.json();
if (data.success) {
document.getElementById('content').value = data.content;
updateCharCount();
} else {
alert('Error: ' + (data.error || 'No se pudo mejorar'));
}
} catch (error) {
alert('Error de conexión: ' + error.message);
} finally {
setAiLoading(false);
}
}
async function adaptContent() {
alert('Función de adaptación con IA - Requiere configurar DEEPSEEK_API_KEY');
if (aiLoading) return;
const content = document.getElementById('content').value;
if (!content.trim()) {
alert('Primero escribe algo de contenido para adaptar');
return;
}
if (selectedPlatforms.length < 2) {
alert('Selecciona al menos 2 plataformas para adaptar el contenido');
return;
}
setAiLoading(true);
try {
// Adaptar a cada plataforma excepto la primera (que es el contenido original)
const adaptedContent = { [selectedPlatforms[0]]: content };
for (let i = 1; i < selectedPlatforms.length; i++) {
const platform = selectedPlatforms[i];
const response = await fetch('/api/generate/adapt', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ content, target_platform: platform })
});
const data = await response.json();
if (data.success) {
adaptedContent[platform] = data.content;
}
}
// Mostrar resultados en preview
const previewDiv = document.getElementById('preview-content');
let html = '<p class="text-gray-400 mb-4">Contenido adaptado por plataforma:</p>';
for (const [platform, text] of Object.entries(adaptedContent)) {
html += `
<div class="bg-gray-800 rounded-lg p-4 mb-2">
<div class="font-semibold mb-2">${platform.toUpperCase()}</div>
<p class="text-gray-300 whitespace-pre-wrap text-sm">${text}</p>
<button onclick="useAdaptedContent('${platform}', this)"
class="mt-2 text-amber-500 text-sm hover:underline"
data-content="${encodeURIComponent(text)}">
Usar este
</button>
</div>
`;
}
previewDiv.innerHTML = html;
document.getElementById('preview-modal').classList.remove('hidden');
document.getElementById('preview-modal').classList.add('flex');
} catch (error) {
alert('Error de conexión: ' + error.message);
} finally {
setAiLoading(false);
}
}
function useAdaptedContent(platform, btn) {
const content = decodeURIComponent(btn.dataset.content);
document.getElementById('content').value = content;
updateCharCount();
closePreview();
}
function saveDraft() {
@@ -423,7 +589,7 @@
}
// Load draft on page load
window.addEventListener('load', () => {
window.addEventListener('load', async () => {
const draftContent = localStorage.getItem('draft_content');
const draftPlatforms = localStorage.getItem('draft_platforms');
@@ -437,6 +603,23 @@
}
updateCharCount();
// Check AI status
const aiStatus = document.getElementById('ai-status');
try {
const response = await fetch('/api/generate/status');
const data = await response.json();
if (data.configured && data.status === 'connected') {
aiStatus.innerHTML = '<span class="text-green-400">IA conectada y lista</span>';
} else if (data.configured) {
aiStatus.innerHTML = '<span class="text-yellow-400">IA configurada pero con error: ' + (data.error || 'desconocido') + '</span>';
} else {
aiStatus.innerHTML = '<span class="text-red-400">IA no configurada. Agrega DEEPSEEK_API_KEY en .env</span>';
}
} catch (error) {
aiStatus.innerHTML = '<span class="text-red-400">No se pudo verificar estado de IA</span>';
}
});
</script>
</body>