// js/ui/ui-processing.js // 文件列表展示与处理进度相关的 UI 逻辑。 (function(global) { 'use strict'; const fileListContainer = document.getElementById('fileListContainer'); const fileList = document.getElementById('fileList'); const downloadAllBtn = document.getElementById('downloadAllBtn'); const processBtn = document.getElementById('processBtn'); const resultsSection = document.getElementById('resultsSection'); const resultsSummary = document.getElementById('resultsSummary'); const progressSection = document.getElementById('progressSection'); const batchProgressText = document.getElementById('batchProgressText'); const concurrentProgressText = document.getElementById('concurrentProgressText'); const progressStep = document.getElementById('progressStep'); const progressPercentage = document.getElementById('progressPercentage'); const progressBar = document.getElementById('progressBar'); const progressLog = document.getElementById('progressLog'); // 使用事件委托绑定验证刷新按钮 document.addEventListener('click', (e) => { const refreshBtn = e.target.closest('#validationRefreshBtn'); if (refreshBtn) { console.log('[Validation] Refresh button clicked'); e.preventDefault(); e.stopPropagation(); if (typeof window.refreshValidationState === 'function') { window.refreshValidationState(); } else { console.warn('[Validation] window.refreshValidationState not available'); } } }); function updateFileListUI(pdfFiles, isProcessing, onRemoveFile) { if (!fileList || !fileListContainer) return; fileList.innerHTML = ''; if (pdfFiles.length > 0) { fileListContainer.classList.remove('hidden'); pdfFiles.forEach((file, index) => { const displayPath = global.getFileDisplayPath(file); const displayName = (displayPath.split('/').pop() || file.name || '').trim() || file.name; const listItem = document.createElement('div'); listItem.className = 'file-list-item'; let virtualBadge = ''; const vType = (file && file.virtualType) ? String(file.virtualType) : ''; const nameLower = (file && file.name) ? file.name.toLowerCase() : ''; const isRetranslate = vType === 'retranslate' || /-retranslate-/.test(nameLower); const isRetryFailed = vType === 'retry-failed' || /-retry-failed-/.test(nameLower); if (isRetranslate) { virtualBadge = '重译'; } else if (isRetryFailed) { virtualBadge = '失败重试'; } const extSource = displayName || file.name || ''; const ext = (extSource.split('.').pop() || '').toLowerCase(); const icon = ext === 'pdf' ? 'carbon:document-pdf' : 'carbon:document'; const iconColor = ext === 'pdf' ? 'text-red-500' : 'text-gray-500'; const isExcluded = typeof global.isExtensionExcluded === 'function' ? global.isExtensionExcluded(ext) : false; listItem.innerHTML = `
${displayName} ${displayPath && displayPath !== displayName ? `${displayPath}` : ''} ${virtualBadge} ${isExcluded ? '已排除' : ''} (${global.formatFileSize(file.size)})
`; if (isExcluded) { listItem.classList.add('opacity-60'); } fileList.appendChild(listItem); }); document.querySelectorAll('.remove-file-btn').forEach(button => { button.addEventListener('click', (e) => { if (isProcessing) return; const indexToRemove = parseInt(e.currentTarget.getAttribute('data-index')); onRemoveFile(indexToRemove); }); }); // 添加预览按钮事件监听器 document.querySelectorAll('.preview-file-btn').forEach(button => { button.addEventListener('click', (e) => { const fileIndex = parseInt(e.currentTarget.getAttribute('data-index')); const fileToPreview = pdfFiles[fileIndex]; if (fileToPreview) { previewFile(fileToPreview); } }); }); if (pdfFiles.length === 1) { global.data = { name: pdfFiles[0].name, ocr: '', translation: '', images: [], summaries: {} }; } else if (pdfFiles.length === 0) { global.data = {}; } else { global.data = { summaries: {} }; } console.log('刷新文件列表:', pdfFiles.map(f => f.name)); } else { fileListContainer.classList.add('hidden'); global.data = {}; } if (typeof global.syncBatchModeControls === 'function') { global.syncBatchModeControls(pdfFiles.length); } } function updateProcessButtonState(pdfFiles, isProcessing) { if (!processBtn) return; const getActiveFiles = typeof global.getActiveFiles === 'function' ? global.getActiveFiles : null; const effectiveFiles = getActiveFiles ? getActiveFiles() : pdfFiles; let ocrConfigAvailable = true; // 重命名:更清晰地表示 OCR 配置是否可用 let translationKeysAvailable = true; const hasPdfFiles = effectiveFiles.some(file => file.name.toLowerCase().endsWith('.pdf')); // 检查 OCR 引擎与所需配置 let ocrEngine = 'mineru'; let ocrConfigValid = true; let ocrConfigMessage = ''; try { if (window.ocrSettingsManager && typeof window.ocrSettingsManager.getCurrentConfig === 'function') { ocrEngine = window.ocrSettingsManager.getCurrentConfig().engine || (localStorage.getItem('ocrEngine') || 'mineru'); // 使用 validateConfig 检查配置是否完整 const validation = window.ocrSettingsManager.validateConfig(); ocrConfigValid = validation.valid; ocrConfigMessage = validation.message; } else { ocrEngine = localStorage.getItem('ocrEngine') || 'mineru'; } } catch {} if (hasPdfFiles) { if (ocrEngine === 'none') { // 选择了"不需要 OCR"但有 PDF 文件,标记为无效 ocrConfigAvailable = false; } else { // OCR 配置不论是否有效(允许后端使用环境变量代填) ocrConfigAvailable = true; } } // 检查翻译 Keys(如果需要翻译)- 直接使用 app.js 的逻辑 const settings = typeof global.loadSettings === 'function' ? global.loadSettings() : {}; const selectedTranslationModelName = settings.selectedTranslationModel || 'none'; let translationModelDisplayName = selectedTranslationModelName; let currentTranslationModelForProvider = null; let translationModelConfigForProcess = null; if (selectedTranslationModelName !== 'none') { if (selectedTranslationModelName === 'custom') { const selectedCustomSourceId = settings.selectedCustomSourceSiteId; if (!selectedCustomSourceId) { translationKeysAvailable = false; } else { const allSourceSites = typeof global.loadAllCustomSourceSites === 'function' ? global.loadAllCustomSourceSites() : {}; const siteConfig = allSourceSites[selectedCustomSourceId]; if (!siteConfig) { translationKeysAvailable = false; } else { currentTranslationModelForProvider = `custom_source_${selectedCustomSourceId}`; translationModelConfigForProcess = siteConfig; translationModelDisplayName = siteConfig.displayName || siteConfig.name || selectedCustomSourceId; } } } else { // 预设模型 currentTranslationModelForProvider = selectedTranslationModelName; translationModelConfigForProcess = typeof global.loadModelConfig === 'function' ? global.loadModelConfig(selectedTranslationModelName) : {}; translationModelDisplayName = translationModelConfigForProcess?.displayName || selectedTranslationModelName; } // 使用 KeyProvider 检查 if (currentTranslationModelForProvider) { const translationKeyProvider = new KeyProvider(currentTranslationModelForProvider); translationKeysAvailable = translationKeyProvider.hasAvailableKeys(); } } // 更新按钮禁用状态和验证提示 const hasValidationIssues = (hasPdfFiles && !ocrConfigAvailable) || (!translationKeysAvailable && selectedTranslationModelName !== 'none'); processBtn.disabled = effectiveFiles.length === 0 || isProcessing || hasValidationIssues; // 更新验证状态提示(传递 OCR 配置信息) updateValidationAlert(effectiveFiles, hasPdfFiles, ocrConfigAvailable, translationKeysAvailable, selectedTranslationModelName, translationModelDisplayName, isProcessing, ocrEngine, ocrConfigValid, ocrConfigMessage); if (isProcessing) { processBtn.innerHTML = `处理中...`; } else { processBtn.innerHTML = `开始处理`; } } function updateValidationAlert(effectiveFiles, hasPdfFiles, ocrConfigAvailable, translationKeysAvailable, selectedTranslationModel, translationModelDisplayName, isProcessing, ocrEngine, ocrConfigValid, ocrConfigMessage) { const validationAlert = document.getElementById('validationAlert'); const validationIcon = document.getElementById('validationIcon'); const validationTitle = document.getElementById('validationTitle'); const validationMessage = document.getElementById('validationMessage'); const validationActions = document.getElementById('validationActions'); if (!validationAlert || !validationIcon || !validationTitle || !validationMessage || !validationActions) return; // 如果正在处理或没有文件,隐藏提示 if (isProcessing || effectiveFiles.length === 0) { validationAlert.classList.add('hidden'); return; } // 检查各种验证条件 const issues = []; if (hasPdfFiles && !ocrConfigAvailable) { if (ocrEngine === 'none') { issues.push({ type: 'ocr-none-with-pdf', title: 'PDF 文件需要 OCR 引擎', message: '您上传了 PDF 文件,但当前选择了"不需要 OCR"。请在上方 OCR 设置中选择 Mistral OCR、MinerU 或 Doc2X。', icon: 'carbon:warning-alt', actions: [] }); } else if (!ocrConfigValid) { // 仅提示但不拦截 const engineNames = { mistral: 'Mistral OCR', mineru: 'MinerU', doc2x: 'Doc2X' }; const engineName = engineNames[ocrEngine] || ocrEngine; issues.push({ type: 'ocr-config-incomplete', title: `${engineName} 提示`, message: ocrConfigMessage ? ocrConfigMessage + ' (如果没有前端Key,代理服务器将尝试使用后端环境变量补充)' : `未完全配置 ${engineName} (如果代理服已配置环境变量则可忽略此提示)。`, icon: 'carbon:information', actions: [ { text: '去配置', onClick: () => { const btn = document.getElementById('modelKeyManagerBtn'); if (btn) btn.click(); }} ] }); } } if (selectedTranslationModel && selectedTranslationModel !== 'none' && !translationKeysAvailable) { const isCustomSource = selectedTranslationModel === 'custom'; const message = isCustomSource ? `源站 "${translationModelDisplayName}" 没有可用的 API Key。请添加 Key 后再处理,配置后可点击右上角刷新按钮更新状态。` : `模型 "${translationModelDisplayName}" 没有可用的 API Key。请添加 Key 后再处理,配置后可点击右上角刷新按钮更新状态。`; issues.push({ type: isCustomSource ? 'no-custom-translation-key' : 'no-translation-key', title: isCustomSource ? `缺少源站 Key` : `缺少翻译模型 Key`, message: message, icon: 'carbon:warning-alt', actions: [ { text: isCustomSource ? '去配置该源站 Key' : '去配置 Key', onClick: () => { const btn = document.getElementById('modelKeyManagerBtn'); if (btn) btn.click(); }}, { text: '关闭翻译', onClick: () => { const select = document.getElementById('translationModel'); if (select) { select.value = 'none'; select.dispatchEvent(new Event('change')); } }} ] }); } // 如果没有问题,隐藏提示 if (issues.length === 0) { validationAlert.classList.add('hidden'); return; } // 显示问题 if (issues.length > 1) { // 多个问题:合并显示 validationIcon.setAttribute('icon', 'carbon:warning-alt'); validationTitle.textContent = '配置检查'; validationMessage.innerHTML = issues.map(issue => `• ${issue.message}`).join('
'); validationActions.innerHTML = ''; const addedButtons = new Set(); issues.forEach(issue => { issue.actions.forEach(action => { if (!addedButtons.has(action.text)) { addedButtons.add(action.text); const btn = document.createElement('button'); btn.className = 'text-xs px-3 py-1.5 bg-white/80 hover:bg-white border border-current/20 hover:border-current/40 rounded-md transition-all font-medium shadow-sm'; btn.textContent = action.text; if (action.onClick) btn.onclick = action.onClick; validationActions.appendChild(btn); } }); }); } else { // 单个问题:正常显示 const issue = issues[0]; validationIcon.setAttribute('icon', issue.icon); validationTitle.textContent = issue.title; validationMessage.textContent = issue.message; validationActions.innerHTML = ''; issue.actions.forEach(action => { const btn = document.createElement('button'); btn.className = 'text-xs px-3 py-1.5 bg-white/80 hover:bg-white border border-current/20 hover:border-current/40 rounded-md transition-all font-medium shadow-sm'; btn.textContent = action.text; if (action.onClick) btn.onclick = action.onClick; validationActions.appendChild(btn); }); } // 设置样式 validationAlert.className = 'mt-6 mb-4 p-3.5 rounded-lg border transition-all shadow-sm border-amber-400 bg-amber-50/50 text-amber-900'; validationAlert.classList.remove('hidden'); } function showResultsSection(successCount, skippedCount, errorCount, pdfFilesLength) { if (!progressSection || !resultsSection || !resultsSummary || !downloadAllBtn || !concurrentProgressText) return; progressSection.classList.add('hidden'); resultsSection.classList.remove('hidden'); concurrentProgressText.textContent = ''; const totalAttempted = successCount + skippedCount + errorCount; resultsSummary.innerHTML = `

处理总结:

在 ${pdfFilesLength} 个选定文件中,尝试处理了 ${totalAttempted} 个。

`; downloadAllBtn.disabled = successCount === 0; window.scrollTo({ top: resultsSection.offsetTop - 20, behavior: 'smooth' }); } function showProgressSection() { if (!resultsSection || !progressSection || !progressLog || !batchProgressText || !concurrentProgressText) return; resultsSection.classList.add('hidden'); progressSection.classList.remove('hidden'); progressLog.innerHTML = ''; batchProgressText.textContent = ''; concurrentProgressText.textContent = ''; updateProgress('初始化...', 0); window.scrollTo({ top: progressSection.offsetTop - 20, behavior: 'smooth' }); } function updateConcurrentProgress(count) { if (concurrentProgressText) { concurrentProgressText.textContent = `当前并发任务数: ${count}`; } } function updateOverallProgress(success, skipped, errors, totalFiles) { if (!batchProgressText || !progressPercentage || !progressBar) return; const completedCount = success + skipped + errors; if (totalFiles > 0) { const percentage = totalFiles > 0 ? Math.round((completedCount / totalFiles) * 100) : 0; batchProgressText.textContent = `整体进度: ${completedCount} / ${totalFiles} 完成`; progressPercentage.textContent = `${percentage}%`; progressBar.style.width = `${percentage}%`; } else { batchProgressText.textContent = ''; progressPercentage.textContent = '0%'; progressBar.style.width = '0%'; } } function updateProgress(stepText, percentage) { if (progressStep) { progressStep.textContent = stepText; } } function addProgressLog(text) { if (!progressLog) return; const timestamp = new Date().toLocaleTimeString(); const logLine = document.createElement('div'); logLine.textContent = `[${timestamp}] ${text}`; progressLog.appendChild(logLine); progressLog.scrollTop = progressLog.scrollHeight; } /** * 文件预览功能 * @param {File} file - 要预览的文件对象 */ function previewFile(file) { const ext = file.name.split('.').pop().toLowerCase(); // 创建预览模态框 const modal = document.createElement('div'); modal.className = 'fixed inset-0 z-[9999] flex items-center justify-center bg-black/60 backdrop-blur-sm'; modal.style.animation = 'fadeIn 0.2s ease-out'; const modalContent = document.createElement('div'); modalContent.className = 'relative bg-white rounded-lg shadow-2xl w-[90vw] h-[90vh] flex flex-col'; modalContent.style.animation = 'slideUp 0.3s ease-out'; // 头部 const header = document.createElement('div'); header.className = 'flex items-center justify-between px-6 py-4 border-b border-gray-200'; header.innerHTML = `

文件预览

`; // 文件信息和控件整合到一行(仅针对 PDF) let fileInfoAndControls = null; if (ext === 'pdf') { fileInfoAndControls = document.createElement('div'); fileInfoAndControls.className = 'px-6 py-3 bg-gradient-to-r from-blue-50 to-indigo-50 border-b border-blue-100 flex items-center justify-between'; fileInfoAndControls.innerHTML = `
${file.name}
${global.formatFileSize ? global.formatFileSize(file.size) : (file.size / 1024).toFixed(2) + ' KB'}
按住 Ctrl 滚动鼠标滚轮可缩放
1 / -
120%
`; } else { // 非 PDF 文件,保持原有的文件信息显示 fileInfoAndControls = document.createElement('div'); fileInfoAndControls.className = 'px-6 py-3 bg-gradient-to-r from-blue-50 to-indigo-50 border-b border-blue-100'; fileInfoAndControls.innerHTML = `
${file.name}
${global.formatFileSize ? global.formatFileSize(file.size) : (file.size / 1024).toFixed(2) + ' KB'}
${ext.toUpperCase()}
`; } // 内容区域 const contentArea = document.createElement('div'); contentArea.className = 'flex-1 overflow-auto p-6'; // 根据文件类型生成预览内容 if (ext === 'pdf') { // PDF 预览 - 使用 PDF.js 渲染,添加左侧缩略图 contentArea.className = 'flex-1 flex overflow-hidden'; contentArea.innerHTML = `
页面缩略图
`; // 使用 PDF.js 渲染 const canvas = contentArea.querySelector('.pdf-canvas'); const ctx = canvas.getContext('2d', { willReadFrequently: false }); const thumbnailsContainer = contentArea.querySelector('.pdf-thumbnails'); const pageNumSpan = fileInfoAndControls.querySelector('.pdf-page-num'); const pageCountSpan = fileInfoAndControls.querySelector('.pdf-page-count'); const zoomLevelSpan = fileInfoAndControls.querySelector('.pdf-zoom-level'); const prevBtn = fileInfoAndControls.querySelector('.pdf-prev-page'); const nextBtn = fileInfoAndControls.querySelector('.pdf-next-page'); const zoomInBtn = fileInfoAndControls.querySelector('.pdf-zoom-in'); const zoomOutBtn = fileInfoAndControls.querySelector('.pdf-zoom-out'); let pdfDoc = null; let currentPage = 1; let currentScale = 1.2; // 调整为更合适的默认缩放比例 const renderPage = async (num) => { const page = await pdfDoc.getPage(num); const viewport = page.getViewport({ scale: currentScale }); canvas.width = viewport.width; canvas.height = viewport.height; await page.render({ canvasContext: ctx, viewport: viewport }).promise; pageNumSpan.textContent = num; prevBtn.disabled = num <= 1; nextBtn.disabled = num >= pdfDoc.numPages; // 更新缩略图高亮 document.querySelectorAll('.pdf-thumbnail').forEach((thumb, idx) => { if (idx + 1 === num) { thumb.classList.add('ring-2', 'ring-blue-500', 'bg-blue-50'); } else { thumb.classList.remove('ring-2', 'ring-blue-500', 'bg-blue-50'); } }); }; // 渲染缩略图 const renderThumbnails = async () => { for (let i = 1; i <= pdfDoc.numPages; i++) { const page = await pdfDoc.getPage(i); const viewport = page.getViewport({ scale: 0.2 }); // 缩略图小比例 const thumbCanvas = document.createElement('canvas'); thumbCanvas.width = viewport.width; thumbCanvas.height = viewport.height; const thumbCtx = thumbCanvas.getContext('2d', { willReadFrequently: false }); await page.render({ canvasContext: thumbCtx, viewport: viewport }).promise; const thumbDiv = document.createElement('div'); thumbDiv.className = 'pdf-thumbnail cursor-pointer p-2 rounded border-2 border-transparent hover:border-blue-300 transition-all'; thumbDiv.innerHTML = `
第 ${i} 页
`; thumbDiv.addEventListener('click', async () => { currentPage = i; await renderPage(currentPage); }); thumbnailsContainer.appendChild(thumbDiv); } }; // 加载 PDF const fileReader = new FileReader(); fileReader.onload = async (e) => { try { const typedArray = new Uint8Array(e.target.result); pdfDoc = await pdfjsLib.getDocument({ data: typedArray }).promise; pageCountSpan.textContent = pdfDoc.numPages; // 渲染第一页 await renderPage(currentPage); // 渲染所有缩略图(异步进行,不阻塞) renderThumbnails(); } catch (error) { console.error('[PDF Preview] Error loading PDF:', error); contentArea.innerHTML = `

PDF 加载失败

${error.message}

`; } }; fileReader.readAsArrayBuffer(file); // 翻页事件 prevBtn.addEventListener('click', async () => { if (currentPage > 1) { currentPage--; await renderPage(currentPage); } }); nextBtn.addEventListener('click', async () => { if (currentPage < pdfDoc.numPages) { currentPage++; await renderPage(currentPage); } }); // 缩放事件 zoomInBtn.addEventListener('click', async () => { currentScale += 0.25; zoomLevelSpan.textContent = Math.round(currentScale * 100) + '%'; await renderPage(currentPage); }); zoomOutBtn.addEventListener('click', async () => { if (currentScale > 0.5) { currentScale -= 0.25; zoomLevelSpan.textContent = Math.round(currentScale * 100) + '%'; await renderPage(currentPage); } }); // 滚轮缩放功能(Ctrl/Cmd + 滚轮) const pdfContainer = contentArea.querySelector('.flex-1.overflow-auto'); let zoomTimeout = null; pdfContainer.addEventListener('wheel', async (e) => { if (e.ctrlKey || e.metaKey) { e.preventDefault(); const delta = e.deltaY; const zoomStep = 0.1; // 更细腻的缩放步进 if (delta < 0) { // 向上滚动 - 放大 currentScale = Math.min(currentScale + zoomStep, 5.0); // 最大 500% } else { // 向下滚动 - 缩小 currentScale = Math.max(currentScale - zoomStep, 0.5); // 最小 50% } // 更新显示 zoomLevelSpan.textContent = Math.round(currentScale * 100) + '%'; // 防抖渲染:等待滚轮停止后再渲染 clearTimeout(zoomTimeout); zoomTimeout = setTimeout(async () => { await renderPage(currentPage); }, 150); } }, { passive: false }); } else if (['docx', 'doc'].includes(ext)) { // Word 文件预览 - 使用 docx-preview.js contentArea.className = 'flex-1 overflow-auto bg-gray-50 p-6'; contentArea.innerHTML = `
`; const docxContainer = contentArea.querySelector('.docx-preview-container'); // 检查 docx-preview 库是否加载 if (typeof docx === 'undefined') { contentArea.innerHTML = `

Word 预览库未加载

请刷新页面重试

`; return; } // 使用 docx-preview 渲染 const reader = new FileReader(); reader.onload = async (e) => { try { await docx.renderAsync(e.target.result, docxContainer, null, { className: "docx-preview", inWrapper: true, ignoreWidth: false, ignoreHeight: false, ignoreFonts: false, breakPages: true, ignoreLastRenderedPageBreak: true, experimental: false, trimXmlDeclaration: true, useBase64URL: false, useMathMLPolyfill: false, renderHeaders: true, renderFooters: true, renderFootnotes: true, renderEndnotes: true }); } catch (error) { console.error('[Word Preview] Error rendering Word:', error); contentArea.innerHTML = `

Word 文件加载失败

${error.message}

`; } }; reader.readAsArrayBuffer(file); } else if (['pptx', 'ppt'].includes(ext)) { // PPT 文件预览 contentArea.className = 'flex-1 overflow-hidden bg-gray-100'; contentArea.innerHTML = `

PowerPoint 文件预览

建议使用 Microsoft PowerPoint 或其他本地应用打开此文件。

文件信息:
名称: ${file.name}
大小: ${global.formatFileSize ? global.formatFileSize(file.size) : (file.size / 1024).toFixed(2) + ' KB'}

`; } else if (['txt', 'md', 'yaml', 'yml', 'html', 'htm'].includes(ext)) { // 文本文件预览 const reader = new FileReader(); reader.onload = (e) => { const text = e.target.result; contentArea.innerHTML = `
${escapeHtml(text)}
`; }; reader.readAsText(file); } else { // 不支持的类型 contentArea.innerHTML = `

暂不支持预览此文件类型

支持的类型: PDF, Word (DOCX), TXT, MD, YAML, HTML

`; } // 组装模态框 modalContent.appendChild(header); modalContent.appendChild(fileInfoAndControls); modalContent.appendChild(contentArea); modal.appendChild(modalContent); document.body.appendChild(modal); // 关闭事件 const closeModal = () => { modal.style.animation = 'fadeOut 0.2s ease-out'; setTimeout(() => modal.remove(), 200); }; header.querySelector('.preview-close-btn').addEventListener('click', closeModal); modal.addEventListener('click', (e) => { if (e.target === modal) closeModal(); }); // ESC 键关闭 const handleEsc = (e) => { if (e.key === 'Escape') { closeModal(); document.removeEventListener('keydown', handleEsc); } }; document.addEventListener('keydown', handleEsc); } /** * HTML 转义函数 */ function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } global.updateFileListUI = updateFileListUI; global.updateProcessButtonState = updateProcessButtonState; global.showResultsSection = showResultsSection; global.showProgressSection = showProgressSection; global.updateConcurrentProgress = updateConcurrentProgress; global.updateOverallProgress = updateOverallProgress; global.updateProgress = updateProgress; global.addProgressLog = addProgressLog; })(window);