summaryrefslogtreecommitdiffstats
path: root/g4f
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--g4f/Provider/You.py2
-rw-r--r--g4f/gui/client/static/css/style.css40
-rw-r--r--g4f/gui/client/static/js/chat.v1.js204
-rw-r--r--g4f/providers/base_provider.py5
-rw-r--r--g4f/requests/aiohttp.py7
-rw-r--r--g4f/requests/curl_cffi.py18
6 files changed, 175 insertions, 101 deletions
diff --git a/g4f/Provider/You.py b/g4f/Provider/You.py
index cfa2c7bf..7816b07a 100644
--- a/g4f/Provider/You.py
+++ b/g4f/Provider/You.py
@@ -10,8 +10,6 @@ from .base_provider import AsyncGeneratorProvider, ProviderModelMixin
from .helper import format_prompt
from ..image import ImageResponse, to_bytes, is_accepted_format
from ..requests import StreamSession, FormData, raise_for_status
-from ..errors import MissingRequirementsError
-
from .you.har_file import get_dfp_telemetry_id
class You(AsyncGeneratorProvider, ProviderModelMixin):
diff --git a/g4f/gui/client/static/css/style.css b/g4f/gui/client/static/css/style.css
index 8e967806..058fd521 100644
--- a/g4f/gui/client/static/css/style.css
+++ b/g4f/gui/client/static/css/style.css
@@ -201,6 +201,7 @@ body {
}
.conversations .convo .left {
+ width: 100%;
cursor: pointer;
display: flex;
align-items: center;
@@ -226,9 +227,11 @@ body {
.convo-title {
color: var(--colour-3);
font-size: 14px;
+ max-width: 100%;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
+ margin-right: 10px;
}
.convo-title .datetime {
@@ -406,7 +409,7 @@ body {
.count_total {
font-size: 12px;
- padding-left: 100px;
+ padding-left: 25px;
padding-top: 10px;
}
@@ -646,6 +649,21 @@ select {
width: 160px;
}
+#systemPrompt, .settings textarea {
+ font-size: 15px;
+ width: 100%;
+ color: var(--colour-3);
+ min-height: 50px;
+ height: 59px;
+ outline: none;
+ padding: var(--inner-gap) var(--section-gap);
+ resize: vertical;
+}
+
+#systemPrompt {
+ padding-left: 35px;
+}
+
@media only screen and (min-width: 40em) {
select {
width: 200px;
@@ -659,6 +677,9 @@ select {
.settings .bottom_buttons {
flex-direction: row;
}
+ .count_total {
+ padding-left: 98px;
+ }
}
.input-box {
@@ -836,6 +857,10 @@ ul {
.mobile-sidebar {
display: flex !important;
}
+
+ #systemPrompt {
+ padding-left: 48px;
+ }
}
.shown {
@@ -1064,22 +1089,11 @@ a:-webkit-any-link {
border: 1px solid #e4d4ffc9;
}
-#systemPrompt, .settings textarea {
- font-size: 15px;
- width: 100%;
- color: var(--colour-3);
- min-height: 50px;
- height: 59px;
- outline: none;
- padding: var(--inner-gap) var(--section-gap);
- resize: vertical;
-}
-
.settings textarea {
height: 51px;
}
-.settings {
+.settings, .images {
width: 100%;
display: flex;
flex-direction: column;
diff --git a/g4f/gui/client/static/js/chat.v1.js b/g4f/gui/client/static/js/chat.v1.js
index 628d0682..d07ca4ea 100644
--- a/g4f/gui/client/static/js/chat.v1.js
+++ b/g4f/gui/client/static/js/chat.v1.js
@@ -10,13 +10,14 @@ 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 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 settings = document.querySelector(".settings")
+const systemPrompt = document.getElementById("systemPrompt");
+const settings = document.querySelector(".settings");
+const album = document.querySelector(".images");
let prompt_lock = false;
@@ -49,6 +50,12 @@ const markdown_render = (content) => {
.replaceAll('<code>', '<code class="language-plaintext">')
}
+function filter_message(text) {
+ return text.replaceAll(
+ /<!-- generated images start -->[\s\S]+<!-- generated images end -->/gm, ""
+ )
+}
+
hljs.addPlugin(new CopyButtonPlugin());
let typesetPromise = Promise.resolve();
const highlight = (container) => {
@@ -57,14 +64,15 @@ const highlight = (container) => {
hljs.highlightElement(el);
}
});
- typesetPromise = typesetPromise.then(
- () => MathJax.typesetPromise([container])
- ).catch(
- (err) => console.log('Typeset failed: ' + err.message)
- );
+ if (window.MathJax) {
+ typesetPromise = typesetPromise.then(
+ () => MathJax.typesetPromise([container])
+ ).catch(
+ (err) => console.log('Typeset failed: ' + err.message)
+ );
+ }
}
-let stopped = false;
const register_message_buttons = async () => {
document.querySelectorAll(".message .fa-xmark").forEach(async (el) => {
if (!("click" in el.dataset)) {
@@ -95,69 +103,76 @@ const register_message_buttons = async () => {
if (!("click" in el.dataset)) {
el.dataset.click = "true";
el.addEventListener("click", async () => {
- if ("active" in el.classList || window.doSpeech) {
- el.classList.add("blink")
- stopped = true;
- return;
+ let playlist = [];
+ function play_next() {
+ const next = playlist.shift();
+ if (next)
+ next.play();
}
- if (stopped) {
+ if (el.dataset.stopped) {
el.classList.remove("blink")
- stopped = false;
+ delete el.dataset.stopped;
return;
}
+ if (el.dataset.running) {
+ el.dataset.stopped = true;
+ el.classList.add("blink")
+ playlist = [];
+ return;
+ }
+ el.dataset.running = true;
el.classList.add("blink")
el.classList.add("active")
- const message_el = el.parentElement.parentElement.parentElement;
const content_el = el.parentElement.parentElement;
+ const message_el = content_el.parentElement;
let speechText = await get_message(window.conversation_id, message_el.dataset.index);
+ speechText = speechText.replaceAll(/([^0-9])\./gm, "$1.;");
+ speechText = speechText.replaceAll("?", "?;");
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;
+ speechText = speechText.replaceAll(/```[a-z]+/gm, "");
+ speechText = filter_message(speechText.replaceAll("`", "").replaceAll("#", ""))
+ const lines = speechText.trim().split(/\n|;/).filter(v => v.trim());
+
window.onSpeechResponse = (url) => {
- el.classList.remove("blink")
+ if (!el.dataset.stopped) {
+ el.classList.remove("blink")
+ }
if (url) {
var sound = document.createElement('audio');
sound.controls = 'controls';
sound.src = url;
sound.type = 'audio/wav';
sound.onended = function() {
- ended = true;
+ el.dataset.do_play = true;
+ setTimeout(play_next, 1000);
};
sound.onplay = function() {
- ended = false;
+ delete el.dataset.do_play;
};
var container = document.createElement('div');
container.classList.add("audio");
container.appendChild(sound);
content_el.appendChild(container);
- if (ended && !stopped) {
- sound.play();
+ if (!el.dataset.stopped) {
+ playlist.push(sound);
+ if (el.dataset.do_play) {
+ play_next();
+ }
}
}
- if (lines.length < 1 || stopped) {
+ let line = lines.length > 0 ? lines.shift() : null;
+ if (line && !el.dataset.stopped) {
+ handleGenerateSpeech(line);
+ } else {
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")
+ el.classList.remove("blink");
+ delete el.dataset.running;
}
}
+ el.dataset.do_play = true;
let line = lines.shift();
- return handleGenerateSpeech(line);
+ handleGenerateSpeech(line);
});
}
});
@@ -399,7 +414,7 @@ const ask_gpt = async () => {
provider = "Bing";
let api_key = null;
if (provider)
- api_key = document.getElementById(`${provider}-api_key`)?.value;
+ api_key = document.getElementById(`${provider}-api_key`)?.value || null;
await api("conversation", {
id: window.token,
conversation_id: window.conversation_id,
@@ -564,12 +579,14 @@ const load_conversation = async (conversation_id, scroll=true) => {
`;
}
- const filtered = prepare_messages(messages, false);
- if (filtered.length > 0) {
- last_model = last_model?.startsWith("gpt-4") ? "gpt-4" : "gpt-3.5-turbo"
- let count_total = GPTTokenizer_cl100k_base?.encodeChat(filtered, last_model).length
- if (count_total > 0) {
- elements += `<div class="count_total">(${count_total} tokens used)</div>`;
+ if (window.GPTTokenizer_cl100k_base) {
+ const filtered = prepare_messages(messages, false);
+ if (filtered.length > 0) {
+ last_model = last_model?.startsWith("gpt-4") ? "gpt-4" : "gpt-3.5-turbo"
+ let count_total = GPTTokenizer_cl100k_base?.encodeChat(filtered, last_model).length
+ if (count_total > 0) {
+ elements += `<div class="count_total">(${count_total} tokens used)</div>`;
+ }
}
}
@@ -603,20 +620,15 @@ async function save_conversation(conversation_id, conversation) {
}
async function get_messages(conversation_id) {
- let conversation = await get_conversation(conversation_id);
+ const conversation = await get_conversation(conversation_id);
return conversation?.items || [];
}
async function add_conversation(conversation_id, content) {
- if (content.length > 18) {
- title = content.substring(0, 18) + '...'
- } else {
- title = content + '&nbsp;'.repeat(20 - content.length)
- }
if (appStorage.getItem(`conversation:${conversation_id}`) == null) {
await save_conversation(conversation_id, {
id: conversation_id,
- title: title,
+ title: "",
added: Date.now(),
system: systemPrompt?.value,
items: [],
@@ -690,13 +702,25 @@ const load_conversations = async () => {
conversations.push(JSON.parse(conversation));
}
}
+ conversations.sort((a, b) => (b.updated||0)-(a.updated||0));
await clear_conversations();
- conversations.sort((a, b) => (b.updated||0)-(a.updated||0));
-
let html = "";
conversations.forEach((conversation) => {
+ if (conversation?.items.length > 0) {
+ let old_value = conversation.title;
+ let new_value = (conversation.items[0]["content"]).trim();
+ let new_lenght = new_value.indexOf("\n");
+ new_lenght = new_lenght > 200 || new_lenght < 0 ? 200 : new_lenght;
+ conversation.title = new_value.substring(0, new_lenght);
+ if (conversation.title != old_value) {
+ appStorage.setItem(
+ `conversation:${conversation.id}`,
+ JSON.stringify(conversation)
+ );
+ }
+ }
let updated = "";
if (conversation.updated) {
const date = new Date(conversation.updated);
@@ -717,7 +741,7 @@ const load_conversations = async () => {
</div>
`;
});
- box_conversations.innerHTML = html;
+ box_conversations.innerHTML += html;
};
document.getElementById("cancelButton").addEventListener("click", async () => {
@@ -790,6 +814,17 @@ function open_settings() {
}
}
+function open_album() {
+ if (album.classList.contains("hidden")) {
+ sidebar.classList.remove("shown");
+ settings.classList.add("hidden");
+ album.classList.remove("hidden");
+ history.pushState({}, null, "/images/");
+ } else {
+ album.classList.add("hidden");
+ }
+}
+
const register_settings_storage = async () => {
optionElements.forEach((element) => {
if (element.type == "textarea") {
@@ -891,14 +926,18 @@ colorThemes.forEach((themeOption) => {
function count_tokens(model, text) {
if (model) {
+ if (window.llamaTokenizer)
if (model.startsWith("llama2") || model.startsWith("codellama")) {
- return llamaTokenizer?.encode(text).length;
+ return llamaTokenizer.encode(text).length;
}
+ if (window.mistralTokenizer)
if (model.startsWith("mistral") || model.startsWith("mixtral")) {
- return mistralTokenizer?.encode(text).length;
+ return mistralTokenizer.encode(text).length;
}
}
- return GPTTokenizer_cl100k_base?.encode(text).length;
+ if (window.GPTTokenizer_cl100k_base) {
+ return GPTTokenizer_cl100k_base.encode(text).length;
+ }
}
function count_words(text) {
@@ -1232,12 +1271,12 @@ if (SpeechRecognition) {
}
let startValue;
- let lastValue;
let timeoutHandle;
+ let lastDebounceTranscript;
recognition.onstart = function() {
microLabel.classList.add("recognition");
startValue = messageInput.value;
- lastValue = "";
+ lastDebounceTranscript = "";
timeoutHandle = window.setTimeout(may_stop, 8000);
};
recognition.onend = function() {
@@ -1248,22 +1287,27 @@ if (SpeechRecognition) {
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;
+
+ let result = event.results[event.resultIndex];
+ let isFinal = result.isFinal && (result[0].confidence > 0);
+ let transcript = result[0].transcript;
+ if (isFinal) {
+ if(transcript == lastDebounceTranscript) {
+ return;
}
- });
- window.clearTimeout(timeoutHandle);
- timeoutHandle = window.setTimeout(may_stop, newText ? 8000 : 5000);
+ lastDebounceTranscript = transcript;
+ }
+ if (transcript) {
+ messageInput.value = `${startValue ? startValue+"\n" : ""}${transcript.trim()}`;
+ if (isFinal) {
+ startValue = messageInput.value;
+ messageInput.focus();
+ }
+ messageInput.style.height = messageInput.scrollHeight + "px";
+ messageInput.scrollTop = messageInput.scrollHeight;
+ }
+
+ timeoutHandle = window.setTimeout(may_stop, transcript ? 8000 : 5000);
};
microLabel.addEventListener("click", () => {
diff --git a/g4f/providers/base_provider.py b/g4f/providers/base_provider.py
index f3483fc2..86789ec2 100644
--- a/g4f/providers/base_provider.py
+++ b/g4f/providers/base_provider.py
@@ -78,6 +78,7 @@ class AbstractProvider(BaseProvider):
timeout=kwargs.get("timeout")
)
+ @classmethod
def get_parameters(cls) -> dict:
return signature(
cls.create_async_generator if issubclass(cls, AsyncGeneratorProvider) else
@@ -107,7 +108,9 @@ class AbstractProvider(BaseProvider):
continue
args += f"\n {name}"
args += f": {get_type_name(param.annotation)}" if param.annotation is not Parameter.empty else ""
- args += f' = "{param.default}"' if param.default == "" else f" = {param.default}" if param.default is not Parameter.empty else ""
+ default_value = f'"{param.default}"' if isinstance(param.default, str) else param.default
+ args += f" = {default_value}" if param.default is not Parameter.empty else ""
+ args += ","
return f"g4f.Provider.{cls.__name__} supports: ({args}\n)"
diff --git a/g4f/requests/aiohttp.py b/g4f/requests/aiohttp.py
index 71e7bde7..cdbedef3 100644
--- a/g4f/requests/aiohttp.py
+++ b/g4f/requests/aiohttp.py
@@ -33,9 +33,14 @@ class StreamSession(ClientSession):
**DEFAULT_HEADERS,
**headers
}
+ connect = None
+ if isinstance(timeout, tuple):
+ connect, timeout = timeout;
+ if timeout is not None:
+ timeout = ClientTimeout(timeout, connect)
super().__init__(
**kwargs,
- timeout=ClientTimeout(timeout) if timeout else None,
+ timeout=timeout,
response_class=StreamResponse,
connector=get_connector(connector, proxies.get("all", proxies.get("https"))),
headers=headers
diff --git a/g4f/requests/curl_cffi.py b/g4f/requests/curl_cffi.py
index 91142365..000448fe 100644
--- a/g4f/requests/curl_cffi.py
+++ b/g4f/requests/curl_cffi.py
@@ -1,6 +1,11 @@
from __future__ import annotations
-from curl_cffi.requests import AsyncSession, Response, CurlMime
+from curl_cffi.requests import AsyncSession, Response
+try:
+ from curl_cffi.requests import CurlMime
+ has_curl_mime = True
+except ImportError:
+ has_curl_mime = False
from typing import AsyncGenerator, Any
from functools import partialmethod
import json
@@ -78,6 +83,11 @@ class StreamSession(AsyncSession):
patch = partialmethod(request, "PATCH")
delete = partialmethod(request, "DELETE")
-class FormData(CurlMime):
- def add_field(self, name, data=None, content_type: str = None, filename: str = None) -> None:
- self.addpart(name, content_type=content_type, filename=filename, data=data) \ No newline at end of file
+if has_curl_mime:
+ class FormData(CurlMime):
+ def add_field(self, name, data=None, content_type: str = None, filename: str = None) -> None:
+ self.addpart(name, content_type=content_type, filename=filename, data=data)
+else:
+ class FormData():
+ def __init__(self) -> None:
+ raise RuntimeError("CurlMimi in curl_cffi is missing | pip install -U g4f[curl_cffi]") \ No newline at end of file