diff options
Diffstat (limited to 'g4f/gui/client/static/js/chat.v1.js')
-rw-r--r-- | g4f/gui/client/static/js/chat.v1.js | 297 |
1 files changed, 258 insertions, 39 deletions
diff --git a/g4f/gui/client/static/js/chat.v1.js b/g4f/gui/client/static/js/chat.v1.js index a3a1cccf..02437bf7 100644 --- a/g4f/gui/client/static/js/chat.v1.js +++ b/g4f/gui/client/static/js/chat.v1.js @@ -10,18 +10,19 @@ const sendButton = document.getElementById("send-button"); const imageInput = document.getElementById("image"); const cameraInput = document.getElementById("camera"); const fileInput = document.getElementById("file"); +const microLabel = document.querySelector(".micro-label") const inputCount = document.getElementById("input-count") const providerSelect = document.getElementById("provider"); const modelSelect = document.getElementById("model"); const modelProvider = document.getElementById("model2"); const systemPrompt = document.getElementById("systemPrompt") -const jailbreak = document.getElementById("jailbreak"); +const settings = document.querySelector(".settings") let prompt_lock = false; let content, content_inner, content_count = null; -const options = ["switch", "model", "model2", "jailbreak", "patch", "provider", "history"]; +const optionElements = document.querySelectorAll(".settings input, .settings textarea, #model, #model2, #provider") messageInput.addEventListener("blur", () => { window.scrollTo(0, 0); @@ -63,7 +64,8 @@ const highlight = (container) => { ); } -const register_remove_message = async () => { +let stopped = false; +const register_message_buttons = async () => { document.querySelectorAll(".message .fa-xmark").forEach(async (el) => { if (!("click" in el.dataset)) { el.dataset.click = "true"; @@ -77,6 +79,86 @@ const register_remove_message = async () => { }) } }); + document.querySelectorAll(".message .fa-clipboard").forEach(async (el) => { + if (!("click" in el.dataset)) { + el.dataset.click = "true"; + el.addEventListener("click", async () => { + const message_el = el.parentElement.parentElement.parentElement; + const copyText = await get_message(window.conversation_id, message_el.dataset.index); + navigator.clipboard.writeText(copyText); + el.classList.add("clicked"); + setTimeout(() => el.classList.remove("clicked"), 1000); + }) + } + }); + document.querySelectorAll(".message .fa-volume-high").forEach(async (el) => { + if (!("click" in el.dataset)) { + el.dataset.click = "true"; + el.addEventListener("click", async () => { + if ("active" in el.classList || window.doSpeech) { + stopped = true; + return; + } + if (stopped) { + stopped = false; + return; + } + el.classList.add("blink") + el.classList.add("active") + const message_el = el.parentElement.parentElement.parentElement; + const content_el = el.parentElement.parentElement; + let speechText = await get_message(window.conversation_id, message_el.dataset.index); + + speechText = speechText.replaceAll(/\[(.+)\]\(.+\)/gm, "($1)"); + speechText = speechText.replaceAll("`", "").replaceAll("#", "") + speechText = speechText.replaceAll( + /<!-- generated images start -->[\s\S]+<!-- generated images end -->/gm, + "" + ) + + const lines = speechText.trim().split(/\n|\.|;/); + let ended = true; + window.onSpeechResponse = (url) => { + el.classList.remove("blink") + if (url) { + var sound = document.createElement('audio'); + sound.controls = 'controls'; + sound.src = url; + sound.type = 'audio/wav'; + if (ended) { + sound.autoplay = true; + } + sound.onended = function() { + ended = true; + }; + sound.onplay = function() { + ended = false; + }; + var container = document.createElement('div'); + container.classList.add("audio"); + container.appendChild(sound); + content_el.appendChild(container); + } + if (lines.length < 1 || stopped) { + el.classList.remove("active"); + return; + } + while (lines.length > 0) { + let line = lines.shift(); + var reg = new RegExp('^[0-9]$'); + if (line && !reg.test(line)) { + return handleGenerateSpeech(line); + } + } + if (!line) { + el.classList.remove("active") + } + } + let line = lines.shift(); + return handleGenerateSpeech(line); + }); + } + }); } const delete_conversations = async () => { @@ -132,7 +214,11 @@ const handle_ask = async () => { : '' } </div> - <div class="count">${count_words_and_tokens(message, get_selected_model())}</div> + <div class="count"> + ${count_words_and_tokens(message, get_selected_model())} + <i class="fa-solid fa-volume-high"></i> + <i class="fa-regular fa-clipboard"></i> + </div> </div> </div> `; @@ -305,15 +391,22 @@ const ask_gpt = async () => { try { const input = imageInput && imageInput.files.length > 0 ? imageInput : cameraInput; const file = input && input.files.length > 0 ? input.files[0] : null; + const provider = providerSelect.options[providerSelect.selectedIndex].value; + const auto_continue = document.getElementById("auto_continue")?.checked; + if (file && !provider) + provider = "Bing"; + let api_key = null; + if (provider) + api_key = document.getElementById(`${provider}-api_key`)?.value; await api("conversation", { id: window.token, conversation_id: window.conversation_id, model: get_selected_model(), - jailbreak: jailbreak?.options[jailbreak.selectedIndex].value, web_search: document.getElementById("switch").checked, - provider: providerSelect.options[providerSelect.selectedIndex].value, - patch_provider: document.getElementById("patch")?.checked, - messages: messages + provider: provider, + messages: messages, + auto_continue: auto_continue, + api_key: api_key }, file); if (!error) { html = markdown_render(text); @@ -341,7 +434,7 @@ const ask_gpt = async () => { window.scrollTo(0, 0); message_box.scrollTop = message_box.scrollHeight; await remove_cancel_button(); - await register_remove_message(); + await register_message_buttons(); prompt_lock = false; await load_conversations(); regenerate.classList.remove("regenerate-hidden"); @@ -459,7 +552,11 @@ const load_conversation = async (conversation_id, scroll=true) => { <div class="content"> ${provider} <div class="content_inner">${markdown_render(item.content)}</div> - <div class="count">${count_words_and_tokens(item.content, next_provider?.model)}</div> + <div class="count"> + ${count_words_and_tokens(item.content, next_provider?.model)} + <i class="fa-solid fa-volume-high"></i> + <i class="fa-regular fa-clipboard"></i> + </div> </div> </div> `; @@ -475,8 +572,9 @@ const load_conversation = async (conversation_id, scroll=true) => { } message_box.innerHTML = elements; - register_remove_message(); + register_message_buttons(); highlight(message_box); + regenerate.classList.remove("regenerate-hidden"); if (scroll) { message_box.scrollTo({ top: message_box.scrollHeight, behavior: "smooth" }); @@ -495,6 +593,7 @@ async function get_conversation(conversation_id) { } async function save_conversation(conversation_id, conversation) { + conversation.updated = Date.now(); appStorage.setItem( `conversation:${conversation_id}`, JSON.stringify(conversation) @@ -517,6 +616,7 @@ async function add_conversation(conversation_id, content) { await save_conversation(conversation_id, { id: conversation_id, title: title, + added: Date.now(), system: systemPrompt?.value, items: [], }); @@ -530,8 +630,10 @@ async function save_system_message() { return; } const conversation = await get_conversation(window.conversation_id); - conversation.system = systemPrompt?.value; - await save_conversation(window.conversation_id, conversation); + if (conversation) { + conversation.system = systemPrompt?.value; + await save_conversation(window.conversation_id, conversation); + } } const hide_last_message = async (conversation_id) => { @@ -563,6 +665,12 @@ const remove_message = async (conversation_id, index) => { await save_conversation(conversation_id, conversation); }; +const get_message = async (conversation_id, index) => { + const messages = await get_messages(conversation_id); + if (index in messages) + return messages[index]["content"]; +}; + const add_message = async (conversation_id, role, content, provider) => { const conversation = await get_conversation(conversation_id); conversation.items.push({ @@ -586,11 +694,17 @@ const load_conversations = async () => { await clear_conversations(); for (conversation of conversations) { + let updated = ""; + if (conversation.updated) { + const date = new Date(conversation.updated); + updated = date.toLocaleString('en-GB', {dateStyle: 'short', timeStyle: 'short', monthStyle: 'short'}); + updated = updated.replace("/" + date.getFullYear(), "") + } box_conversations.innerHTML += ` <div class="convo" id="convo-${conversation.id}"> <div class="left" onclick="set_conversation('${conversation.id}')"> <i class="fa-regular fa-comments"></i> - <span class="convo-title">${conversation.title}</span> + <span class="convo-title"><span class="datetime">${updated}</span> ${conversation.title}</span> </div> <i onclick="show_option('${conversation.id}')" class="fa-regular fa-trash" id="conv-${conversation.id}"></i> <div id="cho-${conversation.id}" class="choise" style="display:none;"> @@ -642,7 +756,8 @@ const message_id = () => { async function hide_sidebar() { sidebar.classList.remove("shown"); sidebar_button.classList.remove("rotated"); - if (window.location.pathname == "/menu/") { + settings.classList.add("hidden"); + if (window.location.pathname == "/menu/" || window.location.pathname == "/settings/") { history.back(); } } @@ -650,6 +765,7 @@ async function hide_sidebar() { window.addEventListener('popstate', hide_sidebar, false); sidebar_button.addEventListener("click", (event) => { + settings.classList.add("hidden"); if (sidebar.classList.contains("shown")) { hide_sidebar(); } else { @@ -660,31 +776,45 @@ sidebar_button.addEventListener("click", (event) => { window.scrollTo(0, 0); }); +function open_settings() { + if (settings.classList.contains("hidden")) { + sidebar.classList.remove("shown"); + settings.classList.remove("hidden"); + history.pushState({}, null, "/settings/"); + } else { + settings.classList.add("hidden"); + } +} + const register_settings_storage = async () => { - options.forEach((id) => { - element = document.getElementById(id); - if (!element) { - return; + optionElements.forEach((element) => { + if (element.type == "textarea") { + element.addEventListener('input', async (event) => { + appStorage.setItem(element.id, element.value); + }); + } else { + element.addEventListener('change', async (event) => { + switch (element.type) { + case "checkbox": + appStorage.setItem(element.id, element.checked); + break; + case "select-one": + appStorage.setItem(element.id, element.selectedIndex); + break; + case "text": + appStorage.setItem(element.id, element.value); + break; + default: + console.warn("Unresolved element type"); + } + }); } - element.addEventListener('change', async (event) => { - switch (event.target.type) { - case "checkbox": - appStorage.setItem(id, event.target.checked); - break; - case "select-one": - appStorage.setItem(id, event.target.selectedIndex); - break; - default: - console.warn("Unresolved element type"); - } - }); }); } const load_settings_storage = async () => { - options.forEach((id) => { - element = document.getElementById(id); - if (!element || !(value = appStorage.getItem(id))) { + optionElements.forEach((element) => { + if (!(value = appStorage.getItem(element.id))) { return; } if (value) { @@ -695,6 +825,10 @@ const load_settings_storage = async () => { case "select-one": element.selectedIndex = parseInt(value); break; + case "text": + case "textarea": + element.value = value; + break; default: console.warn("Unresolved element type"); } @@ -791,7 +925,7 @@ systemPrompt.addEventListener("focus", function() { countFocus = systemPrompt; count_input(); }); -systemPrompt.addEventListener("blur", function() { +systemPrompt.addEventListener("input", function() { countFocus = messageInput; count_input(); }); @@ -859,6 +993,27 @@ async function on_api() { await load_provider_models(appStorage.getItem("provider")); await load_settings_storage() + + const hide_systemPrompt = document.getElementById("hide-systemPrompt") + if (hide_systemPrompt.checked) { + systemPrompt.classList.add("hidden"); + } + hide_systemPrompt.addEventListener('change', async (event) => { + if (event.target.checked) { + systemPrompt.classList.add("hidden"); + } else { + systemPrompt.classList.remove("hidden"); + } + }); + const messageInputHeight = document.getElementById("message-input-height"); + if (messageInputHeight) { + if (messageInputHeight.value) { + messageInput.style.maxHeight = `${messageInputHeight.value}px`; + } + messageInputHeight.addEventListener('change', async () => { + messageInput.style.maxHeight = `${messageInputHeight.value}px`; + }); + } } async function load_version() { @@ -875,7 +1030,7 @@ async function load_version() { } document.getElementById("version_text").innerHTML = text } -setTimeout(load_version, 5000); +setTimeout(load_version, 2000); for (const el of [imageInput, cameraInput]) { el.addEventListener('click', async () => { @@ -928,7 +1083,7 @@ fileInput.addEventListener('change', async (event) => { } }); -systemPrompt?.addEventListener("blur", async () => { +systemPrompt?.addEventListener("input", async () => { await save_system_message(); }); @@ -1035,12 +1190,12 @@ function save_storage() { let item = appStorage.getItem(key); if (key.startsWith("conversation:")) { data[key] = JSON.parse(item); - } else { + } else if (!key.includes("api_key")) { data["options"][key] = item; } } data = JSON.stringify(data, null, 4); - const blob = new Blob([data], {type: 'text/csv'}); + const blob = new Blob([data], {type: 'application/json'}); if(window.navigator.msSaveOrOpenBlob) { window.navigator.msSaveBlob(blob, filename); } else{ @@ -1051,4 +1206,68 @@ function save_storage() { elem.click(); document.body.removeChild(elem); } +} + +const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition; +if (SpeechRecognition) { + const mircoIcon = microLabel.querySelector("i"); + mircoIcon.classList.add("fa-microphone"); + mircoIcon.classList.remove("fa-microphone-slash"); + + const recognition = new SpeechRecognition(); + recognition.continuous = true; + recognition.interimResults = true; + recognition.maxAlternatives = 1; + + function may_stop() { + if (microLabel.classList.contains("recognition")) { + recognition.stop(); + } + } + + let startValue; + let lastValue; + let timeoutHandle; + recognition.onstart = function() { + microLabel.classList.add("recognition"); + startValue = messageInput.value; + lastValue = ""; + timeoutHandle = window.setTimeout(may_stop, 8000); + }; + recognition.onend = function() { + microLabel.classList.remove("recognition"); + }; + recognition.onresult = function(event) { + if (!event.results) { + return; + } + window.clearTimeout(timeoutHandle); + let newText; + Array.from(event.results).forEach((result) => { + newText = result[0].transcript; + if (newText && newText != lastValue) { + messageInput.value = `${startValue ? startValue+"\n" : ""}${newText.trim()}`; + if (result.isFinal) { + lastValue = newText; + startValue = messageInput.value; + messageInput.focus(); + } + messageInput.style.height = messageInput.scrollHeight + "px"; + messageInput.scrollTop = messageInput.scrollHeight; + } + }); + window.clearTimeout(timeoutHandle); + timeoutHandle = window.setTimeout(may_stop, newText ? 8000 : 5000); + }; + + microLabel.addEventListener("click", () => { + if (microLabel.classList.contains("recognition")) { + window.clearTimeout(timeoutHandle); + recognition.stop(); + } else { + const lang = document.getElementById("recognition-language")?.value; + recognition.lang = lang || navigator.language; + recognition.start(); + } + }); }
\ No newline at end of file |