Corrige geracao de quadrinhos, adiciona fallback inteligente de imagens e resiliencia de conexao com banco de dados

This commit is contained in:
2026-08-31 13:53:33 +00:00
parent a8b763bb50
commit a08c741a4f
3 changed files with 235 additions and 106 deletions
+62 -49
View File
@@ -4562,8 +4562,12 @@ Se algum dado não for mencionado, deixe a string vazia ou false para boolean.`
if (!comicsProjectSelect) return;
try {
const resp = await fetch('/api/comics/projects');
if (!resp.ok) throw new Error('Falha ao listar projetos');
if (!resp.ok) {
console.warn('Projetos de quadrinhos temporariamente indisponíveis:', resp.status);
return;
}
const projects = await resp.json();
if (!Array.isArray(projects)) return;
comicsProjectSelect.innerHTML = '<option value="">-- Carregar projeto salvo... --</option>';
projects.forEach(proj => {
@@ -4577,7 +4581,7 @@ Se algum dado não for mencionado, deixe a string vazia ou false para boolean.`
comicsProjectSelect.value = currentComicsProjectId;
}
} catch (err) {
console.error('Erro ao listar projetos:', err);
console.warn('Aviso ao listar projetos:', err.message);
}
};
@@ -4588,7 +4592,10 @@ Se algum dado não for mencionado, deixe a string vazia ou false para boolean.`
}
try {
const resp = await fetch(`/api/comics/projects/${id}`);
if (!resp.ok) throw new Error('Falha ao carregar projeto');
if (!resp.ok) {
const errMsg = await safeExtractError(resp, 'Falha ao carregar projeto');
throw new Error(errMsg);
}
const { project, panels } = await resp.json();
currentComicsProjectId = project.id;
@@ -4692,19 +4699,13 @@ Se algum dado não for mencionado, deixe a string vazia ou false para boolean.`
const caption = document.createElement('div');
caption.className = 'comic-caption-text';
caption.style.cssText = 'padding: 10px 14px; background: var(--bg-primary); border-top: 1px solid var(--border-light);';
const textarea = document.createElement('textarea');
textarea.className = 'comic-caption-input';
textarea.value = panel.dialogue || '';
textarea.placeholder = 'Digite a fala ou legenda do quadrinho...';
textarea.style.cssText = 'width: 100%; border: 1px solid var(--border-light); background: var(--bg-secondary); color: var(--text-primary); border-radius: 6px; padding: 8px; font-family: inherit; font-size: 0.85rem; resize: vertical; box-sizing: border-box; min-height: 50px;';
textarea.addEventListener('input', (e) => {
panel.dialogue = e.target.value;
if (generatedPanelsData[index]) {
generatedPanelsData[index].dialogue = e.target.value;
}
});
caption.appendChild(textarea);
@@ -4714,8 +4715,6 @@ Se algum dado não for mencionado, deixe a string vazia ou false para boolean.`
});
comicsResultArea.style.display = 'flex';
} else {
comicsResultArea.style.display = 'none';
}
comicsVideoResult.style.display = 'none';
@@ -4729,23 +4728,18 @@ Se algum dado não for mencionado, deixe a string vazia ou false para boolean.`
};
const saveComicsProjectFlow = async (forceNew = false) => {
const titulo = comicsProjectTitleInput ? comicsProjectTitleInput.value.trim() : '';
let titulo = comicsProjectTitleInput ? comicsProjectTitleInput.value.trim() : '';
if (!titulo) {
await showCustomAlert('Campo Obrigatório', 'Por favor, digite um título para salvar seu projeto.');
return;
titulo = comicsResultTitle ? comicsResultTitle.textContent.trim() : 'Minha História em Quadrinhos';
}
const characters = [];
document.querySelectorAll('.comic-char-row').forEach(row => {
const typeInput = row.querySelector('.char-type-input');
const nameInput = row.querySelector('.char-name-input');
if (typeInput && nameInput) {
const type = typeInput.value.trim();
const name = nameInput.value.trim();
const isAnimal = row.querySelector('span').textContent === '🐾';
if (type || name) {
characters.push({ type, name, isAnimal });
}
const type = row.querySelector('.char-type-input').value.trim();
const name = row.querySelector('.char-name-input').value.trim();
const isAnimal = row.querySelector('.comic-char-animal-toggle').classList.contains('active');
if (type && name) {
characters.push({ type, name, isAnimal });
}
});
@@ -4780,8 +4774,8 @@ Se algum dado não for mencionado, deixe a string vazia ou false para boolean.`
});
if (!resp.ok) {
const err = await resp.json();
throw new Error(err.error || 'Erro ao salvar projeto');
const errMsg = await safeExtractError(resp, 'Erro ao salvar projeto');
throw new Error(errMsg);
}
const data = await resp.json();
@@ -4814,7 +4808,10 @@ Se algum dado não for mencionado, deixe a string vazia ou false para boolean.`
method: 'DELETE'
});
if (!resp.ok) throw new Error('Falha ao excluir projeto');
if (!resp.ok) {
const errMsg = await safeExtractError(resp, 'Falha ao excluir projeto');
throw new Error(errMsg);
}
await showCustomAlert('Excluído', 'Projeto excluído com sucesso!');
startNewComicsProject();
@@ -4900,6 +4897,21 @@ Se algum dado não for mencionado, deixe a string vazia ou false para boolean.`
});
};
// Helper para execução concorrente controlada
const mapConcurrent = async (items, limit, fn) => {
const results = new Array(items.length);
let currentIdx = 0;
const worker = async () => {
while (currentIdx < items.length) {
const i = currentIdx++;
results[i] = await fn(items[i], i);
}
};
const workers = Array.from({ length: Math.min(limit, items.length) }, () => worker());
await Promise.all(workers);
return results;
};
// Ação de geração da história e imagens
if (btnGenerateComics) {
btnGenerateComics.addEventListener('click', async () => {
@@ -4968,44 +4980,48 @@ Se algum dado não for mencionado, deixe a string vazia ou false para boolean.`
});
if (!scriptResp.ok) {
const err = await scriptResp.json();
throw new Error(err.error || 'Erro ao planejar roteiro');
const errMsg = await safeExtractError(scriptResp, 'Erro ao planejar roteiro');
throw new Error(errMsg);
}
const scriptData = await scriptResp.json();
comicsLoaderProgress.style.width = '15%';
comicsLoaderProgress.style.width = '20%';
if (scriptData.character_description) {
currentComicsCharDescEnglish = scriptData.character_description;
}
// 2. Gerar quadrinhos em PARALELO com tracking de progresso em tempo real
// 2. Gerar quadrinhos com concorrência balanceada (2 por vez) e tracking em tempo real
if (genMode !== 'extend') {
generatedPanelsData = [];
}
const totalPanels = scriptData.panels.length;
let completedCount = 0;
const panelPromises = scriptData.panels.map(async (panel, idx) => {
// Injetamos a proporção e estilo de forma reforçada no prompt
const generateSinglePanel = async (panel) => {
const fullPrompt = `${panel.image_prompt}, high resolution children book illustration, cute Pixar style, ${selectedComicsRatio === '16:9' ? '16:9 aspect ratio' : '4:3 aspect ratio'}`;
const frameResp = await fetch('/api/comics/generate-frame', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ prompt: fullPrompt })
});
let frameResp;
try {
frameResp = await fetch('/api/comics/generate-frame', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ prompt: fullPrompt, proporcao: selectedComicsRatio })
});
} catch (netErr) {
throw new Error(`Falha de conexão no quadro ${panel.panel_number}: ${netErr.message}`);
}
if (!frameResp.ok) {
const err = await frameResp.json();
throw new Error(`Erro no quadrinho ${panel.panel_number}: ${err.error}`);
const errMsg = await safeExtractError(frameResp, 'Erro ao ilustrar quadro');
throw new Error(`Erro no quadrinho ${panel.panel_number}: ${errMsg}`);
}
const frameData = await frameResp.json();
completedCount++;
const progressPercent = 15 + Math.floor((completedCount / totalPanels) * 80);
const progressPercent = 20 + Math.floor((completedCount / totalPanels) * 75);
if (comicsLoaderProgress) comicsLoaderProgress.style.width = `${progressPercent}%`;
if (comicsLoaderText) comicsLoaderText.textContent = ` Ilustrando em paralelo: ${completedCount}/${totalPanels} quadros concluídos...`;
if (comicsLoaderText) comicsLoaderText.textContent = `🎨 Ilustrando quadrinhos: ${completedCount}/${totalPanels} quadros concluídos...`;
return {
panel_number: panel.panel_number,
@@ -5013,9 +5029,10 @@ Se algum dado não for mencionado, deixe a string vazia ou false para boolean.`
dialogue: panel.dialogue || '',
image_prompt: panel.image_prompt || ''
};
});
};
const newPanels = await Promise.all(panelPromises);
// Executa com no máximo 2 quadros em paralelo para estabilidade máxima e resposta fluida
const newPanels = await mapConcurrent(scriptData.panels, 2, generateSinglePanel);
// Ordena por panel_number para garantir a sequência correta
newPanels.sort((a, b) => a.panel_number - b.panel_number);
@@ -5028,10 +5045,6 @@ Se algum dado não for mencionado, deixe a string vazia ou false para boolean.`
// Atualizar cabeçalho e metadados
comicsResultTitle.textContent = scriptData.title || comicsResultTitle.textContent || 'Fábrica de Quadrinhos Pedagog';
const metaPanels = document.getElementById('comicsMetaPanels');
if (metaPanels) metaPanels.textContent = `🖼️ ${generatedPanelsData.length} Quadros`;
const metaRatio = document.getElementById('comicsMetaRatio');
if (metaRatio) metaRatio.textContent = `📐 ${selectedComicsRatio} ${selectedComicsRatio === '16:9' ? 'HD' : 'Retrô'}`;
// Renderizar Storyboard Interativo
renderComicsStoryboard();
@@ -5204,8 +5217,8 @@ Se algum dado não for mencionado, deixe a string vazia ou false para boolean.`
});
if (!resp.ok) {
const err = await resp.json();
throw new Error(err.error || 'Erro na regeneração');
const errMsg = await safeExtractError(resp, 'Erro na regeneração');
throw new Error(errMsg);
}
const data = await resp.json();