diff options
author | Heiner Lohaus <hlohaus@users.noreply.github.com> | 2024-04-08 07:24:00 +0200 |
---|---|---|
committer | Heiner Lohaus <hlohaus@users.noreply.github.com> | 2024-04-08 07:24:00 +0200 |
commit | 926ddfd5438ca06840bdff9b9bd21ddcd8863aa9 (patch) | |
tree | b85cf6df7365e5800d9417ce14b010dc0142d727 /g4f | |
parent | Add nodriver to OpenaiChat (diff) | |
download | gpt4free-926ddfd5438ca06840bdff9b9bd21ddcd8863aa9.tar gpt4free-926ddfd5438ca06840bdff9b9bd21ddcd8863aa9.tar.gz gpt4free-926ddfd5438ca06840bdff9b9bd21ddcd8863aa9.tar.bz2 gpt4free-926ddfd5438ca06840bdff9b9bd21ddcd8863aa9.tar.lz gpt4free-926ddfd5438ca06840bdff9b9bd21ddcd8863aa9.tar.xz gpt4free-926ddfd5438ca06840bdff9b9bd21ddcd8863aa9.tar.zst gpt4free-926ddfd5438ca06840bdff9b9bd21ddcd8863aa9.zip |
Diffstat (limited to 'g4f')
-rw-r--r-- | g4f/Provider/HuggingFace.py | 6 | ||||
-rw-r--r-- | g4f/Provider/WhiteRabbitNeo.py | 57 | ||||
-rw-r--r-- | g4f/Provider/__init__.py | 1 | ||||
-rw-r--r-- | g4f/gui/client/index.html | 101 | ||||
-rw-r--r-- | g4f/gui/client/static/css/style.css | 57 | ||||
-rw-r--r-- | g4f/gui/client/static/js/chat.v1.js | 102 | ||||
-rw-r--r-- | g4f/gui/server/api.py | 13 | ||||
-rw-r--r-- | g4f/gui/server/js_api.py | 1 | ||||
-rw-r--r-- | g4f/gui/server/website.py | 12 |
9 files changed, 255 insertions, 95 deletions
diff --git a/g4f/Provider/HuggingFace.py b/g4f/Provider/HuggingFace.py index 647780fd..6a05c26e 100644 --- a/g4f/Provider/HuggingFace.py +++ b/g4f/Provider/HuggingFace.py @@ -13,6 +13,10 @@ class HuggingFace(AsyncGeneratorProvider, ProviderModelMixin): url = "https://huggingface.co/chat" working = True supports_message_history = True + models = [ + "mistralai/Mixtral-8x7B-Instruct-v0.1", + "mistralai/Mistral-7B-Instruct-v0.2" + ] default_model = "mistralai/Mixtral-8x7B-Instruct-v0.1" @classmethod @@ -29,7 +33,7 @@ class HuggingFace(AsyncGeneratorProvider, ProviderModelMixin): temperature: float = 0.7, **kwargs ) -> AsyncResult: - model = cls.get_model(model) + model = cls.get_model(model) if not model else model headers = {} if api_key is not None: headers["Authorization"] = f"Bearer {api_key}" diff --git a/g4f/Provider/WhiteRabbitNeo.py b/g4f/Provider/WhiteRabbitNeo.py new file mode 100644 index 00000000..339434e6 --- /dev/null +++ b/g4f/Provider/WhiteRabbitNeo.py @@ -0,0 +1,57 @@ +from __future__ import annotations + +from aiohttp import ClientSession, BaseConnector + +from ..typing import AsyncResult, Messages, Cookies +from ..requests.raise_for_status import raise_for_status +from .base_provider import AsyncGeneratorProvider +from .helper import get_cookies, get_connector, get_random_string + +class WhiteRabbitNeo(AsyncGeneratorProvider): + url = "https://www.whiterabbitneo.com" + working = True + supports_message_history = True + needs_auth = True + + @classmethod + async def create_async_generator( + cls, + model: str, + messages: Messages, + cookies: Cookies = None, + connector: BaseConnector = None, + proxy: str = None, + **kwargs + ) -> AsyncResult: + if cookies is None: + cookies = get_cookies("www.whiterabbitneo.com") + headers = { + "User-Agent": "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:123.0) Gecko/20100101 Firefox/123.0", + "Accept": "*/*", + "Accept-Language": "de,en-US;q=0.7,en;q=0.3", + "Accept-Encoding": "gzip, deflate, br", + "Referer": f"{cls.url}/", + "Content-Type": "text/plain;charset=UTF-8", + "Origin": cls.url, + "Connection": "keep-alive", + "Sec-Fetch-Dest": "empty", + "Sec-Fetch-Mode": "cors", + "Sec-Fetch-Site": "same-origin", + "TE": "trailers" + } + async with ClientSession( + headers=headers, + cookies=cookies, + connector=get_connector(connector, proxy) + ) as session: + data = { + "messages": messages, + "id": get_random_string(6), + "enhancePrompt": False, + "useFunctions": False + } + async with session.post(f"{cls.url}/api/chat", json=data, proxy=proxy) as response: + await raise_for_status(response) + async for chunk in response.content.iter_any(): + if chunk: + yield chunk.decode(errors="ignore")
\ No newline at end of file diff --git a/g4f/Provider/__init__.py b/g4f/Provider/__init__.py index 1db29e19..2c6512f0 100644 --- a/g4f/Provider/__init__.py +++ b/g4f/Provider/__init__.py @@ -37,6 +37,7 @@ from .Local import Local from .PerplexityLabs import PerplexityLabs from .Pi import Pi from .Vercel import Vercel +from .WhiteRabbitNeo import WhiteRabbitNeo from .You import You import sys diff --git a/g4f/gui/client/index.html b/g4f/gui/client/index.html index 7103b9c3..31107a6b 100644 --- a/g4f/gui/client/index.html +++ b/g4f/gui/client/index.html @@ -57,13 +57,9 @@ </button> </div> <div class="bottom_buttons"> - <button onclick="delete_conversations()"> - <i class="fa-regular fa-trash"></i> - <span>Clear Conversations</span> - </button> - <button onclick="save_storage()"> - <i class="fa-solid fa-download"></i> - <a href="" onclick="return false;">Export Conversations</a> + <button onclick="open_settings();"> + <i class="fa-solid fa-toolbox"></i> + <span>Open Settings</span> </button> <div class="info"> <i class="fa-brands fa-github"></i> @@ -76,22 +72,38 @@ </div> </div> </div> - <div class="settings"> - <div class="field box"> - <label for="OpenaiChat-api_key" class="label" title="">OpenaiChat: access_token</label> - <textarea id="OpenaiChat-api_key" name="OpenaiChat[api_key]" placeholder="..."></textarea> + <div class="settings hidden"> + <div class="field"> + <span class="label">Web Access</span> + <input type="checkbox" id="switch" /> + <label for="switch" class="toogle" title="Add the pages of the first 5 search results to the query."></label> </div> <div class="field"> - <span class="label">OpenaiChat: Auto continue</span> - <input id="OpenaiChat-auto_continue" type="checkbox" name="OpenaiChat[auto_continue]" checked/> - <label for="OpenaiChat-auto_continue" class="toogle" title=""></label> + <span class="label">Disable History</span> + <input type="checkbox" id="history" /> + <label for="history" class="toogle" title="To improve the reaction time or if you have trouble with large conversations."></label> + </div> + <div class="field"> + <span class="label">Hide System prompt</span> + <input type="checkbox" id="hide-systemPrompt" /> + <label for="hide-systemPrompt" class="toogle" title="For more space on phones"></label> + </div> + <div class="field"> + <span class="label">Auto continue</span> + <input id="auto_continue" type="checkbox" name="auto_continue" checked/> + <label for="auto_continue" class="toogle" title="Continue long responses in OpenaiChat"></label> + </div> + <div class="paper"> + <div class="field box"> + <label for="OpenaiChat-api_key" class="label" title="">OpenaiChat: api_key</label> + <textarea id="OpenaiChat-api_key" name="OpenaiChat[api_key]" placeholder="..."></textarea> </div> <div class="field box"> <label for="Bing-api_key" class="label" title="">Bing: "_U" cookie</label> <textarea id="Bing-api_key" name="Bing[api_key]" placeholder="..."></textarea> </div> <div class="field box"> - <label for="Gemini-api_key" class="label" title="">Gemini: Auth cookies</label> + <label for="Gemini-api_key" class="label" title="">Gemini: Cookies</label> <textarea id="Gemini-api_key" name="Gemini[api_key]" placeholder="..."></textarea> </div> <div class="field box"> @@ -99,6 +111,10 @@ <textarea id="Openai-api_key" name="Openai[api_key]" placeholder="..."></textarea> </div> <div class="field box"> + <label for="Openai-api_key" class="label" title="">Groq: api_key</label> + <textarea id="Openai-api_key" name="Groq[api_key]" placeholder="..."></textarea> + </div> + <div class="field box"> <label for="GeminiPro-api_key" class="label" title="">GeminiPro: api_key</label> <textarea id="GeminiPro-api_key" name="GeminiPro[api_key]" placeholder="..."></textarea> </div> @@ -106,6 +122,17 @@ <label for="HuggingFace-api_key" class="label" title="">HuggingFace: api_key</label> <textarea id="HuggingFace-api_key" name="HuggingFace[api_key]" placeholder="..."></textarea> </div> + </div> + <div class="bottom_buttons"> + <button onclick="delete_conversations()"> + <i class="fa-regular fa-trash"></i> + <span>Clear Conversations</span> + </button> + <button onclick="save_storage()"> + <i class="fa-solid fa-download"></i> + <a href="" onclick="return false;">Export Conversations</a> + </button> + </div> </div> <div class="conversation"> <textarea id="systemPrompt" class="box" placeholder="System prompt"></textarea> @@ -161,43 +188,15 @@ <select name="model2" id="model2" class="hidden"></select> </div> <div class="field"> - <select name="jailbreak" id="jailbreak" style="display: none;"> - <option value="default" selected>Set Jailbreak</option> - <option value="gpt-math-1.0">math 1.0</option> - <option value="gpt-dude-1.0">dude 1.0</option> - <option value="gpt-dan-1.0">dan 1.0</option> - <option value="gpt-dan-2.0">dan 2.0</option> - <option value="gpt-dev-2.0">dev 2.0</option> - <option value="gpt-evil-1.0">evil 1.0</option> + <select name="provider" id="provider"> + <option value="">Provider: Auto</option> + <option value="Bing">Bing</option> + <option value="OpenaiChat">OpenaiChat</option> + <option value="Gemini">Gemini</option> + <option value="Liaobots">Liaobots</option> + <option value="You">You</option> + <option value="">----</option> </select> - <div class="field"> - <select name="provider" id="provider"> - <option value="">Provider: Auto</option> - <option value="Bing">Bing</option> - <option value="OpenaiChat">OpenaiChat</option> - <option value="Gemini">Gemini</option> - <option value="Liaobots">Liaobots</option> - <option value="You">You</option> - <option value="">----</option> - </select> - </div> - </div> - <div class="field"> - <input type="checkbox" id="switch" /> - <label for="switch" title="Add the pages of the first 5 search results to the query."></label> - <span class="about">Web Access</span> - </div> - <!-- - <div class="field"> - <input type="checkbox" id="patch" /> - <label for="patch" title="Enable create images with Bing."></label> - <span class="about">Image Generator</span> - </div> - --> - <div class="field"> - <input type="checkbox" id="history" /> - <label for="history" title="To improve the reaction time or if you have trouble with large conversations."></label> - <span class="about">Disable History</span> </div> </div> </div> diff --git a/g4f/gui/client/static/css/style.css b/g4f/gui/client/static/css/style.css index 4db65863..9e02a3ec 100644 --- a/g4f/gui/client/static/css/style.css +++ b/g4f/gui/client/static/css/style.css @@ -84,7 +84,7 @@ body { } body { - padding: var(--section-gap); + padding: 10px; background: var(--colour-1); color: var(--colour-3); height: 100vh; @@ -92,7 +92,7 @@ body { .row { display: flex; - gap: var(--section-gap); + gap: 10px; height: 100%; } @@ -111,7 +111,7 @@ body { } .conversations { - max-width: 260px; + max-width: 280px; padding: var(--section-gap); overflow: auto; flex-shrink: 0; @@ -183,8 +183,8 @@ body { .conversations { display: flex; flex-direction: column; - gap: var(--inner-gap); - padding: var(--inner-gap); + gap: 10px; + padding: 10px; } .conversations .title { @@ -207,7 +207,12 @@ body { cursor: pointer; display: flex; align-items: center; - gap: 10px; + gap: 4px; +} + +.conversations .convo .fa-trash { + position: absolute; + right: 8px; } .conversations .convo .choise { @@ -216,7 +221,7 @@ body { background-color: var(--blur-bg); } -.conversations i { +.conversations i, .bottom_buttons i { color: var(--conversations); cursor: pointer; } @@ -229,6 +234,10 @@ body { white-space: nowrap; } +.convo-title .datetime { + font-size: 10px; +} + .message { width: 100%; overflow-wrap: break-word; @@ -351,10 +360,23 @@ body { cursor: pointer; } +.message .count .fa-clipboard { + z-index: 1000; + cursor: pointer; +} + .message .user .fa-xmark { color: var(--colour-1); } +.message .count .fa-clipboard { + color: var(--colour-3); +} + +.message .count .fa-clipboard.clicked { + color: var(--accent); +} + .message .assistant:hover .fa-xmark, .message .user:hover .fa-xmark { display: block; @@ -556,6 +578,16 @@ label[for="camera"] { background: var(--accent); } +.settings .bottom_buttons { + flex-direction: row; +} + +.settings .bottom_buttons button { + display: inline-block; + max-width: 210px; + width: 100%; +} + .buttons input:checked+label:after { left: calc(100% - 5px - 20px); } @@ -565,6 +597,7 @@ label[for="camera"] { align-items: center; justify-content: left; width: 100%; + margin-bottom: 2px; } .field { @@ -635,6 +668,7 @@ select { display: flex; flex-direction: column; gap: 10px; + margin: 4px; } .bottom_buttons button { @@ -1026,7 +1060,14 @@ a:-webkit-any-link { .settings { width: 100%; - display: none; + min-width: 700px; + display: flex; + flex-direction: column; +} + +.settings .paper { + overflow: auto; + flex-direction: column; } .settings .field { diff --git a/g4f/gui/client/static/js/chat.v1.js b/g4f/gui/client/static/js/chat.v1.js index a3a1cccf..e0ba020f 100644 --- a/g4f/gui/client/static/js/chat.v1.js +++ b/g4f/gui/client/static/js/chat.v1.js @@ -15,13 +15,13 @@ 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 +63,7 @@ const highlight = (container) => { ); } -const register_remove_message = async () => { +const register_message_buttons = async () => { document.querySelectorAll(".message .fa-xmark").forEach(async (el) => { if (!("click" in el.dataset)) { el.dataset.click = "true"; @@ -77,6 +77,18 @@ 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; + 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); + }) + } + }); } const delete_conversations = async () => { @@ -132,7 +144,7 @@ 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-regular fa-clipboard"></i></div> </div> </div> `; @@ -305,15 +317,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 +360,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 +478,7 @@ 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-regular fa-clipboard"></i></div> </div> </div> `; @@ -475,8 +494,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 +515,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 +538,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: [], }); @@ -563,6 +585,11 @@ const remove_message = async (conversation_id, index) => { await save_conversation(conversation_id, conversation); }; +const get_message = async (conversation_id, index) => { + const conversation = await get_conversation(conversation_id); + return conversation.items[index]["content"]; +}; + const add_message = async (conversation_id, role, content, provider) => { const conversation = await get_conversation(conversation_id); conversation.items.push({ @@ -586,11 +613,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 +675,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 +684,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,19 +695,25 @@ 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) => { element.addEventListener('change', async (event) => { switch (event.target.type) { case "checkbox": - appStorage.setItem(id, event.target.checked); + appStorage.setItem(element.id, event.target.checked); break; case "select-one": - appStorage.setItem(id, event.target.selectedIndex); + appStorage.setItem(element.id, event.target.selectedIndex); break; default: console.warn("Unresolved element type"); @@ -682,9 +723,8 @@ const register_settings_storage = async () => { } 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) { @@ -859,6 +899,18 @@ 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"); + } + }); } async function load_version() { @@ -875,7 +927,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 () => { @@ -1035,7 +1087,7 @@ 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; } } diff --git a/g4f/gui/server/api.py b/g4f/gui/server/api.py index 7c09fdbe..2b3f2fb6 100644 --- a/g4f/gui/server/api.py +++ b/g4f/gui/server/api.py @@ -80,13 +80,12 @@ class Api(): Returns: dict: Arguments prepared for chat completion. """ - provider = json_data.get('provider', None) - if "image" in kwargs and provider is None: - provider = "Bing" - if provider == 'OpenaiChat': - kwargs['auto_continue'] = True - + model = json_data.get('model') or models.default + provider = json_data.get('provider') messages = json_data['messages'] + api_key = json_data.get("api_key") + if api_key is not None: + kwargs["api_key"] = api_key if json_data.get('web_search'): if provider == "Bing": kwargs['web_search'] = True @@ -98,8 +97,6 @@ class Api(): if conversation_id and provider in conversations and conversation_id in conversations[provider]: kwargs["conversation"] = conversations[provider][conversation_id] - model = json_data.get('model') or models.default - return { "model": model, "provider": provider, diff --git a/g4f/gui/server/js_api.py b/g4f/gui/server/js_api.py index e5550f27..08eed947 100644 --- a/g4f/gui/server/js_api.py +++ b/g4f/gui/server/js_api.py @@ -1,6 +1,5 @@ from __future__ import annotations -import logging import json import os.path from typing import Iterator diff --git a/g4f/gui/server/website.py b/g4f/gui/server/website.py index 4e611177..5e633674 100644 --- a/g4f/gui/server/website.py +++ b/g4f/gui/server/website.py @@ -4,9 +4,11 @@ from flask import render_template, redirect class Website: def __init__(self, app) -> None: self.app = app + def redirect_home(): + return redirect('/chat') self.routes = { '/': { - 'function': lambda: redirect('/chat'), + 'function': redirect_home, 'methods': ['GET', 'POST'] }, '/chat/': { @@ -17,6 +19,14 @@ class Website: 'function': self._chat, 'methods': ['GET', 'POST'] }, + '/menu/': { + 'function': redirect_home, + 'methods': ['GET', 'POST'] + }, + '/settings/': { + 'function': redirect_home, + 'methods': ['GET', 'POST'] + }, } def _chat(self, conversation_id): |