|
|
from flask import Flask, request, jsonify, Response, render_template_string, render_template, redirect, url_for, session as flask_session |
|
|
import requests |
|
|
import time |
|
|
import json |
|
|
import uuid |
|
|
import random |
|
|
import io |
|
|
import re |
|
|
from functools import wraps |
|
|
import hashlib |
|
|
import jwt |
|
|
import os |
|
|
import threading |
|
|
from datetime import datetime, timedelta |
|
|
|
|
|
app = Flask(__name__, template_folder='templates') |
|
|
app.secret_key = os.environ.get("SECRET_KEY", "abacus_chat_proxy_secret_key") |
|
|
app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(days=7) |
|
|
|
|
|
|
|
|
TOKENIZER_SERVICE_URL = "https://malt666-tokenizer.hf.space/count_tokens" |
|
|
|
|
|
API_ENDPOINT_URL = "https://abacus.ai/api/v0/describeDeployment" |
|
|
MODEL_LIST_URL = "https://abacus.ai/api/v0/listExternalApplications" |
|
|
CHAT_URL = "https://apps.abacus.ai/api/_chatLLMSendMessageSSE" |
|
|
USER_INFO_URL = "https://abacus.ai/api/v0/_getUserInfo" |
|
|
COMPUTE_POINTS_URL = "https://apps.abacus.ai/api/_getOrganizationComputePoints" |
|
|
COMPUTE_POINTS_LOG_URL = "https://abacus.ai/api/v0/_getOrganizationComputePointLog" |
|
|
CREATE_CONVERSATION_URL = "https://apps.abacus.ai/api/createDeploymentConversation" |
|
|
DELETE_CONVERSATION_URL = "https://apps.abacus.ai/api/deleteDeploymentConversation" |
|
|
GET_CONVERSATION_URL = "https://apps.abacus.ai/api/getDeploymentConversation" |
|
|
COMPUTE_POINT_TOGGLE_URL = "https://abacus.ai/api/v0/_updateOrganizationComputePointToggle" |
|
|
|
|
|
|
|
|
USER_AGENTS = [ |
|
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36" |
|
|
] |
|
|
|
|
|
|
|
|
PASSWORD = None |
|
|
USER_NUM = 0 |
|
|
USER_DATA = [] |
|
|
CURRENT_USER = -1 |
|
|
MODELS = set() |
|
|
|
|
|
|
|
|
LAST_CONVERSATION_IDS = [None] * 100 |
|
|
DELETE_CHAT = True |
|
|
|
|
|
TRACE_ID = "3042e28b3abf475d8d973c7e904935af" |
|
|
SENTRY_TRACE = f"{TRACE_ID}-80d9d2538b2682d0" |
|
|
|
|
|
|
|
|
|
|
|
health_check_counter = 0 |
|
|
|
|
|
|
|
|
|
|
|
model_usage_stats = {} |
|
|
total_tokens = { |
|
|
"prompt": 0, |
|
|
"completion": 0, |
|
|
"total": 0 |
|
|
} |
|
|
|
|
|
|
|
|
model_usage_records = [] |
|
|
MODEL_USAGE_RECORDS_FILE = "model_usage_records.json" |
|
|
|
|
|
|
|
|
compute_points = { |
|
|
"left": 0, |
|
|
"total": 0, |
|
|
"used": 0, |
|
|
"percentage": 0, |
|
|
"last_update": None |
|
|
} |
|
|
|
|
|
|
|
|
compute_points_log = { |
|
|
"columns": {}, |
|
|
"log": [] |
|
|
} |
|
|
|
|
|
|
|
|
users_compute_points = [] |
|
|
|
|
|
|
|
|
START_TIME = datetime.utcnow() + timedelta(hours=8) |
|
|
|
|
|
|
|
|
|
|
|
class DateTimeEncoder(json.JSONEncoder): |
|
|
def default(self, obj): |
|
|
if isinstance(obj, datetime): |
|
|
return obj.strftime('%Y-%m-%d %H:%M:%S') |
|
|
return super(DateTimeEncoder, self).default(obj) |
|
|
|
|
|
|
|
|
|
|
|
def load_model_usage_records(): |
|
|
global model_usage_records |
|
|
try: |
|
|
if os.path.exists(MODEL_USAGE_RECORDS_FILE): |
|
|
with open(MODEL_USAGE_RECORDS_FILE, 'r', encoding='utf-8') as f: |
|
|
records = json.load(f) |
|
|
if isinstance(records, list): |
|
|
model_usage_records = records |
|
|
print(f"成功加载 {len(model_usage_records)} 条模型调用记录") |
|
|
else: |
|
|
print("调用记录文件格式不正确,初始化为空列表") |
|
|
except Exception as e: |
|
|
print(f"加载模型调用记录失败: {e}") |
|
|
model_usage_records = [] |
|
|
|
|
|
|
|
|
def save_model_usage_records(): |
|
|
try: |
|
|
with open(MODEL_USAGE_RECORDS_FILE, 'w', encoding='utf-8') as f: |
|
|
json.dump(model_usage_records, f, ensure_ascii=False, indent=2, cls=DateTimeEncoder) |
|
|
print(f"成功保存 {len(model_usage_records)} 条模型调用记录") |
|
|
except Exception as e: |
|
|
print(f"保存模型调用记录失败: {e}") |
|
|
|
|
|
|
|
|
def update_conversation_id(user_index, conversation_id): |
|
|
"""更新用户的conversation_id并保存到配置文件""" |
|
|
try: |
|
|
with open("config.json", "r") as f: |
|
|
config = json.load(f) |
|
|
|
|
|
if "config" in config and user_index < len(config["config"]): |
|
|
config["config"][user_index]["conversation_id"] = conversation_id |
|
|
|
|
|
|
|
|
with open("config.json", "w") as f: |
|
|
json.dump(config, f, indent=4) |
|
|
|
|
|
print(f"已将用户 {user_index+1} 的conversation_id更新为: {conversation_id}") |
|
|
else: |
|
|
print(f"更新conversation_id失败: 配置文件格式错误或用户索引越界") |
|
|
except Exception as e: |
|
|
print(f"更新conversation_id失败: {e}") |
|
|
|
|
|
|
|
|
def resolve_config(): |
|
|
|
|
|
config_list = [] |
|
|
i = 1 |
|
|
while True: |
|
|
cookie = os.environ.get(f"cookie_{i}") |
|
|
if not cookie: |
|
|
break |
|
|
|
|
|
|
|
|
config_list.append({ |
|
|
"conversation_id": "", |
|
|
"cookies": cookie |
|
|
}) |
|
|
i += 1 |
|
|
|
|
|
|
|
|
if config_list: |
|
|
print(f"从环境变量加载了 {len(config_list)} 个配置") |
|
|
return config_list |
|
|
|
|
|
|
|
|
try: |
|
|
with open("config.json", "r") as f: |
|
|
config = json.load(f) |
|
|
config_list = config.get("config") |
|
|
return config_list |
|
|
except FileNotFoundError: |
|
|
print("未找到config.json文件") |
|
|
return [] |
|
|
except json.JSONDecodeError: |
|
|
print("config.json格式错误") |
|
|
return [] |
|
|
|
|
|
|
|
|
def get_password(): |
|
|
global PASSWORD |
|
|
|
|
|
env_password = os.environ.get("password") |
|
|
if env_password: |
|
|
PASSWORD = hashlib.sha256(env_password.encode()).hexdigest() |
|
|
return |
|
|
|
|
|
|
|
|
try: |
|
|
with open("password.txt", "r") as f: |
|
|
PASSWORD = f.read().strip() |
|
|
except FileNotFoundError: |
|
|
with open("password.txt", "w") as f: |
|
|
PASSWORD = None |
|
|
|
|
|
|
|
|
def require_auth(f): |
|
|
@wraps(f) |
|
|
def decorated(*args, **kwargs): |
|
|
if not PASSWORD: |
|
|
return f(*args, **kwargs) |
|
|
|
|
|
|
|
|
if flask_session.get('logged_in'): |
|
|
return f(*args, **kwargs) |
|
|
|
|
|
|
|
|
auth = request.authorization |
|
|
if not auth or not check_auth(auth.token): |
|
|
|
|
|
if request.headers.get('Accept', '').find('text/html') >= 0: |
|
|
return redirect(url_for('login')) |
|
|
return jsonify({"error": "Unauthorized access"}), 401 |
|
|
return f(*args, **kwargs) |
|
|
|
|
|
return decorated |
|
|
|
|
|
|
|
|
def check_auth(token): |
|
|
return hashlib.sha256(token.encode()).hexdigest() == PASSWORD |
|
|
|
|
|
|
|
|
def is_token_expired(token): |
|
|
if not token: |
|
|
return True |
|
|
|
|
|
try: |
|
|
|
|
|
payload = jwt.decode(token, options={"verify_signature": False}) |
|
|
|
|
|
return payload.get('exp', 0) - time.time() < 300 |
|
|
except: |
|
|
return True |
|
|
|
|
|
|
|
|
def refresh_token(session, cookies): |
|
|
"""Uzu kuketon por refreŝigi session token, nur revenigu novan tokenon""" |
|
|
headers = { |
|
|
"accept": "application/json, text/plain, */*", |
|
|
"accept-language": "zh-CN,zh;q=0.9", |
|
|
"content-type": "application/json", |
|
|
"reai-ui": "1", |
|
|
"sec-ch-ua": "\"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"", |
|
|
"sec-ch-ua-mobile": "?0", |
|
|
"sec-ch-ua-platform": "\"Windows\"", |
|
|
"sec-fetch-dest": "empty", |
|
|
"sec-fetch-mode": "cors", |
|
|
"sec-fetch-site": "same-site", |
|
|
"x-abacus-org-host": "apps", |
|
|
"user-agent": random.choice(USER_AGENTS), |
|
|
"origin": "https://apps.abacus.ai", |
|
|
"referer": "https://apps.abacus.ai/", |
|
|
"cookie": cookies |
|
|
} |
|
|
|
|
|
try: |
|
|
response = session.post( |
|
|
USER_INFO_URL, |
|
|
headers=headers, |
|
|
json={}, |
|
|
cookies=None |
|
|
) |
|
|
|
|
|
if response.status_code == 200: |
|
|
response_data = response.json() |
|
|
if response_data.get('success') and 'sessionToken' in response_data.get('result', {}): |
|
|
return response_data['result']['sessionToken'] |
|
|
else: |
|
|
print(f"刷新token失败: {response_data.get('error', '未知错误')}") |
|
|
return None |
|
|
else: |
|
|
print(f"刷新token失败,状态码: {response.status_code}") |
|
|
return None |
|
|
except Exception as e: |
|
|
print(f"刷新token异常: {e}") |
|
|
return None |
|
|
|
|
|
|
|
|
def get_model_map(session, cookies, session_token): |
|
|
"""Akiru disponeblan modelan liston kaj ĝiajn mapajn rilatojn""" |
|
|
headers = { |
|
|
"accept": "application/json, text/plain, */*", |
|
|
"accept-language": "zh-CN,zh;q=0.9", |
|
|
"content-type": "application/json", |
|
|
"reai-ui": "1", |
|
|
"sec-ch-ua": "\"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"", |
|
|
"sec-ch-ua-mobile": "?0", |
|
|
"sec-ch-ua-platform": "\"Windows\"", |
|
|
"sec-fetch-dest": "empty", |
|
|
"sec-fetch-mode": "cors", |
|
|
"sec-fetch-site": "same-site", |
|
|
"x-abacus-org-host": "apps", |
|
|
"user-agent": random.choice(USER_AGENTS), |
|
|
"origin": "https://apps.abacus.ai", |
|
|
"referer": "https://apps.abacus.ai/", |
|
|
"cookie": cookies |
|
|
} |
|
|
|
|
|
if session_token: |
|
|
headers["session-token"] = session_token |
|
|
|
|
|
model_map = {} |
|
|
models_set = set() |
|
|
|
|
|
try: |
|
|
response = session.post( |
|
|
MODEL_LIST_URL, |
|
|
headers=headers, |
|
|
json={}, |
|
|
cookies=None |
|
|
) |
|
|
|
|
|
if response.status_code != 200: |
|
|
print(f"获取模型列表失败,状态码: {response.status_code}") |
|
|
raise Exception("API请求失败") |
|
|
|
|
|
data = response.json() |
|
|
if not data.get('success'): |
|
|
print(f"获取模型列表失败: {data.get('error', '未知错误')}") |
|
|
raise Exception("API返回错误") |
|
|
|
|
|
applications = [] |
|
|
if isinstance(data.get('result'), dict): |
|
|
applications = data.get('result', {}).get('externalApplications', []) |
|
|
elif isinstance(data.get('result'), list): |
|
|
applications = data.get('result', []) |
|
|
|
|
|
for app in applications: |
|
|
app_name = app.get('name', '') |
|
|
app_id = app.get('externalApplicationId', '') |
|
|
prediction_overrides = app.get('predictionOverrides', {}) |
|
|
llm_name = prediction_overrides.get('llmName', '') if prediction_overrides else '' |
|
|
|
|
|
if not (app_name and app_id and llm_name): |
|
|
continue |
|
|
|
|
|
model_name = app_name |
|
|
model_map[model_name] = (app_id, llm_name) |
|
|
models_set.add(model_name) |
|
|
|
|
|
if not model_map: |
|
|
raise Exception("未找到任何可用模型") |
|
|
|
|
|
return model_map, models_set |
|
|
|
|
|
except Exception as e: |
|
|
print(f"获取模型列表异常: {e}") |
|
|
raise |
|
|
|
|
|
|
|
|
def init_session(): |
|
|
get_password() |
|
|
global USER_NUM, MODELS, USER_DATA, DELETE_CHAT |
|
|
|
|
|
|
|
|
delete_chat_env = os.environ.get("DELETE_CHAT", "true").lower() |
|
|
DELETE_CHAT = delete_chat_env in ["true", "1", "yes", "y"] |
|
|
print(f"删除上一个对话设置: {DELETE_CHAT}") |
|
|
|
|
|
config_list = resolve_config() |
|
|
user_num = len(config_list) |
|
|
all_models = set() |
|
|
|
|
|
for i in range(user_num): |
|
|
user = config_list[i] |
|
|
cookies = user.get("cookies") |
|
|
conversation_id = user.get("conversation_id") |
|
|
session = requests.Session() |
|
|
|
|
|
session_token = refresh_token(session, cookies) |
|
|
if not session_token: |
|
|
print(f"无法获取cookie {i+1}的token") |
|
|
continue |
|
|
|
|
|
try: |
|
|
model_map, models_set = get_model_map(session, cookies, session_token) |
|
|
all_models.update(models_set) |
|
|
USER_DATA.append((session, cookies, session_token, conversation_id, model_map, i)) |
|
|
|
|
|
|
|
|
if i == 0: |
|
|
try: |
|
|
headers = { |
|
|
"accept": "application/json, text/plain, */*", |
|
|
"accept-language": "zh-CN,zh;q=0.9", |
|
|
"content-type": "application/json", |
|
|
"reai-ui": "1", |
|
|
"sec-ch-ua": "\"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"", |
|
|
"sec-ch-ua-mobile": "?0", |
|
|
"sec-ch-ua-platform": "\"Windows\"", |
|
|
"sec-fetch-dest": "empty", |
|
|
"sec-fetch-mode": "cors", |
|
|
"sec-fetch-site": "same-site", |
|
|
"x-abacus-org-host": "apps", |
|
|
"session-token": session_token |
|
|
} |
|
|
|
|
|
response = session.post( |
|
|
COMPUTE_POINT_TOGGLE_URL, |
|
|
headers=headers, |
|
|
json={"alwaysDisplay": True}, |
|
|
cookies=None |
|
|
) |
|
|
|
|
|
if response.status_code == 200: |
|
|
result = response.json() |
|
|
if result.get("success"): |
|
|
print("成功初始化计算点数记录功能为开启状态") |
|
|
else: |
|
|
print(f"初始化计算点数记录功能失败: {result.get('error', '未知错误')}") |
|
|
else: |
|
|
print(f"初始化计算点数记录功能失败,状态码: {response.status_code}") |
|
|
except Exception as e: |
|
|
print(f"初始化计算点数记录功能时出错: {e}") |
|
|
except Exception as e: |
|
|
print(f"配置用户 {i+1} 失败: {e}") |
|
|
continue |
|
|
|
|
|
USER_NUM = len(USER_DATA) |
|
|
if USER_NUM == 0: |
|
|
print("No user available, exiting...") |
|
|
exit(1) |
|
|
|
|
|
MODELS = all_models |
|
|
print(f"启动完成,共配置 {USER_NUM} 个用户") |
|
|
|
|
|
|
|
|
def update_cookie(session, cookies): |
|
|
cookie_jar = {} |
|
|
for key, value in session.cookies.items(): |
|
|
cookie_jar[key] = value |
|
|
cookie_dict = {} |
|
|
for item in cookies.split(";"): |
|
|
key, value = item.strip().split("=", 1) |
|
|
cookie_dict[key] = value |
|
|
cookie_dict.update(cookie_jar) |
|
|
cookies = "; ".join([f"{key}={value}" for key, value in cookie_dict.items()]) |
|
|
return cookies |
|
|
|
|
|
|
|
|
user_data = init_session() |
|
|
|
|
|
|
|
|
@app.route("/v1/models", methods=["GET"]) |
|
|
@require_auth |
|
|
def get_models(): |
|
|
if len(MODELS) == 0: |
|
|
return jsonify({"error": "No models available"}), 500 |
|
|
model_list = [] |
|
|
for model in MODELS: |
|
|
model_list.append( |
|
|
{ |
|
|
"id": model, |
|
|
"object": "model", |
|
|
"created": int(time.time()), |
|
|
"owned_by": "Elbert", |
|
|
"name": model, |
|
|
} |
|
|
) |
|
|
return jsonify({"object": "list", "data": model_list}) |
|
|
|
|
|
|
|
|
@app.route("/v1/chat/completions", methods=["POST"]) |
|
|
@require_auth |
|
|
def chat_completions(): |
|
|
openai_request = request.get_json() |
|
|
stream = openai_request.get("stream", False) |
|
|
messages = openai_request.get("messages") |
|
|
if messages is None: |
|
|
return jsonify({"error": "Messages is required", "status": 400}), 400 |
|
|
model = openai_request.get("model") |
|
|
if model not in MODELS: |
|
|
return ( |
|
|
jsonify( |
|
|
{ |
|
|
"error": "Model not available, check if it is configured properly", |
|
|
"status": 404, |
|
|
} |
|
|
), |
|
|
404, |
|
|
) |
|
|
message = format_message(messages) |
|
|
think = ( |
|
|
openai_request.get("think", False) if model == "Claude Sonnet 3.7" else False |
|
|
) |
|
|
return ( |
|
|
send_message(message, model, think) |
|
|
if stream |
|
|
else send_message_non_stream(message, model, think) |
|
|
) |
|
|
|
|
|
|
|
|
def get_user_data(): |
|
|
global CURRENT_USER |
|
|
CURRENT_USER = (CURRENT_USER + 1) % USER_NUM |
|
|
print(f"使用配置 {CURRENT_USER+1}") |
|
|
|
|
|
|
|
|
session, cookies, session_token, conversation_id, model_map, user_index = USER_DATA[CURRENT_USER] |
|
|
|
|
|
|
|
|
if is_token_expired(session_token): |
|
|
print(f"Cookie {CURRENT_USER+1}的token已过期或即将过期,正在刷新...") |
|
|
new_token = refresh_token(session, cookies) |
|
|
if new_token: |
|
|
|
|
|
USER_DATA[CURRENT_USER] = (session, cookies, new_token, conversation_id, model_map, user_index) |
|
|
session_token = new_token |
|
|
print(f"成功更新token: {session_token[:15]}...{session_token[-15:]}") |
|
|
else: |
|
|
print(f"警告:无法刷新Cookie {CURRENT_USER+1}的token,继续使用当前token") |
|
|
|
|
|
return (session, cookies, session_token, conversation_id, model_map, user_index) |
|
|
|
|
|
|
|
|
def create_conversation(session, cookies, session_token, external_application_id=None, deployment_id=None): |
|
|
"""创建新的会话""" |
|
|
if not (external_application_id and deployment_id): |
|
|
print("无法创建新会话: 缺少必要参数") |
|
|
return None |
|
|
|
|
|
headers = { |
|
|
"accept": "application/json, text/plain, */*", |
|
|
"accept-language": "zh-CN,zh;q=0.9", |
|
|
"content-type": "application/json", |
|
|
"cookie": cookies, |
|
|
"user-agent": random.choice(USER_AGENTS), |
|
|
"x-abacus-org-host": "apps" |
|
|
} |
|
|
|
|
|
if session_token: |
|
|
headers["session-token"] = session_token |
|
|
|
|
|
create_payload = { |
|
|
"deploymentId": deployment_id, |
|
|
"name": "New Chat", |
|
|
"externalApplicationId": external_application_id |
|
|
} |
|
|
|
|
|
try: |
|
|
response = session.post( |
|
|
CREATE_CONVERSATION_URL, |
|
|
headers=headers, |
|
|
json=create_payload |
|
|
) |
|
|
|
|
|
if response.status_code == 200: |
|
|
data = response.json() |
|
|
if data.get("success", False): |
|
|
new_conversation_id = data.get("result", {}).get("deploymentConversationId") |
|
|
if new_conversation_id: |
|
|
print(f"成功创建新的conversation: {new_conversation_id}") |
|
|
return new_conversation_id |
|
|
|
|
|
print(f"创建会话失败: {response.status_code} - {response.text[:100]}") |
|
|
return None |
|
|
except Exception as e: |
|
|
print(f"创建会话时出错: {e}") |
|
|
return None |
|
|
|
|
|
|
|
|
def delete_conversation(session, cookies, session_token, conversation_id, deployment_id="14b2a314cc"): |
|
|
"""删除指定的对话""" |
|
|
if not conversation_id: |
|
|
print("无法删除对话: 缺少conversation_id") |
|
|
return False |
|
|
|
|
|
headers = { |
|
|
"accept": "application/json, text/plain, */*", |
|
|
"accept-language": "zh-CN,zh;q=0.9", |
|
|
"content-type": "application/json", |
|
|
"cookie": cookies, |
|
|
"user-agent": random.choice(USER_AGENTS), |
|
|
"x-abacus-org-host": "apps" |
|
|
} |
|
|
|
|
|
if session_token: |
|
|
headers["session-token"] = session_token |
|
|
|
|
|
delete_payload = { |
|
|
"deploymentId": deployment_id, |
|
|
"deploymentConversationId": conversation_id |
|
|
} |
|
|
|
|
|
try: |
|
|
response = session.post( |
|
|
DELETE_CONVERSATION_URL, |
|
|
headers=headers, |
|
|
json=delete_payload |
|
|
) |
|
|
|
|
|
if response.status_code == 200: |
|
|
data = response.json() |
|
|
if data.get("success", False): |
|
|
print(f"成功删除对话: {conversation_id}") |
|
|
return True |
|
|
|
|
|
print(f"删除对话失败: {response.status_code} - {response.text[:100]}") |
|
|
return False |
|
|
except Exception as e: |
|
|
print(f"删除对话时出错: {e}") |
|
|
return False |
|
|
|
|
|
|
|
|
def is_conversation_valid(session, cookies, session_token, conversation_id, model_map, model): |
|
|
"""检查会话ID是否有效""" |
|
|
if not conversation_id: |
|
|
return False |
|
|
|
|
|
|
|
|
if not (model in model_map and len(model_map[model]) >= 2): |
|
|
return False |
|
|
|
|
|
external_app_id = model_map[model][0] |
|
|
|
|
|
|
|
|
headers = { |
|
|
"accept": "text/event-stream", |
|
|
"content-type": "text/plain;charset=UTF-8", |
|
|
"cookie": cookies, |
|
|
"user-agent": random.choice(USER_AGENTS) |
|
|
} |
|
|
|
|
|
if session_token: |
|
|
headers["session-token"] = session_token |
|
|
|
|
|
payload = { |
|
|
"requestId": str(uuid.uuid4()), |
|
|
"deploymentConversationId": conversation_id, |
|
|
"message": "", |
|
|
"isDesktop": False, |
|
|
"externalApplicationId": external_app_id |
|
|
} |
|
|
|
|
|
try: |
|
|
response = session.post( |
|
|
CHAT_URL, |
|
|
headers=headers, |
|
|
data=json.dumps(payload), |
|
|
stream=False |
|
|
) |
|
|
|
|
|
|
|
|
if response.status_code == 200: |
|
|
return True |
|
|
|
|
|
error_text = response.text |
|
|
if "Missing required parameter" in error_text: |
|
|
return False |
|
|
|
|
|
|
|
|
return True |
|
|
except: |
|
|
|
|
|
return False |
|
|
|
|
|
|
|
|
def get_or_create_conversation(session, cookies, session_token, conversation_id, model_map, model, user_index): |
|
|
"""获取有效的会话ID,如果无效则创建新会话""" |
|
|
|
|
|
print("将为每次对话创建新会话") |
|
|
need_create = True |
|
|
|
|
|
|
|
|
if need_create: |
|
|
if model in model_map and len(model_map[model]) >= 2: |
|
|
external_app_id = model_map[model][0] |
|
|
|
|
|
|
|
|
deployment_id = "14b2a314cc" |
|
|
|
|
|
new_conversation_id = create_conversation( |
|
|
session, cookies, session_token, |
|
|
external_application_id=external_app_id, |
|
|
deployment_id=deployment_id |
|
|
) |
|
|
|
|
|
if new_conversation_id: |
|
|
|
|
|
global USER_DATA, CURRENT_USER, LAST_CONVERSATION_IDS, DELETE_CHAT |
|
|
last_conversation_id = LAST_CONVERSATION_IDS[user_index] |
|
|
|
|
|
|
|
|
session, cookies, session_token, _, model_map, _ = USER_DATA[CURRENT_USER] |
|
|
USER_DATA[CURRENT_USER] = (session, cookies, session_token, new_conversation_id, model_map, user_index) |
|
|
|
|
|
|
|
|
update_conversation_id(user_index, new_conversation_id) |
|
|
|
|
|
|
|
|
LAST_CONVERSATION_IDS[user_index] = new_conversation_id |
|
|
|
|
|
return new_conversation_id |
|
|
|
|
|
|
|
|
return conversation_id |
|
|
|
|
|
|
|
|
def generate_trace_id(): |
|
|
"""Generu novan trace_id kaj sentry_trace""" |
|
|
trace_id = str(uuid.uuid4()).replace('-', '') |
|
|
sentry_trace = f"{trace_id}-{str(uuid.uuid4())[:16]}" |
|
|
return trace_id, sentry_trace |
|
|
|
|
|
|
|
|
def send_message(message, model, think=False): |
|
|
"""Flua traktado kaj plusendo de mesaĝoj""" |
|
|
global DELETE_CHAT, LAST_CONVERSATION_IDS |
|
|
(session, cookies, session_token, conversation_id, model_map, user_index) = get_user_data() |
|
|
|
|
|
|
|
|
last_conversation_id = conversation_id |
|
|
|
|
|
|
|
|
conversation_id = get_or_create_conversation(session, cookies, session_token, conversation_id, model_map, model, user_index) |
|
|
|
|
|
trace_id, sentry_trace = generate_trace_id() |
|
|
|
|
|
|
|
|
prompt_tokens, calculation_method = num_tokens_from_string(message, model) |
|
|
completion_buffer = io.StringIO() |
|
|
|
|
|
headers = { |
|
|
"accept": "text/event-stream", |
|
|
"accept-language": "zh-CN,zh;q=0.9", |
|
|
"baggage": f"sentry-environment=production,sentry-release=975eec6685013679c139fc88db2c48e123d5c604,sentry-public_key=3476ea6df1585dd10e92cdae3a66ff49,sentry-trace_id={trace_id}", |
|
|
"content-type": "text/plain;charset=UTF-8", |
|
|
"cookie": cookies, |
|
|
"sec-ch-ua": "\"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"", |
|
|
"sec-ch-ua-mobile": "?0", |
|
|
"sec-ch-ua-platform": "\"Windows\"", |
|
|
"sec-fetch-dest": "empty", |
|
|
"sec-fetch-mode": "cors", |
|
|
"sec-fetch-site": "same-origin", |
|
|
"sentry-trace": sentry_trace, |
|
|
"user-agent": random.choice(USER_AGENTS), |
|
|
"x-abacus-org-host": "apps" |
|
|
} |
|
|
|
|
|
if session_token: |
|
|
headers["session-token"] = session_token |
|
|
|
|
|
payload = { |
|
|
"requestId": str(uuid.uuid4()), |
|
|
"deploymentConversationId": conversation_id, |
|
|
"message": message, |
|
|
"isDesktop": False, |
|
|
"chatConfig": { |
|
|
"timezone": "Asia/Shanghai", |
|
|
"language": "zh-CN" |
|
|
}, |
|
|
"llmName": model_map[model][1], |
|
|
"externalApplicationId": model_map[model][0] |
|
|
} |
|
|
|
|
|
if think: |
|
|
payload["useThinking"] = think |
|
|
|
|
|
try: |
|
|
response = session.post( |
|
|
CHAT_URL, |
|
|
headers=headers, |
|
|
data=json.dumps(payload), |
|
|
stream=True, |
|
|
cookies=None |
|
|
) |
|
|
|
|
|
response.raise_for_status() |
|
|
|
|
|
def extract_segment(line_data): |
|
|
try: |
|
|
data = json.loads(line_data) |
|
|
if "segment" in data: |
|
|
if isinstance(data["segment"], str): |
|
|
return data["segment"] |
|
|
elif isinstance(data["segment"], dict) and "segment" in data["segment"]: |
|
|
return data["segment"]["segment"] |
|
|
return "" |
|
|
except: |
|
|
return "" |
|
|
|
|
|
def generate(): |
|
|
id = "" |
|
|
think_state = 2 |
|
|
|
|
|
yield "data: " + json.dumps({"object": "chat.completion.chunk", "choices": [{"delta": {"role": "assistant"}}]}) + "\n\n" |
|
|
|
|
|
for line in response.iter_lines(): |
|
|
if line: |
|
|
decoded_line = line.decode("utf-8") |
|
|
try: |
|
|
if think: |
|
|
data = json.loads(decoded_line) |
|
|
if data.get("type") != "text": |
|
|
continue |
|
|
elif think_state == 2: |
|
|
id = data.get("messageId") |
|
|
segment = "<think>\n" + data.get("segment", "") |
|
|
completion_buffer.write(segment) |
|
|
yield f"data: {json.dumps({'object': 'chat.completion.chunk', 'choices': [{'delta': {'content': segment}}]})}\n\n" |
|
|
think_state = 1 |
|
|
elif think_state == 1: |
|
|
if data.get("messageId") != id: |
|
|
segment = data.get("segment", "") |
|
|
completion_buffer.write(segment) |
|
|
yield f"data: {json.dumps({'object': 'chat.completion.chunk', 'choices': [{'delta': {'content': segment}}]})}\n\n" |
|
|
else: |
|
|
segment = "\n</think>\n" + data.get("segment", "") |
|
|
completion_buffer.write(segment) |
|
|
yield f"data: {json.dumps({'object': 'chat.completion.chunk', 'choices': [{'delta': {'content': segment}}]})}\n\n" |
|
|
think_state = 0 |
|
|
else: |
|
|
segment = data.get("segment", "") |
|
|
completion_buffer.write(segment) |
|
|
yield f"data: {json.dumps({'object': 'chat.completion.chunk', 'choices': [{'delta': {'content': segment}}]})}\n\n" |
|
|
else: |
|
|
segment = extract_segment(decoded_line) |
|
|
if segment: |
|
|
completion_buffer.write(segment) |
|
|
yield f"data: {json.dumps({'object': 'chat.completion.chunk', 'choices': [{'delta': {'content': segment}}]})}\n\n" |
|
|
except Exception as e: |
|
|
print(f"处理响应出错: {e}") |
|
|
|
|
|
yield "data: " + json.dumps({"object": "chat.completion.chunk", "choices": [{"delta": {}, "finish_reason": "stop"}]}) + "\n\n" |
|
|
yield "data: [DONE]\n\n" |
|
|
|
|
|
|
|
|
completion_result, _ = num_tokens_from_string(completion_buffer.getvalue(), model) |
|
|
|
|
|
|
|
|
_, compute_points = save_conversation_history(session, cookies, session_token, conversation_id) |
|
|
|
|
|
|
|
|
update_model_stats(model, prompt_tokens, completion_result, calculation_method, compute_points) |
|
|
|
|
|
|
|
|
if DELETE_CHAT and last_conversation_id and last_conversation_id != conversation_id: |
|
|
delete_conversation(session, cookies, session_token, last_conversation_id) |
|
|
|
|
|
return Response(generate(), mimetype="text/event-stream") |
|
|
except requests.exceptions.RequestException as e: |
|
|
error_details = str(e) |
|
|
if hasattr(e, 'response') and e.response is not None: |
|
|
if hasattr(e.response, 'text'): |
|
|
error_details += f" - Response: {e.response.text[:200]}" |
|
|
print(f"发送消息失败: {error_details}") |
|
|
return jsonify({"error": f"Failed to send message: {error_details}"}), 500 |
|
|
|
|
|
|
|
|
def send_message_non_stream(message, model, think=False): |
|
|
"""Ne-flua traktado de mesaĝoj""" |
|
|
global DELETE_CHAT, LAST_CONVERSATION_IDS |
|
|
(session, cookies, session_token, conversation_id, model_map, user_index) = get_user_data() |
|
|
|
|
|
|
|
|
last_conversation_id = conversation_id |
|
|
|
|
|
|
|
|
conversation_id = get_or_create_conversation(session, cookies, session_token, conversation_id, model_map, model, user_index) |
|
|
|
|
|
trace_id, sentry_trace = generate_trace_id() |
|
|
|
|
|
|
|
|
prompt_tokens, calculation_method = num_tokens_from_string(message, model) |
|
|
|
|
|
headers = { |
|
|
"accept": "text/event-stream", |
|
|
"accept-language": "zh-CN,zh;q=0.9", |
|
|
"baggage": f"sentry-environment=production,sentry-release=975eec6685013679c139fc88db2c48e123d5c604,sentry-public_key=3476ea6df1585dd10e92cdae3a66ff49,sentry-trace_id={trace_id}", |
|
|
"content-type": "text/plain;charset=UTF-8", |
|
|
"cookie": cookies, |
|
|
"sec-ch-ua": "\"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"", |
|
|
"sec-ch-ua-mobile": "?0", |
|
|
"sec-ch-ua-platform": "\"Windows\"", |
|
|
"sec-fetch-dest": "empty", |
|
|
"sec-fetch-mode": "cors", |
|
|
"sec-fetch-site": "same-origin", |
|
|
"sentry-trace": sentry_trace, |
|
|
"user-agent": random.choice(USER_AGENTS), |
|
|
"x-abacus-org-host": "apps" |
|
|
} |
|
|
|
|
|
if session_token: |
|
|
headers["session-token"] = session_token |
|
|
|
|
|
payload = { |
|
|
"requestId": str(uuid.uuid4()), |
|
|
"deploymentConversationId": conversation_id, |
|
|
"message": message, |
|
|
"isDesktop": False, |
|
|
"chatConfig": { |
|
|
"timezone": "Asia/Shanghai", |
|
|
"language": "zh-CN" |
|
|
}, |
|
|
"llmName": model_map[model][1], |
|
|
"externalApplicationId": model_map[model][0] |
|
|
} |
|
|
|
|
|
if think: |
|
|
payload["useThinking"] = think |
|
|
|
|
|
try: |
|
|
response = session.post( |
|
|
CHAT_URL, |
|
|
headers=headers, |
|
|
data=json.dumps(payload), |
|
|
stream=True, |
|
|
cookies=None |
|
|
) |
|
|
|
|
|
response.raise_for_status() |
|
|
buffer = io.StringIO() |
|
|
|
|
|
def extract_segment(line_data): |
|
|
try: |
|
|
data = json.loads(line_data) |
|
|
if "segment" in data: |
|
|
if isinstance(data["segment"], str): |
|
|
return data["segment"] |
|
|
elif isinstance(data["segment"], dict) and "segment" in data["segment"]: |
|
|
return data["segment"]["segment"] |
|
|
return "" |
|
|
except: |
|
|
return "" |
|
|
|
|
|
if think: |
|
|
id = "" |
|
|
think_state = 2 |
|
|
think_buffer = io.StringIO() |
|
|
content_buffer = io.StringIO() |
|
|
|
|
|
for line in response.iter_lines(): |
|
|
if line: |
|
|
decoded_line = line.decode("utf-8") |
|
|
try: |
|
|
data = json.loads(decoded_line) |
|
|
if data.get("type") != "text": |
|
|
continue |
|
|
elif think_state == 2: |
|
|
id = data.get("messageId") |
|
|
segment = data.get("segment", "") |
|
|
think_buffer.write(segment) |
|
|
think_state = 1 |
|
|
elif think_state == 1: |
|
|
if data.get("messageId") != id: |
|
|
segment = data.get("segment", "") |
|
|
content_buffer.write(segment) |
|
|
else: |
|
|
segment = data.get("segment", "") |
|
|
think_buffer.write(segment) |
|
|
think_state = 0 |
|
|
else: |
|
|
segment = data.get("segment", "") |
|
|
content_buffer.write(segment) |
|
|
except Exception as e: |
|
|
print(f"处理响应出错: {e}") |
|
|
|
|
|
think_content = think_buffer.getvalue() |
|
|
response_content = content_buffer.getvalue() |
|
|
|
|
|
|
|
|
completion_result, _ = num_tokens_from_string(think_content + response_content, model) |
|
|
|
|
|
|
|
|
_, compute_points = save_conversation_history(session, cookies, session_token, conversation_id) |
|
|
|
|
|
|
|
|
update_model_stats(model, prompt_tokens, completion_result, calculation_method, compute_points) |
|
|
|
|
|
|
|
|
if DELETE_CHAT and last_conversation_id and last_conversation_id != conversation_id: |
|
|
delete_conversation(session, cookies, session_token, last_conversation_id) |
|
|
|
|
|
return jsonify({ |
|
|
"id": f"chatcmpl-{str(uuid.uuid4())}", |
|
|
"object": "chat.completion", |
|
|
"created": int(time.time()), |
|
|
"model": model, |
|
|
"choices": [{ |
|
|
"index": 0, |
|
|
"message": { |
|
|
"role": "assistant", |
|
|
"content": f"<think>\n{think_content}\n</think>\n{response_content}" |
|
|
}, |
|
|
"finish_reason": "stop" |
|
|
}], |
|
|
"usage": { |
|
|
"prompt_tokens": prompt_tokens, |
|
|
"completion_tokens": completion_result, |
|
|
"total_tokens": prompt_tokens + completion_result |
|
|
} |
|
|
}) |
|
|
else: |
|
|
for line in response.iter_lines(): |
|
|
if line: |
|
|
decoded_line = line.decode("utf-8") |
|
|
segment = extract_segment(decoded_line) |
|
|
if segment: |
|
|
buffer.write(segment) |
|
|
|
|
|
response_content = buffer.getvalue() |
|
|
|
|
|
|
|
|
completion_result, _ = num_tokens_from_string(response_content, model) |
|
|
|
|
|
|
|
|
_, compute_points = save_conversation_history(session, cookies, session_token, conversation_id) |
|
|
|
|
|
|
|
|
update_model_stats(model, prompt_tokens, completion_result, calculation_method, compute_points) |
|
|
|
|
|
|
|
|
if DELETE_CHAT and last_conversation_id and last_conversation_id != conversation_id: |
|
|
delete_conversation(session, cookies, session_token, last_conversation_id) |
|
|
|
|
|
return jsonify({ |
|
|
"id": f"chatcmpl-{str(uuid.uuid4())}", |
|
|
"object": "chat.completion", |
|
|
"created": int(time.time()), |
|
|
"model": model, |
|
|
"choices": [{ |
|
|
"index": 0, |
|
|
"message": { |
|
|
"role": "assistant", |
|
|
"content": response_content |
|
|
}, |
|
|
"finish_reason": "stop" |
|
|
}], |
|
|
"usage": { |
|
|
"prompt_tokens": prompt_tokens, |
|
|
"completion_tokens": completion_result, |
|
|
"total_tokens": prompt_tokens + completion_result |
|
|
} |
|
|
}) |
|
|
except requests.exceptions.RequestException as e: |
|
|
error_details = str(e) |
|
|
if hasattr(e, 'response') and e.response is not None: |
|
|
if hasattr(e.response, 'text'): |
|
|
error_details += f" - Response: {e.response.text[:200]}" |
|
|
print(f"发送消息失败: {error_details}") |
|
|
return jsonify({"error": f"Failed to send message: {error_details}"}), 500 |
|
|
|
|
|
|
|
|
def format_message(messages): |
|
|
buffer = io.StringIO() |
|
|
role_map, prefix, messages = extract_role(messages) |
|
|
for message in messages: |
|
|
role = message.get("role") |
|
|
role = "\b" + role_map[role] if prefix else role_map[role] |
|
|
content = message.get("content").replace("\\n", "\n") |
|
|
pattern = re.compile(r"<\|removeRole\|>\n") |
|
|
if pattern.match(content): |
|
|
content = pattern.sub("", content) |
|
|
buffer.write(f"{content}\n") |
|
|
else: |
|
|
buffer.write(f"{role}: {content}\n\n") |
|
|
formatted_message = buffer.getvalue() |
|
|
return formatted_message |
|
|
|
|
|
|
|
|
def extract_role(messages): |
|
|
role_map = {"user": "Human", "assistant": "Assistant", "system": "System"} |
|
|
prefix = False |
|
|
first_message = messages[0]["content"] |
|
|
pattern = re.compile( |
|
|
r""" |
|
|
<roleInfo>\s* |
|
|
user:\s*(?P<user>[^\n]*)\s* |
|
|
assistant:\s*(?P<assistant>[^\n]*)\s* |
|
|
system:\s*(?P<system>[^\n]*)\s* |
|
|
prefix:\s*(?P<prefix>[^\n]*)\s* |
|
|
</roleInfo>\n |
|
|
""", |
|
|
re.VERBOSE, |
|
|
) |
|
|
match = pattern.search(first_message) |
|
|
if match: |
|
|
role_map = { |
|
|
"user": match.group("user"), |
|
|
"assistant": match.group("assistant"), |
|
|
"system": match.group("system"), |
|
|
} |
|
|
prefix = match.group("prefix") == "1" |
|
|
messages[0]["content"] = pattern.sub("", first_message) |
|
|
print(f"Extracted role map:") |
|
|
print( |
|
|
f"User: {role_map['user']}, Assistant: {role_map['assistant']}, System: {role_map['system']}" |
|
|
) |
|
|
print(f"Using prefix: {prefix}") |
|
|
return (role_map, prefix, messages) |
|
|
|
|
|
|
|
|
@app.route("/health", methods=["GET"]) |
|
|
def health_check(): |
|
|
global health_check_counter |
|
|
health_check_counter += 1 |
|
|
return jsonify({ |
|
|
"status": "healthy", |
|
|
"timestamp": datetime.now().isoformat(), |
|
|
"checks": health_check_counter |
|
|
}) |
|
|
|
|
|
|
|
|
def keep_alive(): |
|
|
"""每20分钟进行一次自我健康检查""" |
|
|
while True: |
|
|
try: |
|
|
requests.get("http://127.0.0.1:7860/health") |
|
|
time.sleep(1200) |
|
|
except: |
|
|
pass |
|
|
|
|
|
|
|
|
@app.route("/", methods=["GET"]) |
|
|
def index(): |
|
|
|
|
|
if PASSWORD and not flask_session.get('logged_in'): |
|
|
return redirect(url_for('login')) |
|
|
|
|
|
|
|
|
return redirect(url_for('dashboard')) |
|
|
|
|
|
|
|
|
def num_tokens_from_string(string, model=""): |
|
|
try: |
|
|
request_data = { |
|
|
"model": model, |
|
|
"messages": [{"role": "user", "content": string}] |
|
|
} |
|
|
|
|
|
response = requests.post( |
|
|
TOKENIZER_SERVICE_URL, |
|
|
json=request_data, |
|
|
timeout=10 |
|
|
) |
|
|
|
|
|
|
|
|
if response.status_code == 200: |
|
|
result = response.json() |
|
|
input_tokens = result.get("input_tokens", 0) |
|
|
return input_tokens, "api" |
|
|
elif response.status_code == 400: |
|
|
result = response.json() |
|
|
if "input_tokens" in result: |
|
|
print(f"使用估算token值: {result.get('input_tokens')}") |
|
|
return result.get("input_tokens", 0), "estimate" |
|
|
return len(string) // 4, "estimate" |
|
|
else: |
|
|
print(f"Tokenizer服务错误: {response.status_code} - {response.text}") |
|
|
return len(string) // 4, "estimate" |
|
|
except Exception as e: |
|
|
print(f"计算token错误: {e}") |
|
|
return len(string) // 4, "estimate" |
|
|
|
|
|
|
|
|
|
|
|
def update_model_stats(model, prompt_tokens, completion_tokens, calculation_method="estimate", compute_points=None): |
|
|
global model_usage_stats, total_tokens, model_usage_records |
|
|
|
|
|
|
|
|
|
|
|
utc_now = datetime.utcnow() |
|
|
|
|
|
beijing_time = utc_now + timedelta(hours=8) |
|
|
call_time = beijing_time.strftime('%Y-%m-%d %H:%M:%S') |
|
|
|
|
|
record = { |
|
|
"model": model, |
|
|
"call_time": call_time, |
|
|
"prompt_tokens": prompt_tokens, |
|
|
"completion_tokens": completion_tokens, |
|
|
"calculation_method": "精确" if calculation_method == "api" else "估算", |
|
|
"compute_points": compute_points |
|
|
} |
|
|
model_usage_records.append(record) |
|
|
|
|
|
|
|
|
if len(model_usage_records) > 500: |
|
|
model_usage_records.pop(0) |
|
|
|
|
|
|
|
|
save_model_usage_records() |
|
|
|
|
|
|
|
|
if model not in model_usage_stats: |
|
|
model_usage_stats[model] = { |
|
|
"count": 0, |
|
|
"prompt_tokens": 0, |
|
|
"completion_tokens": 0, |
|
|
"total_tokens": 0 |
|
|
} |
|
|
|
|
|
model_usage_stats[model]["count"] += 1 |
|
|
model_usage_stats[model]["prompt_tokens"] += prompt_tokens |
|
|
model_usage_stats[model]["completion_tokens"] += completion_tokens |
|
|
model_usage_stats[model]["total_tokens"] += (prompt_tokens + completion_tokens) |
|
|
|
|
|
total_tokens["prompt"] += prompt_tokens |
|
|
total_tokens["completion"] += completion_tokens |
|
|
total_tokens["total"] += (prompt_tokens + completion_tokens) |
|
|
|
|
|
|
|
|
|
|
|
def get_compute_points(): |
|
|
global compute_points, USER_DATA, users_compute_points |
|
|
|
|
|
if USER_NUM == 0: |
|
|
return |
|
|
|
|
|
|
|
|
users_compute_points = [] |
|
|
|
|
|
|
|
|
total_left = 0 |
|
|
total_points = 0 |
|
|
|
|
|
|
|
|
for i, user_data in enumerate(USER_DATA): |
|
|
try: |
|
|
session, cookies, session_token, _, _, _ = user_data |
|
|
|
|
|
|
|
|
if is_token_expired(session_token): |
|
|
session_token = refresh_token(session, cookies) |
|
|
if not session_token: |
|
|
print(f"用户{i+1}刷新token失败,无法获取计算点信息") |
|
|
continue |
|
|
USER_DATA[i] = (session, cookies, session_token, user_data[3], user_data[4], i) |
|
|
|
|
|
headers = { |
|
|
"accept": "application/json, text/plain, */*", |
|
|
"accept-language": "zh-CN,zh;q=0.9", |
|
|
"baggage": f"sentry-environment=production,sentry-release=93da8385541a6ce339b1f41b0c94428c70657e22,sentry-public_key=3476ea6df1585dd10e92cdae3a66ff49,sentry-trace_id={TRACE_ID}", |
|
|
"reai-ui": "1", |
|
|
"sec-ch-ua": "\"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"", |
|
|
"sec-ch-ua-mobile": "?0", |
|
|
"sec-ch-ua-platform": "\"Windows\"", |
|
|
"sec-fetch-dest": "empty", |
|
|
"sec-fetch-mode": "cors", |
|
|
"sec-fetch-site": "same-origin", |
|
|
"sentry-trace": SENTRY_TRACE, |
|
|
"session-token": session_token, |
|
|
"x-abacus-org-host": "apps", |
|
|
"cookie": cookies |
|
|
} |
|
|
|
|
|
response = session.get( |
|
|
COMPUTE_POINTS_URL, |
|
|
headers=headers |
|
|
) |
|
|
|
|
|
if response.status_code == 200: |
|
|
result = response.json() |
|
|
if result.get("success") and "result" in result: |
|
|
data = result["result"] |
|
|
left = data.get("computePointsLeft", 0) |
|
|
total = data.get("totalComputePoints", 0) |
|
|
used = total - left |
|
|
percentage = round((used / total) * 100, 2) if total > 0 else 0 |
|
|
|
|
|
|
|
|
beijing_now = datetime.utcnow() + timedelta(hours=8) |
|
|
|
|
|
|
|
|
user_points = { |
|
|
"user_id": i + 1, |
|
|
"left": left, |
|
|
"total": total, |
|
|
"used": used, |
|
|
"percentage": percentage, |
|
|
"last_update": beijing_now |
|
|
} |
|
|
users_compute_points.append(user_points) |
|
|
|
|
|
|
|
|
total_left += left |
|
|
total_points += total |
|
|
|
|
|
print(f"用户{i+1}计算点信息更新成功: 剩余 {left}, 总计 {total}") |
|
|
|
|
|
|
|
|
if i == 0: |
|
|
get_compute_points_log(session, cookies, session_token) |
|
|
else: |
|
|
print(f"获取用户{i+1}计算点信息失败: {result.get('error', '未知错误')}") |
|
|
else: |
|
|
print(f"获取用户{i+1}计算点信息失败,状态码: {response.status_code}") |
|
|
except Exception as e: |
|
|
print(f"获取用户{i+1}计算点信息异常: {e}") |
|
|
|
|
|
|
|
|
if users_compute_points: |
|
|
compute_points["left"] = total_left |
|
|
compute_points["total"] = total_points |
|
|
compute_points["used"] = total_points - total_left |
|
|
compute_points["percentage"] = round((compute_points["used"] / compute_points["total"]) * 100, 2) if compute_points["total"] > 0 else 0 |
|
|
compute_points["last_update"] = datetime.utcnow() + timedelta(hours=8) |
|
|
print(f"所有用户计算点总计: 剩余 {total_left}, 总计 {total_points}") |
|
|
|
|
|
|
|
|
def get_compute_points_log(session, cookies, session_token): |
|
|
global compute_points_log |
|
|
|
|
|
try: |
|
|
headers = { |
|
|
"accept": "application/json, text/plain, */*", |
|
|
"accept-language": "zh-CN,zh;q=0.9", |
|
|
"content-type": "application/json", |
|
|
"reai-ui": "1", |
|
|
"sec-ch-ua": "\"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"", |
|
|
"sec-ch-ua-mobile": "?0", |
|
|
"sec-ch-ua-platform": "\"Windows\"", |
|
|
"sec-fetch-dest": "empty", |
|
|
"sec-fetch-mode": "cors", |
|
|
"sec-fetch-site": "same-site", |
|
|
"session-token": session_token, |
|
|
"x-abacus-org-host": "apps", |
|
|
"cookie": cookies |
|
|
} |
|
|
|
|
|
response = session.post( |
|
|
COMPUTE_POINTS_LOG_URL, |
|
|
headers=headers, |
|
|
json={"byLlm": True} |
|
|
) |
|
|
|
|
|
if response.status_code == 200: |
|
|
result = response.json() |
|
|
if result.get("success") and "result" in result: |
|
|
data = result["result"] |
|
|
compute_points_log["columns"] = data.get("columns", {}) |
|
|
compute_points_log["log"] = data.get("log", []) |
|
|
print(f"计算点使用日志更新成功,获取到 {len(compute_points_log['log'])} 条记录") |
|
|
else: |
|
|
print(f"获取计算点使用日志失败: {result.get('error', '未知错误')}") |
|
|
else: |
|
|
print(f"获取计算点使用日志失败,状态码: {response.status_code}") |
|
|
except Exception as e: |
|
|
print(f"获取计算点使用日志异常: {e}") |
|
|
|
|
|
|
|
|
|
|
|
@app.route("/login", methods=["GET", "POST"]) |
|
|
def login(): |
|
|
error = None |
|
|
if request.method == "POST": |
|
|
password = request.form.get("password") |
|
|
if password and hashlib.sha256(password.encode()).hexdigest() == PASSWORD: |
|
|
flask_session['logged_in'] = True |
|
|
flask_session.permanent = True |
|
|
return redirect(url_for('dashboard')) |
|
|
else: |
|
|
|
|
|
error = "密码不正确。请使用设置的环境变量 password 或 password.txt 中的值作为密码和API认证密钥。" |
|
|
|
|
|
|
|
|
return render_template('login.html', error=error, space_url=SPACE_URL) |
|
|
|
|
|
|
|
|
@app.route("/logout") |
|
|
def logout(): |
|
|
flask_session.clear() |
|
|
return redirect(url_for('login')) |
|
|
|
|
|
|
|
|
@app.route("/dashboard") |
|
|
@require_auth |
|
|
def dashboard(): |
|
|
|
|
|
get_compute_points() |
|
|
|
|
|
|
|
|
beijing_now = datetime.utcnow() + timedelta(hours=8) |
|
|
uptime = beijing_now - START_TIME |
|
|
days = uptime.days |
|
|
hours, remainder = divmod(uptime.seconds, 3600) |
|
|
minutes, seconds = divmod(remainder, 60) |
|
|
|
|
|
if days > 0: |
|
|
uptime_str = f"{days}天 {hours}小时 {minutes}分钟" |
|
|
elif hours > 0: |
|
|
uptime_str = f"{hours}小时 {minutes}分钟" |
|
|
else: |
|
|
uptime_str = f"{minutes}分钟 {seconds}秒" |
|
|
|
|
|
|
|
|
beijing_year = beijing_now.year |
|
|
|
|
|
return render_template( |
|
|
'dashboard.html', |
|
|
uptime=uptime_str, |
|
|
health_checks=health_check_counter, |
|
|
user_count=USER_NUM, |
|
|
models=sorted(list(MODELS)), |
|
|
year=beijing_year, |
|
|
model_stats=model_usage_stats, |
|
|
total_tokens=total_tokens, |
|
|
compute_points=compute_points, |
|
|
compute_points_log=compute_points_log, |
|
|
space_url=SPACE_URL, |
|
|
users_compute_points=users_compute_points, |
|
|
model_usage_records=model_usage_records, |
|
|
delete_chat=DELETE_CHAT |
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
@app.route("/update_delete_chat_setting", methods=["POST"]) |
|
|
@require_auth |
|
|
def update_delete_chat_setting(): |
|
|
try: |
|
|
data = request.get_json() |
|
|
if data and "delete_chat" in data: |
|
|
global DELETE_CHAT |
|
|
DELETE_CHAT = bool(data["delete_chat"]) |
|
|
|
|
|
|
|
|
os.environ["DELETE_CHAT"] = "true" if DELETE_CHAT else "false" |
|
|
|
|
|
print(f"更新删除对话设置为: {DELETE_CHAT}") |
|
|
return jsonify({"success": True}) |
|
|
else: |
|
|
return jsonify({"success": False, "error": "缺少delete_chat参数"}) |
|
|
except Exception as e: |
|
|
print(f"更新删除对话设置失败: {e}") |
|
|
return jsonify({"success": False, "error": str(e)}) |
|
|
|
|
|
|
|
|
|
|
|
@app.route("/update_compute_point_toggle", methods=["POST"]) |
|
|
@require_auth |
|
|
def update_compute_point_toggle(): |
|
|
try: |
|
|
(session, cookies, session_token, conversation_id, model_map, user_index) = get_user_data() |
|
|
data = request.get_json() |
|
|
if data and "always_display" in data: |
|
|
headers = { |
|
|
"accept": "application/json, text/plain, */*", |
|
|
"accept-language": "zh-CN,zh;q=0.9", |
|
|
"content-type": "application/json", |
|
|
"reai-ui": "1", |
|
|
"sec-ch-ua": "\"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"", |
|
|
"sec-ch-ua-mobile": "?0", |
|
|
"sec-ch-ua-platform": "\"Windows\"", |
|
|
"sec-fetch-dest": "empty", |
|
|
"sec-fetch-mode": "cors", |
|
|
"sec-fetch-site": "same-site", |
|
|
"x-abacus-org-host": "apps" |
|
|
} |
|
|
|
|
|
if session_token: |
|
|
headers["session-token"] = session_token |
|
|
|
|
|
response = session.post( |
|
|
COMPUTE_POINT_TOGGLE_URL, |
|
|
headers=headers, |
|
|
json={"alwaysDisplay": data["always_display"]}, |
|
|
cookies=None |
|
|
) |
|
|
|
|
|
if response.status_code == 200: |
|
|
result = response.json() |
|
|
if result.get("success"): |
|
|
print(f"更新计算点数记录设置为: {data['always_display']}") |
|
|
return jsonify({"success": True}) |
|
|
|
|
|
return jsonify({"success": False, "error": "API调用失败"}) |
|
|
else: |
|
|
return jsonify({"success": False, "error": "缺少always_display参数"}) |
|
|
except Exception as e: |
|
|
print(f"更新计算点数记录设置失败: {e}") |
|
|
return jsonify({"success": False, "error": str(e)}) |
|
|
|
|
|
|
|
|
|
|
|
def get_space_url(): |
|
|
|
|
|
space_url = os.environ.get("SPACE_URL") |
|
|
if space_url: |
|
|
return space_url |
|
|
|
|
|
|
|
|
space_id = os.environ.get("SPACE_ID") |
|
|
if space_id: |
|
|
username, space_name = space_id.split("/") |
|
|
|
|
|
|
|
|
|
|
|
space_name = space_name.replace("_", "-") |
|
|
return f"https://{username}-{space_name}.hf.space" |
|
|
|
|
|
|
|
|
username = os.environ.get("SPACE_USERNAME") |
|
|
space_name = os.environ.get("SPACE_NAME") |
|
|
if username and space_name: |
|
|
|
|
|
|
|
|
space_name = space_name.replace("_", "-") |
|
|
return f"https://{username}-{space_name}.hf.space" |
|
|
|
|
|
|
|
|
return None |
|
|
|
|
|
|
|
|
SPACE_URL = get_space_url() |
|
|
if SPACE_URL: |
|
|
print(f"Space URL: {SPACE_URL}") |
|
|
print("注意:Hugging Face生成的URL会自动将空间名称中的下划线(_)替换为连字符(-)") |
|
|
|
|
|
|
|
|
def save_conversation_history(session, cookies, session_token, conversation_id, deployment_id="14b2a314cc"): |
|
|
"""保存对话历史,返回使用的计算点数""" |
|
|
if not conversation_id: |
|
|
return False, None |
|
|
|
|
|
headers = { |
|
|
"accept": "application/json, text/plain, */*", |
|
|
"accept-language": "zh-CN,zh;q=0.9", |
|
|
"baggage": f"sentry-environment=production,sentry-release=946244517de08b08598b94f18098411f5a5352d5,sentry-public_key=3476ea6df1585dd10e92cdae3a66ff49,sentry-trace_id={TRACE_ID}", |
|
|
"reai-ui": "1", |
|
|
"sec-ch-ua": "\"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"", |
|
|
"sec-ch-ua-mobile": "?0", |
|
|
"sec-ch-ua-platform": "\"Windows\"", |
|
|
"sec-fetch-dest": "empty", |
|
|
"sec-fetch-mode": "cors", |
|
|
"sec-fetch-site": "same-origin", |
|
|
"sentry-trace": f"{TRACE_ID}-800cb7f4613dec52", |
|
|
"x-abacus-org-host": "apps" |
|
|
} |
|
|
|
|
|
if session_token: |
|
|
headers["session-token"] = session_token |
|
|
|
|
|
params = { |
|
|
"deploymentId": deployment_id, |
|
|
"deploymentConversationId": conversation_id, |
|
|
"skipDocumentBoundingBoxes": "true", |
|
|
"filterIntermediateConversationEvents": "false", |
|
|
"getUnusedDocumentUploads": "false" |
|
|
} |
|
|
|
|
|
try: |
|
|
response = session.get( |
|
|
GET_CONVERSATION_URL, |
|
|
headers=headers, |
|
|
params=params, |
|
|
cookies=None |
|
|
) |
|
|
|
|
|
if response.status_code == 200: |
|
|
data = response.json() |
|
|
if data.get("success"): |
|
|
|
|
|
history = data.get("result", {}).get("history", []) |
|
|
compute_points = None |
|
|
for msg in reversed(history): |
|
|
if msg.get("role") == "BOT": |
|
|
compute_points = msg.get("computePointsUsed") |
|
|
break |
|
|
print(f"成功保存对话历史: {conversation_id}, 使用计算点: {compute_points}") |
|
|
return True, compute_points |
|
|
else: |
|
|
print(f"保存对话历史失败: {data.get('error', '未知错误')}") |
|
|
else: |
|
|
print(f"保存对话历史失败,状态码: {response.status_code}") |
|
|
return False, None |
|
|
except Exception as e: |
|
|
print(f"保存对话历史时出错: {e}") |
|
|
return False, None |
|
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
|
|
|
|
threading.Thread(target=keep_alive, daemon=True).start() |
|
|
|
|
|
|
|
|
load_model_usage_records() |
|
|
|
|
|
|
|
|
get_compute_points() |
|
|
|
|
|
port = int(os.environ.get("PORT", 9876)) |
|
|
app.run(port=port, host="0.0.0.0") |
|
|
|