GitHub Action
Sync ling-space changes from GitHub commit 8d05d99
c383152
raw
history blame
13.3 kB
<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>