summaryrefslogtreecommitdiffstats
path: root/g4f/gui/client
diff options
context:
space:
mode:
Diffstat (limited to 'g4f/gui/client')
-rw-r--r--g4f/gui/client/index.html24
-rw-r--r--g4f/gui/client/static/css/style.css44
-rw-r--r--g4f/gui/client/static/js/chat.v1.js131
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