Spaces:
Running
Running
| <script> | |
| (function () { | |
| console.info("Space App JS executing..."); | |
| // --- Logger utility (structured, levelled, timestamped) --- | |
| const SpaceApp = { | |
| hasBooted: false, | |
| fnUnloadLastTab: null, | |
| toastInfo: function(...args) { | |
| // use toastify | |
| window.Toastify({ | |
| text: args.join(' '), | |
| duration: 3000, | |
| gravity: "top", | |
| position: "right", | |
| backgroundColor: "green", | |
| }).showToast(); | |
| console.info("TOAST_INFO", ...args); | |
| }, | |
| toastError: function(...args) { | |
| window.Toastify({ | |
| text: args.join(' '), | |
| duration: 5000, | |
| gravity: "top", | |
| position: "right", | |
| backgroundColor: "red", | |
| }).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(); | |
| } | |
| }; | |
| })(), | |
| WritingAssistantTab: (function () { | |
| return { | |
| init: function () { | |
| console.info("WritingAssistantTab initialized."); | |
| }, | |
| toggle: function () { | |
| // Placeholder for future functionality | |
| SpaceApp.toastInfo("WritingAssistantTab toggled."); | |
| SpaceApp.unloadLastTab(); | |
| // 找到 id = 'writing-editor' 的元素,设置操作快捷键。 | |
| // Tab - 'btn-action-accept-flow' 按钮点击 | |
| // Shift + Tab - 'btn-action-change-flow' 按钮点击 | |
| // Cmd/Ctrl + Enter - 'btn-action-create-paragraph' 按钮点击 | |
| // Shift + Enter - 'btn-action-change-paragraph' 按钮点击 | |
| const editor = document.getElementById('writing-editor'); | |
| if (editor) { | |
| // 如果已经设置过监听器,就不重复设置 | |
| if (editor.getAttribute('data-listener-set') === 'true') { | |
| console.info("Writing Assistant Editor already has listeners set."); | |
| return; | |
| } | |
| const idToEventFilterList = [ | |
| ['btn-action-change-flow', (e) => e.shiftKey && e.key === 'Tab'], | |
| ['btn-action-accept-flow', (e) => !e.shiftKey && e.key === 'Tab'], | |
| ['btn-action-change-paragraph', (e) => e.shiftKey && e.key === 'Enter'], | |
| ['btn-action-create-paragraph', (e) => (e.metaKey || e.ctrlKey) && e.key === 'Enter'], | |
| ] | |
| editor.addEventListener('keydown', (e) => { | |
| for (const [buttonId, filterFn] of idToEventFilterList) { | |
| if (filterFn(e)) { | |
| e.preventDefault(); | |
| const button = document.getElementById(buttonId); | |
| if (button) { | |
| SpaceApp.toastInfo(`Writing Assistant: Triggered action for ${buttonId}`); | |
| button.click(); | |
| } else { | |
| SpaceApp.toastError(`Writing Assistant: Button with id ${buttonId} not found.`); | |
| } | |
| break; // Only trigger one action per keydown | |
| } | |
| } | |
| }); | |
| // 对已经设置过监听器的 editor,不要重复设置 | |
| editor.setAttribute('data-listener-set', 'true'); | |
| } | |
| } | |
| }; | |
| })(), | |
| registerEventListeners: function() { | |
| const listeners = { | |
| 'tabSelect.chat': () => SpaceApp.TextGeneratorTab.toggle(), | |
| 'tabSelect.code': () => SpaceApp.WebGeneratorTab.toggle(), | |
| 'tabSelect.writing': () => SpaceApp.WritingAssistantTab.toggle(), | |
| }; | |
| 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; | |
| 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> | |