const MAX_HISTORY_LENGTH = 32; var key_down_history = []; var currentIndex = -1; var user_input_ta; var gradioContainer = null; var user_input_ta = null; var user_input_tb = null; var userInfoDiv = null; var appTitleDiv = null; var chatbot = null; var chatbotWrap = null; var apSwitch = null; var messageBotDivs = null; var loginUserForm = null; var logginUser = null; var updateToast = null; var sendBtn = null; var cancelBtn = null; var sliders = null; var userLogged = false; var usernameGotten = false; var historyLoaded = false; var updateInfoGotten = false; var isLatestVersion = localStorage.getItem('isLatestVersion') || false; var ga = document.getElementsByTagName("gradio-app"); var targetNode = ga[0]; var isInIframe = (window.self !== window.top); var language = navigator.language.slice(0,2); var currentTime = new Date().getTime(); var forView_i18n = { 'zh': "For viewing only", 'en': "For viewing only", 'ru': "История прошлых сообщений", 'ja': "For viewing only", 'ko': "For viewing only", 'fr': "For viewing only", 'es': "For viewing only", }; var deleteConfirm_i18n_pref = { 'zh': "Are you sure you want to delete ", 'en': "Are you sure you want to delete ", 'ru': "Are you sure you want to delete ", 'ja': "Are you sure you want to delete ", 'ko': "Are you sure you want to delete ", }; var deleteConfirm_i18n_suff = { 'zh': "?", 'en': "?", 'ru': "?", 'ja': "?", 'ko': "?", }; var deleteConfirm_msg_pref = "Are you sure you want to delete "; var deleteConfirm_msg_suff = " ?"; var usingLatest_i18n = { 'zh': "You are using the latest version!", 'en': "You are using the latest version!", 'ru': "Вы используете последнюю версию!", 'ja': "You are using the latest version!", 'ko': "You are using the latest version!", }; // Is gradio page loaded? Can interact with elements? function gradioLoaded(mutations) { for (var i = 0; i < mutations.length; i++) { if (mutations[i].addedNodes.length) { loginUserForm = document.querySelector(".gradio-container > .main > .wrap > .panel > .form") gradioContainer = document.querySelector(".gradio-container"); user_input_tb = document.getElementById('user_input_tb'); userInfoDiv = document.getElementById("user_info"); appTitleDiv = document.getElementById("app_title"); chatbot = document.querySelector('#chuanhu_chatbot'); chatbotWrap = document.querySelector('#chuanhu_chatbot > .wrapper > .wrap'); apSwitch = document.querySelector('.apSwitch input[type="checkbox"]'); updateToast = document.querySelector("#toast-update"); sendBtn = document.getElementById("submit_btn"); cancelBtn = document.getElementById("cancel_btn"); sliders = document.querySelectorAll('input[type="range"]'); if (loginUserForm) { localStorage.setItem("userLogged", true); userLogged = true; } if (gradioContainer && apSwitch) { // Is gradioContainer loaded? adjustDarkMode(); } if (user_input_tb) { // Is user_input_tb loaded? selectHistory(); } if (userInfoDiv && appTitleDiv) { // Are userInfoDiv and appTitleDiv loaded? if (!usernameGotten) { getUserInfo(); } setTimeout(showOrHideUserInfo(), 2000); } if (chatbot) { // Is chatbot loaded? setChatbotHeight(); } if (chatbotWrap) { if (!historyLoaded) { loadHistoryHtml(); } setChatbotScroll(); mObserver.observe(chatbotWrap, { attributes: true, childList: true, subtree: true, characterData: true}); } if (sliders) { setSlider(); } if (updateToast) { const lastCheckTime = localStorage.getItem('lastCheckTime') || 0; const longTimeNoCheck = currentTime - lastCheckTime > 3 * 24 * 60 * 60 * 1000; if (longTimeNoCheck && !updateInfoGotten && !isLatestVersion || isLatestVersion && !updateInfoGotten) { updateLatestVersion(); } } if (cancelBtn) { submitObserver.observe(cancelBtn, { attributes: true, characterData: true}); } } } } function webLocale() { //console.log("webLocale", language); if (forView_i18n.hasOwnProperty(language)) { var forView = forView_i18n[language]; var forViewStyle = document.createElement('style'); forViewStyle.innerHTML = '.wrapper>.wrap>.history-message>:last-child::after { content: "' + forView + '"!important; }'; document.head.appendChild(forViewStyle); } if (deleteConfirm_i18n_pref.hasOwnProperty(language)) { deleteConfirm_msg_pref = deleteConfirm_i18n_pref[language]; deleteConfirm_msg_suff = deleteConfirm_i18n_suff[language]; } } function showConfirmationDialog(a, file, c) { if (file != "") { var result = confirm(deleteConfirm_msg_pref + file + deleteConfirm_msg_suff); if (result) { return [a, file, c]; } } return [a, "CANCELED", c]; } function selectHistory() { user_input_ta = user_input_tb.querySelector("textarea"); if (user_input_ta) { observer.disconnect(); // stop observing disableSendBtn(); // listen to keydown event on textarea user_input_ta.addEventListener("keydown", function (event) { var value = user_input_ta.value.trim(); // check if pressed key is up/down arrow if (event.code === 'ArrowUp' || event.code === 'ArrowDown') { // if arrow pressed and input not empty and history doesn't contain value, do nothing if (value && key_down_history.indexOf(value) === -1) return; // prevent default behavior of arrow press event.preventDefault(); var length = key_down_history.length; if (length === 0) { currentIndex = -1; // if history empty, reset current selected record return; } if (currentIndex === -1) { currentIndex = length; } if (event.code === 'ArrowUp' && currentIndex > 0) { currentIndex--; user_input_ta.value = key_down_history[currentIndex]; } else if (event.code === 'ArrowDown' && currentIndex < length - 1) { currentIndex++; user_input_ta.value = key_down_history[currentIndex]; } user_input_ta.selectionStart = user_input_ta.value.length; user_input_ta.selectionEnd = user_input_ta.value.length; const input_event = new InputEvent("input", { bubbles: true, cancelable: true }); user_input_ta.dispatchEvent(input_event); } else if (event.code === "Enter") { if (value) { currentIndex = -1; if (key_down_history.indexOf(value) === -1) { key_down_history.push(value); if (key_down_history.length > MAX_HISTORY_LENGTH) { key_down_history.shift(); } } } } }); } } function disableSendBtn() { sendBtn.disabled = user_input_ta.value.trim() === ''; user_input_ta.addEventListener('input', () => { sendBtn.disabled = user_input_ta.value.trim() === ''; }); } var username = null; function getUserInfo() { if (usernameGotten) { return; } userLogged = localStorage.getItem('userLogged'); if (userLogged) { username = userInfoDiv.innerText; if (username) { if (username.includes("getting user info...")) { setTimeout(getUserInfo, 500); return; } else if (username === " ") { localStorage.removeItem("username"); localStorage.removeItem("userLogged") userLogged = false; usernameGotten = true; return; } else { username = username.match(/User:\s*(.*)/)[1] || username; localStorage.setItem("username", username); usernameGotten = true; clearHistoryHtml(); } } } } function toggleUserInfoVisibility(shouldHide) { if (userInfoDiv) { if (shouldHide) { userInfoDiv.classList.add("hideK"); } else { userInfoDiv.classList.remove("hideK"); } } } function showOrHideUserInfo() { // bind mouse/touch events to show/hide user info appTitleDiv.addEventListener("mouseenter", function () { toggleUserInfoVisibility(false); }); userInfoDiv.addEventListener("mouseenter", function () { toggleUserInfoVisibility(false); }); sendBtn.addEventListener("mouseenter", function () { toggleUserInfoVisibility(false); }); appTitleDiv.addEventListener("mouseleave", function () { toggleUserInfoVisibility(true); }); userInfoDiv.addEventListener("mouseleave", function () { toggleUserInfoVisibility(true); }); sendBtn.addEventListener("mouseleave", function () { toggleUserInfoVisibility(true); }); appTitleDiv.ontouchstart = function () { toggleUserInfoVisibility(false); }; userInfoDiv.ontouchstart = function () { toggleUserInfoVisibility(false); }; sendBtn.ontouchstart = function () { toggleUserInfoVisibility(false); }; appTitleDiv.ontouchend = function () { setTimeout(function () { toggleUserInfoVisibility(true); }, 3000); }; userInfoDiv.ontouchend = function () { setTimeout(function () { toggleUserInfoVisibility(true); }, 3000); }; sendBtn.ontouchend = function () { setTimeout(function () { toggleUserInfoVisibility(true); }, 3000); // 1 sec delay to hide user info }; // hide user info after 2 seconds setTimeout(function () { toggleUserInfoVisibility(true); }, 2000); } function toggleDarkMode(isEnabled) { if (isEnabled) { document.body.classList.add("dark"); document.body.style.setProperty("background-color", "var(--neutral-950)", "important"); } else { document.body.classList.remove("dark"); document.body.style.backgroundColor = ""; } } function adjustDarkMode() { const darkModeQuery = window.matchMedia("(prefers-color-scheme: dark)"); // set initial state based on current color scheme or saved theme let isDarkMode = localStorage.getItem('darkMode') === 'true' || darkModeQuery.matches; apSwitch.checked = isDarkMode; toggleDarkMode(isDarkMode); // listen for changes to color scheme darkModeQuery.addEventListener("change", (e) => { isDarkMode = e.matches; localStorage.setItem('darkMode', isDarkMode); apSwitch.checked = isDarkMode; toggleDarkMode(isDarkMode); }); // apSwitch = document.querySelector(".apSwitch input[type="checkbox"]"); apSwitch.addEventListener("change", (e) => { isDarkMode = e.target.checked; localStorage.setItem('darkMode', isDarkMode); toggleDarkMode(isDarkMode); }); } function setChatbotHeight() { const screenWidth = window.innerWidth; const statusDisplay = document.querySelector('#status_display'); const statusDisplayHeight = statusDisplay ? statusDisplay.offsetHeight : 0; const vh = window.innerHeight * 0.01; document.documentElement.style.setProperty('--vh', `${vh}px`); if (isInIframe) { chatbot.style.height = `700px`; chatbotWrap.style.maxHeight = `calc(700px - var(--line-sm) * 1rem - 2 * var(--block-label-margin))` } else { if (screenWidth <= 320) { chatbot.style.height = `calc(var(--vh, 1vh) * 100 - ${statusDisplayHeight + 150}px)`; chatbotWrap.style.maxHeight = `calc(var(--vh, 1vh) * 100 - ${statusDisplayHeight + 150}px - var(--line-sm) * 1rem - 2 * var(--block-label-margin))`; } else if (screenWidth <= 499) { chatbot.style.height = `calc(var(--vh, 1vh) * 100 - ${statusDisplayHeight + 100}px)`; chatbotWrap.style.maxHeight = `calc(var(--vh, 1vh) * 100 - ${statusDisplayHeight + 100}px - var(--line-sm) * 1rem - 2 * var(--block-label-margin))`; } else { chatbot.style.height = `calc(var(--vh, 1vh) * 100 - ${statusDisplayHeight + 160}px)`; chatbotWrap.style.maxHeight = `calc(var(--vh, 1vh) * 100 - ${statusDisplayHeight + 160}px - var(--line-sm) * 1rem - 2 * var(--block-label-margin))`; } } } function setChatbotScroll() { var scrollHeight = chatbotWrap.scrollHeight; chatbotWrap.scrollTo(0,scrollHeight) } var rangeInputs = null; var numberInputs = null; function setSlider() { rangeInputs = document.querySelectorAll('input[type="range"]'); numberInputs = document.querySelectorAll('input[type="number"]') setSliderRange(); rangeInputs.forEach(rangeInput => { rangeInput.addEventListener('input', setSliderRange); }); numberInputs.forEach(numberInput => { numberInput.addEventListener('input', setSliderRange); }) } function setSliderRange() { var range = document.querySelectorAll('input[type="range"]'); range.forEach(range => { range.style.backgroundSize = (range.value - range.min) / (range.max - range.min) * 100 + '% 100%'; }); } function addChuanhuButton(botElement) { var rawMessage = null; var mdMessage = null; rawMessage = botElement.querySelector('.raw-message'); mdMessage = botElement.querySelector('.md-message'); if (!rawMessage) { var buttons = botElement.querySelectorAll('button.chuanhu-btn'); for (var i = 0; i < buttons.length; i++) { buttons[i].parentNode.removeChild(buttons[i]); } return; } var oldCopyButton = null; var oldToggleButton = null; oldCopyButton = botElement.querySelector('button.copy-bot-btn'); oldToggleButton = botElement.querySelector('button.toggle-md-btn'); if (oldCopyButton) oldCopyButton.remove(); if (oldToggleButton) oldToggleButton.remove(); // Copy bot button var copyButton = document.createElement('button'); copyButton.classList.add('chuanhu-btn'); copyButton.classList.add('copy-bot-btn'); copyButton.setAttribute('aria-label', 'Copy'); copyButton.innerHTML = copyIcon; copyButton.addEventListener('click', async () => { const textToCopy = rawMessage.innerText; try { if ("clipboard" in navigator) { await navigator.clipboard.writeText(textToCopy); copyButton.innerHTML = copiedIcon; setTimeout(() => { copyButton.innerHTML = copyIcon; }, 1500); } else { const textArea = document.createElement("textarea"); textArea.value = textToCopy; document.body.appendChild(textArea); textArea.select(); try { document.execCommand('copy'); copyButton.innerHTML = copiedIcon; setTimeout(() => { copyButton.innerHTML = copyIcon; }, 1500); } catch (error) { console.error("Error copying: ", error); } document.body.removeChild(textArea); } } catch (error) { console.error("Error copying: ", error); } }); botElement.appendChild(copyButton); // Toggle button var toggleButton = document.createElement('button'); toggleButton.classList.add('chuanhu-btn'); toggleButton.classList.add('toggle-md-btn'); toggleButton.setAttribute('aria-label', 'Toggle'); var renderMarkdown = mdMessage.classList.contains('hideM'); toggleButton.innerHTML = renderMarkdown ? mdIcon : rawIcon; toggleButton.addEventListener('click', () => { renderMarkdown = mdMessage.classList.contains('hideM'); if (renderMarkdown){ renderMarkdownText(botElement); toggleButton.innerHTML=rawIcon; } else { removeMarkdownText(botElement); toggleButton.innerHTML=mdIcon; } }); botElement.insertBefore(toggleButton, copyButton); } function renderMarkdownText(message) { var mdDiv = message.querySelector('.md-message'); if (mdDiv) mdDiv.classList.remove('hideM'); var rawDiv = message.querySelector('.raw-message'); if (rawDiv) rawDiv.classList.add('hideM'); } function removeMarkdownText(message) { var rawDiv = message.querySelector('.raw-message'); if (rawDiv) rawDiv.classList.remove('hideM'); var mdDiv = message.querySelector('.md-message'); if (mdDiv) mdDiv.classList.add('hideM'); } let timeoutId; let isThrottled = false; var mmutation // Observe changes inside DOM var mObserver = new MutationObserver(function (mutationsList) { for (mmutation of mutationsList) { if (mmutation.type === 'childList') { for (var node of mmutation.addedNodes) { if (node.nodeType === 1 && node.classList.contains('message')) { saveHistoryHtml(); disableSendBtn(); document.querySelectorAll('#chuanhu_chatbot .message-wrap .message.bot').forEach(addChuanhuButton); } } for (var node of mmutation.removedNodes) { if (node.nodeType === 1 && node.classList.contains('message')) { saveHistoryHtml(); disableSendBtn(); document.querySelectorAll('#chuanhu_chatbot .message-wrap .message.bot').forEach(addChuanhuButton); } } } else if (mmutation.type === 'attributes') { if (isThrottled) break; // avoid re-rendering too much isThrottled = true; clearTimeout(timeoutId); timeoutId = setTimeout(() => { isThrottled = false; document.querySelectorAll('#chuanhu_chatbot .message-wrap .message.bot').forEach(addChuanhuButton); saveHistoryHtml(); disableSendBtn(); }, 1500); } } }); // mObserver.observe(targetNode, { attributes: true, childList: true, subtree: true, characterData: true}); var submitObserver = new MutationObserver(function (mutationsList) { document.querySelectorAll('#chuanhu_chatbot .message-wrap .message.bot').forEach(addChuanhuButton); saveHistoryHtml(); }); var loadhistorytime = 0; // for debugging function saveHistoryHtml() { var historyHtml = document.querySelector('#chuanhu_chatbot>.wrapper>.wrap'); if (!historyHtml) return; // no history, do nothing localStorage.setItem('chatHistory', historyHtml.innerHTML); // console.log("History saved") historyLoaded = false; } function loadHistoryHtml() { var historyHtml = localStorage.getItem('chatHistory'); if (!historyHtml) { historyLoaded = true; return; // no history, do nothing } userLogged = localStorage.getItem('userLogged'); if (userLogged){ historyLoaded = true; return; // user logged in, do nothing } if (!historyLoaded) { var tempDiv = document.createElement('div'); tempDiv.innerHTML = historyHtml; var buttons = tempDiv.querySelectorAll('button.chuanhu-btn'); var gradioCopyButtons = tempDiv.querySelectorAll('button.copy_code_button'); for (var i = 0; i < buttons.length; i++) { buttons[i].parentNode.removeChild(buttons[i]); } for (var i = 0; i < gradioCopyButtons.length; i++) { gradioCopyButtons[i].parentNode.removeChild(gradioCopyButtons[i]); } var fakeHistory = document.createElement('div'); fakeHistory.classList.add('history-message'); fakeHistory.innerHTML = tempDiv.innerHTML; webLocale(); chatbotWrap.insertBefore(fakeHistory, chatbotWrap.firstChild); // var fakeHistory = document.createElement('div'); // fakeHistory.classList.add('history-message'); // fakeHistory.innerHTML = historyHtml; // chatbotWrap.insertBefore(fakeHistory, chatbotWrap.firstChild); historyLoaded = true; console.log("History loaded"); loadhistorytime += 1; // for debugging } else { historyLoaded = false; } } function clearHistoryHtml() { localStorage.removeItem("chatHistory"); historyMessages = chatbotWrap.querySelector('.history-message'); if (historyMessages) { chatbotWrap.removeChild(historyMessages); console.log("History cleared"); } } var showingUpdateInfo = false; async function getLatestRelease() { try { const response = await fetch('https://api.github.com/repos/gaizhenbiao/chuanhuchatgpt/releases/latest'); if (!response.ok) { console.log(`Error: ${response.status} - ${response.statusText}`); updateInfoGotten = true; return null; } const data = await response.json(); updateInfoGotten = true; return data; } catch (error) { console.log(`Error: ${error}`); updateInfoGotten = true; return null; } } async function updateLatestVersion() { const currentVersionElement = document.getElementById('current-version'); const latestVersionElement = document.getElementById('latest-version-title'); const releaseNoteElement = document.getElementById('release-note-content'); const currentVersion = currentVersionElement.textContent; const versionTime = document.getElementById('version-time').innerText; const localVersionTime = versionTime !== "unknown" ? (new Date(versionTime)).getTime() : 0; updateInfoGotten = true; // only execute once regardless of success to avoid exceeding API limit... try { const data = await getLatestRelease(); const releaseNote = data.body; if (releaseNote) { releaseNoteElement.innerHTML = marked.parse(releaseNote); } const latestVersion = data.tag_name; const latestVersionTime = (new Date(data.created_at)).getTime(); if (latestVersionTime) { if (localVersionTime < latestVersionTime) { latestVersionElement.textContent = latestVersion; console.log(`New version ${latestVersion} found!`); if (!isInIframe) {openUpdateToast();} } else { noUpdate(); } currentTime = new Date().getTime(); localStorage.setItem('lastCheckTime', currentTime); } } catch (error) { console.error(error); } } function getUpdate() { window.open('https://github.com/gaizhenbiao/chuanhuchatgpt/releases/latest', '_blank'); closeUpdateToast(); } function cancelUpdate() { closeUpdateToast(); } function openUpdateToast() { showingUpdateInfo = true; setUpdateWindowHeight(); } function closeUpdateToast() { updateToast.style.setProperty('top', '-500px'); showingUpdateInfo = false; } function manualCheckUpdate() { openUpdateToast(); updateLatestVersion(); currentTime = new Date().getTime(); localStorage.setItem('lastCheckTime', currentTime); } function noUpdate() { localStorage.setItem('isLatestVersion', 'true'); isLatestVersion = true; const versionInfoElement = document.getElementById('version-info-title'); const releaseNoteWrap = document.getElementById('release-note-wrap'); const gotoUpdateBtn = document.getElementById('goto-update-btn'); const closeUpdateBtn = document.getElementById('close-update-btn'); versionInfoElement.textContent = usingLatest_i18n.hasOwnProperty(language) ? usingLatest_i18n[language] : usingLatest_i18n['ru']; releaseNoteWrap.style.setProperty('display', 'none'); gotoUpdateBtn.classList.add('hideK'); closeUpdateBtn.classList.remove('hideK'); } function setUpdateWindowHeight() { if (!showingUpdateInfo) {return;} const scrollPosition = window.scrollY; // const originalTop = updateToast.style.getPropertyValue('top'); const resultTop = scrollPosition - 20 + 'px'; updateToast.style.setProperty('top', resultTop); } // Observe changes in page var observer = new MutationObserver(function (mutations) { gradioLoaded(mutations); }); observer.observe(targetNode, { childList: true, subtree: true }); // Observe changes in page window.addEventListener("DOMContentLoaded", function () { isInIframe = (window.self !== window.top); historyLoaded = false; }); window.addEventListener('resize', setChatbotHeight); window.addEventListener('scroll', function(){setChatbotHeight();setUpdateWindowHeight();}); window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", adjustDarkMode); // console surprise var styleTitle1 = ` font-size: 16px; font-family: ui-monospace,monospace; color: rgb(244,167,89); ` var styleDesc1 = ` font-size: 12px; font-family: ui-monospace,monospace; ` function makeML(str) { let l = new String(str) l = l.substring(l.indexOf("/*") + 3, l.lastIndexOf("*/")) return l } let ChuanhuInfo = function () { /* ________ __ ________ __ / ____/ /_ __ ______ _____ / /_ __ __ / ____/ /_ ____ _/ /_ / / / __ \/ / / / __ `/ __ \/ __ \/ / / / / / / __ \/ __ `/ __/ / /___/ / / / /_/ / /_/ / / / / / / / /_/ / / /___/ / / / /_/ / /_ \____/_/ /_/\__,_/\__,_/_/ /_/_/ /_/\__,_/ \____/_/ /_/\__,_/\__/ Chuanhu Chat - Graphical interface for ChatGPT API and many LLMs */ } let description = ` ` console.log(`%c${makeML(ChuanhuInfo)}`,styleTitle1) console.log(`%c${description}`, styleDesc1) // svg icon codes const copyIcon = ''; const copiedIcon = ''; const mdIcon = ''; const rawIcon = '';