| const query = (obj) => |
| Object.keys(obj) |
| .map((k) => encodeURIComponent(k) + "=" + encodeURIComponent(obj[k])) |
| .join("&"); |
| const url_prefix = document.querySelector("body").getAttribute("data-urlprefix"); |
| const markdown = window.markdownit(); |
| const message_box = document.getElementById(`messages`); |
| const message_input = document.getElementById(`message-input`); |
| const box_conversations = document.querySelector(`.top`); |
| const spinner = box_conversations.querySelector(".spinner"); |
| const stop_generating = document.querySelector(`.stop-generating`); |
| const send_button = document.querySelector(`#send-button`); |
| const user_image = `<img src="${url_prefix}/assets/img/user.png" alt="User Avatar">`; |
| const gpt_image = `<img src="${url_prefix}/assets/img/gpt.png" alt="GPT Avatar">`; |
| let prompt_lock = false; |
|
|
| hljs.addPlugin(new CopyButtonPlugin()); |
|
|
| message_input.addEventListener("blur", () => { |
| window.scrollTo(0, 0); |
| }); |
|
|
| message_input.addEventListener("focus", () => { |
| document.documentElement.scrollTop = document.documentElement.scrollHeight; |
| }); |
|
|
| const delete_conversations = async () => { |
| localStorage.clear(); |
| await new_conversation(); |
| }; |
|
|
| const handle_ask = async () => { |
| message_input.style.height = `80px`; |
| window.scrollTo(0, 0); |
| let message = message_input.value; |
|
|
| if (message.length > 0) { |
| message_input.value = ``; |
| message_input.dispatchEvent(new Event("input")); |
| await ask_gpt(message); |
| } |
| }; |
|
|
| const remove_cancel_button = async () => { |
| stop_generating.classList.add(`stop-generating-hiding`); |
|
|
| setTimeout(() => { |
| stop_generating.classList.remove(`stop-generating-hiding`); |
| stop_generating.classList.add(`stop-generating-hidden`); |
| }, 300); |
| }; |
|
|
| const ask_gpt = async (message) => { |
| try { |
| message_input.value = ``; |
| message_input.innerHTML = ``; |
| message_input.innerText = ``; |
|
|
| add_conversation(window.conversation_id, message.substr(0, 16)); |
| window.scrollTo(0, 0); |
| window.controller = new AbortController(); |
|
|
| jailbreak = document.getElementById("jailbreak"); |
| model = document.getElementById("model"); |
| prompt_lock = true; |
| window.text = ``; |
| window.token = message_id(); |
|
|
| stop_generating.classList.remove(`stop-generating-hidden`); |
|
|
| add_user_message_box(message); |
|
|
| message_box.scrollTop = message_box.scrollHeight; |
| window.scrollTo(0, 0); |
| await new Promise((r) => setTimeout(r, 500)); |
| window.scrollTo(0, 0); |
|
|
| message_box.innerHTML += ` |
| <div class="message"> |
| <div class="avatar-container"> |
| ${gpt_image} |
| </div> |
| <div class="content" id="gpt_${window.token}"> |
| <div id="cursor"></div> |
| </div> |
| </div> |
| `; |
|
|
| message_box.scrollTop = message_box.scrollHeight; |
| window.scrollTo(0, 0); |
| await new Promise((r) => setTimeout(r, 1000)); |
| window.scrollTo(0, 0); |
|
|
| const response = await fetch(`${url_prefix}/backend-api/v2/conversation`, { |
| method: `POST`, |
| signal: window.controller.signal, |
| headers: { |
| "content-type": `application/json`, |
| accept: `text/event-stream`, |
| }, |
| body: JSON.stringify({ |
| conversation_id: window.conversation_id, |
| action: `_ask`, |
| model: model.options[model.selectedIndex].value, |
| jailbreak: jailbreak.options[jailbreak.selectedIndex].value, |
| meta: { |
| id: window.token, |
| content: { |
| conversation: await get_conversation(window.conversation_id), |
| internet_access: document.getElementById("switch").checked, |
| content_type: "text", |
| parts: [ |
| { |
| content: message, |
| role: "user", |
| }, |
| ], |
| }, |
| }, |
| }), |
| }); |
|
|
| const reader = response.body.getReader(); |
|
|
| while (true) { |
| const { value, done } = await reader.read(); |
| if (done) break; |
|
|
| chunk = decodeUnicode(new TextDecoder().decode(value)); |
|
|
| if ( |
| chunk.includes(`<form id="challenge-form" action="${url_prefix}/backend-api/v2/conversation?`) |
| ) { |
| chunk = `cloudflare token expired, please refresh the page.`; |
| } |
|
|
| text += chunk; |
|
|
| document.getElementById(`gpt_${window.token}`).innerHTML = markdown.render(text); |
| document.querySelectorAll(`code`).forEach((el) => { |
| hljs.highlightElement(el); |
| }); |
|
|
| window.scrollTo(0, 0); |
| message_box.scrollTo({ top: message_box.scrollHeight, behavior: "auto" }); |
| } |
|
|
| |
| if (text.includes(`instead. Maintaining this website and API costs a lot of money`)) { |
| document.getElementById(`gpt_${window.token}`).innerHTML = |
| "An error occurred, please reload / refresh cache and try again."; |
| } |
|
|
| add_message(window.conversation_id, "user", message); |
| add_message(window.conversation_id, "assistant", text); |
|
|
| message_box.scrollTop = message_box.scrollHeight; |
| await remove_cancel_button(); |
| prompt_lock = false; |
|
|
| await load_conversations(20, 0); |
| window.scrollTo(0, 0); |
| } catch (e) { |
| add_message(window.conversation_id, "user", message); |
|
|
| message_box.scrollTop = message_box.scrollHeight; |
| await remove_cancel_button(); |
| prompt_lock = false; |
|
|
| await load_conversations(20, 0); |
|
|
| console.log(e); |
|
|
| let cursorDiv = document.getElementById(`cursor`); |
| if (cursorDiv) cursorDiv.parentNode.removeChild(cursorDiv); |
|
|
| if (e.name != `AbortError`) { |
| let error_message = `oops ! something went wrong, please try again / reload. [stacktrace in console]`; |
|
|
| document.getElementById(`gpt_${window.token}`).innerHTML = error_message; |
| add_message(window.conversation_id, "assistant", error_message); |
| } else { |
| document.getElementById(`gpt_${window.token}`).innerHTML += ` [aborted]`; |
| add_message(window.conversation_id, "assistant", text + ` [aborted]`); |
| } |
|
|
| window.scrollTo(0, 0); |
| } |
| }; |
|
|
| const add_user_message_box = (message) => { |
| const messageDiv = createElement("div", { classNames: ["message"] }); |
| const avatarContainer = createElement("div", { classNames: ["avatar-container"], innerHTML: user_image }); |
| const contentDiv = createElement("div", { |
| classNames: ["content"], |
| id: `user_${token}`, |
| textContent: message, |
| }); |
|
|
| messageDiv.append(avatarContainer, contentDiv); |
| message_box.appendChild(messageDiv); |
| }; |
|
|
| const decodeUnicode = (str) => { |
| return str.replace(/\\u([a-fA-F0-9]{4})/g, function (match, grp) { |
| return String.fromCharCode(parseInt(grp, 16)); |
| }); |
| }; |
|
|
| const clear_conversations = async () => { |
| const elements = box_conversations.childNodes; |
| let index = elements.length; |
|
|
| if (index > 0) { |
| while (index--) { |
| const element = elements[index]; |
| if (element.nodeType === Node.ELEMENT_NODE && element.tagName.toLowerCase() !== `button`) { |
| box_conversations.removeChild(element); |
| } |
| } |
| } |
| }; |
|
|
| const clear_conversation = async () => { |
| let messages = message_box.getElementsByTagName(`div`); |
|
|
| while (messages.length > 0) { |
| message_box.removeChild(messages[0]); |
| } |
| }; |
|
|
| const delete_conversation = async (conversation_id) => { |
| localStorage.removeItem(`conversation:${conversation_id}`); |
|
|
| if (window.conversation_id == conversation_id) { |
| await new_conversation(); |
| } |
|
|
| await load_conversations(20, 0, true); |
| }; |
|
|
| const set_conversation = async (conversation_id) => { |
| history.pushState({}, null, `${url_prefix}/chat/${conversation_id}`); |
| window.conversation_id = conversation_id; |
|
|
| await clear_conversation(); |
| await load_conversation(conversation_id); |
| await load_conversations(20, 0, true); |
| }; |
|
|
| const new_conversation = async () => { |
| history.pushState({}, null, `${url_prefix}/chat/`); |
| window.conversation_id = uuid(); |
|
|
| await clear_conversation(); |
| await load_conversations(20, 0, true); |
| }; |
|
|
| const load_conversation = async (conversation_id) => { |
| let conversation = await JSON.parse(localStorage.getItem(`conversation:${conversation_id}`)); |
| console.log(conversation, conversation_id); |
|
|
| for (item of conversation.items) { |
| if (is_assistant(item.role)) { |
| message_box.innerHTML += load_gpt_message_box(item.content); |
| } else { |
| message_box.innerHTML += load_user_message_box(item.content); |
| } |
| } |
|
|
| document.querySelectorAll(`code`).forEach((el) => { |
| hljs.highlightElement(el); |
| }); |
|
|
| message_box.scrollTo({ top: message_box.scrollHeight, behavior: "smooth" }); |
|
|
| setTimeout(() => { |
| message_box.scrollTop = message_box.scrollHeight; |
| }, 500); |
| }; |
|
|
| const load_user_message_box = (content) => { |
| const messageDiv = createElement("div", { classNames: ["message"] }); |
| const avatarContainer = createElement("div", { classNames: ["avatar-container"], innerHTML: user_image }); |
| const contentDiv = createElement("div", { classNames: ["content"] }); |
| const preElement = document.createElement("pre"); |
| preElement.textContent = content; |
| contentDiv.appendChild(preElement); |
|
|
| messageDiv.append(avatarContainer, contentDiv); |
|
|
| return messageDiv.outerHTML; |
| }; |
|
|
| const load_gpt_message_box = (content) => { |
| return ` |
| <div class="message"> |
| <div class="avatar-container"> |
| ${gpt_image} |
| </div> |
| <div class="content"> |
| ${markdown.render(content)} |
| </div> |
| </div> |
| `; |
| }; |
|
|
| const is_assistant = (role) => { |
| return role == "assistant"; |
| }; |
|
|
| const get_conversation = async (conversation_id) => { |
| let conversation = await JSON.parse(localStorage.getItem(`conversation:${conversation_id}`)); |
| return conversation.items; |
| }; |
|
|
| const add_conversation = async (conversation_id, title) => { |
| if (localStorage.getItem(`conversation:${conversation_id}`) == null) { |
| localStorage.setItem( |
| `conversation:${conversation_id}`, |
| JSON.stringify({ |
| id: conversation_id, |
| title: title, |
| items: [], |
| }) |
| ); |
| } |
| }; |
|
|
| const add_message = async (conversation_id, role, content) => { |
| before_adding = JSON.parse(localStorage.getItem(`conversation:${conversation_id}`)); |
|
|
| before_adding.items.push({ |
| role: role, |
| content: content, |
| }); |
|
|
| localStorage.setItem(`conversation:${conversation_id}`, JSON.stringify(before_adding)); |
| }; |
|
|
| const load_conversations = async (limit, offset, loader) => { |
| |
| |
|
|
| let conversations = []; |
| for (let i = 0; i < localStorage.length; i++) { |
| if (localStorage.key(i).startsWith("conversation:")) { |
| let conversation = localStorage.getItem(localStorage.key(i)); |
| conversations.push(JSON.parse(conversation)); |
| } |
| } |
|
|
| |
| await clear_conversations(); |
|
|
| for (conversation of conversations) { |
| box_conversations.innerHTML += ` |
| <div class="conversation-sidebar"> |
| <div class="left" onclick="set_conversation('${conversation.id}')"> |
| <i class="fa-regular fa-comments"></i> |
| <span class="conversation-title">${conversation.title}</span> |
| </div> |
| <i onclick="delete_conversation('${conversation.id}')" class="fa-regular fa-trash"></i> |
| </div> |
| `; |
| } |
|
|
| document.querySelectorAll(`code`).forEach((el) => { |
| hljs.highlightElement(el); |
| }); |
| }; |
|
|
| document.getElementById(`cancelButton`).addEventListener(`click`, async () => { |
| window.controller.abort(); |
| console.log(`aborted ${window.conversation_id}`); |
| }); |
|
|
| function h2a(str1) { |
| var hex = str1.toString(); |
| var str = ""; |
|
|
| for (var n = 0; n < hex.length; n += 2) { |
| str += String.fromCharCode(parseInt(hex.substr(n, 2), 16)); |
| } |
|
|
| return str; |
| } |
|
|
| const uuid = () => { |
| return `xxxxxxxx-xxxx-4xxx-yxxx-${Date.now().toString(16)}`.replace(/[xy]/g, function (c) { |
| var r = (Math.random() * 16) | 0, |
| v = c == "x" ? r : (r & 0x3) | 0x8; |
| return v.toString(16); |
| }); |
| }; |
|
|
| const message_id = () => { |
| random_bytes = (Math.floor(Math.random() * 1338377565) + 2956589730).toString(2); |
| unix = Math.floor(Date.now() / 1000).toString(2); |
|
|
| return BigInt(`0b${unix}${random_bytes}`).toString(); |
| }; |
|
|
| window.onload = async () => { |
| load_settings_localstorage(); |
|
|
| conversations = 0; |
| for (let i = 0; i < localStorage.length; i++) { |
| if (localStorage.key(i).startsWith("conversation:")) { |
| conversations += 1; |
| } |
| } |
|
|
| if (conversations == 0) localStorage.clear(); |
|
|
| await setTimeout(() => { |
| load_conversations(20, 0); |
| }, 1); |
|
|
| if (!window.location.href.endsWith(`#`)) { |
| if (/\/chat\/.+/.test(window.location.href.slice(url_prefix.length))) { |
| await load_conversation(window.conversation_id); |
| } |
| } |
|
|
| message_input.addEventListener("keydown", async (evt) => { |
| if (prompt_lock) return; |
|
|
| if (evt.key === "Enter" && !evt.shiftKey) { |
| evt.preventDefault(); |
| await handle_ask(); |
| } |
| }); |
|
|
| send_button.addEventListener("click", async (event) => { |
| event.preventDefault(); |
| if (prompt_lock) return; |
| message_input.blur(); |
| await handle_ask(); |
| }); |
|
|
| register_settings_localstorage(); |
| }; |
|
|
| const register_settings_localstorage = async () => { |
| settings_ids = ["switch", "model", "jailbreak"]; |
| settings_elements = settings_ids.map((id) => document.getElementById(id)); |
| settings_elements.map((element) => |
| element.addEventListener(`change`, async (event) => { |
| switch (event.target.type) { |
| case "checkbox": |
| localStorage.setItem(event.target.id, event.target.checked); |
| break; |
| case "select-one": |
| localStorage.setItem(event.target.id, event.target.selectedIndex); |
| break; |
| default: |
| console.warn("Unresolved element type"); |
| } |
| }) |
| ); |
| }; |
|
|
| const load_settings_localstorage = async () => { |
| settings_ids = ["switch", "model", "jailbreak"]; |
| settings_elements = settings_ids.map((id) => document.getElementById(id)); |
| settings_elements.map((element) => { |
| if (localStorage.getItem(element.id)) { |
| switch (element.type) { |
| case "checkbox": |
| element.checked = localStorage.getItem(element.id) === "true"; |
| break; |
| case "select-one": |
| element.selectedIndex = parseInt(localStorage.getItem(element.id)); |
| break; |
| default: |
| console.warn("Unresolved element type"); |
| } |
| } |
| }); |
| }; |
|
|
| function clearTextarea(textarea) { |
| textarea.style.removeProperty("height"); |
| textarea.style.height = `${textarea.scrollHeight + 4}px`; |
| if (textarea.value.trim() === "" && textarea.value.includes("\n")) { |
| textarea.value = ""; |
| } |
| } |
|
|
| function createElement(tag, { classNames, id, innerHTML, textContent } = {}) { |
| const el = document.createElement(tag); |
| if (classNames) { |
| el.classList.add(...classNames); |
| } |
| if (id) { |
| el.id = id; |
| } |
| if (innerHTML) { |
| el.innerHTML = innerHTML; |
| } |
| if (textContent) { |
| const preElement = document.createElement("pre"); |
| preElement.textContent = textContent; |
| el.appendChild(preElement); |
| } |
| return el; |
| } |
|
|