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:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user