Merge pull request #185 from lanqian528/dev

Dev to Main
This commit is contained in:
LanQian 2024-11-09 13:17:29 +08:00 committed by GitHub
commit 95f4f5720c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 7698 additions and 1297 deletions

View File

@ -2,12 +2,13 @@ import asyncio
import types
from apscheduler.schedulers.asyncio import AsyncIOScheduler
from fastapi import Request, Depends, HTTPException, Form
from fastapi import Request, HTTPException, Form, Security
from fastapi.responses import HTMLResponse, StreamingResponse, JSONResponse
from fastapi.security import HTTPAuthorizationCredentials
from starlette.background import BackgroundTask
import utils.globals as globals
from app import templates, oauth2_scheme, app
from app import app, templates, security_scheme
from chatgpt.ChatService import ChatService
from chatgpt.authorization import refresh_all_tokens
from utils.Logger import logger
@ -49,7 +50,8 @@ async def process(request_data, req_token):
@app.post(f"/{api_prefix}/v1/chat/completions" if api_prefix else "/v1/chat/completions")
async def send_conversation(request: Request, req_token: str = Depends(oauth2_scheme)):
async def send_conversation(request: Request, credentials: HTTPAuthorizationCredentials = Security(security_scheme)):
req_token = credentials.credentials
try:
request_data = await request.json()
except Exception:
@ -87,7 +89,7 @@ async def upload_post(text: str = Form(...)):
for line in lines:
if line.strip() and not line.startswith("#"):
globals.token_list.append(line.strip())
with open("../data/token.txt", "a", encoding="utf-8") as f:
with open(globals.TOKENS_FILE, "a", encoding="utf-8") as f:
f.write(line.strip() + "\n")
logger.info(f"Token count: {len(globals.token_list)}, Error token count: {len(globals.error_token_list)}")
tokens_count = len(set(globals.token_list) - set(globals.error_token_list))
@ -98,7 +100,7 @@ async def upload_post(text: str = Form(...)):
async def upload_post():
globals.token_list.clear()
globals.error_token_list.clear()
with open("../data/token.txt", "w", encoding="utf-8") as f:
with open(globals.TOKENS_FILE, "w", encoding="utf-8") as f:
pass
logger.info(f"Token count: {len(globals.token_list)}, Error token count: {len(globals.error_token_list)}")
tokens_count = len(set(globals.token_list) - set(globals.error_token_list))
@ -115,7 +117,7 @@ async def error_tokens():
async def add_token(token: str):
if token.strip() and not token.startswith("#"):
globals.token_list.append(token.strip())
with open("../data/token.txt", "a", encoding="utf-8") as f:
with open(globals.TOKENS_FILE, "a", encoding="utf-8") as f:
f.write(token.strip() + "\n")
logger.info(f"Token count: {len(globals.token_list)}, Error token count: {len(globals.error_token_list)}")
tokens_count = len(set(globals.token_list) - set(globals.error_token_list))

32
app.py
View File

@ -1,11 +1,13 @@
import warnings
import uvicorn
from fastapi import FastAPI
from fastapi.security import OAuth2PasswordBearer
from fastapi import FastAPI, HTTPException
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from fastapi.middleware.cors import CORSMiddleware
from fastapi.templating import Jinja2Templates
from utils.configs import enable_gateway, api_prefix
warnings.filterwarnings("ignore")
@ -15,7 +17,11 @@ access_format = r'%(asctime)s | %(levelname)s | %(client_addr)s: %(request_line)
log_config["formatters"]["default"]["fmt"] = default_format
log_config["formatters"]["access"]["fmt"] = access_format
app = FastAPI()
app = FastAPI(
docs_url=f"/{api_prefix}/docs", # 设置 Swagger UI 文档路径
redoc_url=f"/{api_prefix}/redoc", # 设置 Redoc 文档路径
openapi_url=f"/{api_prefix}/openapi.json" # 设置 OpenAPI JSON 路径
)
app.add_middleware(
CORSMiddleware,
@ -26,12 +32,26 @@ app.add_middleware(
)
templates = Jinja2Templates(directory="templates")
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token", auto_error=False)
security_scheme = HTTPBearer()
from app import app
import gateway.backend
import gateway.share
import api.chat2api
if enable_gateway:
import gateway.share
import gateway.login
import gateway.chatgpt
import gateway.gpts
import gateway.admin
import gateway.v1
import gateway.backend
else:
@app.api_route("/{path:path}", methods=["GET", "POST", "PUT", "DELETE", "OPTIONS", "HEAD", "PATCH", "TRACE"])
async def reverse_proxy():
raise HTTPException(status_code=404, detail="Gateway is disabled")
if __name__ == "__main__":
uvicorn.run("app:app", host="0.0.0.0", port=5005)
# uvicorn.run("app:app", host="0.0.0.0", port=5005, ssl_keyfile="key.pem", ssl_certfile="cert.pem")

View File

@ -105,6 +105,9 @@ async def verify_token(req_token):
return access_token
elif len(req_token) == 45:
try:
if req_token in globals.error_token_list:
raise HTTPException(status_code=401, detail="Error RefreshToken")
access_token = await rt2ac(req_token, force_refresh=False)
return access_token
except HTTPException as e:

View File

@ -1,4 +1,3 @@
import threading
import time
from datetime import datetime

0
gateway/admin.py Normal file
View File

View File

@ -1,150 +1,149 @@
import json
import random
import re
import time
import uuid
from fastapi import Request, HTTPException
from fastapi.responses import HTMLResponse, RedirectResponse, Response
from fastapi.responses import RedirectResponse, StreamingResponse, Response
from starlette.background import BackgroundTask
from starlette.concurrency import run_in_threadpool
import utils.globals as globals
from app import app, templates
from gateway.reverseProxy import chatgpt_reverse_proxy
from utils.configs import enable_gateway
from app import app
from chatgpt.authorization import verify_token, get_fp
from chatgpt.proofofWork import get_answer_token, get_config, get_requirements_token
from gateway.chatgpt import chatgpt_html
from gateway.reverseProxy import chatgpt_reverse_proxy, content_generator, get_real_req_token, headers_reject_list
from utils.Client import Client
from utils.Logger import logger
from utils.configs import x_sign, turnstile_solver_url, chatgpt_base_url_list, no_sentinel
with open("templates/remix_context.json", "r", encoding="utf-8") as f:
remix_context = json.load(f)
banned_paths = [
"backend-api/accounts/logout_all",
"backend-api/accounts/deactivate",
"backend-api/user_system_messages",
"backend-api/memories",
"backend-api/settings/clear_account_user_memory",
"backend-api/conversations/[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}"
"backend-api/accounts/[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}/invites",
"admin",
]
redirect_paths = ["auth/logout"]
chatgpt_paths = ["c/[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}"]
def set_value_for_key(data, target_key, new_value):
if isinstance(data, dict):
for key, value in data.items():
if key == target_key:
data[key] = new_value
else:
set_value_for_key(value, target_key, new_value)
elif isinstance(data, list):
for item in data:
set_value_for_key(item, target_key, new_value)
@app.get("/backend-api/accounts/check/v4-2023-04-27")
async def check_account(request: Request):
token = request.headers.get("Authorization").replace("Bearer ", "")
check_account_response = await chatgpt_reverse_proxy(request, "backend-api/accounts/check/v4-2023-04-27")
if len(token) == 45 or token.startswith("eyJhbGciOi"):
return check_account_response
else:
check_account_str = check_account_response.body.decode('utf-8')
check_account_info = json.loads(check_account_str)
for key in check_account_info.get("accounts", {}).keys():
account_id = check_account_info["accounts"][key]["account"]["account_id"]
globals.seed_map[token]["user_id"] = \
check_account_info["accounts"][key]["account"]["account_user_id"].split("__")[0]
check_account_info["accounts"][key]["account"]["account_user_id"] = f"user-chatgpt__{account_id}"
with open(globals.SEED_MAP_FILE, "w", encoding="utf-8") as f:
json.dump(globals.seed_map, f, indent=4)
return check_account_info
if enable_gateway:
@app.get("/", response_class=HTMLResponse)
async def chatgpt_html(request: Request):
token = request.query_params.get("token")
if not token:
token = request.cookies.get("token")
if not token:
return await login_html(request)
user_remix_context = remix_context.copy()
set_value_for_key(user_remix_context, "user", {"id": "user-chatgpt"})
set_value_for_key(user_remix_context, "accessToken", token)
response = templates.TemplateResponse("chatgpt.html", {"request": request, "remix_context": user_remix_context})
response.set_cookie("token", value=token, expires="Thu, 01 Jan 2099 00:00:00 GMT")
return response
# @app.get("/backend-api/accounts/check/v4-2023-04-27")
# async def check_account(request: Request):
# token = request.headers.get("Authorization").replace("Bearer ", "")
# check_account_response = await chatgpt_reverse_proxy(request, "backend-api/accounts/check/v4-2023-04-27")
# if len(token) == 45 or token.startswith("eyJhbGciOi"):
# return check_account_response
# else:
# check_account_str = check_account_response.body.decode('utf-8')
# check_account_info = json.loads(check_account_str)
# for key in check_account_info.get("accounts", {}).keys():
# account_id = check_account_info["accounts"][key]["account"]["account_id"]
# globals.seed_map[token]["user_id"] = check_account_info["accounts"][key]["account"]["account_user_id"].split("__")[0]
# check_account_info["accounts"][key]["account"]["account_user_id"] = f"user-chatgpt__{account_id}"
# with open(globals.SEED_MAP_FILE, "w", encoding="utf-8") as f:
# json.dump(globals.seed_map, f, indent=4)
# return check_account_info
@app.get("/login", response_class=HTMLResponse)
async def login_html(request: Request):
response = templates.TemplateResponse("login.html", {"request": request})
return response
@app.get("/gpts")
async def get_gpts():
return {"kind": "store"}
@app.get("/backend-api/gizmos/bootstrap")
async def get_gizmos_bootstrap():
@app.get("/backend-api/gizmos/bootstrap")
async def get_gizmos_bootstrap(request: Request):
token = request.headers.get("Authorization", "").replace("Bearer ", "")
if len(token) == 45 or token.startswith("eyJhbGciOi"):
return await chatgpt_reverse_proxy(request, "backend-api/gizmos/bootstrap")
else:
return {"gizmos": []}
@app.get("/backend-api/conversations")
async def get_conversations(request: Request):
limit = int(request.query_params.get("limit", 28))
offset = int(request.query_params.get("offset", 0))
token = request.headers.get("Authorization", "").replace("Bearer ", "")
if len(token) == 45 or token.startswith("eyJhbGciOi"):
return await chatgpt_reverse_proxy(request, "backend-api/conversations")
else:
items = []
for conversation_id in globals.seed_map.get(token, {}).get("conversations", []):
conversation = globals.conversation_map.get(conversation_id, None)
if conversation:
items.append(conversation)
items = items[int(offset):int(offset) + int(limit)]
conversations = {
"items": items,
"total": len(items),
"limit": limit,
"offset": offset,
"has_missing_conversations": False
}
return Response(content=json.dumps(conversations, indent=4), media_type="application/json")
@app.get("/backend-api/conversation/{conversation_id}")
async def update_conversation(request: Request, conversation_id: str):
token = request.headers.get("Authorization", "").replace("Bearer ", "")
conversation_details_response = await chatgpt_reverse_proxy(request, f"backend-api/conversation/{conversation_id}")
if len(token) == 45 or token.startswith("eyJhbGciOi"):
return conversation_details_response
else:
conversation_details_str = conversation_details_response.body.decode('utf-8')
conversation_details = json.loads(conversation_details_str)
if conversation_id in globals.seed_map[token]["conversations"] and conversation_id in globals.conversation_map:
globals.conversation_map[conversation_id]["title"] = conversation_details.get("title", None)
globals.conversation_map[conversation_id]["is_archived"] = conversation_details.get("is_archived", False)
globals.conversation_map[conversation_id]["conversation_template_id"] = conversation_details.get("conversation_template_id", None)
globals.conversation_map[conversation_id]["gizmo_id"] = conversation_details.get("gizmo_id", None)
globals.conversation_map[conversation_id]["async_status"] = conversation_details.get("async_status", None)
with open(globals.CONVERSATION_MAP_FILE, "w", encoding="utf-8") as f:
json.dump(globals.conversation_map, f, indent=4)
return conversation_details_response
@app.patch("/backend-api/conversation/{conversation_id}")
async def patch_conversation(request: Request, conversation_id: str):
token = request.headers.get("Authorization", "").replace("Bearer ", "")
patch_response = (await chatgpt_reverse_proxy(request, f"backend-api/conversation/{conversation_id}"))
if len(token) == 45 or token.startswith("eyJhbGciOi"):
return patch_response
else:
data = await request.json()
if conversation_id in globals.seed_map[token][
"conversations"] and conversation_id in globals.conversation_map:
if not data.get("is_visible", True):
globals.conversation_map.pop(conversation_id)
globals.seed_map[token]["conversations"].remove(conversation_id)
with open(globals.SEED_MAP_FILE, "w", encoding="utf-8") as f:
json.dump(globals.seed_map, f, indent=4)
@app.get("/backend-api/conversations")
async def get_conversations(request: Request):
limit = int(request.query_params.get("limit", 28))
offset = int(request.query_params.get("offset", 0))
is_archived = request.query_params.get("is_archived", None)
token = request.headers.get("Authorization", "").replace("Bearer ", "")
if len(token) == 45 or token.startswith("eyJhbGciOi"):
return await chatgpt_reverse_proxy(request, "backend-api/conversations")
else:
items = []
for conversation_id in globals.seed_map.get(token, {}).get("conversations", []):
conversation = globals.conversation_map.get(conversation_id, None)
if conversation:
if is_archived == "true":
if conversation.get("is_archived", False):
items.append(conversation)
else:
globals.conversation_map[conversation_id].update(data)
with open(globals.CONVERSATION_MAP_FILE, "w", encoding="utf-8") as f:
json.dump(globals.conversation_map, f, indent=4)
return patch_response
if not conversation.get("is_archived", False):
items.append(conversation)
items = items[int(offset):int(offset) + int(limit)]
conversations = {
"items": items,
"total": len(items),
"limit": limit,
"offset": offset,
"has_missing_conversations": False
}
return Response(content=json.dumps(conversations, indent=4), media_type="application/json")
@app.get("/backend-api/me")
async def get_me(request: Request):
@app.get("/backend-api/conversation/{conversation_id}")
async def update_conversation(request: Request, conversation_id: str):
token = request.headers.get("Authorization", "").replace("Bearer ", "")
conversation_details_response = await chatgpt_reverse_proxy(request,
f"backend-api/conversation/{conversation_id}")
if len(token) == 45 or token.startswith("eyJhbGciOi"):
return conversation_details_response
else:
conversation_details_str = conversation_details_response.body.decode('utf-8')
conversation_details = json.loads(conversation_details_str)
if conversation_id in globals.seed_map[token][
"conversations"] and conversation_id in globals.conversation_map:
globals.conversation_map[conversation_id]["title"] = conversation_details.get("title", None)
globals.conversation_map[conversation_id]["is_archived"] = conversation_details.get("is_archived",
False)
globals.conversation_map[conversation_id]["conversation_template_id"] = conversation_details.get(
"conversation_template_id", None)
globals.conversation_map[conversation_id]["gizmo_id"] = conversation_details.get("gizmo_id", None)
globals.conversation_map[conversation_id]["async_status"] = conversation_details.get("async_status",
None)
with open(globals.CONVERSATION_MAP_FILE, "w", encoding="utf-8") as f:
json.dump(globals.conversation_map, f, indent=4)
return conversation_details_response
@app.patch("/backend-api/conversation/{conversation_id}")
async def patch_conversation(request: Request, conversation_id: str):
token = request.headers.get("Authorization", "").replace("Bearer ", "")
patch_response = (await chatgpt_reverse_proxy(request, f"backend-api/conversation/{conversation_id}"))
if len(token) == 45 or token.startswith("eyJhbGciOi"):
return patch_response
else:
data = await request.json()
if conversation_id in globals.seed_map[token][
"conversations"] and conversation_id in globals.conversation_map:
if not data.get("is_visible", True):
globals.conversation_map.pop(conversation_id)
globals.seed_map[token]["conversations"].remove(conversation_id)
with open(globals.SEED_MAP_FILE, "w", encoding="utf-8") as f:
json.dump(globals.seed_map, f, indent=4)
else:
globals.conversation_map[conversation_id].update(data)
with open(globals.CONVERSATION_MAP_FILE, "w", encoding="utf-8") as f:
json.dump(globals.conversation_map, f, indent=4)
return patch_response
@app.get("/backend-api/me")
async def get_me(request: Request):
token = request.headers.get("Authorization", "").replace("Bearer ", "")
if len(token) == 45 or token.startswith("eyJhbGciOi"):
return await chatgpt_reverse_proxy(request, "backend-api/me")
else:
me = {
"object": "user",
"id": "org-chatgpt",
@ -188,50 +187,136 @@ if enable_gateway:
},
"has_payg_project_spend_limit": True
}
return me
return Response(content=json.dumps(me, indent=4), media_type="application/json")
banned_paths = [
"backend-api/accounts/logout_all",
"backend-api/accounts/deactivate",
"backend-api/user_system_messages",
"backend-api/memories",
"backend-api/settings/clear_account_user_memory",
"backend-api/conversations/[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}"
"backend-api/accounts/[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}/invites",
"admin",
]
redirect_paths = ["auth/logout"]
chatgpt_paths = ["c/[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}"]
@app.post("/backend-api/edge")
async def edge():
return Response(status_code=204)
@app.api_route("/{path:path}", methods=["GET", "POST", "PUT", "DELETE", "OPTIONS", "HEAD", "PATCH", "TRACE"])
async def reverse_proxy(request: Request, path: str):
if re.match("/v1/rgstr", path):
return Response(status_code=202, content=b'{"success":true}')
if no_sentinel:
@app.post("/backend-api/sentinel/chat-requirements")
async def sentinel_chat_conversations():
return {
"arkose": {
"dx": None,
"required": False
},
"persona": "chatgpt-paid",
"proofofwork": {
"difficulty": None,
"required": False,
"seed": None
},
"token": str(uuid.uuid4()),
"turnstile": {
"dx": None,
"required": False
}
}
if re.match("ces/v1", path):
return {"success": True}
if re.match("backend-api/edge", path):
return Response(status_code=204)
@app.post("/backend-api/conversation")
async def chat_conversations(request: Request):
token = request.headers.get("Authorization", "").replace("Bearer ", "")
req_token = await get_real_req_token(token)
access_token = await verify_token(req_token)
fp = get_fp(req_token)
proxy_url = fp.pop("proxy_url", None)
impersonate = fp.pop("impersonate", "safari15_3")
user_agent = fp.get("user-agent",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36 Edg/130.0.0.0")
for chatgpt_path in chatgpt_paths:
if re.match(chatgpt_path, path):
return await chatgpt_html(request)
host_url = random.choice(chatgpt_base_url_list) if chatgpt_base_url_list else "https://chatgpt.com"
proof_token = None
turnstile_token = None
headers = {
key: value for key, value in request.headers.items()
if (key.lower() not in ["host", "origin", "referer", "priority",
"oai-device-id"] and key.lower() not in headers_reject_list)
}
headers.update(fp)
headers.update({
"authorization": f"Bearer {access_token}",
"oai-device-id": fp.get("oai-device-id", str(uuid.uuid4()))
})
client = Client(proxy=proxy_url, impersonate=impersonate)
config = get_config(user_agent)
p = get_requirements_token(config)
data = {'p': p}
r = await client.post(f'{host_url}/backend-api/sentinel/chat-requirements', headers=headers, json=data,
timeout=10)
resp = r.json()
turnstile = resp.get('turnstile', {})
turnstile_required = turnstile.get('required')
if turnstile_required:
turnstile_dx = turnstile.get("dx")
try:
if turnstile_solver_url:
res = await client.post(turnstile_solver_url,
json={"url": "https://chatgpt.com", "p": p, "dx": turnstile_dx})
turnstile_token = res.json().get("t")
except Exception as e:
logger.info(f"Turnstile ignored: {e}")
proofofwork = resp.get('proofofwork', {})
proofofwork_required = proofofwork.get('required')
if proofofwork_required:
proofofwork_diff = proofofwork.get("difficulty")
proofofwork_seed = proofofwork.get("seed")
proof_token, solved = await run_in_threadpool(
get_answer_token, proofofwork_seed, proofofwork_diff, config
)
if not solved:
raise HTTPException(status_code=403, detail="Failed to solve proof of work")
chat_token = resp.get('token')
headers.update({
"openai-sentinel-chat-requirements-token": chat_token,
"openai-sentinel-proof-token": proof_token,
"openai-sentinel-turnstile-token": turnstile_token,
})
params = dict(request.query_params)
data = await request.body()
request_cookies = dict(request.cookies)
background = BackgroundTask(client.close)
r = await client.post_stream(f"{host_url}/backend-api/conversation", params=params, headers=headers,
cookies=request_cookies, data=data, stream=True, allow_redirects=False)
rheaders = r.headers
if x_sign:
rheaders.update({"x-sign": x_sign})
if 'stream' in rheaders.get("content-type", ""):
logger.info(f"Request token: {req_token}")
logger.info(f"Request proxy: {proxy_url}")
logger.info(f"Request UA: {user_agent}")
logger.info(f"Request impersonate: {impersonate}")
return StreamingResponse(content_generator(r, token), headers=rheaders,
media_type=rheaders.get("content-type"), background=background)
else:
return Response(content=(await r.atext()), headers=rheaders, media_type=rheaders.get("content-type"),
status_code=r.status_code, background=background)
@app.api_route("/{path:path}", methods=["GET", "POST", "PUT", "DELETE", "OPTIONS", "HEAD", "PATCH", "TRACE"])
async def reverse_proxy(request: Request, path: str):
token = request.headers.get("Authorization", "").replace("Bearer ", "")
if len(token) != 45 and not token.startswith("eyJhbGciOi"):
for banned_path in banned_paths:
if re.match(banned_path, path):
raise HTTPException(status_code=403, detail="Forbidden")
for redirect_path in redirect_paths:
if re.match(redirect_path, path):
redirect_url = str(request.base_url)
response = RedirectResponse(url=f"{redirect_url}login", status_code=302)
return response
for chatgpt_path in chatgpt_paths:
if re.match(chatgpt_path, path):
return await chatgpt_html(request)
return await chatgpt_reverse_proxy(request, path)
else:
@app.api_route("/{path:path}", methods=["GET", "POST", "PUT", "DELETE", "OPTIONS", "HEAD", "PATCH", "TRACE"])
async def reverse_proxy():
raise HTTPException(status_code=404, detail="Gateway is disabled")
for redirect_path in redirect_paths:
if re.match(redirect_path, path):
redirect_url = str(request.base_url)
response = RedirectResponse(url=f"{redirect_url}login", status_code=302)
return response
return await chatgpt_reverse_proxy(request, path)

28
gateway/chatgpt.py Normal file
View File

@ -0,0 +1,28 @@
import json
from fastapi import Request
from fastapi.responses import HTMLResponse
from app import app, templates
from gateway.login import login_html
from utils.kv_utils import set_value_for_key
with open("templates/chatgpt_context.json", "r", encoding="utf-8") as f:
chatgpt_context = json.load(f)
@app.get("/", response_class=HTMLResponse)
async def chatgpt_html(request: Request):
token = request.query_params.get("token")
if not token:
token = request.cookies.get("token")
if not token:
return await login_html(request)
user_remix_context = chatgpt_context.copy()
set_value_for_key(user_remix_context, "user", {"id": "user-chatgpt"})
set_value_for_key(user_remix_context, "accessToken", token)
response = templates.TemplateResponse("chatgpt.html", {"request": request, "remix_context": user_remix_context})
response.set_cookie("token", value=token, expires="Thu, 01 Jan 2099 00:00:00 GMT")
return response

24
gateway/gpts.py Normal file
View File

@ -0,0 +1,24 @@
import json
from fastapi import Request
from fastapi.responses import Response
from app import app
from gateway.chatgpt import chatgpt_html
with open("templates/gpts_context.json", "r", encoding="utf-8") as f:
gpts_context = json.load(f)
@app.get("/gpts")
async def get_gpts():
return {"kind": "store"}
@app.get("/g/g-{gizmo_id}")
async def get_gizmo_json(request: Request, gizmo_id: str):
params = request.query_params
if params.get("_data") == "routes/g.$gizmoId._index":
return Response(content=json.dumps(gpts_context, indent=4), media_type="application/json")
else:
return await chatgpt_html(request)

10
gateway/login.py Normal file
View File

@ -0,0 +1,10 @@
from fastapi import Request
from fastapi.responses import HTMLResponse
from app import app, templates
@app.get("/login", response_class=HTMLResponse)
async def login_html(request: Request):
response = templates.TemplateResponse("login.html", {"request": request})
return response

View File

@ -2,25 +2,25 @@ import json
import random
import time
import uuid
from datetime import datetime, timezone
from fastapi import Request, HTTPException
from fastapi.responses import StreamingResponse, Response
from starlette.background import BackgroundTask
from chatgpt.authorization import verify_token, get_req_token, get_fp
import utils.globals as globals
from chatgpt.authorization import verify_token, get_req_token, get_fp
from utils.Client import Client
from utils.Logger import logger
from utils.configs import chatgpt_base_url_list, proxy_url_list
from utils.configs import chatgpt_base_url_list
from datetime import datetime, timezone
def generate_current_time():
current_time = datetime.now(timezone.utc)
formatted_time = current_time.isoformat(timespec='microseconds').replace('+00:00', 'Z')
return formatted_time
headers_reject_list = [
"x-real-ip",
"x-forwarded-for",
@ -159,7 +159,8 @@ async def chatgpt_reverse_proxy(request: Request, path: str):
headers = {
key: value for key, value in request.headers.items()
if (key.lower() not in ["host", "origin", "referer", "priority", "oai-device-id"] and key.lower() not in headers_reject_list)
if (key.lower() not in ["host", "origin", "referer", "priority",
"oai-device-id"] and key.lower() not in headers_reject_list)
}
base_url = random.choice(chatgpt_base_url_list) if chatgpt_base_url_list else "https://chatgpt.com"
@ -173,9 +174,9 @@ async def chatgpt_reverse_proxy(request: Request, path: str):
token = request.cookies.get("token", "")
req_token = await get_real_req_token(token)
fp = get_fp(req_token)
proxy_url = fp.get("proxy_url")
proxy_url = fp.pop("proxy_url", None)
impersonate = fp.pop("impersonate", "safari15_3")
user_agent = fp.get("user-agent")
impersonate = fp.get("impersonate", "safari15_3")
headers.update(fp)
headers.update({
@ -185,12 +186,13 @@ async def chatgpt_reverse_proxy(request: Request, path: str):
"referer": f"{base_url}/"
})
if "ab.chatgpt.com" in base_url:
headers.update({
"statsig-sdk-type": "js-client",
"statsig-api-key": "client-tnE5GCU2F2cTxRiMbvTczMDT1jpwIigZHsZSdqiy4u",
"statsig-sdk-version": "5.1.0",
"statsig-client-time": int(time.time() * 1000)
})
if "statsig-api-key" not in headers:
headers.update({
"statsig-sdk-type": "js-client",
"statsig-api-key": "client-tnE5GCU2F2cTxRiMbvTczMDT1jpwIigZHsZSdqiy4u",
"statsig-sdk-version": "5.1.0",
"statsig-client-time": int(time.time() * 1000)
})
token = headers.get("authorization", "").replace("Bearer ", "")
if token:
@ -208,14 +210,11 @@ async def chatgpt_reverse_proxy(request: Request, path: str):
background = BackgroundTask(client.close)
r = await client.request(request.method, f"{base_url}/{path}", params=params, headers=headers,
cookies=request_cookies, data=data, stream=True, allow_redirects=False)
if r.status_code == 307:
if "bing" in path:
return Response(status_code=307,
headers={"Location": r.headers.get("Location").replace("chatgpt.com", origin_host)
.replace("https", petrol)}, background=background)
if r.status_code == 302:
return Response(status_code=302,
headers={"Location": r.headers.get("Location").replace("chatgpt.com", origin_host)
if r.status_code == 307 or r.status_code == 302 or r.status_code == 301:
return Response(status_code=307,
headers={"Location": r.headers.get("Location")
.replace("ab.chatgpt.com", origin_host)
.replace("chatgpt.com", origin_host)
.replace("cdn.oaistatic.com", origin_host)
.replace("https", petrol)}, background=background)
elif 'stream' in r.headers.get("content-type", ""):

0
gateway/route.py Normal file
View File

View File

@ -1,22 +1,19 @@
import json
import random
import uuid
import time
from fastapi import Request, HTTPException
import jwt
from fastapi import Request, HTTPException, Security
from fastapi.responses import Response
from starlette.background import BackgroundTask
from starlette.concurrency import run_in_threadpool
from starlette.responses import StreamingResponse
from fastapi.security import HTTPAuthorizationCredentials
import utils.globals as globals
from app import app
from app import app, security_scheme
from chatgpt.authorization import get_fp, verify_token
from chatgpt.proofofWork import get_config, get_requirements_token, get_answer_token
from gateway.reverseProxy import get_real_req_token, content_generator
from gateway.reverseProxy import get_real_req_token
from utils.Client import Client
from utils.Logger import logger
from utils.configs import proxy_url_list, chatgpt_base_url_list, turnstile_solver_url, x_sign, no_sentinel, \
authorization_list
from utils.configs import proxy_url_list, chatgpt_base_url_list, authorization_list
base_headers = {
'accept': '*/*',
@ -31,18 +28,16 @@ base_headers = {
}
def verify_authorization(request: Request):
auth_header = request.headers.get("Authorization").replace("Bearer ", "")
if not auth_header:
def verify_authorization(bearer_token):
if not bearer_token:
raise HTTPException(status_code=401, detail="Authorization header is missing")
if auth_header not in authorization_list:
if bearer_token not in authorization_list:
raise HTTPException(status_code=401, detail="Invalid authorization")
@app.get("/seedtoken")
async def get_seedtoken(request: Request):
verify_authorization(request)
async def get_seedtoken(request: Request, credentials: HTTPAuthorizationCredentials = Security(security_scheme)):
verify_authorization(credentials.credentials)
try:
params = request.query_params
seed = params.get("seed")
@ -69,8 +64,8 @@ async def get_seedtoken(request: Request):
@app.post("/seedtoken")
async def set_seedtoken(request: Request):
verify_authorization(request)
async def set_seedtoken(request: Request, credentials: HTTPAuthorizationCredentials = Security(security_scheme)):
verify_authorization(credentials.credentials)
data = await request.json()
seed = data.get("seed")
@ -91,8 +86,8 @@ async def set_seedtoken(request: Request):
@app.delete("/seedtoken")
async def delete_seedtoken(request: Request):
verify_authorization(request)
async def delete_seedtoken(request: Request, credentials: HTTPAuthorizationCredentials = Security(security_scheme)):
verify_authorization(credentials.credentials)
try:
data = await request.json()
@ -129,17 +124,18 @@ async def chatgpt_account_check(access_token):
auth_info = {}
client = Client(proxy=random.choice(proxy_url_list) if proxy_url_list else None)
try:
proxy_url = random.choice(proxy_url_list) if proxy_url_list else None
host_url = random.choice(chatgpt_base_url_list) if chatgpt_base_url_list else "https://chatgpt.com"
req_token = await get_real_req_token(access_token)
access_token = await verify_token(req_token)
fp = get_fp(req_token)
proxy_url = fp.pop("proxy_url", None)
impersonate = fp.pop("impersonate", "safari15_3")
headers = base_headers.copy()
headers.update({"authorization": f"Bearer {access_token}"})
headers.update(fp)
client = Client(proxy=proxy_url, impersonate=fp.get("impersonate", "safari15_3"))
client = Client(proxy=proxy_url, impersonate=impersonate)
r = await client.get(f"{host_url}/backend-api/models?history_and_training_disabled=false", headers=headers,
timeout=10)
if r.status_code != 200:
@ -225,14 +221,17 @@ async def refresh(request: Request):
if not refresh_token and not access_token:
raise HTTPException(status_code=401, detail="refresh_token or access_token is required")
need_refresh = True
if access_token:
account_check_info = await chatgpt_account_check(access_token)
if account_check_info:
auth_info.update(account_check_info)
auth_info.update({"accessToken": access_token})
return Response(content=json.dumps(auth_info), media_type="application/json")
try:
access_token_info = jwt.decode(access_token, options={"verify_signature": False})
exp = access_token_info.get("exp", 0)
if exp > int(time.time()) + 60 * 60 * 24 * 5:
need_refresh = False
except Exception as e:
logger.error(f"access_token: {e}")
if refresh_token:
if refresh_token and need_refresh:
chatgpt_refresh_info = await chatgpt_refresh(refresh_token)
if chatgpt_refresh_info:
auth_info.update(chatgpt_refresh_info)
@ -242,106 +241,13 @@ async def refresh(request: Request):
auth_info.update(account_check_info)
auth_info.update({"accessToken": access_token})
return Response(content=json.dumps(auth_info), media_type="application/json")
elif access_token:
account_check_info = await chatgpt_account_check(access_token)
if account_check_info:
auth_info.update(account_check_info)
auth_info.update({"accessToken": access_token})
return Response(content=json.dumps(auth_info), media_type="application/json")
raise HTTPException(status_code=401, detail="Unauthorized")
if no_sentinel:
@app.post("/backend-api/sentinel/chat-requirements")
async def sentinel_chat_conversations():
return {
"arkose": {
"dx": None,
"required": False
},
"persona": "chatgpt-paid",
"proofofwork": {
"difficulty": None,
"required": False,
"seed": None
},
"token": str(uuid.uuid4()),
"turnstile": {
"dx": None,
"required": False
}
}
@app.post("/backend-api/conversation")
async def chat_conversations(request: Request):
token = request.headers.get("Authorization", "").replace("Bearer ", "")
req_token = await get_real_req_token(token)
access_token = await verify_token(req_token)
fp = get_fp(req_token)
proxy_url = fp.get("proxy_url", None)
user_agent = fp.get("user-agent",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36 Edg/130.0.0.0")
impersonate = fp.get("impersonate", "safari15_3")
host_url = random.choice(chatgpt_base_url_list) if chatgpt_base_url_list else "https://chatgpt.com"
proof_token = None
turnstile_token = None
headers = base_headers.copy()
headers.update(fp)
headers.update({
"authorization": f"Bearer {access_token}",
"oai-device-id": fp.get("oai-device-id", str(uuid.uuid4()))
})
client = Client(proxy=proxy_url, impersonate=impersonate)
config = get_config(user_agent)
p = get_requirements_token(config)
data = {'p': p}
r = await client.post(f'{host_url}/backend-api/sentinel/chat-requirements', headers=headers, json=data,
timeout=10)
resp = r.json()
turnstile = resp.get('turnstile', {})
turnstile_required = turnstile.get('required')
if turnstile_required:
turnstile_dx = turnstile.get("dx")
try:
if turnstile_solver_url:
res = await client.post(turnstile_solver_url,
json={"url": "https://chatgpt.com", "p": p, "dx": turnstile_dx})
turnstile_token = res.json().get("t")
except Exception as e:
logger.info(f"Turnstile ignored: {e}")
proofofwork = resp.get('proofofwork', {})
proofofwork_required = proofofwork.get('required')
if proofofwork_required:
proofofwork_diff = proofofwork.get("difficulty")
proofofwork_seed = proofofwork.get("seed")
proof_token, solved = await run_in_threadpool(
get_answer_token, proofofwork_seed, proofofwork_diff, config
)
if not solved:
raise HTTPException(status_code=403, detail="Failed to solve proof of work")
chat_token = resp.get('token')
headers.update({
"openai-sentinel-chat-requirements-token": chat_token,
"openai-sentinel-proof-token": proof_token,
"openai-sentinel-turnstile-token": turnstile_token,
})
params = dict(request.query_params)
data = await request.body()
request_cookies = dict(request.cookies)
background = BackgroundTask(client.close)
r = await client.post_stream(f"{host_url}/backend-api/conversation", params=params, headers=headers,
cookies=request_cookies, data=data, stream=True, allow_redirects=False)
rheaders = r.headers
if x_sign:
rheaders.update({"x-sign": x_sign})
if 'stream' in rheaders.get("content-type", ""):
logger.info(f"Request token: {req_token}")
logger.info(f"Request proxy: {proxy_url}")
logger.info(f"Request UA: {user_agent}")
logger.info(f"Request impersonate: {impersonate}")
return StreamingResponse(content_generator(r, token), headers=rheaders,
media_type=rheaders.get("content-type"), background=background)
else:
return Response(content=(await r.atext()), headers=rheaders, media_type=rheaders.get("content-type"),
status_code=r.status_code, background=background)

28
gateway/v1.py Normal file
View File

@ -0,0 +1,28 @@
import json
from fastapi import Request
from fastapi.responses import Response
from app import app
from gateway.reverseProxy import chatgpt_reverse_proxy
from utils.kv_utils import set_value_for_key
@app.post("/v1/initialize")
async def initialize(request: Request):
initialize_response = (await chatgpt_reverse_proxy(request, f"/v1/initialize"))
initialize_str = initialize_response.body.decode('utf-8')
initialize_json = json.loads(initialize_str)
set_value_for_key(initialize_json, "ip", "8.8.8.8")
set_value_for_key(initialize_json, "country", "US")
return Response(content=json.dumps(initialize_json, indent=4), media_type="application/json")
@app.post("/v1/rgstr")
async def rgstr():
return Response(status_code=202, content=json.dumps({"success": True}, indent=4), media_type="application/json")
@app.post("/ces/v1/{path:path}")
async def ces_v1():
return Response(status_code=202, content=json.dumps({"success": True}, indent=4), media_type="application/json")

View File

@ -10,3 +10,4 @@ pybase64
jinja2
APScheduler
ua-generator
pyjwt

View File

@ -1,5 +1,5 @@
<!DOCTYPE html>
<html data-build="prod-bf34a49b1a069459615ed830ce45441d6b9a54ee" dir="ltr" class="">
<html data-build="prod-ecc0207d57b2a1317adeee3f3b5e693f287547aa" dir="ltr" class="">
<head>
<meta charSet="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
@ -18,55 +18,54 @@
<meta property="og:title" content="ChatGPT"/>
<meta property="og:image" content="/assets/chatgpt-share-og-u7j5uyao.webp"/>
<meta property="og:url" content="https://chatgpt.com"/>
<link rel="modulepreload" href="/assets/manifest-3ade92e0.js"/>
<link rel="modulepreload" href="/assets/d9z3tlgc2shput1x.js"/>
<link rel="modulepreload" href="/assets/jipkz7wahhvzzuqx.js"/>
<link rel="modulepreload" href="/assets/36ro7pf3i6oxqv6i.js"/>
<link rel="modulepreload" href="/assets/n5nz8vnsxibd6bik.js"/>
<link rel="modulepreload" href="/assets/mdo2ehpmzqbbrcw9.js"/>
<link rel="modulepreload" href="/assets/bvxb7ko3h6ykpbi0.js"/>
<link rel="modulepreload" href="/assets/e6zhtx99a7i1g9zc.js"/>
<link rel="modulepreload" href="/assets/npl77qwfuvi2jyha.js"/>
<link rel="modulepreload" href="/assets/fwm14uzkyzsxflxc.js"/>
<link rel="modulepreload" href="/assets/lbv1zpv4g2ap41f2.js"/>
<link rel="modulepreload" href="/assets/hu4ya3hu1q2mtj3c.js"/>
<link rel="modulepreload" href="/assets/fqai4h0y4a7y6yiz.js"/>
<link rel="modulepreload" href="/assets/kgz011geyku0c84g.js"/>
<link rel="modulepreload" href="/assets/lb2bw82esu8k4ire.js"/>
<link rel="modulepreload" href="/assets/j625ckqact10y3my.js"/>
<link rel="modulepreload" href="/assets/it892upc0b17konk.js"/>
<link rel="modulepreload" href="/assets/ojtyfghvwqol77ni.js"/>
<link rel="modulepreload" href="/assets/gf1rzma6lb3hhiaa.js"/>
<link rel="modulepreload" href="/assets/n7shmditr84egzzk.js"/>
<link rel="modulepreload" href="/assets/ncnmvo4fatoh9z2b.js"/>
<link rel="modulepreload" href="/assets/efad9c3t7hoskfux.js"/>
<link rel="modulepreload" href="/assets/erjy4zyeqfne1prk.js"/>
<link rel="modulepreload" href="/assets/i21elbjdr0dt7siq.js"/>
<link rel="modulepreload" href="/assets/nh5giofbgo8o5iz3.js"/>
<link rel="modulepreload" href="/assets/hz1kryv1rsw2w11f.js"/>
<link rel="modulepreload" href="/assets/m02oxzinki55f1a4.js"/>
<link rel="modulepreload" href="/assets/c78o8wyvzedeoudw.js"/>
<link rel="modulepreload" href="/assets/gecwa6udk6b6d8e5.js"/>
<link rel="modulepreload" href="/assets/g70459ispqthxwkc.js"/>
<link rel="modulepreload" href="/assets/kl3gqai6eyrksb78.js"/>
<link rel="modulepreload" href="/assets/l6n2tuza1m4x9jmr.js"/>
<link rel="modulepreload" href="/assets/jwoa39qbou2pb90k.js"/>
<link rel="modulepreload" href="/assets/lxkd2uemxq651aut.js"/>
<link rel="modulepreload" href="/assets/f2w9prfaqv5jn6b6.js"/>
<link rel="modulepreload" href="/assets/dp98tj9u73hn9ufz.js"/>
<link rel="modulepreload" href="/assets/k5lnvw1mrt1nlhku.js"/>
<link rel="modulepreload" href="/assets/nsw6odx5xh6i9cc3.js"/>
<link rel="modulepreload" href="/assets/dp2xls1tq8hl6hdd.js"/>
<link rel="modulepreload" href="/assets/o8ne9dgj82ugno4a.js"/>
<link rel="modulepreload" href="/assets/hpwzwf5t45kgizxl.js"/>
<link rel="modulepreload" href="/assets/fcr06eqoxz5nj0zr.js"/>
<link rel="modulepreload" href="/assets/cyzxbqfgo2fnhaph.js"/>
<link rel="modulepreload" href="/assets/f1ccpn6bozgd3iqc.js"/>
<link rel="modulepreload" href="/assets/m5dixe5n71jzyt0l.js"/>
<link rel="modulepreload" href="/assets/ifx68p1cwmj10rjy.js"/>
<link rel="modulepreload" href="/assets/koa1s917opmrb6zb.js"/>
<link rel="modulepreload" href="/assets/manifest-317e0442.js"/>
<link rel="modulepreload" href="/assets/l0flmalf4tyvsm1b.js"/>
<link rel="modulepreload" href="/assets/cenhwbprxai3toix.js"/>
<link rel="modulepreload" href="/assets/mej6dcn3epzdp940.js"/>
<link rel="modulepreload" href="/assets/lzqwtbp50roqvxnh.js"/>
<link rel="modulepreload" href="/assets/esp1o2lmeinam0sh.js"/>
<link rel="modulepreload" href="/assets/wg6ecvahwsvfbs7t.js"/>
<link rel="modulepreload" href="/assets/bomvf441parvbl6l.js"/>
<link rel="modulepreload" href="/assets/jyh9xl3syf7yrebg.js"/>
<link rel="modulepreload" href="/assets/jth9gz8y4nfsu03x.js"/>
<link rel="modulepreload" href="/assets/h0w2cfyxquh3a88t.js"/>
<link rel="modulepreload" href="/assets/i2mam4exf28ww00h.js"/>
<link rel="modulepreload" href="/assets/kp41qb109q7zsu08.js"/>
<link rel="modulepreload" href="/assets/e3lzzvpbrfw34hi0.js"/>
<link rel="modulepreload" href="/assets/s26g8cj5crlmzrhm.js"/>
<link rel="modulepreload" href="/assets/cd7rmveqys68yu6w.js"/>
<link rel="modulepreload" href="/assets/hn586u5on2jlex6e.js"/>
<link rel="modulepreload" href="/assets/hn877s1av7risab0.js"/>
<link rel="modulepreload" href="/assets/c657bb6sfxb67al1.js"/>
<link rel="modulepreload" href="/assets/i5dl7qcorvwptxtt.js"/>
<link rel="modulepreload" href="/assets/j72yt11gyo474tkr.js"/>
<link rel="modulepreload" href="/assets/d01hwntyf2775ji3.js"/>
<link rel="modulepreload" href="/assets/kcdclgxeuzpy449e.js"/>
<link rel="modulepreload" href="/assets/bh0hpeydruybhwyo.js"/>
<link rel="modulepreload" href="/assets/ghlhzub07tt8683j.js"/>
<link rel="modulepreload" href="/assets/er1c9qkbvqkr07px.js"/>
<link rel="modulepreload" href="/assets/f994slaknt0q0gbk.js"/>
<link rel="modulepreload" href="/assets/id6ryfq9914uxpp5.js"/>
<link rel="modulepreload" href="/assets/oc6674sla3qapg4k.js"/>
<link rel="modulepreload" href="/assets/olbio5uf8m7nh5z5.js"/>
<link rel="modulepreload" href="/assets/if0s6lpo3mwf7mho.js"/>
<link rel="modulepreload" href="/assets/jmi851zx6qdtjhdj.js"/>
<link rel="modulepreload" href="/assets/i51i3k321hvaxal9.js"/>
<link rel="modulepreload" href="/assets/lfvhb0nf0ozjlpqm.js"/>
<link rel="modulepreload" href="/assets/lwsvc0prcm5qybau.js"/>
<link rel="modulepreload" href="/assets/dmau43v1lisg5o8k.js"/>
<link rel="modulepreload" href="/assets/leqgnttd8q44vj5y.js"/>
<link rel="modulepreload" href="/assets/opnnku3lurllu7tw.js"/>
<link rel="modulepreload" href="/assets/bj8mbuhov5xcqf0f.js"/>
<link rel="modulepreload" href="/assets/kehs2ll784eynnt2.js"/>
<link rel="modulepreload" href="/assets/fxwpnfsczjkyx99q.js"/>
<link rel="modulepreload" href="/assets/cw70vbnpved29h73.js"/>
<link rel="modulepreload" href="/assets/hibtdcbfc20dw1xa.js"/>
<link rel="modulepreload" href="/assets/jrixumr7y4kpqw3k.js"/>
<link rel="modulepreload" href="/assets/lrh8wz5t6uhmx4c9.js"/>
<link rel="modulepreload" href="/assets/fnze8ev0ssatnf92.js"/>
<link rel="modulepreload" href="/assets/jjr9on9cxlrbskjq.js"/>
<link rel="stylesheet" href="/assets/root-fm8m2265.css"/>
<link rel="stylesheet" href="/assets/root-o2r7uhf3.css"/>
<link rel="stylesheet" href="/assets/conversation-small-cll5buey.css"/>
</head>
<body class="">
@ -96,7 +95,7 @@
}()
</script>
<div class="relative flex h-full w-full overflow-hidden transition-colors z-0">
<div class="z-[1] flex-shrink-0 overflow-x-hidden bg-token-sidebar-surface-primary max-md:!w-0" style="width:260px">
<div class="z-[21] flex-shrink-0 overflow-x-hidden bg-token-sidebar-surface-primary max-md:!w-0" style="width:260px">
<div class="h-full w-[260px]">
<div class="flex h-full min-h-0 flex-col">
<div class="draggable relative h-full w-full flex-1 items-start border-white/20">
@ -194,7 +193,7 @@
</div>
</div>
<div class="w-full">
<form class="w-full" type="button" aria-haspopup="dialog" aria-expanded="false" aria-controls="radix-:Rpn99iii4lj5:" data-state="closed">
<form class="w-full" type="button" aria-haspopup="dialog" aria-expanded="false" aria-controls="radix-:Rdf99iii4lj5:" data-state="closed">
<div class="relative flex h-full max-w-full flex-1 flex-col">
<div class="relative h-0"></div>
<div class="group relative flex w-full items-center">
@ -203,7 +202,7 @@
<div class="flex min-w-0 flex-1 flex-col">
<div class="_prosemirror-parent_15ceg_1 text-token-text-primary max-h-[25dvh] max-h-52 overflow-auto default-browser">
<textarea class="block h-10 w-full resize-none border-0 bg-transparent px-0 py-2 text-token-text-primary placeholder:text-token-text-secondary" autofocus="" placeholder="Message ChatGPT"></textarea>
<script nonce="2887053f-167d-4b8d-b616-8ea78c3e74b7">
<script nonce="c5a3ef22-c71c-4ae3-9939-b19d1edaee92">
window.__oai_logHTML ? window.__oai_logHTML() : window.__oai_SSR_HTML = window.__oai_SSR_HTML || Date.now();
requestAnimationFrame((function() {
window.__oai_logTTI ? window.__oai_logTTI() : window.__oai_SSR_TTI = window.__oai_SSR_TTI || Date.now()
@ -257,7 +256,7 @@
<div aria-live="assertive" aria-atomic="true" class="sr-only"></div>
<div aria-live="polite" aria-atomic="true" class="sr-only"></div>
<audio class="fixed bottom-0 left-0 hidden h-0 w-0" autoPlay="" crossorigin="anonymous"></audio>
<script nonce="2887053f-167d-4b8d-b616-8ea78c3e74b7">
<script nonce="c5a3ef22-c71c-4ae3-9939-b19d1edaee92">
window.__remixContext = {{ remix_context|tojson }};
__remixContext.p = function(v, e, p, x) {
if (typeof e !== 'undefined') {
@ -312,10 +311,10 @@
Object.assign(__remixContext.state.loaderData["routes/_conversation"], {});
__remixContext.a = 1;
</script>
<script nonce="2887053f-167d-4b8d-b616-8ea78c3e74b7" type="module" async="">
import "/assets/manifest-3ade92e0.js";
import*as route0 from "/assets/mdo2ehpmzqbbrcw9.js";
import*as route1 from "/assets/koa1s917opmrb6zb.js";
<script nonce="c5a3ef22-c71c-4ae3-9939-b19d1edaee92" type="module" async="">
import "/assets/manifest-317e0442.js";
import*as route0 from "/assets/esp1o2lmeinam0sh.js";
import*as route1 from "/assets/fnze8ev0ssatnf92.js";
import*as route2 from "/assets/jjr9on9cxlrbskjq.js";
window.__remixRouteModules = {
@ -324,7 +323,46 @@
"routes/_conversation._index": route2
};
import("/assets/d9z3tlgc2shput1x.js");
import("/assets/l0flmalf4tyvsm1b.js");
</script>
<script nonce="c5a3ef22-c71c-4ae3-9939-b19d1edaee92">
$RC = function(b, c, e) {
c = document.getElementById(c);
c.parentNode.removeChild(c);
var a = document.getElementById(b);
if (a) {
b = a.previousSibling;
if (e)
b.data = "$!",
a.setAttribute("data-dgst", e);
else {
e = b.parentNode;
a = b.nextSibling;
var f = 0;
do {
if (a && 8 === a.nodeType) {
var d = a.data;
if ("/$" === d)
if (0 === f)
break;
else
f--;
else
"$" !== d && "$?" !== d && "$!" !== d || f++
}
d = a.nextSibling;
e.removeChild(a);
a = d
} while (a);
for (; c.firstChild; )
e.insertBefore(c.firstChild, a);
b.data = "$"
}
b._reactRetry && b._reactRetry()
}
}
;
$RC("B:0", "S:0")
</script>
</body>
</html>

File diff suppressed because it is too large Load Diff

5510
templates/gpts_context.json Normal file

File diff suppressed because it is too large Load Diff

10
utils/kv_utils.py Normal file
View File

@ -0,0 +1,10 @@
def set_value_for_key(data, target_key, new_value):
if isinstance(data, dict):
for key, value in data.items():
if key == target_key:
data[key] = new_value
else:
set_value_for_key(value, target_key, new_value)
elif isinstance(data, list):
for item in data:
set_value_for_key(item, target_key, new_value)

View File

@ -1 +1 @@
1.6.9
1.7.0-beta3