diff options
-rw-r--r-- | .gitignore | 5 | ||||
-rw-r--r-- | g4f/Provider/You.py | 2 | ||||
-rw-r--r-- | g4f/gui/client/static/css/style.css | 40 | ||||
-rw-r--r-- | g4f/gui/client/static/js/chat.v1.js | 204 | ||||
-rw-r--r-- | g4f/providers/base_provider.py | 5 | ||||
-rw-r--r-- | g4f/requests/aiohttp.py | 7 | ||||
-rw-r--r-- | g4f/requests/curl_cffi.py | 18 | ||||
-rw-r--r-- | projects/text_to_speech/package.json | 5 | ||||
-rw-r--r-- | setup.py | 3 |
9 files changed, 183 insertions, 106 deletions
@@ -54,4 +54,7 @@ local.py *.gguf image.py .buildozer -hardir
\ No newline at end of file +hardir +node_modules +models +projects/windows/g4f 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 + ' '.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 diff --git a/projects/text_to_speech/package.json b/projects/text_to_speech/package.json index a5da57df..1f79c32e 100644 --- a/projects/text_to_speech/package.json +++ b/projects/text_to_speech/package.json @@ -12,8 +12,5 @@ "pack": "^2.2.0", "web": "^0.0.2", "webpack-cli": "^5.1.4" - }, - "bundleDependencies": [ - "@xenova/transformers" - ] + } } @@ -74,6 +74,9 @@ EXTRA_REQUIRE = { ], "local": [ "gpt4all" + ], + "curl_cffi": [ + "curl_cffi>=0.6.2", ] } |