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