From 6c422b2965d07e33a4c03fd7e1963316500bd527 Mon Sep 17 00:00:00 2001 From: Heiner Lohaus Date: Sun, 11 Feb 2024 01:59:57 +0100 Subject: Add GPT 4 support in You, Add camera input, Enable logging on debug in GUI, Don't load expired cookies --- g4f/Provider/You.py | 66 ++++++++++++++++++++++++++++++++++++++++-- g4f/cookies.py | 4 ++- g4f/gui/__init__.py | 3 ++ g4f/gui/client/css/style.css | 25 +++++++++++++--- g4f/gui/client/html/index.html | 8 +++-- g4f/gui/client/js/chat.v1.js | 51 ++++++++++++++++++++------------ g4f/gui/server/backend.py | 9 ++++-- 7 files changed, 136 insertions(+), 30 deletions(-) (limited to 'g4f') diff --git a/g4f/Provider/You.py b/g4f/Provider/You.py index 91a195cf..17e6269a 100644 --- a/g4f/Provider/You.py +++ b/g4f/Provider/You.py @@ -1,6 +1,8 @@ from __future__ import annotations import json +import base64 +import uuid from ..requests import StreamSession from ..typing import AsyncGenerator, Messages @@ -11,7 +13,9 @@ class You(AsyncGeneratorProvider): url = "https://you.com" working = True supports_gpt_35_turbo = True - + supports_gpt_4 = True + _session_used = 0 + _session_token = None @classmethod async def create_async_generator( @@ -27,14 +31,70 @@ class You(AsyncGeneratorProvider): "Accept": "text/event-stream", "Referer": f"{cls.url}/search?fromSearchBar=true&tbm=youchat", } - data = {"q": format_prompt(messages), "domain": "youchat", "chat": ""} + data = { + "q": format_prompt(messages), + "domain": "youchat", + "chat": "", "selectedChatMode": "gpt-4" if model == "gpt-4" else "default" + } async with session.get( f"{cls.url}/api/streamingSearch", params=data, - headers=headers + headers=headers, + cookies=cls.get_cookies(await cls.get_session_token(proxy, timeout)) if model == "gpt-4" else None ) as response: response.raise_for_status() start = b'data: {"youChatToken": ' async for line in response.iter_lines(): if line.startswith(start): yield json.loads(line[len(start):-1]) + + @classmethod + async def get_session_token(cls, proxy: str, timeout: int): + if not cls._session_token or cls._session_used >= 5: + cls._session_token = await cls.create_session_token(proxy, timeout) + cls._session_used += 1 + return cls._session_token + + def get_cookies(access_token: str, session_jwt: str = "0"): + return { + 'stytch_session_jwt': session_jwt, + 'ydc_stytch_session': access_token, + 'ydc_stytch_session_jwt': session_jwt + } + + @classmethod + def get_jwt(cls): + return base64.standard_b64encode(json.dumps({ + "event_id":f"event-id-{str(uuid.uuid4())}", + "app_session_id":f"app-session-id-{str(uuid.uuid4())}", + "persistent_id":f"persistent-id-{uuid.uuid4()}", + "client_sent_at":"","timezone":"", + "stytch_user_id":f"user-live-{uuid.uuid4()}", + "stytch_session_id":f"session-live-{uuid.uuid4()}", + "app":{"identifier":"you.com"}, + "sdk":{"identifier":"Stytch.js Javascript SDK","version":"3.3.0" + }}).encode()).decode() + + @classmethod + async def create_session_token(cls, proxy: str, timeout: int): + async with StreamSession(proxies={"https": proxy}, impersonate="chrome110", timeout=timeout) as session: + user_uuid = str(uuid.uuid4()) + auth_uuid = "507a52ad-7e69-496b-aee0-1c9863c7c8" + auth_token = f"public-token-live-{auth_uuid}bb:public-token-live-{auth_uuid}19" + auth = base64.standard_b64encode(auth_token.encode()).decode() + async with session.post( + "https://web.stytch.com/sdk/v1/passwords", + headers={ + "Authorization": f"Basic {auth}", + "X-SDK-Client": cls.get_jwt(), + "X-SDK-Parent-Host": "https://you.com" + }, + json={ + "email": f"{user_uuid}@gmail.com", + "password": f"{user_uuid}#{user_uuid}", + "session_duration_minutes": 129600 + } + ) as response: + if not response.ok: + raise RuntimeError(f"Response: {await response.text()}") + return (await response.json())["data"]["session_token"] \ No newline at end of file diff --git a/g4f/cookies.py b/g4f/cookies.py index af0e6192..a38488c2 100644 --- a/g4f/cookies.py +++ b/g4f/cookies.py @@ -1,6 +1,7 @@ from __future__ import annotations import os +import time try: from platformdirs import user_config_dir @@ -72,7 +73,8 @@ def load_cookies_from_browsers(domain_name: str, raise_requirements_error: bool print(f"Read cookies from {cookie_fn.__name__} for {domain_name}") for cookie in cookie_jar: if cookie.name not in cookies: - cookies[cookie.name] = cookie.value + if not cookie.expires or cookie.expires > time.time(): + cookies[cookie.name] = cookie.value if single_browser and len(cookie_jar): break except BrowserCookieError: diff --git a/g4f/gui/__init__.py b/g4f/gui/__init__.py index 46b4f56a..dff720ac 100644 --- a/g4f/gui/__init__.py +++ b/g4f/gui/__init__.py @@ -7,6 +7,9 @@ except ImportError: raise MissingRequirementsError('Install "flask" package for the gui') def run_gui(host: str = '0.0.0.0', port: int = 8080, debug: bool = False) -> None: + if debug: + import g4f + g4f.debug.logging = True config = { 'host' : host, 'port' : port, diff --git a/g4f/gui/client/css/style.css b/g4f/gui/client/css/style.css index e03f36d2..8752dee5 100644 --- a/g4f/gui/client/css/style.css +++ b/g4f/gui/client/css/style.css @@ -404,7 +404,7 @@ body { display: none; } -#image, #file { +#image, #file, #camera { display: none; } @@ -412,20 +412,37 @@ label[for="image"]:has(> input:valid){ color: var(--accent); } +label[for="camera"]:has(> input:valid){ + color: var(--accent); +} + label[for="file"]:has(> input:valid){ color: var(--accent); } -label[for="image"], label[for="file"] { +label[for="image"], label[for="file"], label[for="camera"] { cursor: pointer; position: absolute; top: 10px; left: 10px; } -label[for="file"] { +label[for="image"] { top: 32px; - left: 10px; +} + +label[for="camera"] { + top: 54px; +} + +label[for="camera"] { + display: none; +} + +@media (pointer:none), (pointer:coarse) { + label[for="camera"] { + display: block; + } } .buttons input[type="checkbox"] { diff --git a/g4f/gui/client/html/index.html b/g4f/gui/client/html/index.html index 55b54b48..175b7dc8 100644 --- a/g4f/gui/client/html/index.html +++ b/g4f/gui/client/html/index.html @@ -114,10 +114,14 @@
-
`; @@ -141,9 +146,10 @@ const ask_gpt = async () => { const headers = { accept: 'text/event-stream' } - if (imageInput && imageInput.files.length > 0) { + const input = imageInput && imageInput.files.length > 0 ? imageInput : cameraInput + if (input && input.files.length > 0) { const formData = new FormData(); - formData.append('image', imageInput.files[0]); + formData.append('image', input.files[0]); formData.append('json', body); body = formData; } else { @@ -211,8 +217,11 @@ const ask_gpt = async () => { message_box.scrollTo({ top: message_box.scrollHeight, behavior: "auto" }); } } - if (!error && imageInput) imageInput.value = ""; - if (!error && fileInput) fileInput.value = ""; + if (!error) { + if (imageInput) imageInput.value = ""; + if (cameraInput) cameraInput.value = ""; + if (fileInput) fileInput.value = ""; + } } catch (e) { console.error(e); @@ -668,21 +677,27 @@ observer.observe(message_input, { attributes: true }); } document.getElementById("version_text").innerHTML = text })() -imageInput.addEventListener('click', async (event) => { - imageInput.value = ''; - delete imageInput.dataset.src; -}); -imageInput.addEventListener('change', async (event) => { - if (imageInput.files.length) { - const reader = new FileReader(); - reader.addEventListener('load', (event) => { - imageInput.dataset.src = event.target.result; - }); - reader.readAsDataURL(imageInput.files[0]); - } else { - delete imageInput.dataset.src; +for (el of [imageInput, cameraInput]) { + console.log(el.files); + el.addEventListener('click', async () => { + el.value = ''; + delete el.dataset.src; + }); + do_load = async () => { + if (el.files.length) { + delete imageInput.dataset.src; + delete cameraInput.dataset.src; + const reader = new FileReader(); + reader.addEventListener('load', (event) => { + el.dataset.src = event.target.result; + console.log(el.dataset.src); + }); + reader.readAsDataURL(el.files[0]); + } } -}); + do_load() + el.addEventListener('change', do_load); +} fileInput.addEventListener('click', async (event) => { fileInput.value = ''; delete fileInput.dataset.text; diff --git a/g4f/gui/server/backend.py b/g4f/gui/server/backend.py index 2218452c..14f6cece 100644 --- a/g4f/gui/server/backend.py +++ b/g4f/gui/server/backend.py @@ -134,25 +134,30 @@ class Backend_Api: dict: Arguments prepared for chat completion. """ kwargs = {} - if 'image' in request.files: + if "image" in request.files: file = request.files['image'] if file.filename != '' and is_allowed_extension(file.filename): kwargs['image'] = to_image(file.stream, file.filename.endswith('.svg')) - if 'json' in request.form: + if "json" in request.form: json_data = json.loads(request.form['json']) else: json_data = request.json provider = json_data.get('provider', '').replace('g4f.Provider.', '') provider = provider if provider and provider != "Auto" else None + + if "image" in kwargs and not provider: + provider = "Bing" if provider == 'OpenaiChat': kwargs['auto_continue'] = True + messages = json_data['messages'] if json_data.get('web_search'): if provider == "Bing": kwargs['web_search'] = True else: messages[-1]["content"] = get_search_message(messages[-1]["content"]) + model = json_data.get('model') model = model if model else models.default patch = patch_provider if json_data.get('patch_provider') else None -- cgit v1.2.3 From daf2b6ac3bf607f4f2e279545125c7559edb450e Mon Sep 17 00:00:00 2001 From: Heiner Lohaus Date: Sun, 11 Feb 2024 03:33:02 +0100 Subject: Fix convert image to jpg in Bing Fix display upload image in GUI --- g4f/gui/client/js/chat.v1.js | 3 +-- g4f/image.py | 28 +++++++++++++++------------- 2 files changed, 16 insertions(+), 15 deletions(-) (limited to 'g4f') diff --git a/g4f/gui/client/js/chat.v1.js b/g4f/gui/client/js/chat.v1.js index 4b2f1c1a..a925572a 100644 --- a/g4f/gui/client/js/chat.v1.js +++ b/g4f/gui/client/js/chat.v1.js @@ -677,7 +677,7 @@ observer.observe(message_input, { attributes: true }); } document.getElementById("version_text").innerHTML = text })() -for (el of [imageInput, cameraInput]) { +for (const el of [imageInput, cameraInput]) { console.log(el.files); el.addEventListener('click', async () => { el.value = ''; @@ -690,7 +690,6 @@ for (el of [imageInput, cameraInput]) { const reader = new FileReader(); reader.addEventListener('load', (event) => { el.dataset.src = event.target.result; - console.log(el.dataset.src); }); reader.readAsDataURL(el.files[0]); } diff --git a/g4f/image.py b/g4f/image.py index 31c3d577..93922c2e 100644 --- a/g4f/image.py +++ b/g4f/image.py @@ -137,12 +137,12 @@ def get_orientation(image: Image) -> int: if orientation is not None: return orientation -def process_image(img: Image, new_width: int, new_height: int) -> Image: +def process_image(image: Image, new_width: int, new_height: int) -> Image: """ Processes the given image by adjusting its orientation and resizing it. Args: - img (Image): The image to process. + image (Image): The image to process. new_width (int): The new width of the image. new_height (int): The new height of the image. @@ -150,25 +150,27 @@ def process_image(img: Image, new_width: int, new_height: int) -> Image: Image: The processed image. """ # Fix orientation - orientation = get_orientation(img) + orientation = get_orientation(image) if orientation: if orientation > 4: - img = img.transpose(FLIP_LEFT_RIGHT) + image = image.transpose(FLIP_LEFT_RIGHT) if orientation in [3, 4]: - img = img.transpose(ROTATE_180) + image = image.transpose(ROTATE_180) if orientation in [5, 6]: - img = img.transpose(ROTATE_270) + image = image.transpose(ROTATE_270) if orientation in [7, 8]: - img = img.transpose(ROTATE_90) + image = image.transpose(ROTATE_90) # Resize image - img.thumbnail((new_width, new_height)) + image.thumbnail((new_width, new_height)) # Remove transparency - if img.mode == "RGBA": - img.load() - white = new_image('RGB', img.size, (255, 255, 255)) - white.paste(img, mask=img.split()[-1]) + if image.mode == "RGBA": + image.load() + white = new_image('RGB', image.size, (255, 255, 255)) + white.paste(image, mask=image.split()[-1]) return white - return img + elif image.mode != "RGB": + image = image.convert("RGB") + return image def to_base64_jpg(image: Image, compression_rate: float) -> str: """ -- cgit v1.2.3 From 331826b003fae451c70e17807d1a105af1453413 Mon Sep 17 00:00:00 2001 From: Heiner Lohaus Date: Sun, 11 Feb 2024 06:59:53 +0100 Subject: Add upload image in You provider --- g4f/gui/client/html/index.html | 3 ++- g4f/gui/server/backend.py | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) (limited to 'g4f') diff --git a/g4f/gui/client/html/index.html b/g4f/gui/client/html/index.html index 175b7dc8..d91e9b77 100644 --- a/g4f/gui/client/html/index.html +++ b/g4f/gui/client/html/index.html @@ -114,7 +114,7 @@
-
diff --git a/g4f/gui/server/backend.py b/g4f/gui/server/backend.py index 14f6cece..6847be34 100644 --- a/g4f/gui/server/backend.py +++ b/g4f/gui/server/backend.py @@ -138,6 +138,7 @@ class Backend_Api: file = request.files['image'] if file.filename != '' and is_allowed_extension(file.filename): kwargs['image'] = to_image(file.stream, file.filename.endswith('.svg')) + kwargs['image_name'] = file.filename if "json" in request.form: json_data = json.loads(request.form['json']) else: -- cgit v1.2.3 From 98c7b5ed68e78bf8e4854d0c304430074efaa44b Mon Sep 17 00:00:00 2001 From: Heiner Lohaus Date: Sun, 11 Feb 2024 07:48:07 +0100 Subject: Add upload image support in You --- g4f/Provider/You.py | 155 +++++++++++++++++++++++++++++++++++----------------- 1 file changed, 105 insertions(+), 50 deletions(-) (limited to 'g4f') diff --git a/g4f/Provider/You.py b/g4f/Provider/You.py index 17e6269a..e1ae7f9e 100644 --- a/g4f/Provider/You.py +++ b/g4f/Provider/You.py @@ -3,67 +3,114 @@ from __future__ import annotations import json import base64 import uuid +from aiohttp import ClientSession, FormData -from ..requests import StreamSession -from ..typing import AsyncGenerator, Messages -from .base_provider import AsyncGeneratorProvider, format_prompt - +from ..typing import AsyncGenerator, Messages, ImageType, Cookies +from .base_provider import AsyncGeneratorProvider +from .helper import get_connector, format_prompt +from ..image import to_bytes +from ..defaults import DEFAULT_HEADERS class You(AsyncGeneratorProvider): url = "https://you.com" working = True supports_gpt_35_turbo = True supports_gpt_4 = True - _session_used = 0 - _session_token = None + _cookies = None + _cookies_used = 0 @classmethod async def create_async_generator( cls, model: str, messages: Messages, + image: ImageType = None, + image_name: str = None, proxy: str = None, - timeout: int = 120, + chat_mode: str = "default", **kwargs, ) -> AsyncGenerator: - async with StreamSession(proxies={"https": proxy}, impersonate="chrome107", timeout=timeout) as session: + async with ClientSession( + connector=get_connector(kwargs.get("connector"), proxy), + headers=DEFAULT_HEADERS + ) as client: + if image: + chat_mode = "agent" + elif model == "gpt-4": + chat_mode = model + cookies = await cls.get_cookies(client) if chat_mode != "default" else None + upload = json.dumps([await cls.upload_file(client, cookies, to_bytes(image), image_name)]) if image else "" + #questions = [message["content"] for message in messages if message["role"] == "user"] + # chat = [ + # {"question": questions[idx-1], "answer": message["content"]} + # for idx, message in enumerate(messages) + # if message["role"] == "assistant" + # and idx < len(questions) + # ] headers = { "Accept": "text/event-stream", "Referer": f"{cls.url}/search?fromSearchBar=true&tbm=youchat", } data = { + "userFiles": upload, + "selectedChatMode": chat_mode, "q": format_prompt(messages), "domain": "youchat", - "chat": "", "selectedChatMode": "gpt-4" if model == "gpt-4" else "default" + #"chat": json.dumps(chat), } - async with session.get( + async with client.post( f"{cls.url}/api/streamingSearch", - params=data, + data=data, headers=headers, - cookies=cls.get_cookies(await cls.get_session_token(proxy, timeout)) if model == "gpt-4" else None + cookies=cookies ) as response: response.raise_for_status() - start = b'data: {"youChatToken": ' - async for line in response.iter_lines(): - if line.startswith(start): - yield json.loads(line[len(start):-1]) + async for line in response.content: + if line.startswith(b'event: '): + event = line[7:-1] + elif line.startswith(b'data: '): + if event == b"youChatUpdate" or event == b"youChatToken": + data = json.loads(line[6:-1]) + if event == b"youChatToken" and "youChatToken" in data: + yield data["youChatToken"] + elif event == b"youChatUpdate" and "t" in data: + yield data["t"] @classmethod - async def get_session_token(cls, proxy: str, timeout: int): - if not cls._session_token or cls._session_used >= 5: - cls._session_token = await cls.create_session_token(proxy, timeout) - cls._session_used += 1 - return cls._session_token + async def upload_file(cls, client: ClientSession, cookies: Cookies, file: bytes, filename: str = None) -> dict: + async with client.get( + f"{cls.url}/api/get_nonce", + cookies=cookies, + ) as response: + response.raise_for_status() + upload_nonce = await response.text() + data = FormData() + data.add_field('file', file, filename=filename) + async with client.post( + f"{cls.url}/api/upload", + data=data, + headers={ + "X-Upload-Nonce": upload_nonce, + }, + cookies=cookies + ) as response: + if not response.ok: + raise RuntimeError(f"Response: {await response.text()}") + result = await response.json() + result["user_filename"] = filename + result["size"] = len(file) + return result - def get_cookies(access_token: str, session_jwt: str = "0"): - return { - 'stytch_session_jwt': session_jwt, - 'ydc_stytch_session': access_token, - 'ydc_stytch_session_jwt': session_jwt - } + @classmethod + async def get_cookies(cls, client: ClientSession) -> Cookies: + if not cls._cookies or cls._cookies_used >= 5: + cls._cookies = await cls.create_cookies(client) + cls._cookies_used = 0 + cls._cookies_used += 1 + return cls._cookies @classmethod - def get_jwt(cls): + def get_sdk(cls) -> str: return base64.standard_b64encode(json.dumps({ "event_id":f"event-id-{str(uuid.uuid4())}", "app_session_id":f"app-session-id-{str(uuid.uuid4())}", @@ -75,26 +122,34 @@ class You(AsyncGeneratorProvider): "sdk":{"identifier":"Stytch.js Javascript SDK","version":"3.3.0" }}).encode()).decode() + def get_auth() -> str: + auth_uuid = "507a52ad-7e69-496b-aee0-1c9863c7c8" + auth_token = f"public-token-live-{auth_uuid}bb:public-token-live-{auth_uuid}19" + auth = base64.standard_b64encode(auth_token.encode()).decode() + return f"Basic {auth}", + @classmethod - async def create_session_token(cls, proxy: str, timeout: int): - async with StreamSession(proxies={"https": proxy}, impersonate="chrome110", timeout=timeout) as session: - user_uuid = str(uuid.uuid4()) - auth_uuid = "507a52ad-7e69-496b-aee0-1c9863c7c8" - auth_token = f"public-token-live-{auth_uuid}bb:public-token-live-{auth_uuid}19" - auth = base64.standard_b64encode(auth_token.encode()).decode() - async with session.post( - "https://web.stytch.com/sdk/v1/passwords", - headers={ - "Authorization": f"Basic {auth}", - "X-SDK-Client": cls.get_jwt(), - "X-SDK-Parent-Host": "https://you.com" - }, - json={ - "email": f"{user_uuid}@gmail.com", - "password": f"{user_uuid}#{user_uuid}", - "session_duration_minutes": 129600 - } - ) as response: - if not response.ok: - raise RuntimeError(f"Response: {await response.text()}") - return (await response.json())["data"]["session_token"] \ No newline at end of file + async def create_cookies(cls, client: ClientSession) -> Cookies: + user_uuid = str(uuid.uuid4()) + async with client.post( + "https://web.stytch.com/sdk/v1/passwords", + headers={ + "Authorization": cls.get_auth(), + "X-SDK-Client": cls.get_sdk(), + "X-SDK-Parent-Host": cls.url + }, + json={ + "email": f"{user_uuid}@gmail.com", + "password": f"{user_uuid}#{user_uuid}", + "session_duration_minutes": 129600 + } + ) as response: + if not response.ok: + raise RuntimeError(f"Response: {await response.text()}") + session = (await response.json())["data"] + return { + "stytch_session": session["session_token"], + 'stytch_session_jwt': session["session_jwt"], + 'ydc_stytch_session': session["session_token"], + 'ydc_stytch_session_jwt': session["session_jwt"], + } \ No newline at end of file -- cgit v1.2.3 From 333fb4d2c3e75b0a251fd684adcc229c89004fab Mon Sep 17 00:00:00 2001 From: Heiner Lohaus Date: Sun, 11 Feb 2024 07:59:45 +0100 Subject: Fix select chat mode, fix auth value in You --- g4f/Provider/You.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'g4f') diff --git a/g4f/Provider/You.py b/g4f/Provider/You.py index e1ae7f9e..001f775d 100644 --- a/g4f/Provider/You.py +++ b/g4f/Provider/You.py @@ -53,12 +53,12 @@ class You(AsyncGeneratorProvider): } data = { "userFiles": upload, - "selectedChatMode": chat_mode, "q": format_prompt(messages), "domain": "youchat", + "selectedChatMode": chat_mode, #"chat": json.dumps(chat), } - async with client.post( + async with (client.post if chat_mode == "default" else client.get)( f"{cls.url}/api/streamingSearch", data=data, headers=headers, @@ -126,7 +126,7 @@ class You(AsyncGeneratorProvider): auth_uuid = "507a52ad-7e69-496b-aee0-1c9863c7c8" auth_token = f"public-token-live-{auth_uuid}bb:public-token-live-{auth_uuid}19" auth = base64.standard_b64encode(auth_token.encode()).decode() - return f"Basic {auth}", + return f"Basic {auth}" @classmethod async def create_cookies(cls, client: ClientSession) -> Cookies: -- cgit v1.2.3 From 76ec2064adab572de46a2d11ef0a5e38ef181295 Mon Sep 17 00:00:00 2001 From: Heiner Lohaus Date: Sun, 11 Feb 2024 08:29:23 +0100 Subject: Add disable history button in GUI --- g4f/gui/client/html/index.html | 9 +++++++-- g4f/gui/client/js/chat.v1.js | 9 +++++++-- 2 files changed, 14 insertions(+), 4 deletions(-) (limited to 'g4f') diff --git a/g4f/gui/client/html/index.html b/g4f/gui/client/html/index.html index d91e9b77..70b8c75f 100644 --- a/g4f/gui/client/html/index.html +++ b/g4f/gui/client/html/index.html @@ -168,14 +168,19 @@
- + Web Access
- + Image Generator
+
+ + + Disable History +
diff --git a/g4f/gui/client/js/chat.v1.js b/g4f/gui/client/js/chat.v1.js index a925572a..a243b1de 100644 --- a/g4f/gui/client/js/chat.v1.js +++ b/g4f/gui/client/js/chat.v1.js @@ -100,6 +100,11 @@ const ask_gpt = async () => { delete messages[i]["provider"]; } + // Remove history, if it is selected + if (document.getElementById('history')?.checked) { + messages = [messages[messages.length-1]] + } + window.scrollTo(0, 0); window.controller = new AbortController(); @@ -491,7 +496,7 @@ document.querySelector(".mobile-sidebar").addEventListener("click", (event) => { }); const register_settings_localstorage = async () => { - for (id of ["switch", "model", "jailbreak", "patch", "provider"]) { + for (id of ["switch", "model", "jailbreak", "patch", "provider", "history"]) { element = document.getElementById(id); element.addEventListener('change', async (event) => { switch (event.target.type) { @@ -509,7 +514,7 @@ const register_settings_localstorage = async () => { } const load_settings_localstorage = async () => { - for (id of ["switch", "model", "jailbreak", "patch", "provider"]) { + for (id of ["switch", "model", "jailbreak", "patch", "provider", "history"]) { element = document.getElementById(id); value = localStorage.getItem(element.id); if (value) { -- cgit v1.2.3