Spaces:
Runtime error
Runtime error
| // Initialize the app container. | |
| const app = document.getElementById('app'); | |
| app.innerHTML = ` | |
| <div style="height: 100vh; display: flex; flex-direction: column;"> | |
| <div style="background: #f5f5f5; border-bottom: 1px solid #ddd; padding: 10px;"> | |
| <div id="statusIndicator"> | |
| <span class="spinner"></span> Translation services starting... | |
| </div> | |
| <div id="translationControls"> | |
| <select id="sourceLanguageSelect"> | |
| <option value="English" selected>English</option> | |
| <option value="Spanish">Spanish</option> | |
| <option value="French">French</option> | |
| <option value="German">German</option> | |
| </select> | |
| <span style="margin: 0 10px;">→</span> | |
| <select id="targetLanguageSelect"> | |
| <option value="English">English</option> | |
| <option value="Spanish">Spanish</option> | |
| <option value="French">French</option> | |
| <option value="German">German</option> | |
| </select> | |
| <button id="translateButton">Translate Document</button> | |
| </div> | |
| </div> | |
| <div style="flex: 1; display: flex; flex-direction: column; position: relative;"> | |
| <div id="editor" style="position: absolute; top: 0; left: 0; right: 0; bottom: 0;"></div> | |
| </div> | |
| </div> | |
| `; | |
| // Add retry count | |
| let retryCount = 0; | |
| const MAX_RETRIES = 10; // Will try for 20 seconds (10 attempts * 2 second interval) | |
| function checkServicesStatus() { | |
| fetch('/healthcheck') | |
| .then(response => { | |
| if (response.ok) { | |
| window.servicesReady = true; | |
| const translationControls = document.getElementById('translationControls'); | |
| const statusIndicator = document.getElementById('statusIndicator'); | |
| if (translationControls) { | |
| translationControls.style.display = 'block'; | |
| } | |
| if (statusIndicator) { | |
| statusIndicator.style.display = 'none'; | |
| } | |
| return true; | |
| } | |
| throw new Error('Services not ready'); | |
| }) | |
| .catch(error => { | |
| retryCount++; | |
| const statusIndicator = document.getElementById('statusIndicator'); | |
| if (statusIndicator) { | |
| if (retryCount >= MAX_RETRIES) { | |
| statusIndicator.innerHTML = '❌ Failed to initialize translation services. Try restarting the space.'; | |
| statusIndicator.style.color = '#dc3545'; | |
| clearInterval(statusInterval); | |
| } else { | |
| statusIndicator.innerHTML = `<span class="spinner"></span> Translation services starting... (attempt ${retryCount}/${MAX_RETRIES})`; | |
| } | |
| } | |
| console.log('Waiting for services...', error); | |
| return false; | |
| }); | |
| } | |
| // Check status every 2 seconds until ready or max retries reached | |
| const statusInterval = setInterval(() => { | |
| if (window.servicesReady || retryCount >= MAX_RETRIES) { | |
| clearInterval(statusInterval); | |
| } else { | |
| checkServicesStatus(); | |
| } | |
| }, 2000); | |
| // Load Document Authoring SDK | |
| const script = document.createElement('script'); | |
| script.src = 'https://document-authoring-cdn.pspdfkit.com/releases/document-authoring-1.0.26-umd.js'; | |
| script.crossOrigin = true; | |
| document.head.appendChild(script); | |
| script.onload = async () => { | |
| const docAuthSystem = await DocAuth.createDocAuthSystem({}); | |
| const editor = await docAuthSystem.createEditor( | |
| document.getElementById("editor"), | |
| { | |
| document: await docAuthSystem.importDOCX(fetch('./Sample.docx')), | |
| } | |
| ); | |
| // Helper function to extract text runs with their paths | |
| function flattenTextRuns(obj, path = [], result = []) { | |
| for (let key in obj) { | |
| const newPath = [...path, key]; | |
| if (typeof obj[key] === "string" && key === "text" && obj[key].length > 0) { | |
| result.push({ text: obj[key], path: newPath }); | |
| } else if (typeof obj[key] === "object") { | |
| flattenTextRuns(obj[key], newPath, result); | |
| } | |
| } | |
| return result; | |
| } | |
| // Helper function to group adjacent text runs | |
| function groupAdjacentRuns(textRuns, maxChunkSize = 1000) { | |
| const groups = []; | |
| let currentGroup = []; | |
| let currentSize = 0; | |
| for (let run of textRuns) { | |
| if (currentSize + run.text.length > maxChunkSize && currentGroup.length > 0) { | |
| groups.push([...currentGroup]); | |
| currentGroup = []; | |
| currentSize = 0; | |
| } | |
| currentGroup.push(run); | |
| currentSize += run.text.length; | |
| } | |
| if (currentGroup.length > 0) { | |
| groups.push(currentGroup); | |
| } | |
| return groups; | |
| } | |
| async function translate(content, targetLang, sourceLang = 'English') { | |
| try { | |
| const response = await fetch('/client/api/v1/chat/completions', { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| }, | |
| body: JSON.stringify({ | |
| messages: [ | |
| { | |
| role: "system", | |
| content: `Translate the following text from ${sourceLang} to ${targetLang}. Each segment is marked with ||| delimiters. Translate each segment independently while preserving original capitalization and punctuation. Add spaces at the start or end of segments only if they exist in the original text.` | |
| }, | |
| { | |
| role: "user", | |
| content: content | |
| } | |
| ], | |
| model: "gemma-2b", | |
| stream: false | |
| }) | |
| }); | |
| if (!response.ok) { | |
| throw new Error('Translation request failed'); | |
| } | |
| const data = await response.json(); | |
| return data.choices[0].message.content; | |
| } catch (error) { | |
| console.error("Translation error:", error); | |
| throw new Error("Failed to generate translation"); | |
| } | |
| } | |
| // Function to update nested object value by path | |
| function setNestedValue(obj, path, value) { | |
| let current = obj; | |
| for (let i = 0; i < path.length - 1; i++) { | |
| current = current[path[i]]; | |
| } | |
| current[path[path.length - 1]] = value; | |
| } | |
| // Enhanced recursive translation function | |
| async function translateRecursive(docJson, targetLang, sourceLang = 'English') { | |
| const textRuns = flattenTextRuns(docJson); | |
| const groups = groupAdjacentRuns(textRuns); | |
| for (let group of groups) { | |
| const content = group.map(run => `|||${run.text}|||`).join('\n'); | |
| const translatedContent = await translate(content, targetLang, sourceLang); | |
| const translations = translatedContent | |
| .split('|||') | |
| .filter(t => t.trim()) | |
| .map(t => t.trim()); | |
| group.forEach((run, index) => { | |
| if (translations[index]) { | |
| const originalText = run.text; | |
| let translatedText = translations[index]; | |
| if (originalText.startsWith(' ')) { | |
| translatedText = ' ' + translatedText; | |
| } | |
| if (originalText.endsWith(' ')) { | |
| translatedText = translatedText + ' '; | |
| } | |
| setNestedValue(docJson, run.path, translatedText); | |
| } | |
| }); | |
| } | |
| } | |
| // Add translation button handler | |
| const translateButton = document.getElementById('translateButton'); | |
| const sourceLanguageSelect = document.getElementById('sourceLanguageSelect'); | |
| const targetLanguageSelect = document.getElementById('targetLanguageSelect'); | |
| translateButton.addEventListener('click', async () => { | |
| try { | |
| const sourceLanguage = sourceLanguageSelect.value; | |
| const targetLanguage = targetLanguageSelect.value; | |
| if (sourceLanguage === targetLanguage) { | |
| alert('Please select different languages for source and target'); | |
| return; | |
| } | |
| translateButton.disabled = true; | |
| translateButton.innerHTML = '<span class="spinner"></span> Translating...'; | |
| const jsonDoc = await editor.currentDocument().saveDocument(); | |
| await translateRecursive(jsonDoc.container.document, targetLanguage, sourceLanguage); | |
| const translatedDoc = await docAuthSystem.loadDocument(jsonDoc); | |
| editor.setCurrentDocument(translatedDoc); | |
| } catch (error) { | |
| alert('Translation failed: ' + error.message); | |
| } finally { | |
| translateButton.disabled = false; | |
| translateButton.innerHTML = 'Translate Document'; | |
| } | |
| }); | |
| }; | |