diff --git a/.gitignore b/.gitignore index a4c228ac..24b9256d 100644 --- a/.gitignore +++ b/.gitignore @@ -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..6bd9c540 100644 --- a/g4f/gui/client/static/css/style.css +++ b/g4f/gui/client/static/css/style.css @@ -646,6 +646,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; @@ -836,6 +851,10 @@ ul { .mobile-sidebar { display: flex !important; } + + #systemPrompt { + padding-left: 48px; + } } .shown { @@ -1064,22 +1083,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..68800bdd 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('', '') } +function filter_message(text) { + return text.replaceAll( + /[\s\S]+/gm, "" + ) +} + hljs.addPlugin(new CopyButtonPlugin()); let typesetPromise = Promise.resolve(); const highlight = (container) => { @@ -64,7 +71,6 @@ const highlight = (container) => { ); } -let stopped = false; const register_message_buttons = async () => { document.querySelectorAll(".message .fa-xmark").forEach(async (el) => { if (!("click" in el.dataset)) { @@ -95,69 +101,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( - /[\s\S]+/gm, - "" - ) + speechText = speechText.replaceAll(/```[a-z]+/gm, ""); + speechText = filter_message(speechText.replaceAll("`", "").replaceAll("#", "")) + const lines = speechText.trim().split(/\n|;/).filter(v => v.trim()); - const lines = speechText.trim().split(/\n|\.|;/); - let ended = true; 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 +412,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, @@ -717,7 +730,7 @@ const load_conversations = async () => { `; }); - box_conversations.innerHTML = html; + box_conversations.innerHTML += html; }; document.getElementById("cancelButton").addEventListener("click", async () => { @@ -790,6 +803,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") { @@ -1232,12 +1256,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 +1272,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" - ] + } } diff --git a/setup.py b/setup.py index 9b3f12d0..7d0fbed0 100644 --- a/setup.py +++ b/setup.py @@ -74,6 +74,9 @@ EXTRA_REQUIRE = { ], "local": [ "gpt4all" + ], + "curl_cffi": [ + "curl_cffi>=0.6.2", ] }