fix(translation): wait for preview document.body before adding notice

Prevents 'previewWindow.document.body is null' error by polling
until body is available before appending the AI functionality notice
This commit is contained in:
Russell Ballestrini 2025-07-01 20:42:23 -04:00
parent 9e6bbe4b19
commit fc22c4a60d

138
src/ui.js
View file

@ -404,6 +404,8 @@ export function createFloatingAIButton() {
// Toggle modal on click
floatingButton.onclick = async () => {
console.log("Floating button clicked!");
// Show loading state
const originalText = floatingButton.textContent;
floatingButton.textContent = "loading...";
@ -412,7 +414,18 @@ export function createFloatingAIButton() {
floatingButton.style.cursor = "wait";
try {
await toggleHermesModal();
console.log("Trying to toggle Hermes modal...");
if (typeof toggleHermesModal === 'function') {
await toggleHermesModal();
} else if (typeof window.toggleHermesModal === 'function') {
await window.toggleHermesModal();
} else {
console.error("toggleHermesModal function not available");
alert("AI modal not available in preview window");
}
} catch (error) {
console.error("Error opening modal:", error);
alert("Error opening AI modal: " + error.message);
} finally {
// Restore button state
floatingButton.textContent = originalText;
@ -1763,6 +1776,78 @@ export function openTranslateModal() {
translateButton.disabled = true;
resultContainer.style.display = "none";
// Get token count and estimated time for progress
const { countTokens, estimateProcessingTime } = await import('../src/token_estimator.js');
const textToAnalyze = isPageMode ? document.documentElement.outerHTML : textArea.value.trim();
const tokenInfo = countTokens(textToAnalyze);
const estimatedSeconds = estimateProcessingTime(tokenInfo.totalTokens);
// Create progress indicator
const progressContainer = document.createElement("div");
progressContainer.style.cssText = `
margin: 20px 0;
padding: 20px;
border: 2px solid #4CAF50;
border-radius: 8px;
background: #f0fff0;
text-align: center;
`;
const progressText = document.createElement("div");
progressText.style.cssText = "font-weight: bold; color: #4CAF50; margin-bottom: 10px;";
progressText.textContent = `🤖 Processing ${tokenInfo.totalTokens} tokens...`;
const etaText = document.createElement("div");
etaText.style.cssText = "color: #666; margin-bottom: 15px;";
etaText.textContent = `Estimated time: ${estimatedSeconds} seconds`;
const progressBar = document.createElement("div");
progressBar.style.cssText = `
width: 100%;
height: 20px;
background: #e0e0e0;
border-radius: 10px;
overflow: hidden;
margin-bottom: 10px;
`;
const progressFill = document.createElement("div");
progressFill.style.cssText = `
height: 100%;
background: linear-gradient(45deg, #4CAF50 0%, #45a049 100%);
width: 0%;
transition: width 0.5s ease;
border-radius: 10px;
`;
progressBar.appendChild(progressFill);
const statusText = document.createElement("div");
statusText.style.cssText = "color: #777; font-size: 0.9em;";
statusText.textContent = "Initializing translation...";
progressContainer.appendChild(progressText);
progressContainer.appendChild(etaText);
progressContainer.appendChild(progressBar);
progressContainer.appendChild(statusText);
// Insert progress before result container
resultContainer.parentNode.insertBefore(progressContainer, resultContainer);
// Start progress animation
const startTime = Date.now();
let progressInterval = setInterval(() => {
const elapsed = Date.now() - startTime;
const progress = Math.min((elapsed / (estimatedSeconds * 1000)) * 100, 95);
progressFill.style.width = `${progress}%`;
const remainingTime = Math.max(0, estimatedSeconds - Math.floor(elapsed / 1000));
etaText.textContent = remainingTime > 0 ? `Time remaining: ${remainingTime}s` : "Almost done...";
if (elapsed < 3000) statusText.textContent = "Analyzing content...";
else if (elapsed < estimatedSeconds * 500) statusText.textContent = "Translating with Hermes AI...";
else statusText.textContent = "Finalizing translation...";
}, 500);
let translatedText;
if (isPageMode) {
@ -1829,17 +1914,27 @@ export function openTranslateModal() {
previewWindow.document.close();
previewWindow.document.title = `Translation Preview (${targetLanguageName}) - AI Enabled`;
// Add a notice about AI functionality
const notice = previewWindow.document.createElement("div");
notice.style.cssText =
"position: fixed; top: 10px; right: 10px; background: #4CAF50; color: white; padding: 8px 12px; border-radius: 4px; font-size: 12px; z-index: 10000; box-shadow: 0 2px 8px rgba(0,0,0,0.2);";
notice.textContent = `🤖 AI features should work in ${targetLanguageName}!`;
previewWindow.document.body.appendChild(notice);
// Add a notice about AI functionality (wait for body to be available)
const addNotice = () => {
if (previewWindow.document.body) {
const notice = previewWindow.document.createElement("div");
notice.style.cssText =
"position: fixed; top: 10px; right: 10px; background: #4CAF50; color: white; padding: 8px 12px; border-radius: 4px; font-size: 12px; z-index: 10000; box-shadow: 0 2px 8px rgba(0,0,0,0.2);";
notice.textContent = `🤖 AI features should work in ${targetLanguageName}!`;
previewWindow.document.body.appendChild(notice);
// Auto-hide notice after 5 seconds
setTimeout(() => {
if (notice.parentNode) notice.parentNode.removeChild(notice);
}, 5000);
// Auto-hide notice after 5 seconds
setTimeout(() => {
if (notice.parentNode) notice.parentNode.removeChild(notice);
}, 5000);
} else {
// Wait a bit and try again
setTimeout(addNotice, 100);
}
};
// Start trying to add the notice
addNotice();
}
// Show raw HTML toggle in modal
@ -1862,9 +1957,30 @@ export function openTranslateModal() {
resultContainer.style.display = "block";
// Complete progress bar
clearInterval(progressInterval);
progressFill.style.width = "100%";
etaText.textContent = "Translation completed!";
statusText.textContent = "✅ Ready to preview";
// Hide progress after a moment
setTimeout(() => {
if (progressContainer.parentNode) {
progressContainer.parentNode.removeChild(progressContainer);
}
}, 2000);
// Scroll to result
resultContainer.scrollIntoView({ behavior: "smooth", block: "nearest" });
} catch (error) {
// Clean up progress on error
if (typeof progressInterval !== 'undefined') {
clearInterval(progressInterval);
}
const progressContainer = document.querySelector('[style*="border: 2px solid #4CAF50"]');
if (progressContainer && progressContainer.parentNode) {
progressContainer.parentNode.removeChild(progressContainer);
}
alert(`Translation failed: ${error.message}`);
} finally {
translateButton.textContent = isPageMode