Spaces:
Running
Running
| <script> | |
| (function () { | |
| console.info("Space App JS executing..."); | |
| // --- Logger utility (structured, levelled, timestamped) --- | |
| const SpaceApp = { | |
| STORAGE_KEY_LANG: 'ling_space_locale', | |
| URL_PARAM_LANG: 'lang', | |
| hasBooted: false, | |
| fnUnloadLastTab: null, | |
| supportedLangs: function() { | |
| return ['en', 'zh']; | |
| }, | |
| setLanguage: function(lang, reload = true) { | |
| if (!SpaceApp.supportedLangs().includes(lang)) { | |
| console.error(`[I18n] Unsupported language: ${lang}`); | |
| return; | |
| } | |
| localStorage.setItem(SpaceApp.STORAGE_KEY_LANG, lang); | |
| console.log(`[I18n] Language set to: ${lang}`); | |
| if (reload) { | |
| // Update URL to reflect language or just reload | |
| const url = new URL(window.location); | |
| url.searchParams.set(SpaceApp.URL_PARAM_LANG, lang); | |
| window.location.href = url.toString(); | |
| } | |
| }, | |
| detectLanguage: function() { | |
| // 1. Check URL Parameter, if any. | |
| const urlParams = new URLSearchParams(window.location.search); | |
| const urlLang = urlParams.get(SpaceApp.URL_PARAM_LANG); | |
| if (urlLang && SpaceApp.supportedLangs().includes(urlLang)) { | |
| console.log(`[I18n] Detected language from URL: ${urlLang}`); | |
| return urlLang; | |
| } else { | |
| console.log(`[I18n] No valid language found in URL parameters, found: ${urlLang}`); | |
| } | |
| // 2. Check LocalStorage, if any. | |
| const storedLang = localStorage.getItem(SpaceApp.STORAGE_KEY_LANG); | |
| if (storedLang && SpaceApp.supportedLangs().includes(storedLang)) { | |
| console.log(`[I18n] Detected language from LocalStorage: ${storedLang}`); | |
| return storedLang; | |
| } else { | |
| console.log(`[I18n] No valid language found in LocalStorage, found: ${storedLang}`); | |
| } | |
| // 3. Check Browser User Agent | |
| const browserLang = navigator.language || navigator.userLanguage; | |
| if (browserLang) { | |
| // Extract primary language code (e.g., 'zh-CN' -> 'zh') | |
| const primaryLang = browserLang.split('-')[0].toLowerCase(); | |
| if (SpaceApp.supportedLangs().includes(primaryLang)) { | |
| console.log(`[I18n] Detected language from UserAgent: ${primaryLang} (of ${browserLang})`); | |
| return primaryLang; | |
| } else { | |
| console.log(`[I18n] No valid language found in UserAgent, found: ${browserLang}`); | |
| } | |
| } else { | |
| console.log(`[I18n] No language found in UserAgent, navigator is ${JSON.stringify(navigator)}`); | |
| } | |
| console.log(`[I18n] cannot determine lang, using default language: ${DEFAULT_LOCALE}`); | |
| return DEFAULT_LOCALE; | |
| }, | |
| toastInfo: function(...args) { | |
| // use toastify | |
| window.Toastify({ | |
| text: args.join(' '), | |
| duration: 3000, | |
| gravity: "top", | |
| position: "right", | |
| style: { | |
| background: "green" | |
| } | |
| }).showToast(); | |
| console.info("TOAST_INFO", ...args); | |
| }, | |
| toastError: function(...args) { | |
| window.Toastify({ | |
| text: args.join(' '), | |
| duration: 5000, | |
| gravity: "top", | |
| position: "right", | |
| style: { | |
| background: "green" | |
| } | |
| }).showToast(); | |
| console.error("TOAST_ERROR", ...args); | |
| }, | |
| unloadLastTab: function() { | |
| if (this.fnUnloadLastTab) { | |
| this.fnUnloadLastTab(); | |
| this.fnUnloadLastTab = null; | |
| } | |
| }, | |
| TextGeneratorTab: (function () { | |
| return { | |
| init: function () { | |
| console.info("TextGeneratorTab initialized."); | |
| }, | |
| toggle: function () { | |
| // Placeholder for future functionality | |
| SpaceApp.toastInfo("TextGeneratorTab toggled."); | |
| SpaceApp.unloadLastTab(); | |
| } | |
| }; | |
| })(), | |
| WebGeneratorTab: (function () { | |
| return { | |
| init: function () { | |
| console.info("WebGeneratorTab initialized."); | |
| }, | |
| toggle: function () { | |
| // Placeholder for future functionality | |
| SpaceApp.toastInfo("WebGeneratorTab toggled."); | |
| SpaceApp.unloadLastTab(); | |
| }, | |
| toggleFullscreen: function (event) { | |
| const isFullscreen = event.detail?.isFullscreen; | |
| console.info(`Handling Fullscreen Toggle. Target State: ${isFullscreen}`); | |
| const left = document.getElementById('left-panel'); | |
| const right = document.getElementById('right-panel'); | |
| // If isFullscreen is true, we want to HIDE panels (enter fullscreen) | |
| // If isFullscreen is false, we want to SHOW panels (exit fullscreen) | |
| const shouldHide = isFullscreen; | |
| const displayStyle = shouldHide ? 'none' : 'flex'; | |
| if (left) { | |
| left.style.display = displayStyle; | |
| // Also toggle class for good measure/CSS priority | |
| left.classList.toggle('hidden-panel', shouldHide); | |
| } | |
| if (right) { | |
| right.style.display = displayStyle; | |
| right.classList.toggle('hidden-panel', shouldHide); | |
| } | |
| // Trigger resize to help charts/iframes adjust | |
| setTimeout(() => window.dispatchEvent(new Event('resize')), 100); | |
| } | |
| }; | |
| })(), | |
| WritingAssistantTab: (function () { | |
| return { | |
| init: function () { | |
| console.info("WritingAssistantTab initialized."); | |
| }, | |
| }; | |
| })(), | |
| registerEventListeners: function() { | |
| const listeners = { | |
| 'tabSelect.chat': () => SpaceApp.TextGeneratorTab.toggle(), | |
| 'tabSelect.code': () => SpaceApp.WebGeneratorTab.toggle(), | |
| 'tabSelect.writing': () => SpaceApp.WritingAssistantTab.toggle(), | |
| // New listener for fullscreen toggle | |
| 'tab.code.toggleFullscreen': (e) => SpaceApp.WebGeneratorTab.toggleFullscreen(e), | |
| 'langChange.SpaceApp': (e) => { | |
| const lang = e.detail.lang; | |
| if (lang) { | |
| SpaceApp.setLanguage(lang, true); | |
| } else { | |
| console.error("langChange.SpaceApp event received without 'lang' detail."); | |
| } | |
| }, | |
| }; | |
| for (const [event, handler] of Object.entries(listeners)) { | |
| window.addEventListener(event, handler); | |
| console.info(`Registered event listener for ${event}`); | |
| } | |
| // type_filter, handler | |
| // Listen for messages {type: string, payload: object} | |
| const messages = { | |
| 'iframeError': (data) => { | |
| // 1. Show visual toast | |
| SpaceApp.toastError("Iframe Error Detected:", JSON.stringify(data)); | |
| // 2. Propagate to Backend via Gradio | |
| const jsErrorChannel = document.querySelector('.js_error_channel textarea'); | |
| if (jsErrorChannel) { | |
| // Format as string for backend | |
| const errorMessage = data.message || JSON.stringify(data); | |
| const timestamp = new Date().toLocaleTimeString(); | |
| jsErrorChannel.value = `[${timestamp}] ${errorMessage}\nStack: ${data.stack || 'N/A'}`; | |
| jsErrorChannel.dispatchEvent(new Event('input', { bubbles: true })); | |
| } else { | |
| console.warn("Gradio channel '.js_error_channel' not found. Backend logging skipped."); | |
| } | |
| }, | |
| } | |
| window.addEventListener('message', (event) => { | |
| const {type, payload} = event.data || {}; | |
| if (type && messages[type]) { | |
| messages[type](payload); | |
| } | |
| }); | |
| }, | |
| }; | |
| // Expose bootstrap globally so external scripts can call it after page load | |
| window.bootApp = function () { | |
| if (SpaceApp.hasBooted) { | |
| console.warn("Space App has already booted. Ignoring duplicate boot call."); | |
| return; | |
| } | |
| SpaceApp.hasBooted = true; | |
| const currentLang = SpaceApp.detectLanguage(); | |
| SpaceApp.setLanguage(currentLang, false); | |
| SpaceApp.toastInfo("Booting Space App..."); | |
| SpaceApp.registerEventListeners(); | |
| SpaceApp.TextGeneratorTab.init(); | |
| SpaceApp.WebGeneratorTab.init(); | |
| SpaceApp.WritingAssistantTab.init(); | |
| SpaceApp.toastInfo("Space App booted successfully."); | |
| }; | |
| window.SpaceApp = SpaceApp; | |
| console.info("Space App JS execution completed."); | |
| })(); | |
| </script> | |
| <script> | |
| // 注册一个 appStart 事件监听器,在 Gradio app 启动时调用 bootApp | |
| window.addEventListener('appStart', function () { | |
| console.info("Gradio appStart event detected. Booting Space App..."); | |
| if (typeof window.bootApp === "function") { | |
| window.bootApp(); | |
| } else { | |
| console.error("bootApp function is not defined."); | |
| } | |
| }); | |
| </script> | |
| <script> | |
| (function() { | |
| const ErrorPoller = { | |
| lastErrorMessage: '', | |
| startPolling: () => { | |
| }, | |
| stopPolling: () => { | |
| } | |
| } | |
| console.info('Iframe Error Poller Initialized'); | |
| let lastErrorMessage = ''; | |
| // Function to start polling for errors in the iframe | |
| function startPolling() { | |
| console.info('Starting to poll iframe for errors...'); | |
| setInterval(() => { | |
| // Re-select the iframe on every interval tick because Gradio replaces it. | |
| const previewIframe = document.querySelector('iframe[srcdoc]'); | |
| if (!previewIframe) return; | |
| try { | |
| const iframeDoc = previewIframe.contentWindow.document; | |
| const errorContainer = iframeDoc.querySelector('#global-error'); | |
| const jsErrorChannel = document.querySelector('.js_error_channel textarea'); | |
| if (!jsErrorChannel) { | |
| console.error("Gradio channel '.js_error_channel' not found."); | |
| return; | |
| } | |
| if (errorContainer && errorContainer.style.display !== 'none') { | |
| const currentErrorMessage = errorContainer.innerText; | |
| // If the error message is new, send it to the backend | |
| if (currentErrorMessage && currentErrorMessage !== lastErrorMessage) { | |
| lastErrorMessage = currentErrorMessage; | |
| // Set the value on the hidden Gradio textbox | |
| jsErrorChannel.value = currentErrorMessage; | |
| // Dispatch an 'input' event to notify Gradio of the change | |
| jsErrorChannel.dispatchEvent(new Event('input', { bubbles: true })); | |
| } | |
| } else { | |
| // If the error container is not visible or doesn't exist, and there was a previous error, clear it. | |
| if (lastErrorMessage) { | |
| lastErrorMessage = ''; | |
| jsErrorChannel.value = ''; | |
| jsErrorChannel.dispatchEvent(new Event('input', { bubbles: true })); | |
| } | |
| } | |
| } catch (e) { | |
| // This can happen due to cross-origin restrictions if the iframe src changes. | |
| // We can ignore it for srcdoc iframes. | |
| } | |
| }, 1000); | |
| } | |
| // 在页面加载之后设置 | |
| document.addEventListener('readystatechange', (event) => { | |
| if (document.readyState === 'complete') { | |
| setTimeout(startPolling, 500); | |
| } | |
| }); | |
| console.info('Iframe Error Poller Script Loaded'); | |
| })(); | |
| </script> | |
| <script> | |
| // 我们当前存活在一个 iframe 里面。观察 document, window, window.parent。 | |
| console.log('document is', document); | |
| console.log('window is ', window); | |
| console.log('window parent is ', window.parent); | |
| </script> | |