Tamanskaya/abacus_chat_proxy
0
1from flask import Flask, request, jsonify, Response, render_template_string, render_template, redirect, url_for, session as flask_session2import requests3import time4import json5import uuid6import random7import io8import re9from functools import wraps10import hashlib11import jwt 12import os13import threading14from datetime import datetime, timedelta15import tiktoken # 导入tiktoken来计算token数量16 17app = Flask(__name__, template_folder='templates')18app.secret_key = os.environ.get("SECRET_KEY", "abacus_chat_proxy_secret_key")19app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(days=7)20 21 22API_ENDPOINT_URL = "https://abacus.ai/api/v0/describeDeployment"23MODEL_LIST_URL = "https://abacus.ai/api/v0/listExternalApplications"24CHAT_URL = "https://apps.abacus.ai/api/_chatLLMSendMessageSSE"25USER_INFO_URL = "https://abacus.ai/api/v0/_getUserInfo"26COMPUTE_POINTS_URL = "https://apps.abacus.ai/api/_getOrganizationComputePoints"27COMPUTE_POINTS_LOG_URL = "https://abacus.ai/api/v0/_getOrganizationComputePointLog"28CREATE_CONVERSATION_URL = "https://apps.abacus.ai/api/createDeploymentConversation"29 30 31USER_AGENTS = [32 "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36"33]34 35 36PASSWORD = None37USER_NUM = 038USER_DATA = []39CURRENT_USER = -140MODELS = set()41 42 43TRACE_ID = "3042e28b3abf475d8d973c7e904935af"44SENTRY_TRACE = f"{TRACE_ID}-80d9d2538b2682d0"45 46 47# 添加一个计数器记录健康检查次数48health_check_counter = 049 50 51# 添加统计变量52model_usage_stats = {} # 模型使用次数统计53total_tokens = {54 "prompt": 0, # 输入token统计55 "completion": 0, # 输出token统计56 "total": 0 # 总token统计57}58 59# 模型调用记录60model_usage_records = [] # 每次调用详细记录61MODEL_USAGE_RECORDS_FILE = "/app/model_usage_records.json" # 调用记录保存文件,使用/app目录确保HF中持久化62 63# 计算点信息64compute_points = {65 "left": 0, # 剩余计算点66 "total": 0, # 总计算点67 "used": 0, # 已使用计算点68 "percentage": 0, # 使用百分比69 "last_update": None # 最后更新时间70}71 72# 计算点使用日志73compute_points_log = {74 "columns": {}, # 列名75 "log": [] # 日志数据76}77 78# 多用户计算点信息79users_compute_points = []80 81# 记录启动时间82START_TIME = datetime.utcnow() + timedelta(hours=8) # 北京时间83 84 85# 自定义JSON编码器,处理datetime对象86class DateTimeEncoder(json.JSONEncoder):87 def default(self, obj):88 if isinstance(obj, datetime):89 return obj.strftime('%Y-%m-%d %H:%M:%S')90 return super(DateTimeEncoder, self).default(obj)91 92 93# 加载模型调用记录94def load_model_usage_records():95 global model_usage_records96 try:97 if os.path.exists(MODEL_USAGE_RECORDS_FILE):98 with open(MODEL_USAGE_RECORDS_FILE, 'r', encoding='utf-8') as f:99 records = json.load(f)100 if isinstance(records, list):101 model_usage_records = records102 print(f"成功加载 {len(model_usage_records)} 条模型调用记录")103 else:104 print("调用记录文件格式不正确,初始化为空列表")105 except Exception as e:106 print(f"加载模型调用记录失败: {e}")107 model_usage_records = []108 109# 保存模型调用记录110def save_model_usage_records():111 try:112 with open(MODEL_USAGE_RECORDS_FILE, 'w', encoding='utf-8') as f:113 json.dump(model_usage_records, f, ensure_ascii=False, indent=2, cls=DateTimeEncoder)114 print(f"成功保存 {len(model_usage_records)} 条模型调用记录")115 except Exception as e:116 print(f"保存模型调用记录失败: {e}")117 118 119def update_conversation_id(user_index, conversation_id):120 """更新用户的conversation_id并保存到配置文件"""121 try:122 with open("config.json", "r") as f:123 config = json.load(f)124 125 if "config" in config and user_index < len(config["config"]):126 config["config"][user_index]["conversation_id"] = conversation_id127 128 # 保存到配置文件129 with open("config.json", "w") as f:130 json.dump(config, f, indent=4)131 132 print(f"已将用户 {user_index+1} 的conversation_id更新为: {conversation_id}")133 else:134 print(f"更新conversation_id失败: 配置文件格式错误或用户索引越界")135 except Exception as e:136 print(f"更新conversation_id失败: {e}")137 138 139def resolve_config():140 # 从环境变量读取多组配置141 config_list = []142 i = 1143 while True:144 cookie = os.environ.get(f"cookie_{i}")145 if not cookie:146 break147 148 # 为每个cookie创建一个配置项,conversation_id初始为空149 config_list.append({150 "conversation_id": "", # 初始为空,将通过get_or_create_conversation自动创建151 "cookies": cookie152 })153 i += 1154 155 # 如果环境变量存在配置,使用环境变量的配置156 if config_list:157 print(f"从环境变量加载了 {len(config_list)} 个配置")158 return config_list159 160 # 如果环境变量不存在,从文件读取161 try:162 with open("config.json", "r") as f:163 config = json.load(f)164 config_list = config.get("config")165 return config_list166 except FileNotFoundError:167 print("未找到config.json文件")168 return []169 except json.JSONDecodeError:170 print("config.json格式错误")171 return []172 173 174def get_password():175 global PASSWORD176 # 从环境变量读取密码177 env_password = os.environ.get("password")178 if env_password:179 PASSWORD = hashlib.sha256(env_password.encode()).hexdigest()180 return181 182 # 如果环境变量不存在,从文件读取183 try:184 with open("password.txt", "r") as f:185 PASSWORD = f.read().strip()186 except FileNotFoundError:187 with open("password.txt", "w") as f:188 PASSWORD = None189 190 191def require_auth(f):192 @wraps(f)193 def decorated(*args, **kwargs):194 if not PASSWORD:195 return f(*args, **kwargs)196 197 # 检查Flask会话是否已登录198 if flask_session.get('logged_in'):199 return f(*args, **kwargs)200 201 # 如果是API请求,检查Authorization头202 auth = request.authorization203 if not auth or not check_auth(auth.token):204 # 如果是浏览器请求,重定向到登录页面205 if request.headers.get('Accept', '').find('text/html') >= 0:206 return redirect(url_for('login'))207 return jsonify({"error": "Unauthorized access"}), 401208 return f(*args, **kwargs)209 210 return decorated211 212 213def check_auth(token):214 return hashlib.sha256(token.encode()).hexdigest() == PASSWORD215 216 217def is_token_expired(token):218 if not token:219 return True220 221 try:222 # Malkodi tokenon sen validigo de subskribo223 payload = jwt.decode(token, options={"verify_signature": False})224 # Akiru eksvalidiĝan tempon, konsideru eksvalidiĝinta 5 minutojn antaŭe225 return payload.get('exp', 0) - time.time() < 300226 except:227 return True228 229 230def refresh_token(session, cookies):231 """Uzu kuketon por refreŝigi session token, nur revenigu novan tokenon"""232 headers = {233 "accept": "application/json, text/plain, */*",234 "accept-language": "zh-CN,zh;q=0.9",235 "content-type": "application/json",236 "reai-ui": "1",237 "sec-ch-ua": "\"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"",238 "sec-ch-ua-mobile": "?0",239 "sec-ch-ua-platform": "\"Windows\"",240 "sec-fetch-dest": "empty",241 "sec-fetch-mode": "cors",242 "sec-fetch-site": "same-site",243 "x-abacus-org-host": "apps",244 "user-agent": random.choice(USER_AGENTS),245 "origin": "https://apps.abacus.ai",246 "referer": "https://apps.abacus.ai/",247 "cookie": cookies248 }249 250 try:251 response = session.post(252 USER_INFO_URL,253 headers=headers,254 json={},255 cookies=None256 )257 258 if response.status_code == 200:259 response_data = response.json()260 if response_data.get('success') and 'sessionToken' in response_data.get('result', {}):261 return response_data['result']['sessionToken']262 else:263 print(f"刷新token失败: {response_data.get('error', '未知错误')}")264 return None265 else:266 print(f"刷新token失败,状态码: {response.status_code}")267 return None268 except Exception as e:269 print(f"刷新token异常: {e}")270 return None271 272 273def get_model_map(session, cookies, session_token):274 """Akiru disponeblan modelan liston kaj ĝiajn mapajn rilatojn"""275 headers = {276 "accept": "application/json, text/plain, */*",277 "accept-language": "zh-CN,zh;q=0.9",278 "content-type": "application/json",279 "reai-ui": "1",280 "sec-ch-ua": "\"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"",281 "sec-ch-ua-mobile": "?0",282 "sec-ch-ua-platform": "\"Windows\"",283 "sec-fetch-dest": "empty",284 "sec-fetch-mode": "cors",285 "sec-fetch-site": "same-site",286 "x-abacus-org-host": "apps",287 "user-agent": random.choice(USER_AGENTS),288 "origin": "https://apps.abacus.ai",289 "referer": "https://apps.abacus.ai/",290 "cookie": cookies291 }292 293 if session_token:294 headers["session-token"] = session_token295 296 model_map = {}297 models_set = set()298 299 try:300 response = session.post(301 MODEL_LIST_URL,302 headers=headers,303 json={},304 cookies=None305 )306 307 if response.status_code != 200:308 print(f"获取模型列表失败,状态码: {response.status_code}")309 raise Exception("API请求失败")310 311 data = response.json()312 if not data.get('success'):313 print(f"获取模型列表失败: {data.get('error', '未知错误')}")314 raise Exception("API返回错误")315 316 applications = []317 if isinstance(data.get('result'), dict):318 applications = data.get('result', {}).get('externalApplications', [])319 elif isinstance(data.get('result'), list):320 applications = data.get('result', [])321 322 for app in applications:323 app_name = app.get('name', '')324 app_id = app.get('externalApplicationId', '')325 prediction_overrides = app.get('predictionOverrides', {})326 llm_name = prediction_overrides.get('llmName', '') if prediction_overrides else ''327 328 if not (app_name and app_id and llm_name):329 continue330 331 model_name = app_name332 model_map[model_name] = (app_id, llm_name)333 models_set.add(model_name)334 335 if not model_map:336 raise Exception("未找到任何可用模型")337 338 return model_map, models_set339 340 except Exception as e:341 print(f"获取模型列表异常: {e}")342 raise343 344 345def init_session():346 get_password()347 global USER_NUM, MODELS, USER_DATA348 config_list = resolve_config()349 user_num = len(config_list)350 all_models = set()351 352 for i in range(user_num):353 user = config_list[i]354 cookies = user.get("cookies")355 conversation_id = user.get("conversation_id")356 session = requests.Session()357 358 session_token = refresh_token(session, cookies)359 if not session_token:360 print(f"无法获取cookie {i+1}的token")361 continue362 363 try:364 model_map, models_set = get_model_map(session, cookies, session_token)365 all_models.update(models_set)366 USER_DATA.append((session, cookies, session_token, conversation_id, model_map, i))367 except Exception as e:368 print(f"配置用户 {i+1} 失败: {e}")369 continue370 371 USER_NUM = len(USER_DATA)372 if USER_NUM == 0:373 print("No user available, exiting...")374 exit(1)375 376 MODELS = all_models377 print(f"启动完成,共配置 {USER_NUM} 个用户")378 379 380def update_cookie(session, cookies):381 cookie_jar = {}382 for key, value in session.cookies.items():383 cookie_jar[key] = value384 cookie_dict = {}385 for item in cookies.split(";"):386 key, value = item.strip().split("=", 1)387 cookie_dict[key] = value388 cookie_dict.update(cookie_jar)389 cookies = "; ".join([f"{key}={value}" for key, value in cookie_dict.items()])390 return cookies391 392 393user_data = init_session()394 395 396@app.route("/v1/models", methods=["GET"])397@require_auth398def get_models():399 if len(MODELS) == 0:400 return jsonify({"error": "No models available"}), 500401 model_list = []402 for model in MODELS:403 model_list.append(404 {405 "id": model,406 "object": "model",407 "created": int(time.time()),408 "owned_by": "Elbert",409 "name": model,410 }411 )412 return jsonify({"object": "list", "data": model_list})413 414 415@app.route("/v1/chat/completions", methods=["POST"])416@require_auth417def chat_completions():418 openai_request = request.get_json()419 stream = openai_request.get("stream", False)420 messages = openai_request.get("messages")421 if messages is None:422 return jsonify({"error": "Messages is required", "status": 400}), 400423 model = openai_request.get("model")424 if model not in MODELS:425 return (426 jsonify(427 {428 "error": "Model not available, check if it is configured properly",429 "status": 404,430 }431 ),432 404,433 )434 message = format_message(messages)435 think = (436 openai_request.get("think", False) if model == "Claude Sonnet 3.7" else False437 )438 return (439 send_message(message, model, think)440 if stream441 else send_message_non_stream(message, model, think)442 )443 444 445def get_user_data():446 global CURRENT_USER447 CURRENT_USER = (CURRENT_USER + 1) % USER_NUM448 print(f"使用配置 {CURRENT_USER+1}")449 450 # Akiru uzantajn datumojn451 session, cookies, session_token, conversation_id, model_map, user_index = USER_DATA[CURRENT_USER]452 453 # Kontrolu ĉu la tokeno eksvalidiĝis, se jes, refreŝigu ĝin454 if is_token_expired(session_token):455 print(f"Cookie {CURRENT_USER+1}的token已过期或即将过期,正在刷新...")456 new_token = refresh_token(session, cookies)457 if new_token:458 # Ĝisdatigu la globale konservitan tokenon459 USER_DATA[CURRENT_USER] = (session, cookies, new_token, conversation_id, model_map, user_index)460 session_token = new_token461 print(f"成功更新token: {session_token[:15]}...{session_token[-15:]}")462 else:463 print(f"警告:无法刷新Cookie {CURRENT_USER+1}的token,继续使用当前token")464 465 return (session, cookies, session_token, conversation_id, model_map, user_index)466 467 468def create_conversation(session, cookies, session_token, external_application_id=None, deployment_id=None):469 """创建新的会话"""470 if not (external_application_id and deployment_id):471 print("无法创建新会话: 缺少必要参数")472 return None473 474 headers = {475 "accept": "application/json, text/plain, */*",476 "accept-language": "zh-CN,zh;q=0.9",477 "content-type": "application/json",478 "cookie": cookies,479 "user-agent": random.choice(USER_AGENTS),480 "x-abacus-org-host": "apps"481 }482 483 if session_token:484 headers["session-token"] = session_token485 486 create_payload = {487 "deploymentId": deployment_id,488 "name": "New Chat",489 "externalApplicationId": external_application_id490 }491 492 try:493 response = session.post(494 CREATE_CONVERSATION_URL,495 headers=headers,496 json=create_payload497 )498 499 if response.status_code == 200:500 data = response.json()501 if data.get("success", False):502 new_conversation_id = data.get("result", {}).get("deploymentConversationId")503 if new_conversation_id:504 print(f"成功创建新的conversation: {new_conversation_id}")505 return new_conversation_id506 507 print(f"创建会话失败: {response.status_code} - {response.text[:100]}")508 return None509 except Exception as e:510 print(f"创建会话时出错: {e}")511 return None512 513 514def is_conversation_valid(session, cookies, session_token, conversation_id, model_map, model):515 """检查会话ID是否有效"""516 if not conversation_id:517 return False518 519 # 如果没有这些信息,无法验证520 if not (model in model_map and len(model_map[model]) >= 2):521 return False522 523 external_app_id = model_map[model][0]524 525 # 尝试发送一个空消息来测试会话ID是否有效526 headers = {527 "accept": "text/event-stream",528 "content-type": "text/plain;charset=UTF-8",529 "cookie": cookies,530 "user-agent": random.choice(USER_AGENTS)531 }532 533 if session_token:534 headers["session-token"] = session_token535 536 payload = {537 "requestId": str(uuid.uuid4()),538 "deploymentConversationId": conversation_id,539 "message": "", # 空消息540 "isDesktop": False,541 "externalApplicationId": external_app_id542 }543 544 try:545 response = session.post(546 CHAT_URL,547 headers=headers,548 data=json.dumps(payload),549 stream=False550 )551 552 # 即使返回错误,只要不是缺少ID的错误,也说明ID是有效的553 if response.status_code == 200:554 return True555 556 error_text = response.text557 if "Missing required parameter" in error_text:558 return False559 560 # 其他类型的错误,可能ID是有效的但有其他问题561 return True562 except:563 # 如果请求出错,无法确定,返回False让系统创建新ID564 return False565 566 567def get_or_create_conversation(session, cookies, session_token, conversation_id, model_map, model, user_index):568 """获取有效的会话ID,如果无效则创建新会话"""569 # 如果conversation_id为None或为空字符串,直接创建新会话570 if not conversation_id:571 print("会话ID为空,将创建新会话")572 need_create = True573 else:574 # 检查现有会话ID是否有效575 need_create = not is_conversation_valid(session, cookies, session_token, conversation_id, model_map, model)576 if need_create:577 print(f"会话ID {conversation_id} 无效,将创建新会话")578 579 # 如果需要创建新会话580 if need_create:581 if model in model_map and len(model_map[model]) >= 2:582 external_app_id = model_map[model][0]583 # 创建会话时需要deployment_id,我们先使用一个固定值584 # 在实际应用中应从API响应中获取585 deployment_id = "14b2a314cc" # 这是从您提供的请求中获取的586 587 new_conversation_id = create_conversation(588 session, cookies, session_token, 589 external_application_id=external_app_id,590 deployment_id=deployment_id591 )592 593 if new_conversation_id:594 # 更新全局存储的会话ID595 global USER_DATA, CURRENT_USER596 session, cookies, session_token, _, model_map, _ = USER_DATA[CURRENT_USER]597 USER_DATA[CURRENT_USER] = (session, cookies, session_token, new_conversation_id, model_map, user_index)598 599 # 保存到配置文件600 update_conversation_id(user_index, new_conversation_id)601 602 return new_conversation_id603 604 # 如果无法创建,返回原始ID605 return conversation_id606 607 608def generate_trace_id():609 """Generu novan trace_id kaj sentry_trace"""610 trace_id = str(uuid.uuid4()).replace('-', '')611 sentry_trace = f"{trace_id}-{str(uuid.uuid4())[:16]}"612 return trace_id, sentry_trace613 614 615def send_message(message, model, think=False):616 """Flua traktado kaj plusendo de mesaĝoj"""617 (session, cookies, session_token, conversation_id, model_map, user_index) = get_user_data()618 619 # 确保有有效的会话ID620 conversation_id = get_or_create_conversation(session, cookies, session_token, conversation_id, model_map, model, user_index)621 622 trace_id, sentry_trace = generate_trace_id()623 624 # 计算输入token625 prompt_tokens = num_tokens_from_string(message)626 completion_buffer = io.StringIO() # 收集所有输出用于计算token627 628 headers = {629 "accept": "text/event-stream",630 "accept-language": "zh-CN,zh;q=0.9",631 "baggage": f"sentry-environment=production,sentry-release=975eec6685013679c139fc88db2c48e123d5c604,sentry-public_key=3476ea6df1585dd10e92cdae3a66ff49,sentry-trace_id={trace_id}",632 "content-type": "text/plain;charset=UTF-8",633 "cookie": cookies,634 "sec-ch-ua": "\"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"",635 "sec-ch-ua-mobile": "?0",636 "sec-ch-ua-platform": "\"Windows\"",637 "sec-fetch-dest": "empty",638 "sec-fetch-mode": "cors",639 "sec-fetch-site": "same-origin",640 "sentry-trace": sentry_trace,641 "user-agent": random.choice(USER_AGENTS)642 }643 644 if session_token:645 headers["session-token"] = session_token646 647 payload = {648 "requestId": str(uuid.uuid4()),649 "deploymentConversationId": conversation_id,650 "message": message,651 "isDesktop": False,652 "chatConfig": {653 "timezone": "Asia/Shanghai",654 "language": "zh-CN"655 },656 "llmName": model_map[model][1],657 "externalApplicationId": model_map[model][0],658 "regenerate": True,659 "editPrompt": True660 }661 662 if think:663 payload["useThinking"] = think664 665 try:666 response = session.post(667 CHAT_URL,668 headers=headers,669 data=json.dumps(payload),670 stream=True671 )672 673 response.raise_for_status()674 675 def extract_segment(line_data):676 try:677 data = json.loads(line_data)678 if "segment" in data:679 if isinstance(data["segment"], str):680 return data["segment"]681 elif isinstance(data["segment"], dict) and "segment" in data["segment"]:682 return data["segment"]["segment"]683 return ""684 except:685 return ""686 687 def generate():688 id = ""689 think_state = 2690 691 yield "data: " + json.dumps({"object": "chat.completion.chunk", "choices": [{"delta": {"role": "assistant"}}]}) + "\n\n"692 693 for line in response.iter_lines():694 if line:695 decoded_line = line.decode("utf-8")696 try:697 if think:698 data = json.loads(decoded_line)699 if data.get("type") != "text":700 continue701 elif think_state == 2:702 id = data.get("messageId")703 segment = "<think>\n" + data.get("segment", "")704 completion_buffer.write(segment) # 收集输出705 yield f"data: {json.dumps({'object': 'chat.completion.chunk', 'choices': [{'delta': {'content': segment}}]})}\n\n"706 think_state = 1707 elif think_state == 1:708 if data.get("messageId") != id:709 segment = data.get("segment", "")710 completion_buffer.write(segment) # 收集输出711 yield f"data: {json.dumps({'object': 'chat.completion.chunk', 'choices': [{'delta': {'content': segment}}]})}\n\n"712 else:713 segment = "\n</think>\n" + data.get("segment", "")714 completion_buffer.write(segment) # 收集输出715 yield f"data: {json.dumps({'object': 'chat.completion.chunk', 'choices': [{'delta': {'content': segment}}]})}\n\n"716 think_state = 0717 else:718 segment = data.get("segment", "")719 completion_buffer.write(segment) # 收集输出720 yield f"data: {json.dumps({'object': 'chat.completion.chunk', 'choices': [{'delta': {'content': segment}}]})}\n\n"721 else:722 segment = extract_segment(decoded_line)723 if segment:724 completion_buffer.write(segment) # 收集输出725 yield f"data: {json.dumps({'object': 'chat.completion.chunk', 'choices': [{'delta': {'content': segment}}]})}\n\n"726 except Exception as e:727 print(f"处理响应出错: {e}")728 729 yield "data: " + json.dumps({"object": "chat.completion.chunk", "choices": [{"delta": {}, "finish_reason": "stop"}]}) + "\n\n"730 yield "data: [DONE]\n\n"731 732 # 在流式传输完成后计算token并更新统计733 completion_tokens = num_tokens_from_string(completion_buffer.getvalue())734 update_model_stats(model, prompt_tokens, completion_tokens)735 736 return Response(generate(), mimetype="text/event-stream")737 except requests.exceptions.RequestException as e:738 error_details = str(e)739 if hasattr(e, 'response') and e.response is not None:740 if hasattr(e.response, 'text'):741 error_details += f" - Response: {e.response.text[:200]}"742 print(f"发送消息失败: {error_details}")743 return jsonify({"error": f"Failed to send message: {error_details}"}), 500744 745 746def send_message_non_stream(message, model, think=False):747 """Ne-flua traktado de mesaĝoj"""748 (session, cookies, session_token, conversation_id, model_map, user_index) = get_user_data()749 750 # 确保有有效的会话ID751 conversation_id = get_or_create_conversation(session, cookies, session_token, conversation_id, model_map, model, user_index)752 753 trace_id, sentry_trace = generate_trace_id()754 755 # 计算输入token756 prompt_tokens = num_tokens_from_string(message)757 758 headers = {759 "accept": "text/event-stream",760 "accept-language": "zh-CN,zh;q=0.9",761 "baggage": f"sentry-environment=production,sentry-release=975eec6685013679c139fc88db2c48e123d5c604,sentry-public_key=3476ea6df1585dd10e92cdae3a66ff49,sentry-trace_id={trace_id}",762 "content-type": "text/plain;charset=UTF-8",763 "cookie": cookies,764 "sec-ch-ua": "\"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"",765 "sec-ch-ua-mobile": "?0",766 "sec-ch-ua-platform": "\"Windows\"",767 "sec-fetch-dest": "empty",768 "sec-fetch-mode": "cors",769 "sec-fetch-site": "same-origin",770 "sentry-trace": sentry_trace,771 "user-agent": random.choice(USER_AGENTS)772 }773 774 if session_token:775 headers["session-token"] = session_token776 777 payload = {778 "requestId": str(uuid.uuid4()),779 "deploymentConversationId": conversation_id,780 "message": message,781 "isDesktop": False,782 "chatConfig": {783 "timezone": "Asia/Shanghai",784 "language": "zh-CN"785 },786 "llmName": model_map[model][1],787 "externalApplicationId": model_map[model][0],788 "regenerate": True,789 "editPrompt": True790 }791 792 if think:793 payload["useThinking"] = think794 795 try:796 response = session.post(797 CHAT_URL,798 headers=headers,799 data=json.dumps(payload),800 stream=True801 )802 803 response.raise_for_status()804 buffer = io.StringIO()805 806 def extract_segment(line_data):807 try:808 data = json.loads(line_data)809 if "segment" in data:810 if isinstance(data["segment"], str):811 return data["segment"]812 elif isinstance(data["segment"], dict) and "segment" in data["segment"]:813 return data["segment"]["segment"]814 return ""815 except:816 return ""817 818 if think:819 id = ""820 think_state = 2821 think_buffer = io.StringIO()822 content_buffer = io.StringIO()823 824 for line in response.iter_lines():825 if line:826 decoded_line = line.decode("utf-8")827 try:828 data = json.loads(decoded_line)829 if data.get("type") != "text":830 continue831 elif think_state == 2:832 id = data.get("messageId")833 segment = data.get("segment", "")834 think_buffer.write(segment)835 think_state = 1836 elif think_state == 1:837 if data.get("messageId") != id:838 segment = data.get("segment", "")839 content_buffer.write(segment)840 else:841 segment = data.get("segment", "")842 think_buffer.write(segment)843 think_state = 0844 else:845 segment = data.get("segment", "")846 content_buffer.write(segment)847 except Exception as e:848 print(f"处理响应出错: {e}")849 850 think_content = think_buffer.getvalue()851 response_content = content_buffer.getvalue()852 853 # 计算输出token并更新统计信息854 completion_tokens = num_tokens_from_string(think_content + response_content)855 update_model_stats(model, prompt_tokens, completion_tokens)856 857 return jsonify({858 "id": f"chatcmpl-{str(uuid.uuid4())}",859 "object": "chat.completion",860 "created": int(time.time()),861 "model": model,862 "choices": [{863 "index": 0,864 "message": {865 "role": "assistant",866 "content": f"<think>\n{think_content}\n</think>\n{response_content}"867 },868 "finish_reason": "stop"869 }],870 "usage": {871 "prompt_tokens": prompt_tokens,872 "completion_tokens": completion_tokens,873 "total_tokens": prompt_tokens + completion_tokens874 }875 })876 else:877 for line in response.iter_lines():878 if line:879 decoded_line = line.decode("utf-8")880 segment = extract_segment(decoded_line)881 if segment:882 buffer.write(segment)883 884 response_content = buffer.getvalue()885 886 # 计算输出token并更新统计信息887 completion_tokens = num_tokens_from_string(response_content)888 update_model_stats(model, prompt_tokens, completion_tokens)889 890 return jsonify({891 "id": f"chatcmpl-{str(uuid.uuid4())}",892 "object": "chat.completion",893 "created": int(time.time()),894 "model": model,895 "choices": [{896 "index": 0,897 "message": {898 "role": "assistant",899 "content": response_content900 },901 "finish_reason": "stop"902 }],903 "usage": {904 "prompt_tokens": prompt_tokens,905 "completion_tokens": completion_tokens,906 "total_tokens": prompt_tokens + completion_tokens907 }908 })909 except requests.exceptions.RequestException as e:910 error_details = str(e)911 if hasattr(e, 'response') and e.response is not None:912 if hasattr(e.response, 'text'):913 error_details += f" - Response: {e.response.text[:200]}"914 print(f"发送消息失败: {error_details}")915 return jsonify({"error": f"Failed to send message: {error_details}"}), 500916 917 918def format_message(messages):919 buffer = io.StringIO()920 role_map, prefix, messages = extract_role(messages)921 for message in messages:922 role = message.get("role")923 role = "\b" + role_map[role] if prefix else role_map[role]924 content = message.get("content").replace("\\n", "\n")925 pattern = re.compile(r"<\|removeRole\|>\n")926 if pattern.match(content):927 content = pattern.sub("", content)928 buffer.write(f"{content}\n")929 else:930 buffer.write(f"{role}: {content}\n\n")931 formatted_message = buffer.getvalue()932 return formatted_message933 934 935def extract_role(messages):936 role_map = {"user": "Human", "assistant": "Assistant", "system": "System"}937 prefix = False938 first_message = messages[0]["content"]939 pattern = re.compile(940 r"""941 <roleInfo>\s*942 user:\s*(?P<user>[^\n]*)\s*943 assistant:\s*(?P<assistant>[^\n]*)\s*944 system:\s*(?P<system>[^\n]*)\s*945 prefix:\s*(?P<prefix>[^\n]*)\s*946 </roleInfo>\n947 """,948 re.VERBOSE,949 )950 match = pattern.search(first_message)951 if match:952 role_map = {953 "user": match.group("user"),954 "assistant": match.group("assistant"),955 "system": match.group("system"),956 }957 prefix = match.group("prefix") == "1"958 messages[0]["content"] = pattern.sub("", first_message)959 print(f"Extracted role map:")960 print(961 f"User: {role_map['user']}, Assistant: {role_map['assistant']}, System: {role_map['system']}"962 )963 print(f"Using prefix: {prefix}")964 return (role_map, prefix, messages)965 966 967@app.route("/health", methods=["GET"])968def health_check():969 global health_check_counter970 health_check_counter += 1971 return jsonify({972 "status": "healthy",973 "timestamp": datetime.now().isoformat(),974 "checks": health_check_counter975 })976 977 978def keep_alive():979 """每20分钟进行一次自我健康检查"""980 while True:981 try:982 requests.get("http://127.0.0.1:7860/health")983 time.sleep(1200) # 20分钟984 except:985 pass # 忽略错误,保持运行986 987 988@app.route("/", methods=["GET"])989def index():990 # 如果需要密码且用户未登录,重定向到登录页面991 if PASSWORD and not flask_session.get('logged_in'):992 return redirect(url_for('login'))993 994 # 否则重定向到仪表盘995 return redirect(url_for('dashboard'))996 997 998# 使用tiktoken计算字符串的token数量999def num_tokens_from_string(text):1000 """使用tiktoken计算字符串的token数量"""1001 try:1002 # 使用cl100k_base编码器,这是gpt-4和gpt-3.5-turbo使用的编码器1003 enc = tiktoken.get_encoding("cl100k_base")1004 tokens = enc.encode(text)1005 return len(tokens)1006 except Exception as e:1007 # 如果出错,使用字符长度作为粗略估计(大约每4个字符1个token)1008 print(f"计算token数量出错: {e},使用估算方法")1009 return len(text) // 41010 1011# 更新模型使用统计1012def update_model_stats(model, prompt_tokens, completion_tokens):1013 """更新模型使用统计数据"""1014 global model_usage_stats, total_tokens, model_usage_records1015 1016 # 获取北京时间1017 beijing_now = datetime.utcnow() + timedelta(hours=8)1018 call_time = beijing_now.strftime("%Y-%m-%d %H:%M:%S")1019 1020 # 首次使用该模型时初始化统计1021 if model not in model_usage_stats:1022 model_usage_stats[model] = {1023 "count": 0,1024 "prompt_tokens": 0,1025 "completion_tokens": 01026 }1027 1028 # 更新模型统计1029 model_usage_stats[model]["count"] += 11030 model_usage_stats[model]["prompt_tokens"] += prompt_tokens1031 model_usage_stats[model]["completion_tokens"] += completion_tokens1032 1033 # 更新总token统计1034 total_tokens["prompt"] += prompt_tokens1035 total_tokens["completion"] += completion_tokens1036 total_tokens["total"] += (prompt_tokens + completion_tokens)1037 1038 # 添加使用记录1039 usage_record = {1040 "call_time": call_time,1041 "model": model,1042 "prompt_tokens": prompt_tokens,1043 "completion_tokens": completion_tokens,1044 "total_tokens": prompt_tokens + completion_tokens,1045 "calculation_method": "tiktoken"1046 }1047 model_usage_records.append(usage_record)1048 1049 # 保存记录到文件,确保数据持久化1050 save_model_usage_records()1051 1052 print(f"模型使用记录: {model}, 输入: {prompt_tokens}, 输出: {completion_tokens}, 时间: {call_time}")1053 1054 1055# 获取计算点信息1056def get_compute_points():1057 global compute_points, USER_DATA, users_compute_points1058 1059 if USER_NUM == 0:1060 return1061 1062 # 清空用户计算点列表1063 users_compute_points = []1064 1065 # 累计总计算点1066 total_left = 01067 total_points = 01068 1069 # 获取每个用户的计算点信息1070 for i, user_data in enumerate(USER_DATA):1071 try:1072 session, cookies, session_token, _, _, _ = user_data1073 1074 # 检查token是否有效1075 if is_token_expired(session_token):1076 session_token = refresh_token(session, cookies)1077 if not session_token:1078 print(f"用户{i+1}刷新token失败,无法获取计算点信息")1079 continue1080 USER_DATA[i] = (session, cookies, session_token, user_data[3], user_data[4], i)1081 1082 headers = {1083 "accept": "application/json, text/plain, */*",1084 "accept-language": "zh-CN,zh;q=0.9",1085 "baggage": f"sentry-environment=production,sentry-release=93da8385541a6ce339b1f41b0c94428c70657e22,sentry-public_key=3476ea6df1585dd10e92cdae3a66ff49,sentry-trace_id={TRACE_ID}",1086 "reai-ui": "1",1087 "sec-ch-ua": "\"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"",1088 "sec-ch-ua-mobile": "?0",1089 "sec-ch-ua-platform": "\"Windows\"",1090 "sec-fetch-dest": "empty",1091 "sec-fetch-mode": "cors",1092 "sec-fetch-site": "same-origin",1093 "sentry-trace": SENTRY_TRACE,1094 "session-token": session_token,1095 "x-abacus-org-host": "apps",1096 "cookie": cookies1097 }1098 1099 response = session.get(1100 COMPUTE_POINTS_URL,1101 headers=headers1102 )1103 1104 if response.status_code == 200:1105 result = response.json()1106 if result.get("success") and "result" in result:1107 data = result["result"]1108 left = data.get("computePointsLeft", 0)1109 total = data.get("totalComputePoints", 0)1110 used = total - left1111 percentage = round((used / total) * 100, 2) if total > 0 else 01112 1113 # 获取北京时间1114 beijing_now = datetime.utcnow() + timedelta(hours=8)1115 1116 # 添加到用户列表1117 user_points = {1118 "user_id": i + 1, # 用户ID从1开始1119 "left": left,1120 "total": total,1121 "used": used,1122 "percentage": percentage,1123 "last_update": beijing_now1124 }1125 users_compute_points.append(user_points)1126 1127 # 累计总数1128 total_left += left1129 total_points += total1130 1131 print(f"用户{i+1}计算点信息更新成功: 剩余 {left}, 总计 {total}")1132 1133 # 对于第一个用户,获取计算点使用日志1134 if i == 0:1135 get_compute_points_log(session, cookies, session_token)1136 else:1137 print(f"获取用户{i+1}计算点信息失败: {result.get('error', '未知错误')}")1138 else:1139 print(f"获取用户{i+1}计算点信息失败,状态码: {response.status_code}")1140 except Exception as e:1141 print(f"获取用户{i+1}计算点信息异常: {e}")1142 1143 # 更新全局计算点信息(所有用户总和)1144 if users_compute_points:1145 compute_points["left"] = total_left1146 compute_points["total"] = total_points1147 compute_points["used"] = total_points - total_left1148 compute_points["percentage"] = round((compute_points["used"] / compute_points["total"]) * 100, 2) if compute_points["total"] > 0 else 01149 compute_points["last_update"] = datetime.utcnow() + timedelta(hours=8) # 北京时间1150 print(f"所有用户计算点总计: 剩余 {total_left}, 总计 {total_points}")1151 1152# 获取计算点使用日志1153def get_compute_points_log(session, cookies, session_token):1154 global compute_points_log1155 1156 try:1157 headers = {1158 "accept": "application/json, text/plain, */*",1159 "accept-language": "zh-CN,zh;q=0.9",1160 "content-type": "application/json",1161 "reai-ui": "1",1162 "sec-ch-ua": "\"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"",1163 "sec-ch-ua-mobile": "?0",1164 "sec-ch-ua-platform": "\"Windows\"",1165 "sec-fetch-dest": "empty",1166 "sec-fetch-mode": "cors",1167 "sec-fetch-site": "same-site",1168 "session-token": session_token,1169 "x-abacus-org-host": "apps",1170 "cookie": cookies1171 }1172 1173 response = session.post(1174 COMPUTE_POINTS_LOG_URL,1175 headers=headers,1176 json={"byLlm": True}1177 )1178 1179 if response.status_code == 200:1180 result = response.json()1181 if result.get("success") and "result" in result:1182 data = result["result"]1183 compute_points_log["columns"] = data.get("columns", {})1184 compute_points_log["log"] = data.get("log", [])1185 print(f"计算点使用日志更新成功,获取到 {len(compute_points_log['log'])} 条记录")1186 else:1187 print(f"获取计算点使用日志失败: {result.get('error', '未知错误')}")1188 else:1189 print(f"获取计算点使用日志失败,状态码: {response.status_code}")1190 except Exception as e:1191 print(f"获取计算点使用日志异常: {e}")1192 1193 1194# 添加登录相关路由1195@app.route("/login", methods=["GET", "POST"])1196def login():1197 error = None1198 if request.method == "POST":1199 password = request.form.get("password")1200 if password and hashlib.sha256(password.encode()).hexdigest() == PASSWORD: