diff options
Diffstat (limited to 'g4f/gui/client')
-rw-r--r-- | g4f/gui/client/index.html | 24 | ||||
-rw-r--r-- | g4f/gui/client/static/css/style.css | 44 | ||||
-rw-r--r-- | g4f/gui/client/static/js/chat.v1.js | 131 |
3 files changed, 162 insertions, 37 deletions
diff --git a/g4f/gui/client/index.html b/g4f/gui/client/index.html index 31107a6b..440d8922 100644 --- a/g4f/gui/client/index.html +++ b/g4f/gui/client/index.html @@ -49,7 +49,7 @@ <body> <div class="gradient"></div> <div class="row"> - <div class="box conversations hidden"> + <div class="box conversations"> <div class="top"> <button class="new_convo" onclick="new_conversation()"> <i class="fa-regular fa-plus"></i> @@ -62,8 +62,13 @@ <span>Open Settings</span> </button> <div class="info"> + <i class="fa-brands fa-discord"></i> + <span class="convo-title">discord ~ <a href="https://discord.gg/XfybzPXPH5">discord.gg/XfybzPXPH5</a> + </span> + </div> + <div class="info"> <i class="fa-brands fa-github"></i> - <span class="convo-title">github ~ <a href="https://github.com/xtekky/gpt4free">@gpt4free</a> + <span class="convo-title">github ~ <a href="https://github.com/xtekky/gpt4free">@xtekky/gpt4free</a> </span> </div> <div class="info"> @@ -73,6 +78,7 @@ </div> </div> <div class="settings hidden"> + <div class="paper"> <div class="field"> <span class="label">Web Access</span> <input type="checkbox" id="switch" /> @@ -91,9 +97,16 @@ <div class="field"> <span class="label">Auto continue</span> <input id="auto_continue" type="checkbox" name="auto_continue" checked/> - <label for="auto_continue" class="toogle" title="Continue long responses in OpenaiChat"></label> + <label for="auto_continue" class="toogle" title="Continue large responses in OpenaiChat"></label> + </div> + <div class="field box"> + <label for="message-input-height" class="label" title="">Input max. grow height</label> + <input type="number" id="message-input-height" value="200"/> + </div> + <div class="field box"> + <label for="recognition-language" class="label" title="">Speech recognition lang</label> + <input type="text" id="recognition-language" value="" placeholder="navigator.language"/> </div> - <div class="paper"> <div class="field box"> <label for="OpenaiChat-api_key" class="label" title="">OpenaiChat: api_key</label> <textarea id="OpenaiChat-api_key" name="OpenaiChat[api_key]" placeholder="..."></textarea> @@ -170,6 +183,9 @@ <input type="file" id="file" name="file" accept="text/plain, text/html, text/xml, application/json, text/javascript, .sh, .py, .php, .css, .yaml, .sql, .log, .csv, .twig, .md" required/> <i class="fa-solid fa-paperclip"></i> </label> + <label class="micro-label" for="micro"> + <i class="fa-solid fa-microphone-slash"></i> + </label> <div id="send-button"> <i class="fa-solid fa-paper-plane-top"></i> </div> diff --git a/g4f/gui/client/static/css/style.css b/g4f/gui/client/static/css/style.css index 9e02a3ec..ebfe2f2d 100644 --- a/g4f/gui/client/static/css/style.css +++ b/g4f/gui/client/static/css/style.css @@ -88,6 +88,8 @@ body { background: var(--colour-1); color: var(--colour-3); height: 100vh; + max-width: 1600px; + margin: auto; } .row { @@ -106,10 +108,6 @@ body { border: 1px solid var(--blur-border); } -.hidden { - display: none; -} - .conversations { max-width: 280px; padding: var(--section-gap); @@ -138,8 +136,7 @@ body { } .conversation .user-input { - max-height: 200px; - margin-bottom: 10px; + margin-bottom: 4px; } .conversation .user-input input { @@ -504,7 +501,8 @@ body { display: none; } -.file-label { +.file-label, +.micro-label { cursor: pointer; position: absolute; top: 10px; @@ -512,7 +510,8 @@ body { } .file-label:has(> input:valid), -.file-label.selected { +.file-label.selected, +.micro-label.recognition { color: var(--accent); } @@ -520,11 +519,12 @@ label[for="image"] { top: 32px; } -label[for="camera"] { +label[for="micro"] { top: 54px; } label[for="camera"] { + top: 74px; display: none; } @@ -597,7 +597,7 @@ label[for="camera"] { align-items: center; justify-content: left; width: 100%; - margin-bottom: 2px; + margin-bottom: 4px; } .field { @@ -668,7 +668,7 @@ select { display: flex; flex-direction: column; gap: 10px; - margin: 4px; + margin: 4px 0; } .bottom_buttons button { @@ -841,7 +841,7 @@ a:-webkit-any-link { color: var(--colour-3); resize: vertical; - max-height: 150px; + max-height: 200px; min-height: 80px; } @@ -1080,6 +1080,19 @@ a:-webkit-any-link { padding: var(--inner-gap) 0; } +.settings input { + background-color: transparent; + padding: 2px; + border: none; + font-size: 15px; + width: 100%; + color: var(--colour-3); +} + +.settings input:focus { + outline: none; +} + .settings .label { font-size: 15px; padding: var(--inner-gap) 0; @@ -1100,7 +1113,7 @@ a:-webkit-any-link { border-radius: 5px; } ::-webkit-scrollbar-thumb:hover { - background: var(--accent) + background: var(--accent); } .hljs { @@ -1114,8 +1127,13 @@ a:-webkit-any-link { #message-input { height: 82px; margin-left: 20px; + max-height: 200px; } #message-input::-webkit-scrollbar { width: 5px; +} + +.hidden { + display: none; }
\ No newline at end of file diff --git a/g4f/gui/client/static/js/chat.v1.js b/g4f/gui/client/static/js/chat.v1.js index e0ba020f..87a06aa6 100644 --- a/g4f/gui/client/static/js/chat.v1.js +++ b/g4f/gui/client/static/js/chat.v1.js @@ -10,6 +10,7 @@ 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"); @@ -81,7 +82,7 @@ const register_message_buttons = async () => { if (!("click" in el.dataset)) { el.dataset.click = "true"; el.addEventListener("click", async () => { - const message_el = el.parentElement.parentElement; + 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"); @@ -552,8 +553,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) => { @@ -586,8 +589,9 @@ const remove_message = async (conversation_id, index) => { }; const get_message = async (conversation_id, index) => { - const conversation = await get_conversation(conversation_id); - return conversation.items[index]["content"]; + const messages = await get_messages(conversation_id); + if (index in messages) + return messages[index]["content"]; }; const add_message = async (conversation_id, role, content, provider) => { @@ -707,18 +711,27 @@ function open_settings() { const register_settings_storage = async () => { optionElements.forEach((element) => { - element.addEventListener('change', async (event) => { - switch (event.target.type) { - case "checkbox": - appStorage.setItem(element.id, event.target.checked); - break; - case "select-one": - appStorage.setItem(element.id, event.target.selectedIndex); - break; - default: - console.warn("Unresolved element type"); - } - }); + 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"); + } + }); + } }); } @@ -735,6 +748,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"); } @@ -831,7 +848,7 @@ systemPrompt.addEventListener("focus", function() { countFocus = systemPrompt; count_input(); }); -systemPrompt.addEventListener("blur", function() { +systemPrompt.addEventListener("input", function() { countFocus = messageInput; count_input(); }); @@ -911,6 +928,15 @@ async function on_api() { 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() { @@ -980,7 +1006,7 @@ fileInput.addEventListener('change', async (event) => { } }); -systemPrompt?.addEventListener("blur", async () => { +systemPrompt?.addEventListener("input", async () => { await save_system_message(); }); @@ -1092,7 +1118,7 @@ function save_storage() { } } 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{ @@ -1103,4 +1129,69 @@ 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 timeoutHandle; + recognition.onstart = function() { + microLabel.classList.add("recognition"); + startValue = messageInput.value; + 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 notFinal = ""; + event.results.forEach((result) => { + const newText = result[0].transcript; + if (newText) { + if (result.isFinal) { + messageInput.value = `${startValue ? startValue+"\n" : ""}${newText.trim()}`; + startValue = messageInput.value; + notFinal = ""; + messageInput.focus(); + } else { + notFinal += newText; + messageInput.value = `${startValue ? startValue+"\n" : ""}${notFinal.trim()}`; + } + messageInput.style.height = messageInput.scrollHeight + "px"; + messageInput.scrollTop = messageInput.scrollHeight; + } + }); + window.clearTimeout(timeoutHandle); + timeoutHandle = window.setTimeout(may_stop, notFinal ? 5000 : 8000); + }; + + microLabel.addEventListener("click", () => { + if (microLabel.classList.contains("recognition")) { + window.clearTimeout(timeoutHandle); + recognition.stop(); + } else { + const lang = document.getElementById("recognition-language")?.value || navigator.language; + recognition.lang = lang; + recognition.start(); + } + }); }
\ No newline at end of file |