Remove main application code, dependencies, and versioning files
Browse files- Dockerfile +10 -22
- README.md +7 -9
- app.py +0 -397
- go.mod +36 -0
- go.sum +90 -0
- main.go +750 -0
- main.ts +0 -679
- requirements.txt +0 -8
- version.py +0 -1
Dockerfile
CHANGED
|
@@ -1,26 +1,14 @@
|
|
| 1 |
-
#
|
| 2 |
-
FROM
|
| 3 |
|
| 4 |
-
# 安装时区依赖
|
| 5 |
-
RUN apt-get update && apt-get install -y tzdata
|
| 6 |
-
|
| 7 |
-
# 设置东八区时区
|
| 8 |
-
ENV TZ=Asia/Shanghai
|
| 9 |
-
|
| 10 |
-
# 设置工作目录
|
| 11 |
WORKDIR /app
|
| 12 |
-
|
| 13 |
-
# 复制依赖文件到工作目录
|
| 14 |
-
COPY requirements.txt .
|
| 15 |
-
|
| 16 |
-
# 安装依赖
|
| 17 |
-
RUN pip install --no-cache-dir -r requirements.txt
|
| 18 |
-
|
| 19 |
-
# 复制应用代码到工作目录
|
| 20 |
COPY . .
|
|
|
|
| 21 |
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
|
|
|
|
|
|
|
|
| 1 |
+
# 使用官方Go基础镜像
|
| 2 |
+
FROM golang:1.22-alpine as builder
|
| 3 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
WORKDIR /app
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
COPY . .
|
| 6 |
+
RUN go mod tidy && go build -o app .
|
| 7 |
|
| 8 |
+
FROM alpine:latest
|
| 9 |
+
WORKDIR /app
|
| 10 |
+
COPY --from=builder /app/app .
|
| 11 |
+
COPY --from=builder /app/version.py ./version.py
|
| 12 |
+
ENV TZ=Asia/Shanghai
|
| 13 |
+
EXPOSE 7860
|
| 14 |
+
CMD ["./app"]
|
README.md
CHANGED
|
@@ -1,10 +1,8 @@
|
|
| 1 |
-
|
| 2 |
-
title: AIChatbot2API
|
| 3 |
-
emoji: 🏃
|
| 4 |
-
colorFrom: yellow
|
| 5 |
-
colorTo: indigo
|
| 6 |
-
sdk: docker
|
| 7 |
-
pinned: false
|
| 8 |
-
---
|
| 9 |
|
| 10 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# AIChatbot2API (Go版)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
|
| 3 |
+
本项目为AIChatbot2API的Go语言实现,提供与原Python版本相同的API接口:
|
| 4 |
+
- /v1/chat/completions
|
| 5 |
+
- /v1/models
|
| 6 |
+
- /health
|
| 7 |
+
|
| 8 |
+
请参考源码和Dockerfile进行部署。
|
app.py
DELETED
|
@@ -1,397 +0,0 @@
|
|
| 1 |
-
import re
|
| 2 |
-
import os
|
| 3 |
-
import uuid
|
| 4 |
-
import time
|
| 5 |
-
import json
|
| 6 |
-
import httpx
|
| 7 |
-
import uvicorn
|
| 8 |
-
import hashlib
|
| 9 |
-
import secrets
|
| 10 |
-
import logging
|
| 11 |
-
from pydantic import BaseModel
|
| 12 |
-
from dotenv import load_dotenv
|
| 13 |
-
from fake_useragent import UserAgent
|
| 14 |
-
from fastapi.security import APIKeyHeader
|
| 15 |
-
from typing import List, Literal, Optional
|
| 16 |
-
from fastapi.responses import StreamingResponse
|
| 17 |
-
from fastapi.responses import Response
|
| 18 |
-
from fastapi.middleware.cors import CORSMiddleware
|
| 19 |
-
from fastapi import FastAPI, HTTPException, Depends
|
| 20 |
-
|
| 21 |
-
# Load .env file
|
| 22 |
-
load_dotenv()
|
| 23 |
-
|
| 24 |
-
# Retrieve environment variables
|
| 25 |
-
API_KEY = os.getenv("API_KEY")
|
| 26 |
-
if not API_KEY:
|
| 27 |
-
raise ValueError("API_KEY not found in .env file")
|
| 28 |
-
|
| 29 |
-
ENABLE_CORS = os.getenv("ENABLE_CORS", "True").lower() in ("true", "1", "yes")
|
| 30 |
-
LOG_LEVEL = os.getenv("LOG_LEVEL", "INFO").upper()
|
| 31 |
-
MAX_CHARS = int(os.getenv("MAX_CHARS", "80000"))
|
| 32 |
-
RANDOM_UA = os.getenv("RANDOM_UA", "False").lower() in ("true", "1", "yes")
|
| 33 |
-
|
| 34 |
-
# Configure logging
|
| 35 |
-
logging.basicConfig(
|
| 36 |
-
level=getattr(logging, LOG_LEVEL, logging.INFO),
|
| 37 |
-
format="%(asctime)s - %(levelname)s - %(message)s",
|
| 38 |
-
handlers=[logging.StreamHandler()]
|
| 39 |
-
)
|
| 40 |
-
logger = logging.getLogger(__name__)
|
| 41 |
-
|
| 42 |
-
# Suppress httpx and httpcore debug logs
|
| 43 |
-
logging.getLogger("httpx").setLevel(logging.ERROR)
|
| 44 |
-
logging.getLogger("httpcore").setLevel(logging.ERROR)
|
| 45 |
-
|
| 46 |
-
# Initialize FastAPI app
|
| 47 |
-
app = FastAPI()
|
| 48 |
-
|
| 49 |
-
# Enable CORS if configured
|
| 50 |
-
if ENABLE_CORS:
|
| 51 |
-
app.add_middleware(
|
| 52 |
-
CORSMiddleware,
|
| 53 |
-
allow_origins=["*"],
|
| 54 |
-
allow_credentials=True,
|
| 55 |
-
allow_methods=["*"],
|
| 56 |
-
allow_headers=["*"],
|
| 57 |
-
)
|
| 58 |
-
logger.info("CORS enabled")
|
| 59 |
-
else:
|
| 60 |
-
logger.info("CORS disabled")
|
| 61 |
-
|
| 62 |
-
# Constants
|
| 63 |
-
api_domain = "https://ai-chatbot.top"
|
| 64 |
-
default_user_agent = (
|
| 65 |
-
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:136.0) "
|
| 66 |
-
"Gecko/20100101 Firefox/136.0"
|
| 67 |
-
)
|
| 68 |
-
ua = UserAgent()
|
| 69 |
-
|
| 70 |
-
# Supported models
|
| 71 |
-
supported_models = ["DeepSeek-R1", "DeepSeek-V3"]
|
| 72 |
-
model_to_config = {
|
| 73 |
-
"DeepSeek-R1": {"model": "deepseek-huoshan", "isWebSearchEnabled": False},
|
| 74 |
-
"DeepSeek-V3": {"model": "deepseek-guiji", "isWebSearchEnabled": False},
|
| 75 |
-
}
|
| 76 |
-
|
| 77 |
-
# Device ID and UA mapping
|
| 78 |
-
device_ua_map = {}
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
# Utility functions
|
| 82 |
-
def nanoid(size=21):
|
| 83 |
-
url_alphabet = "abcdefgh0ijkl1mno2pqrs3tuv4wxyz5ABCDEFGH6IJKL7MNO8PQRS9TUV-WXYZ_"
|
| 84 |
-
return "".join(secrets.choice(url_alphabet) for _ in range(size))
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
def generate_device_id():
|
| 88 |
-
return f"{uuid.uuid4().hex}_{nanoid(20)}"
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
def get_user_agent(device_id: str) -> str:
|
| 92 |
-
if not RANDOM_UA:
|
| 93 |
-
return default_user_agent
|
| 94 |
-
if device_id not in device_ua_map:
|
| 95 |
-
device_ua_map[device_id] = ua.random
|
| 96 |
-
return device_ua_map[device_id]
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
def generate_sign(chat_id: str, timestamp: int) -> str:
|
| 100 |
-
message = f"{chat_id}{timestamp}@!~chatbot.0868"
|
| 101 |
-
return hashlib.md5(message.encode("utf-8")).hexdigest()
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
async def get_chat_id() -> str:
|
| 105 |
-
cookies = {
|
| 106 |
-
'_ga_HVMZBNYJML': 'GS1.1.1742013194.1.0.1742013194.0.0.0',
|
| 107 |
-
'_ga': 'GA1.1.1029622546.1742013195',
|
| 108 |
-
}
|
| 109 |
-
headers = {
|
| 110 |
-
"User-Agent": default_user_agent,
|
| 111 |
-
"Accept": "*/*",
|
| 112 |
-
"Accept-Language": "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2",
|
| 113 |
-
"Referer": "https://ai-chatbot.top/",
|
| 114 |
-
"RSC": "1",
|
| 115 |
-
"Next-Router-State-Tree": '["",{"children":["(chat)",{"children":["__PAGE__",{}, "/", "refresh"]}]},null,"refetch"]',
|
| 116 |
-
"DNT": "1",
|
| 117 |
-
"Sec-GPC": "1",
|
| 118 |
-
"Connection": "keep-alive",
|
| 119 |
-
"Sec-Fetch-Dest": "empty",
|
| 120 |
-
"Sec-Fetch-Mode": "cors",
|
| 121 |
-
"Sec-Fetch-Site": "same-origin",
|
| 122 |
-
"Priority": "u=0",
|
| 123 |
-
}
|
| 124 |
-
params = {"_rsc": "l4cx"}
|
| 125 |
-
async with httpx.AsyncClient(http2=True) as client:
|
| 126 |
-
response = await client.get(f"{api_domain}/", params=params, cookies=cookies, headers=headers, timeout=30)
|
| 127 |
-
chat_ids = re.findall(r'[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}',
|
| 128 |
-
response.text)
|
| 129 |
-
return chat_ids[-1]
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
# API key validation
|
| 133 |
-
api_key_header = APIKeyHeader(name="Authorization", auto_error=False)
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
async def verify_api_key(authorization: str = Depends(api_key_header)):
|
| 137 |
-
if not authorization:
|
| 138 |
-
logger.error("Missing Authorization header")
|
| 139 |
-
raise HTTPException(status_code=401, detail="Missing Authorization header")
|
| 140 |
-
api_key = authorization.replace("Bearer ", "").strip() if authorization.startswith("Bearer ") else authorization
|
| 141 |
-
if api_key != API_KEY:
|
| 142 |
-
logger.error(f"Invalid API key: {api_key}")
|
| 143 |
-
raise HTTPException(status_code=401, detail="Invalid API key")
|
| 144 |
-
return api_key
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
# Request models
|
| 148 |
-
class Message(BaseModel):
|
| 149 |
-
role: Literal["system", "user", "assistant"]
|
| 150 |
-
content: str
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
class ChatCompletionRequest(BaseModel):
|
| 154 |
-
model: str
|
| 155 |
-
messages: List[Message]
|
| 156 |
-
stream: Optional[bool] = False
|
| 157 |
-
temperature: Optional[float] = 0.7
|
| 158 |
-
top_p: Optional[float] = 1.0
|
| 159 |
-
presence_penalty: Optional[float] = 0
|
| 160 |
-
frequency_penalty: Optional[float] = 0
|
| 161 |
-
max_tokens: Optional[int] = None
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
# Response generation
|
| 165 |
-
def generate_chunk(_id: str, created: int, model: str, content: str = "", finish_reason: Optional[str] = None):
|
| 166 |
-
chunk = {
|
| 167 |
-
"id": _id,
|
| 168 |
-
"object": "chat.completion.chunk",
|
| 169 |
-
"created": created,
|
| 170 |
-
"model": model,
|
| 171 |
-
"choices": [{"index": 0, "delta": {"content": content}, "finish_reason": finish_reason}]
|
| 172 |
-
}
|
| 173 |
-
return f"data: {json.dumps(chunk, ensure_ascii=False)}\n\n"
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
def truncate_messages(messages: List[Message], max_chars: int = MAX_CHARS) -> List[Message]:
|
| 177 |
-
total_chars = sum(len(msg.content) for msg in messages)
|
| 178 |
-
if total_chars <= max_chars:
|
| 179 |
-
return messages
|
| 180 |
-
truncated = []
|
| 181 |
-
current_chars = 0
|
| 182 |
-
for msg in reversed(messages):
|
| 183 |
-
if current_chars + len(msg.content) <= max_chars:
|
| 184 |
-
truncated.insert(0, msg)
|
| 185 |
-
current_chars += len(msg.content)
|
| 186 |
-
else:
|
| 187 |
-
break
|
| 188 |
-
logger.info(f"Truncated messages: original {total_chars}, truncated {current_chars}")
|
| 189 |
-
return truncated
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
async def stream_response(request: ChatCompletionRequest, device_id: str, chat_id: str, timestamp: int, sign: str):
|
| 193 |
-
truncated_messages = truncate_messages(request.messages)
|
| 194 |
-
messages = [{"role": msg.role, "content": msg.content} for msg in truncated_messages]
|
| 195 |
-
payload = {
|
| 196 |
-
"id": chat_id,
|
| 197 |
-
"messages": messages,
|
| 198 |
-
"selectedChatModel": model_to_config[request.model]["model"],
|
| 199 |
-
"isDeepThinkingEnabled": True,
|
| 200 |
-
"isWebSearchEnabled": model_to_config[request.model]["isWebSearchEnabled"],
|
| 201 |
-
}
|
| 202 |
-
headers = {
|
| 203 |
-
"User-Agent": get_user_agent(device_id),
|
| 204 |
-
"Accept": "*/*",
|
| 205 |
-
"Accept-Language": "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2",
|
| 206 |
-
"Referer": f"https://ai-chatbot.top/chat/{chat_id}",
|
| 207 |
-
"Content-Type": "application/json",
|
| 208 |
-
"currentTime": str(timestamp),
|
| 209 |
-
"sign": sign,
|
| 210 |
-
"Origin": "https://ai-chatbot.top",
|
| 211 |
-
"DNT": "1",
|
| 212 |
-
"Sec-GPC": "1",
|
| 213 |
-
"Connection": "keep-alive",
|
| 214 |
-
"Sec-Fetch-Dest": "empty",
|
| 215 |
-
"Sec-Fetch-Mode": "cors",
|
| 216 |
-
"Sec-Fetch-Site": "same-origin",
|
| 217 |
-
"Priority": "u=0",
|
| 218 |
-
}
|
| 219 |
-
cookies = {
|
| 220 |
-
'_ga_HVMZBNYJML': 'GS1.1.1742013194.1.1.1742013780.0.0.0',
|
| 221 |
-
'_ga': 'GA1.1.1029622546.1742013195',
|
| 222 |
-
}
|
| 223 |
-
_id = f"chatcmpl-{uuid.uuid4().hex}"
|
| 224 |
-
created = int(time.time())
|
| 225 |
-
logger.info(f"[stream] Start: chat_id={chat_id}, UA={headers['User-Agent']}")
|
| 226 |
-
try:
|
| 227 |
-
# 首先发送一个空chunk,兼容SSE客户端
|
| 228 |
-
yield generate_chunk(_id, created, request.model, "")
|
| 229 |
-
async with httpx.AsyncClient(http2=True, timeout=900) as client:
|
| 230 |
-
async with client.stream("POST", f"{api_domain}/api/chat", json=payload, headers=headers, cookies=cookies) as response:
|
| 231 |
-
if response.status_code != 200:
|
| 232 |
-
logger.error(f"[stream] API error: status_code={response.status_code}")
|
| 233 |
-
yield generate_chunk(_id, created, request.model, f"Error: HTTP {response.status_code}", "stop")
|
| 234 |
-
# 明确结束
|
| 235 |
-
yield "data: [DONE]\n\n"
|
| 236 |
-
return
|
| 237 |
-
thinking = False
|
| 238 |
-
content_parts = []
|
| 239 |
-
async for line in response.aiter_lines():
|
| 240 |
-
line = line.strip()
|
| 241 |
-
if not line:
|
| 242 |
-
continue
|
| 243 |
-
if line.startswith("g:"):
|
| 244 |
-
if not thinking:
|
| 245 |
-
thinking = True
|
| 246 |
-
content_parts.append("<think>")
|
| 247 |
-
yield generate_chunk(_id, created, request.model, "<think>")
|
| 248 |
-
content = line[2:].strip().replace('"', '').replace("\\n", "\n")
|
| 249 |
-
content_parts.append(content)
|
| 250 |
-
yield generate_chunk(_id, created, request.model, content)
|
| 251 |
-
elif line.startswith("0:"):
|
| 252 |
-
if thinking:
|
| 253 |
-
thinking = False
|
| 254 |
-
content_parts.append("</think>")
|
| 255 |
-
yield generate_chunk(_id, created, request.model, "</think>")
|
| 256 |
-
content = line[2:].strip().replace('"', '').replace("\\n", "\n")
|
| 257 |
-
content_parts.append(content)
|
| 258 |
-
yield generate_chunk(_id, created, request.model, content)
|
| 259 |
-
if thinking:
|
| 260 |
-
content_parts.append("</think>")
|
| 261 |
-
yield generate_chunk(_id, created, request.model, "</think>")
|
| 262 |
-
# 结束信号
|
| 263 |
-
yield generate_chunk(_id, created, request.model, "", "stop")
|
| 264 |
-
yield "data: [DONE]\n\n"
|
| 265 |
-
logger.info(f"[stream] Completed: chat_id={chat_id}, content={''.join(content_parts)}")
|
| 266 |
-
except Exception as e:
|
| 267 |
-
logger.error(f"[stream] Exception: {e}")
|
| 268 |
-
yield generate_chunk(_id, created, request.model, f"[流式异常] {str(e)}", "stop")
|
| 269 |
-
yield "data: [DONE]\n\n"
|
| 270 |
-
|
| 271 |
-
|
| 272 |
-
# not_stream_response
|
| 273 |
-
async def not_stream_response(request: ChatCompletionRequest, device_id: str, chat_id: str, timestamp: int, sign: str):
|
| 274 |
-
truncated_messages = truncate_messages(request.messages) if callable(truncate_messages) else truncate_messages(request.messages)
|
| 275 |
-
print(truncated_messages)
|
| 276 |
-
messages = [{"role": msg.role, "content": msg.content} for msg in truncated_messages]
|
| 277 |
-
payload = {
|
| 278 |
-
"id": chat_id,
|
| 279 |
-
"messages": messages,
|
| 280 |
-
"selectedChatModel": model_to_config[request.model]["model"],
|
| 281 |
-
"isDeepThinkingEnabled": True,
|
| 282 |
-
"isWebSearchEnabled": model_to_config[request.model]["isWebSearchEnabled"],
|
| 283 |
-
}
|
| 284 |
-
headers = {
|
| 285 |
-
"User-Agent": get_user_agent(device_id),
|
| 286 |
-
"Accept": "*/*",
|
| 287 |
-
"Accept-Language": "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2",
|
| 288 |
-
"Referer": f"https://ai-chatbot.top/chat/{chat_id}",
|
| 289 |
-
"Content-Type": "application/json",
|
| 290 |
-
"currentTime": str(timestamp),
|
| 291 |
-
"sign": sign,
|
| 292 |
-
"Origin": "https://ai-chatbot.top",
|
| 293 |
-
"DNT": "1",
|
| 294 |
-
"Sec-GPC": "1",
|
| 295 |
-
"Connection": "keep-alive",
|
| 296 |
-
"Sec-Fetch-Dest": "empty",
|
| 297 |
-
"Sec-Fetch-Mode": "cors",
|
| 298 |
-
"Sec-Fetch-Site": "same-origin",
|
| 299 |
-
"Priority": "u=0",
|
| 300 |
-
}
|
| 301 |
-
cookies = {
|
| 302 |
-
'_ga_HVMZBNYJML': 'GS1.1.1742013194.1.1.1742013780.0.0.0',
|
| 303 |
-
'_ga': 'GA1.1.1029622546.1742013195',
|
| 304 |
-
}
|
| 305 |
-
_id = f"chatcmpl-{uuid.uuid4().hex}"
|
| 306 |
-
created = int(time.time())
|
| 307 |
-
logger.info(f"Starting non-stream response: chat_id={chat_id}, UA={headers['User-Agent']}")
|
| 308 |
-
|
| 309 |
-
async with httpx.AsyncClient(http2=True, timeout=900) as client:
|
| 310 |
-
response = await client.post(f"{api_domain}/api/chat", json=payload, headers=headers, cookies=cookies)
|
| 311 |
-
|
| 312 |
-
if response.status_code != 200:
|
| 313 |
-
logger.error(f"API error: status_code={response.status_code}")
|
| 314 |
-
return {"error": {"message": f"Error: HTTP {response.status_code}", "type": "invalid_request_error"}}
|
| 315 |
-
else:
|
| 316 |
-
contents = ''
|
| 317 |
-
think_content = ''
|
| 318 |
-
print(response.text)
|
| 319 |
-
for line in response.iter_lines():
|
| 320 |
-
line = line.strip()
|
| 321 |
-
if not line:
|
| 322 |
-
continue
|
| 323 |
-
if line.startswith("g:"):
|
| 324 |
-
content = line[3:-1].replace('\\n', '\n')
|
| 325 |
-
think_content += content
|
| 326 |
-
continue
|
| 327 |
-
elif line.startswith("0:"):
|
| 328 |
-
if think_content:
|
| 329 |
-
contents = '<think>' + think_content + '</think>'
|
| 330 |
-
think_content = ''
|
| 331 |
-
content = line[3:-1].replace('\\n', '\n')
|
| 332 |
-
contents += content
|
| 333 |
-
continue
|
| 334 |
-
elif line.startswith("e:"):
|
| 335 |
-
continue
|
| 336 |
-
elif line.startswith("d:"):
|
| 337 |
-
continue
|
| 338 |
-
else:
|
| 339 |
-
continue
|
| 340 |
-
print({
|
| 341 |
-
"id": _id,
|
| 342 |
-
"object": "chat.completion",
|
| 343 |
-
"created": created,
|
| 344 |
-
"model": request.model,
|
| 345 |
-
"choices": [{"index": 0, "message": {"role": "assistant", "content": contents}, "finish_reason": None}],
|
| 346 |
-
})
|
| 347 |
-
return {
|
| 348 |
-
"id": _id,
|
| 349 |
-
"object": "chat.completion",
|
| 350 |
-
"created": created,
|
| 351 |
-
"model": request.model,
|
| 352 |
-
"choices": [{"index": 0, "message": {"role": "assistant", "content": contents}, "finish_reason": None}],
|
| 353 |
-
}
|
| 354 |
-
|
| 355 |
-
|
| 356 |
-
# Endpoints
|
| 357 |
-
@app.post("/v1/chat/completions")
|
| 358 |
-
async def chat_completions(request: ChatCompletionRequest, _: str = Depends(verify_api_key)):
|
| 359 |
-
if request.model not in supported_models:
|
| 360 |
-
request.model = "DeepSeek-R1"
|
| 361 |
-
device_id = generate_device_id()
|
| 362 |
-
chat_id = await get_chat_id()
|
| 363 |
-
timestamp = int(time.time() * 1000)
|
| 364 |
-
sign = generate_sign(chat_id, timestamp)
|
| 365 |
-
logger.info(f"Chat request: model={request.model}, stream={request.stream}, chat_id={chat_id}")
|
| 366 |
-
|
| 367 |
-
if request.stream:
|
| 368 |
-
return StreamingResponse(stream_response(request, device_id, chat_id, timestamp, sign),
|
| 369 |
-
media_type="text/event-stream")
|
| 370 |
-
else:
|
| 371 |
-
# not_stream_response(request, device_id, chat_id, timestamp, sign)
|
| 372 |
-
return await not_stream_response(request, device_id, chat_id, timestamp, sign)
|
| 373 |
-
|
| 374 |
-
|
| 375 |
-
@app.get("/v1/models")
|
| 376 |
-
async def list_models(_: str = Depends(verify_api_key)):
|
| 377 |
-
current_time = int(time.time())
|
| 378 |
-
models = [
|
| 379 |
-
{
|
| 380 |
-
"id": model,
|
| 381 |
-
"object": "model",
|
| 382 |
-
"created": current_time,
|
| 383 |
-
"owned_by": "aichatbot",
|
| 384 |
-
} for model in supported_models
|
| 385 |
-
]
|
| 386 |
-
logger.info("Returning model list")
|
| 387 |
-
return {"object": "list", "data": models}
|
| 388 |
-
|
| 389 |
-
|
| 390 |
-
@app.get("/health")
|
| 391 |
-
async def health_check():
|
| 392 |
-
chat_id = await get_chat_id()
|
| 393 |
-
return {"status": "ok", "session": "active" if chat_id else "inactive"}
|
| 394 |
-
|
| 395 |
-
|
| 396 |
-
if __name__ == "__main__":
|
| 397 |
-
uvicorn.run(app, host="0.0.0.0", port=7860)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
go.mod
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
module aichatbot2api
|
| 2 |
+
|
| 3 |
+
go 1.22
|
| 4 |
+
|
| 5 |
+
require (
|
| 6 |
+
github.com/gin-gonic/gin v1.9.1
|
| 7 |
+
github.com/google/uuid v1.3.1
|
| 8 |
+
github.com/joho/godotenv v1.5.1
|
| 9 |
+
)
|
| 10 |
+
|
| 11 |
+
require (
|
| 12 |
+
github.com/bytedance/sonic v1.9.1 // indirect
|
| 13 |
+
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
|
| 14 |
+
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
|
| 15 |
+
github.com/gin-contrib/sse v0.1.0 // indirect
|
| 16 |
+
github.com/go-playground/locales v0.14.1 // indirect
|
| 17 |
+
github.com/go-playground/universal-translator v0.18.1 // indirect
|
| 18 |
+
github.com/go-playground/validator/v10 v10.14.0 // indirect
|
| 19 |
+
github.com/goccy/go-json v0.10.2 // indirect
|
| 20 |
+
github.com/json-iterator/go v1.1.12 // indirect
|
| 21 |
+
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
|
| 22 |
+
github.com/leodido/go-urn v1.2.4 // indirect
|
| 23 |
+
github.com/mattn/go-isatty v0.0.19 // indirect
|
| 24 |
+
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
| 25 |
+
github.com/modern-go/reflect2 v1.0.2 // indirect
|
| 26 |
+
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
|
| 27 |
+
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
| 28 |
+
github.com/ugorji/go/codec v1.2.11 // indirect
|
| 29 |
+
golang.org/x/arch v0.3.0 // indirect
|
| 30 |
+
golang.org/x/crypto v0.9.0 // indirect
|
| 31 |
+
golang.org/x/net v0.10.0 // indirect
|
| 32 |
+
golang.org/x/sys v0.8.0 // indirect
|
| 33 |
+
golang.org/x/text v0.9.0 // indirect
|
| 34 |
+
google.golang.org/protobuf v1.30.0 // indirect
|
| 35 |
+
gopkg.in/yaml.v3 v3.0.1 // indirect
|
| 36 |
+
)
|
go.sum
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
| 2 |
+
github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
|
| 3 |
+
github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
|
| 4 |
+
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
| 5 |
+
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
|
| 6 |
+
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
| 7 |
+
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
| 8 |
+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
| 9 |
+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
| 10 |
+
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
|
| 11 |
+
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
|
| 12 |
+
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
| 13 |
+
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
| 14 |
+
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
|
| 15 |
+
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
|
| 16 |
+
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
| 17 |
+
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
| 18 |
+
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
| 19 |
+
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
| 20 |
+
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
| 21 |
+
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
| 22 |
+
github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js=
|
| 23 |
+
github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
|
| 24 |
+
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
| 25 |
+
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
| 26 |
+
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
| 27 |
+
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
| 28 |
+
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
| 29 |
+
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
| 30 |
+
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
|
| 31 |
+
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
| 32 |
+
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
| 33 |
+
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
| 34 |
+
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
| 35 |
+
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
| 36 |
+
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
| 37 |
+
github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
|
| 38 |
+
github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
|
| 39 |
+
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
|
| 40 |
+
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
|
| 41 |
+
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
| 42 |
+
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
| 43 |
+
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
| 44 |
+
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
| 45 |
+
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
| 46 |
+
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
| 47 |
+
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
| 48 |
+
github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
|
| 49 |
+
github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
|
| 50 |
+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
| 51 |
+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
| 52 |
+
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
| 53 |
+
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
| 54 |
+
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
| 55 |
+
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
| 56 |
+
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
| 57 |
+
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
| 58 |
+
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
| 59 |
+
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
| 60 |
+
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
| 61 |
+
github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
|
| 62 |
+
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
| 63 |
+
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
| 64 |
+
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
| 65 |
+
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
|
| 66 |
+
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
| 67 |
+
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
| 68 |
+
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
|
| 69 |
+
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
| 70 |
+
golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
|
| 71 |
+
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
|
| 72 |
+
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
|
| 73 |
+
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
| 74 |
+
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
| 75 |
+
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
| 76 |
+
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
|
| 77 |
+
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
| 78 |
+
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
| 79 |
+
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
| 80 |
+
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
| 81 |
+
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
| 82 |
+
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
| 83 |
+
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
|
| 84 |
+
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
| 85 |
+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
| 86 |
+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
| 87 |
+
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
| 88 |
+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
| 89 |
+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
| 90 |
+
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
main.go
ADDED
|
@@ -0,0 +1,750 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package main
|
| 2 |
+
|
| 3 |
+
import (
|
| 4 |
+
"bufio"
|
| 5 |
+
"crypto/md5"
|
| 6 |
+
"encoding/json"
|
| 7 |
+
"fmt"
|
| 8 |
+
"io"
|
| 9 |
+
"log"
|
| 10 |
+
"math/rand"
|
| 11 |
+
"net"
|
| 12 |
+
"net/http"
|
| 13 |
+
"os"
|
| 14 |
+
"regexp"
|
| 15 |
+
"strings"
|
| 16 |
+
"sync"
|
| 17 |
+
"time"
|
| 18 |
+
|
| 19 |
+
"github.com/gin-gonic/gin"
|
| 20 |
+
"github.com/google/uuid"
|
| 21 |
+
"github.com/joho/godotenv"
|
| 22 |
+
)
|
| 23 |
+
|
| 24 |
+
// Message 消息结构体,表示对话中的一条消息
|
| 25 |
+
type Message struct {
|
| 26 |
+
Role string `json:"role" binding:"required,oneof=system user assistant"`
|
| 27 |
+
Content string `json:"content" binding:"required"`
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
// ChatCompletionRequest 聊天完成请求结构体
|
| 31 |
+
type ChatCompletionRequest struct {
|
| 32 |
+
Model string `json:"model"`
|
| 33 |
+
Messages []Message `json:"messages"`
|
| 34 |
+
Stream bool `json:"stream"`
|
| 35 |
+
Temperature float64 `json:"temperature"`
|
| 36 |
+
TopP float64 `json:"top_p"`
|
| 37 |
+
PresencePenalty float64 `json:"presence_penalty"`
|
| 38 |
+
FrequencyPenalty float64 `json:"frequency_penalty"`
|
| 39 |
+
MaxTokens int `json:"max_tokens"`
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
// Choice OpenAI 格式的选择结构体
|
| 43 |
+
type Choice struct {
|
| 44 |
+
Index int `json:"index"`
|
| 45 |
+
Delta *Delta `json:"delta,omitempty"`
|
| 46 |
+
Message *Message `json:"message,omitempty"`
|
| 47 |
+
FinishReason *string `json:"finish_reason"`
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
// Delta 流式响应中的增量内容
|
| 51 |
+
type Delta struct {
|
| 52 |
+
Role string `json:"role,omitempty"`
|
| 53 |
+
Content string `json:"content,omitempty"`
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
// Usage 令牌使用统计
|
| 57 |
+
type Usage struct {
|
| 58 |
+
PromptTokens int `json:"prompt_tokens"`
|
| 59 |
+
CompletionTokens int `json:"completion_tokens"`
|
| 60 |
+
TotalTokens int `json:"total_tokens"`
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
// ChatCompletionResponse OpenAI 格式的聊天响应
|
| 64 |
+
type ChatCompletionResponse struct {
|
| 65 |
+
ID string `json:"id"`
|
| 66 |
+
Object string `json:"object"`
|
| 67 |
+
Created int64 `json:"created"`
|
| 68 |
+
Model string `json:"model"`
|
| 69 |
+
Choices []Choice `json:"choices"`
|
| 70 |
+
Usage *Usage `json:"usage,omitempty"`
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
+
// ModelInfo 模型信息结构体
|
| 74 |
+
type ModelInfo struct {
|
| 75 |
+
ID string `json:"id"`
|
| 76 |
+
Object string `json:"object"`
|
| 77 |
+
Created int64 `json:"created"`
|
| 78 |
+
OwnedBy string `json:"owned_by"`
|
| 79 |
+
}
|
| 80 |
+
|
| 81 |
+
// ModelList 模型列表结构体
|
| 82 |
+
type ModelList struct {
|
| 83 |
+
Object string `json:"object"`
|
| 84 |
+
Data []ModelInfo `json:"data"`
|
| 85 |
+
}
|
| 86 |
+
|
| 87 |
+
var (
|
| 88 |
+
apiKey string
|
| 89 |
+
enableCORS bool
|
| 90 |
+
randomUA bool
|
| 91 |
+
supportedModels = []string{"DeepSeek-R1", "DeepSeek-V3"}
|
| 92 |
+
modelToConfig = map[string]map[string]interface{}{
|
| 93 |
+
"DeepSeek-R1": {"model": "deepseek-huoshan", "isWebSearchEnabled": false},
|
| 94 |
+
"DeepSeek-V3": {"model": "deepseek-guiji", "isWebSearchEnabled": false},
|
| 95 |
+
}
|
| 96 |
+
apiDomain = "https://ai-chatbot.top"
|
| 97 |
+
defaultUA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:136.0) Gecko/20100101 Firefox/136.0"
|
| 98 |
+
deviceUAMap = make(map[string]string)
|
| 99 |
+
deviceUAMutex sync.Mutex
|
| 100 |
+
|
| 101 |
+
// 随机User-Agent列表
|
| 102 |
+
userAgents = []string{
|
| 103 |
+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
| 104 |
+
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
| 105 |
+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0",
|
| 106 |
+
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Safari/605.1.15",
|
| 107 |
+
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
| 108 |
+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Edge/120.0.0.0",
|
| 109 |
+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:136.0) Gecko/20100101 Firefox/136.0",
|
| 110 |
+
}
|
| 111 |
+
|
| 112 |
+
// 全局 HTTP 客户端配置,支持连接池和 HTTP/2
|
| 113 |
+
httpClient *http.Client
|
| 114 |
+
|
| 115 |
+
// chatID 缓存系统
|
| 116 |
+
chatIDCache struct {
|
| 117 |
+
sync.RWMutex
|
| 118 |
+
value string
|
| 119 |
+
expiresAt time.Time
|
| 120 |
+
}
|
| 121 |
+
|
| 122 |
+
// chatID 获取的并发控制
|
| 123 |
+
chatIDFetching sync.Mutex
|
| 124 |
+
)
|
| 125 |
+
|
| 126 |
+
// initHTTPClient 初始化优化的 HTTP 客户端,支持连接池和长连接
|
| 127 |
+
func initHTTPClient() {
|
| 128 |
+
transport := &http.Transport{
|
| 129 |
+
DialContext: (&net.Dialer{
|
| 130 |
+
Timeout: 10 * time.Second,
|
| 131 |
+
KeepAlive: 30 * time.Second,
|
| 132 |
+
}).DialContext,
|
| 133 |
+
ForceAttemptHTTP2: true,
|
| 134 |
+
MaxIdleConns: 100,
|
| 135 |
+
MaxIdleConnsPerHost: 10,
|
| 136 |
+
IdleConnTimeout: 90 * time.Second,
|
| 137 |
+
TLSHandshakeTimeout: 10 * time.Second,
|
| 138 |
+
ExpectContinueTimeout: 1 * time.Second,
|
| 139 |
+
DisableKeepAlives: false,
|
| 140 |
+
}
|
| 141 |
+
|
| 142 |
+
httpClient = &http.Client{
|
| 143 |
+
Transport: transport,
|
| 144 |
+
Timeout: 10 * time.Minute, // 10分钟超时支持长时间思考
|
| 145 |
+
}
|
| 146 |
+
}
|
| 147 |
+
|
| 148 |
+
// nanoid 生成指定长度的随机字符串
|
| 149 |
+
func nanoid(size int) string {
|
| 150 |
+
alphabet := "abcdefgh0ijkl1mno2pqrs3tuv4wxyz5ABCDEFGH6IJKL7MNO8PQRS9TUV-WXYZ_"
|
| 151 |
+
b := make([]byte, size)
|
| 152 |
+
for i := range b {
|
| 153 |
+
b[i] = alphabet[rand.Intn(len(alphabet))]
|
| 154 |
+
}
|
| 155 |
+
return string(b)
|
| 156 |
+
}
|
| 157 |
+
|
| 158 |
+
// generateDeviceID 生成设备ID
|
| 159 |
+
func generateDeviceID() string {
|
| 160 |
+
return fmt.Sprintf("%s_%s", uuid.New().String(), nanoid(20))
|
| 161 |
+
}
|
| 162 |
+
|
| 163 |
+
// getRandomUserAgent 获取随机User-Agent
|
| 164 |
+
func getRandomUserAgent() string {
|
| 165 |
+
return userAgents[rand.Intn(len(userAgents))]
|
| 166 |
+
}
|
| 167 |
+
|
| 168 |
+
// getUserAgent 获取或生成用户代理字符串
|
| 169 |
+
func getUserAgent(deviceID string) string {
|
| 170 |
+
if !randomUA {
|
| 171 |
+
return defaultUA
|
| 172 |
+
}
|
| 173 |
+
|
| 174 |
+
deviceUAMutex.Lock()
|
| 175 |
+
defer deviceUAMutex.Unlock()
|
| 176 |
+
if ua, ok := deviceUAMap[deviceID]; ok {
|
| 177 |
+
return ua
|
| 178 |
+
}
|
| 179 |
+
// 生成随机用户代理
|
| 180 |
+
randomUserAgent := getRandomUserAgent()
|
| 181 |
+
deviceUAMap[deviceID] = randomUserAgent
|
| 182 |
+
return randomUserAgent
|
| 183 |
+
}
|
| 184 |
+
|
| 185 |
+
// generateSign 生成签名用于API认证
|
| 186 |
+
func generateSign(chatID string, timestamp int64) string {
|
| 187 |
+
msg := fmt.Sprintf("%s%d@!~chatbot.0868", chatID, timestamp)
|
| 188 |
+
hash := md5.Sum([]byte(msg))
|
| 189 |
+
return fmt.Sprintf("%x", hash)
|
| 190 |
+
}
|
| 191 |
+
|
| 192 |
+
// getChatID 获取聊天ID,支持缓存和并发控制
|
| 193 |
+
func getChatID() (string, error) {
|
| 194 |
+
// 读取缓存
|
| 195 |
+
chatIDCache.RLock()
|
| 196 |
+
if time.Now().Before(chatIDCache.expiresAt) && chatIDCache.value != "" {
|
| 197 |
+
cachedID := chatIDCache.value
|
| 198 |
+
chatIDCache.RUnlock()
|
| 199 |
+
return cachedID, nil
|
| 200 |
+
}
|
| 201 |
+
chatIDCache.RUnlock()
|
| 202 |
+
|
| 203 |
+
// 使用互斥锁避免重复请求
|
| 204 |
+
chatIDFetching.Lock()
|
| 205 |
+
defer chatIDFetching.Unlock()
|
| 206 |
+
|
| 207 |
+
// 双重检查,可能在等待锁的过程中其他 goroutine 已经更新了缓存
|
| 208 |
+
chatIDCache.RLock()
|
| 209 |
+
if time.Now().Before(chatIDCache.expiresAt) && chatIDCache.value != "" {
|
| 210 |
+
cachedID := chatIDCache.value
|
| 211 |
+
chatIDCache.RUnlock()
|
| 212 |
+
return cachedID, nil
|
| 213 |
+
}
|
| 214 |
+
chatIDCache.RUnlock()
|
| 215 |
+
|
| 216 |
+
// 使用全局 HTTP 客户端获取新的 chatID
|
| 217 |
+
req, _ := http.NewRequest("GET", apiDomain+"/", nil)
|
| 218 |
+
req.Header.Set("User-Agent", defaultUA)
|
| 219 |
+
req.Header.Set("Accept", "*/*")
|
| 220 |
+
req.Header.Set("Accept-Language", "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2")
|
| 221 |
+
req.Header.Set("Referer", "https://ai-chatbot.top/")
|
| 222 |
+
|
| 223 |
+
resp, err := httpClient.Do(req)
|
| 224 |
+
if err != nil {
|
| 225 |
+
return "", err
|
| 226 |
+
}
|
| 227 |
+
defer resp.Body.Close()
|
| 228 |
+
|
| 229 |
+
body, _ := io.ReadAll(resp.Body)
|
| 230 |
+
reg := regexp.MustCompile(`[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}`)
|
| 231 |
+
ids := reg.FindAllString(string(body), -1)
|
| 232 |
+
if len(ids) == 0 {
|
| 233 |
+
return "", fmt.Errorf("no chat id found")
|
| 234 |
+
}
|
| 235 |
+
|
| 236 |
+
chatID := ids[len(ids)-1]
|
| 237 |
+
|
| 238 |
+
// 更新缓存
|
| 239 |
+
chatIDCache.Lock()
|
| 240 |
+
chatIDCache.value = chatID
|
| 241 |
+
chatIDCache.expiresAt = time.Now().Add(5 * time.Minute)
|
| 242 |
+
chatIDCache.Unlock()
|
| 243 |
+
|
| 244 |
+
return chatID, nil
|
| 245 |
+
}
|
| 246 |
+
|
| 247 |
+
// preloadChatID 异步预取 chatID,在缓存过期前自动刷新
|
| 248 |
+
func preloadChatID() {
|
| 249 |
+
go func() {
|
| 250 |
+
for {
|
| 251 |
+
// 在缓存过期前 1 分钟刷新
|
| 252 |
+
sleepDuration := 4 * time.Minute
|
| 253 |
+
if !chatIDCache.expiresAt.IsZero() {
|
| 254 |
+
timeToExpiry := time.Until(chatIDCache.expiresAt)
|
| 255 |
+
if timeToExpiry > time.Minute {
|
| 256 |
+
sleepDuration = timeToExpiry - time.Minute
|
| 257 |
+
}
|
| 258 |
+
}
|
| 259 |
+
|
| 260 |
+
time.Sleep(sleepDuration)
|
| 261 |
+
getChatID() // 刷新缓存
|
| 262 |
+
}
|
| 263 |
+
}()
|
| 264 |
+
}
|
| 265 |
+
|
| 266 |
+
// ChatbotFinish ai-chatbot.top 的结束信息结构
|
| 267 |
+
type ChatbotFinish struct {
|
| 268 |
+
FinishReason string `json:"finishReason"`
|
| 269 |
+
Usage Usage `json:"usage"`
|
| 270 |
+
IsContinued bool `json:"isContinued"`
|
| 271 |
+
}
|
| 272 |
+
|
| 273 |
+
// parseChatbotLine 解析 ai-chatbot.top 响应的单行内容
|
| 274 |
+
func parseChatbotLine(line string) (string, string, error) {
|
| 275 |
+
if len(line) < 2 {
|
| 276 |
+
return "", "", fmt.Errorf("line too short")
|
| 277 |
+
}
|
| 278 |
+
|
| 279 |
+
colonIndex := strings.Index(line, ":")
|
| 280 |
+
if colonIndex == -1 {
|
| 281 |
+
return "", "", fmt.Errorf("no colon found")
|
| 282 |
+
}
|
| 283 |
+
|
| 284 |
+
prefix := line[:colonIndex]
|
| 285 |
+
content := line[colonIndex+1:]
|
| 286 |
+
|
| 287 |
+
return prefix, content, nil
|
| 288 |
+
}
|
| 289 |
+
|
| 290 |
+
// handleStreamResponse 处理流式响应并转换为 OpenAI 格式
|
| 291 |
+
func handleStreamResponse(resp *http.Response, c *gin.Context, model string) {
|
| 292 |
+
// 设置 SSE 响应头
|
| 293 |
+
c.Header("Content-Type", "text/event-stream")
|
| 294 |
+
c.Header("Cache-Control", "no-cache")
|
| 295 |
+
c.Header("Connection", "keep-alive")
|
| 296 |
+
|
| 297 |
+
scanner := bufio.NewScanner(resp.Body)
|
| 298 |
+
created := time.Now().Unix()
|
| 299 |
+
chatID := fmt.Sprintf("chatcmpl-%s", nanoid(29))
|
| 300 |
+
thinking := false
|
| 301 |
+
|
| 302 |
+
// 首先发送一个空chunk,兼容SSE客户端
|
| 303 |
+
chunk := ChatCompletionResponse{
|
| 304 |
+
ID: chatID,
|
| 305 |
+
Object: "chat.completion.chunk",
|
| 306 |
+
Created: created,
|
| 307 |
+
Model: model,
|
| 308 |
+
Choices: []Choice{{
|
| 309 |
+
Index: 0,
|
| 310 |
+
Delta: &Delta{Content: ""},
|
| 311 |
+
FinishReason: nil,
|
| 312 |
+
}},
|
| 313 |
+
}
|
| 314 |
+
jsonData, _ := json.Marshal(chunk)
|
| 315 |
+
fmt.Fprintf(c.Writer, "data: %s\n\n", jsonData)
|
| 316 |
+
c.Writer.(http.Flusher).Flush()
|
| 317 |
+
|
| 318 |
+
for scanner.Scan() {
|
| 319 |
+
line := strings.TrimSpace(scanner.Text())
|
| 320 |
+
if line == "" {
|
| 321 |
+
continue
|
| 322 |
+
}
|
| 323 |
+
|
| 324 |
+
prefix, content, err := parseChatbotLine(line)
|
| 325 |
+
if err != nil {
|
| 326 |
+
continue
|
| 327 |
+
}
|
| 328 |
+
|
| 329 |
+
switch prefix {
|
| 330 |
+
case "f":
|
| 331 |
+
// 忽略 messageId,OpenAI 不需要这个
|
| 332 |
+
continue
|
| 333 |
+
case "g":
|
| 334 |
+
// 思考过程内容
|
| 335 |
+
if !thinking {
|
| 336 |
+
thinking = true
|
| 337 |
+
// 发送 <think> 开始标记
|
| 338 |
+
chunk := ChatCompletionResponse{
|
| 339 |
+
ID: chatID,
|
| 340 |
+
Object: "chat.completion.chunk",
|
| 341 |
+
Created: created,
|
| 342 |
+
Model: model,
|
| 343 |
+
Choices: []Choice{{
|
| 344 |
+
Index: 0,
|
| 345 |
+
Delta: &Delta{Content: "<think>"},
|
| 346 |
+
FinishReason: nil,
|
| 347 |
+
}},
|
| 348 |
+
}
|
| 349 |
+
jsonData, _ := json.Marshal(chunk)
|
| 350 |
+
fmt.Fprintf(c.Writer, "data: %s\n\n", jsonData)
|
| 351 |
+
c.Writer.(http.Flusher).Flush()
|
| 352 |
+
}
|
| 353 |
+
|
| 354 |
+
var contentStr string
|
| 355 |
+
if err := json.Unmarshal([]byte(content), &contentStr); err != nil {
|
| 356 |
+
continue
|
| 357 |
+
}
|
| 358 |
+
contentStr = strings.ReplaceAll(contentStr, "\\n", "\n")
|
| 359 |
+
|
| 360 |
+
chunk := ChatCompletionResponse{
|
| 361 |
+
ID: chatID,
|
| 362 |
+
Object: "chat.completion.chunk",
|
| 363 |
+
Created: created,
|
| 364 |
+
Model: model,
|
| 365 |
+
Choices: []Choice{{
|
| 366 |
+
Index: 0,
|
| 367 |
+
Delta: &Delta{Content: contentStr},
|
| 368 |
+
FinishReason: nil,
|
| 369 |
+
}},
|
| 370 |
+
}
|
| 371 |
+
|
| 372 |
+
jsonData, _ := json.Marshal(chunk)
|
| 373 |
+
fmt.Fprintf(c.Writer, "data: %s\n\n", jsonData)
|
| 374 |
+
c.Writer.(http.Flusher).Flush()
|
| 375 |
+
|
| 376 |
+
case "0":
|
| 377 |
+
// 最终回答内容
|
| 378 |
+
if thinking {
|
| 379 |
+
thinking = false
|
| 380 |
+
// 发送 </think> 结束标记
|
| 381 |
+
chunk := ChatCompletionResponse{
|
| 382 |
+
ID: chatID,
|
| 383 |
+
Object: "chat.completion.chunk",
|
| 384 |
+
Created: created,
|
| 385 |
+
Model: model,
|
| 386 |
+
Choices: []Choice{{
|
| 387 |
+
Index: 0,
|
| 388 |
+
Delta: &Delta{Content: "</think>"},
|
| 389 |
+
FinishReason: nil,
|
| 390 |
+
}},
|
| 391 |
+
}
|
| 392 |
+
jsonData, _ := json.Marshal(chunk)
|
| 393 |
+
fmt.Fprintf(c.Writer, "data: %s\n\n", jsonData)
|
| 394 |
+
c.Writer.(http.Flusher).Flush()
|
| 395 |
+
}
|
| 396 |
+
|
| 397 |
+
var contentStr string
|
| 398 |
+
if err := json.Unmarshal([]byte(content), &contentStr); err != nil {
|
| 399 |
+
continue
|
| 400 |
+
}
|
| 401 |
+
contentStr = strings.ReplaceAll(contentStr, "\\n", "\n")
|
| 402 |
+
|
| 403 |
+
chunk := ChatCompletionResponse{
|
| 404 |
+
ID: chatID,
|
| 405 |
+
Object: "chat.completion.chunk",
|
| 406 |
+
Created: created,
|
| 407 |
+
Model: model,
|
| 408 |
+
Choices: []Choice{{
|
| 409 |
+
Index: 0,
|
| 410 |
+
Delta: &Delta{Content: contentStr},
|
| 411 |
+
FinishReason: nil,
|
| 412 |
+
}},
|
| 413 |
+
}
|
| 414 |
+
|
| 415 |
+
jsonData, _ := json.Marshal(chunk)
|
| 416 |
+
fmt.Fprintf(c.Writer, "data: %s\n\n", jsonData)
|
| 417 |
+
c.Writer.(http.Flusher).Flush()
|
| 418 |
+
|
| 419 |
+
case "e", "d":
|
| 420 |
+
// 结束信息
|
| 421 |
+
if thinking {
|
| 422 |
+
// 如果还在思考模式,发送结束标记
|
| 423 |
+
chunk := ChatCompletionResponse{
|
| 424 |
+
ID: chatID,
|
| 425 |
+
Object: "chat.completion.chunk",
|
| 426 |
+
Created: created,
|
| 427 |
+
Model: model,
|
| 428 |
+
Choices: []Choice{{
|
| 429 |
+
Index: 0,
|
| 430 |
+
Delta: &Delta{Content: "</think>"},
|
| 431 |
+
FinishReason: nil,
|
| 432 |
+
}},
|
| 433 |
+
}
|
| 434 |
+
jsonData, _ := json.Marshal(chunk)
|
| 435 |
+
fmt.Fprintf(c.Writer, "data: %s\n\n", jsonData)
|
| 436 |
+
c.Writer.(http.Flusher).Flush()
|
| 437 |
+
}
|
| 438 |
+
|
| 439 |
+
var finishInfo ChatbotFinish
|
| 440 |
+
if err := json.Unmarshal([]byte(content), &finishInfo); err != nil {
|
| 441 |
+
continue
|
| 442 |
+
}
|
| 443 |
+
|
| 444 |
+
finishReason := finishInfo.FinishReason
|
| 445 |
+
chunk := ChatCompletionResponse{
|
| 446 |
+
ID: chatID,
|
| 447 |
+
Object: "chat.completion.chunk",
|
| 448 |
+
Created: created,
|
| 449 |
+
Model: model,
|
| 450 |
+
Choices: []Choice{{
|
| 451 |
+
Index: 0,
|
| 452 |
+
Delta: &Delta{},
|
| 453 |
+
FinishReason: &finishReason,
|
| 454 |
+
}},
|
| 455 |
+
Usage: &Usage{
|
| 456 |
+
PromptTokens: finishInfo.Usage.PromptTokens,
|
| 457 |
+
CompletionTokens: finishInfo.Usage.CompletionTokens,
|
| 458 |
+
TotalTokens: finishInfo.Usage.PromptTokens + finishInfo.Usage.CompletionTokens,
|
| 459 |
+
},
|
| 460 |
+
}
|
| 461 |
+
|
| 462 |
+
jsonData, _ := json.Marshal(chunk)
|
| 463 |
+
fmt.Fprintf(c.Writer, "data: %s\n\n", jsonData)
|
| 464 |
+
c.Writer.(http.Flusher).Flush()
|
| 465 |
+
|
| 466 |
+
// 发送 [DONE] 信号
|
| 467 |
+
fmt.Fprintf(c.Writer, "data: [DONE]\n\n")
|
| 468 |
+
c.Writer.(http.Flusher).Flush()
|
| 469 |
+
return
|
| 470 |
+
}
|
| 471 |
+
}
|
| 472 |
+
}
|
| 473 |
+
|
| 474 |
+
// handleNonStreamResponse 处理非流式响应并转换为 OpenAI 格式
|
| 475 |
+
func handleNonStreamResponse(resp *http.Response, c *gin.Context, model string) {
|
| 476 |
+
scanner := bufio.NewScanner(resp.Body)
|
| 477 |
+
created := time.Now().Unix()
|
| 478 |
+
chatID := fmt.Sprintf("chatcmpl-%s", nanoid(29))
|
| 479 |
+
|
| 480 |
+
var fullContent strings.Builder
|
| 481 |
+
var thinkContent strings.Builder
|
| 482 |
+
var usage *Usage
|
| 483 |
+
|
| 484 |
+
for scanner.Scan() {
|
| 485 |
+
line := strings.TrimSpace(scanner.Text())
|
| 486 |
+
if line == "" {
|
| 487 |
+
continue
|
| 488 |
+
}
|
| 489 |
+
|
| 490 |
+
prefix, content, err := parseChatbotLine(line)
|
| 491 |
+
if err != nil {
|
| 492 |
+
continue
|
| 493 |
+
}
|
| 494 |
+
|
| 495 |
+
switch prefix {
|
| 496 |
+
case "g":
|
| 497 |
+
// 思考过程内容
|
| 498 |
+
var contentStr string
|
| 499 |
+
if err := json.Unmarshal([]byte(content), &contentStr); err != nil {
|
| 500 |
+
continue
|
| 501 |
+
}
|
| 502 |
+
contentStr = strings.ReplaceAll(contentStr, "\\n", "\n")
|
| 503 |
+
thinkContent.WriteString(contentStr)
|
| 504 |
+
|
| 505 |
+
case "0":
|
| 506 |
+
// 最终回答内容
|
| 507 |
+
if thinkContent.Len() > 0 {
|
| 508 |
+
fullContent.WriteString("<think>")
|
| 509 |
+
fullContent.WriteString(thinkContent.String())
|
| 510 |
+
fullContent.WriteString("</think>")
|
| 511 |
+
thinkContent.Reset()
|
| 512 |
+
}
|
| 513 |
+
|
| 514 |
+
var contentStr string
|
| 515 |
+
if err := json.Unmarshal([]byte(content), &contentStr); err != nil {
|
| 516 |
+
continue
|
| 517 |
+
}
|
| 518 |
+
contentStr = strings.ReplaceAll(contentStr, "\\n", "\n")
|
| 519 |
+
fullContent.WriteString(contentStr)
|
| 520 |
+
|
| 521 |
+
case "e", "d":
|
| 522 |
+
// 结束信息
|
| 523 |
+
var finishInfo ChatbotFinish
|
| 524 |
+
if err := json.Unmarshal([]byte(content), &finishInfo); err != nil {
|
| 525 |
+
continue
|
| 526 |
+
}
|
| 527 |
+
|
| 528 |
+
usage = &Usage{
|
| 529 |
+
PromptTokens: finishInfo.Usage.PromptTokens,
|
| 530 |
+
CompletionTokens: finishInfo.Usage.CompletionTokens,
|
| 531 |
+
TotalTokens: finishInfo.Usage.PromptTokens + finishInfo.Usage.CompletionTokens,
|
| 532 |
+
}
|
| 533 |
+
break
|
| 534 |
+
}
|
| 535 |
+
}
|
| 536 |
+
|
| 537 |
+
// 如果还有未处理的思考内容,添加到最终内容中
|
| 538 |
+
if thinkContent.Len() > 0 {
|
| 539 |
+
fullContent.WriteString("<think>")
|
| 540 |
+
fullContent.WriteString(thinkContent.String())
|
| 541 |
+
fullContent.WriteString("</think>")
|
| 542 |
+
}
|
| 543 |
+
|
| 544 |
+
response := ChatCompletionResponse{
|
| 545 |
+
ID: chatID,
|
| 546 |
+
Object: "chat.completion",
|
| 547 |
+
Created: created,
|
| 548 |
+
Model: model,
|
| 549 |
+
Choices: []Choice{{
|
| 550 |
+
Index: 0,
|
| 551 |
+
Message: &Message{
|
| 552 |
+
Role: "assistant",
|
| 553 |
+
Content: fullContent.String(),
|
| 554 |
+
},
|
| 555 |
+
FinishReason: func() *string { s := "stop"; return &s }(),
|
| 556 |
+
}},
|
| 557 |
+
Usage: usage,
|
| 558 |
+
}
|
| 559 |
+
|
| 560 |
+
c.JSON(http.StatusOK, response)
|
| 561 |
+
}
|
| 562 |
+
|
| 563 |
+
// verifyAPIKey API密钥验证中间件
|
| 564 |
+
func verifyAPIKey(c *gin.Context) {
|
| 565 |
+
key := c.GetHeader("Authorization")
|
| 566 |
+
fmt.Printf("[DEBUG] Authorization header: '%s'\n", key)
|
| 567 |
+
fmt.Printf("[DEBUG] apiKey: '%s'\n", apiKey)
|
| 568 |
+
|
| 569 |
+
if key == "" {
|
| 570 |
+
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"detail": "Missing Authorization header"})
|
| 571 |
+
return
|
| 572 |
+
}
|
| 573 |
+
|
| 574 |
+
if strings.HasPrefix(key, "Bearer ") {
|
| 575 |
+
key = strings.TrimSpace(strings.TrimPrefix(key, "Bearer "))
|
| 576 |
+
}
|
| 577 |
+
|
| 578 |
+
fmt.Printf("[DEBUG] Processed key: '%s'\n", key)
|
| 579 |
+
|
| 580 |
+
if key != apiKey {
|
| 581 |
+
fmt.Printf("[DEBUG] Key comparison failed: '%s' != '%s'\n", key, apiKey)
|
| 582 |
+
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"detail": "Invalid API key"})
|
| 583 |
+
return
|
| 584 |
+
}
|
| 585 |
+
|
| 586 |
+
fmt.Println("[DEBUG] API key verification passed!")
|
| 587 |
+
c.Next()
|
| 588 |
+
}
|
| 589 |
+
|
| 590 |
+
// main 主函数,初始化并启动服务器
|
| 591 |
+
func main() {
|
| 592 |
+
_ = godotenv.Load()
|
| 593 |
+
apiKey = os.Getenv("API_KEY")
|
| 594 |
+
fmt.Println("[DEBUG] Loaded API_KEY:", apiKey)
|
| 595 |
+
if apiKey == "" {
|
| 596 |
+
log.Fatal("API_KEY not found in .env file")
|
| 597 |
+
}
|
| 598 |
+
|
| 599 |
+
// 读取RANDOM_UA环境变量
|
| 600 |
+
randomUAStr := strings.ToLower(os.Getenv("RANDOM_UA"))
|
| 601 |
+
randomUA = randomUAStr == "true" || randomUAStr == "1" || randomUAStr == "yes"
|
| 602 |
+
fmt.Println("[DEBUG] RANDOM_UA enabled:", randomUA)
|
| 603 |
+
|
| 604 |
+
// 初始化优化的 HTTP 客户端
|
| 605 |
+
initHTTPClient()
|
| 606 |
+
|
| 607 |
+
// 启动异步 chatID 预加载
|
| 608 |
+
preloadChatID()
|
| 609 |
+
|
| 610 |
+
enableCORS = true
|
| 611 |
+
gin.SetMode(gin.ReleaseMode)
|
| 612 |
+
r := gin.Default()
|
| 613 |
+
|
| 614 |
+
// CORS 中间件配置
|
| 615 |
+
if enableCORS {
|
| 616 |
+
r.Use(func(c *gin.Context) {
|
| 617 |
+
c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
|
| 618 |
+
c.Writer.Header().Set("Access-Control-Allow-Methods", "*")
|
| 619 |
+
c.Writer.Header().Set("Access-Control-Allow-Headers", "*")
|
| 620 |
+
if c.Request.Method == "OPTIONS" {
|
| 621 |
+
c.AbortWithStatus(204)
|
| 622 |
+
return
|
| 623 |
+
}
|
| 624 |
+
c.Next()
|
| 625 |
+
})
|
| 626 |
+
}
|
| 627 |
+
|
| 628 |
+
// 健康检查端点不需要 API 验证
|
| 629 |
+
r.GET("/health", func(c *gin.Context) {
|
| 630 |
+
chatID, err := getChatID()
|
| 631 |
+
status := "inactive"
|
| 632 |
+
if err == nil && chatID != "" {
|
| 633 |
+
status = "active"
|
| 634 |
+
}
|
| 635 |
+
c.JSON(http.StatusOK, gin.H{"status": "ok", "session": status})
|
| 636 |
+
})
|
| 637 |
+
|
| 638 |
+
// 需要 API 验证的端点组
|
| 639 |
+
authorized := r.Group("/")
|
| 640 |
+
authorized.Use(verifyAPIKey)
|
| 641 |
+
|
| 642 |
+
// 聊天完成接口
|
| 643 |
+
authorized.POST("/v1/chat/completions", func(c *gin.Context) {
|
| 644 |
+
var req ChatCompletionRequest
|
| 645 |
+
if err := c.ShouldBindJSON(&req); err != nil {
|
| 646 |
+
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
| 647 |
+
return
|
| 648 |
+
}
|
| 649 |
+
if !contains(supportedModels, req.Model) {
|
| 650 |
+
req.Model = "DeepSeek-R1"
|
| 651 |
+
}
|
| 652 |
+
deviceID := generateDeviceID()
|
| 653 |
+
chatID, err := getChatID()
|
| 654 |
+
if err != nil {
|
| 655 |
+
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get chat id"})
|
| 656 |
+
return
|
| 657 |
+
}
|
| 658 |
+
timestamp := time.Now().UnixNano() / 1e6
|
| 659 |
+
sign := generateSign(chatID, timestamp)
|
| 660 |
+
|
| 661 |
+
// 构造请求载荷
|
| 662 |
+
messages := []map[string]string{}
|
| 663 |
+
for _, m := range req.Messages {
|
| 664 |
+
messages = append(messages, map[string]string{"role": m.Role, "content": m.Content})
|
| 665 |
+
}
|
| 666 |
+
payload := map[string]interface{}{
|
| 667 |
+
"id": chatID,
|
| 668 |
+
"messages": messages,
|
| 669 |
+
"selectedChatModel": modelToConfig[req.Model]["model"],
|
| 670 |
+
"isDeepThinkingEnabled": true,
|
| 671 |
+
"isWebSearchEnabled": modelToConfig[req.Model]["isWebSearchEnabled"],
|
| 672 |
+
}
|
| 673 |
+
|
| 674 |
+
// 构造请求头
|
| 675 |
+
headers := map[string]string{
|
| 676 |
+
"User-Agent": getUserAgent(deviceID),
|
| 677 |
+
"Accept": "*/*",
|
| 678 |
+
"Accept-Language": "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2",
|
| 679 |
+
"Referer": "https://ai-chatbot.top/chat/" + chatID,
|
| 680 |
+
"Content-Type": "application/json",
|
| 681 |
+
"currentTime": fmt.Sprintf("%d", timestamp),
|
| 682 |
+
"sign": sign,
|
| 683 |
+
"Origin": "https://ai-chatbot.top",
|
| 684 |
+
"DNT": "1",
|
| 685 |
+
"Sec-GPC": "1",
|
| 686 |
+
"Connection": "keep-alive",
|
| 687 |
+
"Sec-Fetch-Dest": "empty",
|
| 688 |
+
"Sec-Fetch-Mode": "cors",
|
| 689 |
+
"Sec-Fetch-Site": "same-origin",
|
| 690 |
+
"Priority": "u=0",
|
| 691 |
+
}
|
| 692 |
+
|
| 693 |
+
// 构造cookies
|
| 694 |
+
cookies := []*http.Cookie{
|
| 695 |
+
{Name: "_ga_HVMZBNYJML", Value: "GS1.1.1742013194.1.1.1742013780.0.0.0"},
|
| 696 |
+
{Name: "_ga", Value: "GA1.1.1029622546.1742013195"},
|
| 697 |
+
}
|
| 698 |
+
|
| 699 |
+
// 使用全局 HTTP 客户端发送请求
|
| 700 |
+
jsonBytes, _ := json.Marshal(payload)
|
| 701 |
+
req2, _ := http.NewRequest("POST", apiDomain+"/api/chat", strings.NewReader(string(jsonBytes)))
|
| 702 |
+
for k, v := range headers {
|
| 703 |
+
req2.Header.Set(k, v)
|
| 704 |
+
}
|
| 705 |
+
for _, ck := range cookies {
|
| 706 |
+
req2.AddCookie(ck)
|
| 707 |
+
}
|
| 708 |
+
|
| 709 |
+
resp, err := httpClient.Do(req2)
|
| 710 |
+
if err != nil {
|
| 711 |
+
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to call ai-chatbot.top"})
|
| 712 |
+
return
|
| 713 |
+
}
|
| 714 |
+
defer resp.Body.Close()
|
| 715 |
+
|
| 716 |
+
// 根据 stream 参数选择处理方式
|
| 717 |
+
if req.Stream {
|
| 718 |
+
handleStreamResponse(resp, c, req.Model)
|
| 719 |
+
} else {
|
| 720 |
+
handleNonStreamResponse(resp, c, req.Model)
|
| 721 |
+
}
|
| 722 |
+
})
|
| 723 |
+
|
| 724 |
+
// 模型列表接口
|
| 725 |
+
authorized.GET("/v1/models", func(c *gin.Context) {
|
| 726 |
+
currentTime := time.Now().Unix()
|
| 727 |
+
models := []ModelInfo{}
|
| 728 |
+
for _, m := range supportedModels {
|
| 729 |
+
models = append(models, ModelInfo{
|
| 730 |
+
ID: m,
|
| 731 |
+
Object: "model",
|
| 732 |
+
Created: currentTime,
|
| 733 |
+
OwnedBy: "aichatbot",
|
| 734 |
+
})
|
| 735 |
+
}
|
| 736 |
+
c.JSON(http.StatusOK, ModelList{Object: "list", Data: models})
|
| 737 |
+
})
|
| 738 |
+
|
| 739 |
+
r.Run(":7860")
|
| 740 |
+
}
|
| 741 |
+
|
| 742 |
+
// contains 检查字符串数组中是否包含指定字符串
|
| 743 |
+
func contains(arr []string, s string) bool {
|
| 744 |
+
for _, v := range arr {
|
| 745 |
+
if v == s {
|
| 746 |
+
return true
|
| 747 |
+
}
|
| 748 |
+
}
|
| 749 |
+
return false
|
| 750 |
+
}
|
main.ts
DELETED
|
@@ -1,679 +0,0 @@
|
|
| 1 |
-
import { serve } from "https://deno.land/std@0.140.0/http/server.ts";
|
| 2 |
-
import { Md5 } from "https://deno.land/std@0.140.0/hash/md5.ts";
|
| 3 |
-
|
| 4 |
-
const API_DOMAIN = 'https://ai-api.dangbei.net';
|
| 5 |
-
const USER_AGENTS = [
|
| 6 |
-
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36',
|
| 7 |
-
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36',
|
| 8 |
-
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36',
|
| 9 |
-
'Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1',
|
| 10 |
-
'Mozilla/5.0 (iPad; CPU OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1'
|
| 11 |
-
];
|
| 12 |
-
const VALID_API_KEY = Deno.env.get('VALID_API_KEY');
|
| 13 |
-
const MAX_CONVERSATIONS_PER_DEVICE = 10; // 每个设备最多创建的会话数
|
| 14 |
-
|
| 15 |
-
class ChatManage {
|
| 16 |
-
private currentDeviceId: string | null = null;
|
| 17 |
-
private currentConversationId: string | null = null;
|
| 18 |
-
private conversationCount = 0;
|
| 19 |
-
private currentUserAgent: string;
|
| 20 |
-
|
| 21 |
-
constructor() {
|
| 22 |
-
this.currentUserAgent = this.getRandomUserAgent();
|
| 23 |
-
}
|
| 24 |
-
|
| 25 |
-
private getRandomUserAgent(): string {
|
| 26 |
-
return USER_AGENTS[Math.floor(Math.random() * USER_AGENTS.length)];
|
| 27 |
-
}
|
| 28 |
-
|
| 29 |
-
getOrCreateIds(forceNew = false) {
|
| 30 |
-
let newDeviceId = this.currentDeviceId;
|
| 31 |
-
let newConversationId = this.currentConversationId;
|
| 32 |
-
|
| 33 |
-
if (forceNew || !newDeviceId || this.conversationCount >= MAX_CONVERSATIONS_PER_DEVICE) {
|
| 34 |
-
newDeviceId = this.generateDeviceId();
|
| 35 |
-
newConversationId = null;
|
| 36 |
-
this.conversationCount = 0;
|
| 37 |
-
// 在生成新设备ID时更新 User-Agent
|
| 38 |
-
this.currentUserAgent = this.getRandomUserAgent();
|
| 39 |
-
}
|
| 40 |
-
|
| 41 |
-
this.currentDeviceId = newDeviceId;
|
| 42 |
-
this.currentConversationId = newConversationId;
|
| 43 |
-
|
| 44 |
-
return {
|
| 45 |
-
deviceId: newDeviceId,
|
| 46 |
-
conversationId: newConversationId,
|
| 47 |
-
userAgent: this.currentUserAgent
|
| 48 |
-
};
|
| 49 |
-
}
|
| 50 |
-
|
| 51 |
-
updateConversationId(conversationId: string) {
|
| 52 |
-
this.currentConversationId = conversationId;
|
| 53 |
-
this.conversationCount++;
|
| 54 |
-
}
|
| 55 |
-
|
| 56 |
-
generateDeviceId() {
|
| 57 |
-
const uuid = crypto.randomUUID();
|
| 58 |
-
const urlAlphabet = 'useandom26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict';
|
| 59 |
-
const nanoid = Array.from(crypto.getRandomValues(new Uint8Array(20)))
|
| 60 |
-
.map(b => urlAlphabet[b % urlAlphabet.length])
|
| 61 |
-
.join('');
|
| 62 |
-
return `${uuid.replace(/-/g, '')}_${nanoid}`;
|
| 63 |
-
}
|
| 64 |
-
}
|
| 65 |
-
|
| 66 |
-
class Pipe {
|
| 67 |
-
private dataPrefix = 'data:';
|
| 68 |
-
private chatManage = new ChatManage();
|
| 69 |
-
private searchModels: Record<string, string> = {
|
| 70 |
-
'DeepSeek-R1-Search': 'deepseek',
|
| 71 |
-
'DeepSeek-V3-Search': 'deepseek',
|
| 72 |
-
'Doubao-Search': 'doubao',
|
| 73 |
-
'Qwen-Search': 'qwen'
|
| 74 |
-
};
|
| 75 |
-
|
| 76 |
-
// 创建新的会话
|
| 77 |
-
async _create_conversation(deviceId: string) {
|
| 78 |
-
const { userAgent } = this.chatManage.getOrCreateIds(false);
|
| 79 |
-
const payload = { botCode: "AI_SEARCH" };
|
| 80 |
-
const timestamp = Math.floor(Date.now() / 1000).toString();
|
| 81 |
-
const nonce = this.nanoid(21);
|
| 82 |
-
const sign = await this.generateSign(timestamp, payload, nonce);
|
| 83 |
-
|
| 84 |
-
const headers = {
|
| 85 |
-
"Origin": "https://ai.dangbei.com",
|
| 86 |
-
"Referer": "https://ai.dangbei.com/",
|
| 87 |
-
"User-Agent": userAgent,
|
| 88 |
-
"deviceId": deviceId,
|
| 89 |
-
"nonce": nonce,
|
| 90 |
-
"sign": sign,
|
| 91 |
-
"timestamp": timestamp,
|
| 92 |
-
"Content-Type": "application/json"
|
| 93 |
-
};
|
| 94 |
-
|
| 95 |
-
try {
|
| 96 |
-
console.log('Creating conversation with:', {
|
| 97 |
-
url: `${API_DOMAIN}/ai-search/conversationApi/v1/create`,
|
| 98 |
-
headers,
|
| 99 |
-
payload
|
| 100 |
-
});
|
| 101 |
-
|
| 102 |
-
const response = await fetch(`${API_DOMAIN}/ai-search/conversationApi/v1/create`, {
|
| 103 |
-
method: 'POST',
|
| 104 |
-
headers,
|
| 105 |
-
body: JSON.stringify(payload),
|
| 106 |
-
});
|
| 107 |
-
|
| 108 |
-
console.log('Response status:', response.status);
|
| 109 |
-
const responseText = await response.text();
|
| 110 |
-
console.log('Response body:', responseText);
|
| 111 |
-
|
| 112 |
-
if (response.ok) {
|
| 113 |
-
try {
|
| 114 |
-
const data = JSON.parse(responseText);
|
| 115 |
-
if (data.success) {
|
| 116 |
-
console.log('Successfully created conversation:', data.data.conversationId);
|
| 117 |
-
return data.data.conversationId;
|
| 118 |
-
} else {
|
| 119 |
-
console.error('API returned success: false:', data);
|
| 120 |
-
}
|
| 121 |
-
} catch (e) {
|
| 122 |
-
console.error('Failed to parse response:', e);
|
| 123 |
-
}
|
| 124 |
-
} else {
|
| 125 |
-
console.error('HTTP error:', response.status, responseText);
|
| 126 |
-
}
|
| 127 |
-
} catch (e) {
|
| 128 |
-
console.error('Error creating conversation:', e);
|
| 129 |
-
}
|
| 130 |
-
return null;
|
| 131 |
-
}
|
| 132 |
-
|
| 133 |
-
// 新增方法:构建完整提示
|
| 134 |
-
_buildFullPrompt(messages: any[]): string {
|
| 135 |
-
if (!messages || messages.length === 0) {
|
| 136 |
-
return '';
|
| 137 |
-
}
|
| 138 |
-
|
| 139 |
-
let systemPrompt = '';
|
| 140 |
-
const history: string[] = [];
|
| 141 |
-
let lastUserMessage = '';
|
| 142 |
-
|
| 143 |
-
for (const msg of messages) {
|
| 144 |
-
if (msg.role === 'system' && !systemPrompt) {
|
| 145 |
-
systemPrompt = msg.content;
|
| 146 |
-
} else if (msg.role === 'user') {
|
| 147 |
-
history.push(`user: ${msg.content}`);
|
| 148 |
-
lastUserMessage = msg.content;
|
| 149 |
-
} else if (msg.role === 'assistant') {
|
| 150 |
-
history.push(`assistant: ${msg.content}`);
|
| 151 |
-
}
|
| 152 |
-
}
|
| 153 |
-
|
| 154 |
-
const parts: string[] = [];
|
| 155 |
-
if (systemPrompt) {
|
| 156 |
-
parts.push(`[System Prompt]\n${systemPrompt}`);
|
| 157 |
-
}
|
| 158 |
-
if (history.length > 1) {
|
| 159 |
-
parts.push(`[Chat History]\n${history.slice(0, -1).join('\n')}`);
|
| 160 |
-
}
|
| 161 |
-
parts.push(`[Question]\n${lastUserMessage}`);
|
| 162 |
-
|
| 163 |
-
return parts.join('\n\n');
|
| 164 |
-
}
|
| 165 |
-
|
| 166 |
-
async* pipe(body: any) {
|
| 167 |
-
const thinkingState = { thinking: -1 };
|
| 168 |
-
|
| 169 |
-
// Build full prompt
|
| 170 |
-
const fullPrompt = this._buildFullPrompt(body.messages);
|
| 171 |
-
|
| 172 |
-
// Check if we need to force new conversation
|
| 173 |
-
let forceNew = false;
|
| 174 |
-
const messages = body.messages;
|
| 175 |
-
if (messages.length === 1) {
|
| 176 |
-
forceNew = true;
|
| 177 |
-
} else if (messages.length >= 2) {
|
| 178 |
-
const lastTwo = messages.slice(-2);
|
| 179 |
-
if (lastTwo[0].role === 'user' && lastTwo[1].role === 'user') {
|
| 180 |
-
forceNew = true;
|
| 181 |
-
}
|
| 182 |
-
}
|
| 183 |
-
|
| 184 |
-
// Get or create device ID and conversation ID with User-Agent
|
| 185 |
-
const { deviceId, conversationId: storedConversationId, userAgent } = this.chatManage.getOrCreateIds(forceNew);
|
| 186 |
-
let conversationId = storedConversationId;
|
| 187 |
-
|
| 188 |
-
// Create new conversation if needed
|
| 189 |
-
if (!conversationId) {
|
| 190 |
-
conversationId = await this._create_conversation(deviceId);
|
| 191 |
-
if (!conversationId) {
|
| 192 |
-
yield { error: 'Failed to create conversation' };
|
| 193 |
-
return;
|
| 194 |
-
}
|
| 195 |
-
this.chatManage.updateConversationId(conversationId);
|
| 196 |
-
}
|
| 197 |
-
|
| 198 |
-
// Model name handling
|
| 199 |
-
let modelName;
|
| 200 |
-
const isSearchModel = body.model.endsWith('-Search');
|
| 201 |
-
if (isSearchModel) {
|
| 202 |
-
modelName = this.searchModels[body.model] || body.model.replace('-Search', '').toLowerCase();
|
| 203 |
-
} else {
|
| 204 |
-
const isDeepSeekModel = ['DeepSeek-R1', 'DeepSeek-V3'].includes(body.model);
|
| 205 |
-
modelName = isDeepSeekModel ? 'deepseek' : body.model.toLowerCase();
|
| 206 |
-
}
|
| 207 |
-
|
| 208 |
-
// 确定 userAction 参数
|
| 209 |
-
let userAction = '';
|
| 210 |
-
if (body.model.includes('DeepSeek-R1')) {
|
| 211 |
-
userAction = 'deep';
|
| 212 |
-
}
|
| 213 |
-
if (isSearchModel) {
|
| 214 |
-
userAction = userAction ? `${userAction},online` : 'online';
|
| 215 |
-
}
|
| 216 |
-
|
| 217 |
-
const payload = {
|
| 218 |
-
stream: true,
|
| 219 |
-
botCode: 'AI_SEARCH',
|
| 220 |
-
userAction,
|
| 221 |
-
model: modelName,
|
| 222 |
-
conversationId: conversationId,
|
| 223 |
-
question: fullPrompt,
|
| 224 |
-
};
|
| 225 |
-
|
| 226 |
-
const timestamp = Math.floor(Date.now() / 1000).toString();
|
| 227 |
-
const nonce = this.nanoid(21);
|
| 228 |
-
const sign = await this.generateSign(timestamp, payload, nonce);
|
| 229 |
-
|
| 230 |
-
const headers = {
|
| 231 |
-
'Origin': 'https://ai.dangbei.com',
|
| 232 |
-
'Referer': 'https://ai.dangbei.com/',
|
| 233 |
-
'User-Agent': userAgent,
|
| 234 |
-
'deviceId': deviceId,
|
| 235 |
-
'nonce': nonce,
|
| 236 |
-
'sign': sign,
|
| 237 |
-
'timestamp': timestamp,
|
| 238 |
-
'Content-Type': 'application/json',
|
| 239 |
-
};
|
| 240 |
-
|
| 241 |
-
try {
|
| 242 |
-
const response = await fetch(`${API_DOMAIN}/ai-search/chatApi/v1/chat`, {
|
| 243 |
-
method: 'POST',
|
| 244 |
-
headers,
|
| 245 |
-
body: JSON.stringify(payload),
|
| 246 |
-
});
|
| 247 |
-
|
| 248 |
-
if (!response.ok) {
|
| 249 |
-
const error = await response.text();
|
| 250 |
-
console.error('HTTP Error:', response.status, error);
|
| 251 |
-
yield { error: `HTTP ${response.status}: ${error}` };
|
| 252 |
-
return;
|
| 253 |
-
}
|
| 254 |
-
|
| 255 |
-
const reader = response.body!.getReader();
|
| 256 |
-
const decoder = new TextDecoder();
|
| 257 |
-
let buffer = '';
|
| 258 |
-
let cardMessages: string[] = [];
|
| 259 |
-
|
| 260 |
-
while (true) {
|
| 261 |
-
const { done, value } = await reader.read();
|
| 262 |
-
if (done) break;
|
| 263 |
-
|
| 264 |
-
buffer += decoder.decode(value, { stream: true });
|
| 265 |
-
const lines = buffer.split('\n');
|
| 266 |
-
buffer = lines.pop() || '';
|
| 267 |
-
|
| 268 |
-
for (const line of lines) {
|
| 269 |
-
if (!line.startsWith(this.dataPrefix)) continue;
|
| 270 |
-
|
| 271 |
-
try {
|
| 272 |
-
const data = JSON.parse(line.slice(this.dataPrefix.length));
|
| 273 |
-
if (data.type === 'answer') {
|
| 274 |
-
const content = data.content;
|
| 275 |
-
const contentType = data.content_type;
|
| 276 |
-
|
| 277 |
-
if (thinkingState.thinking === -1 && contentType === 'thinking') {
|
| 278 |
-
thinkingState.thinking = 0;
|
| 279 |
-
yield { choices: [{ delta: { content: '<think>\n\n' }, finish_reason: null }] };
|
| 280 |
-
} else if (thinkingState.thinking === 0 && contentType === 'text') {
|
| 281 |
-
thinkingState.thinking = 1;
|
| 282 |
-
yield { choices: [{ delta: { content: '\n' }, finish_reason: null }] };
|
| 283 |
-
yield { choices: [{ delta: { content: '</think>' }, finish_reason: null }] };
|
| 284 |
-
yield { choices: [{ delta: { content: '\n\n' }, finish_reason: null }] };
|
| 285 |
-
}
|
| 286 |
-
|
| 287 |
-
if (contentType === 'card') {
|
| 288 |
-
try {
|
| 289 |
-
const cardContent = JSON.parse(content);
|
| 290 |
-
const cardItems = cardContent.cardInfo.cardItems;
|
| 291 |
-
let markdownOutput = '\n\n---\n\n';
|
| 292 |
-
|
| 293 |
-
const searchKeywords = cardItems.find((item: any) => item.type === '2001');
|
| 294 |
-
if (searchKeywords) {
|
| 295 |
-
const keywords = JSON.parse(searchKeywords.content);
|
| 296 |
-
markdownOutput += `搜索关键字:${keywords.join('; ')}\n\n`;
|
| 297 |
-
}
|
| 298 |
-
|
| 299 |
-
const searchResults = cardItems.find((item: any) => item.type === '2002');
|
| 300 |
-
if (searchResults) {
|
| 301 |
-
const results = JSON.parse(searchResults.content);
|
| 302 |
-
markdownOutput += `共找到 ${results.length} 个搜索结果:\n\n`;
|
| 303 |
-
|
| 304 |
-
results.forEach((result: any) => {
|
| 305 |
-
markdownOutput += `[${result.idIndex}] [${result.name}](${result.url}) 来源:${result.siteName}\n`;
|
| 306 |
-
});
|
| 307 |
-
}
|
| 308 |
-
|
| 309 |
-
cardMessages.push(markdownOutput);
|
| 310 |
-
} catch (e) {
|
| 311 |
-
console.error('Error processing card:', e);
|
| 312 |
-
}
|
| 313 |
-
}
|
| 314 |
-
|
| 315 |
-
if (content && (contentType === 'text' || contentType === 'thinking')) {
|
| 316 |
-
yield { choices: [{ delta: { content }, finish_reason: null }] };
|
| 317 |
-
}
|
| 318 |
-
}
|
| 319 |
-
} catch (e) {
|
| 320 |
-
console.error('Parse error:', e, 'Line:', line);
|
| 321 |
-
yield { error: `JSONDecodeError: ${(e as Error).message}` };
|
| 322 |
-
return;
|
| 323 |
-
}
|
| 324 |
-
}
|
| 325 |
-
}
|
| 326 |
-
|
| 327 |
-
if (cardMessages.length > 0) {
|
| 328 |
-
yield { choices: [{ delta: { content: cardMessages.join('') }, finish_reason: null }] };
|
| 329 |
-
}
|
| 330 |
-
|
| 331 |
-
yield {
|
| 332 |
-
choices: [{
|
| 333 |
-
delta: {
|
| 334 |
-
meta: {
|
| 335 |
-
device_id: deviceId,
|
| 336 |
-
conversation_id: conversationId
|
| 337 |
-
}
|
| 338 |
-
},
|
| 339 |
-
finish_reason: null
|
| 340 |
-
}]
|
| 341 |
-
};
|
| 342 |
-
|
| 343 |
-
} catch (e) {
|
| 344 |
-
console.error('Error in pipe:', e);
|
| 345 |
-
yield { error: `${(e as Error).name}: ${(e as Error).message}` };
|
| 346 |
-
}
|
| 347 |
-
}
|
| 348 |
-
|
| 349 |
-
nanoid(size = 21) {
|
| 350 |
-
const urlAlphabet = 'useandom26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict';
|
| 351 |
-
const bytes = new Uint8Array(size);
|
| 352 |
-
crypto.getRandomValues(bytes);
|
| 353 |
-
return Array.from(bytes).reverse().map(b => urlAlphabet[b & 63]).join('');
|
| 354 |
-
}
|
| 355 |
-
|
| 356 |
-
async generateSign(timestamp: string, payload: any, nonce: string) {
|
| 357 |
-
const payloadStr = JSON.stringify(payload);
|
| 358 |
-
const signStr = `${timestamp}${payloadStr}${nonce}`;
|
| 359 |
-
console.log('Sign string:', signStr);
|
| 360 |
-
|
| 361 |
-
// 使用 Deno 标准库的 MD5
|
| 362 |
-
const sign = new Md5()
|
| 363 |
-
.update(signStr)
|
| 364 |
-
.toString()
|
| 365 |
-
.toUpperCase();
|
| 366 |
-
|
| 367 |
-
console.log('Generated sign:', sign);
|
| 368 |
-
return sign;
|
| 369 |
-
}
|
| 370 |
-
}
|
| 371 |
-
|
| 372 |
-
const pipe = new Pipe();
|
| 373 |
-
|
| 374 |
-
// 验证 API 密钥
|
| 375 |
-
function verifyApiKey(request: Request) {
|
| 376 |
-
const authorization = request.headers.get('Authorization');
|
| 377 |
-
// 检查环境变量是否配置
|
| 378 |
-
if (!VALID_API_KEY) {
|
| 379 |
-
return new Response(JSON.stringify({ error: 'API key not configured' }), {
|
| 380 |
-
status: 500,
|
| 381 |
-
headers: { 'Content-Type': 'application/json' },
|
| 382 |
-
});
|
| 383 |
-
}
|
| 384 |
-
|
| 385 |
-
if (!authorization) {
|
| 386 |
-
return new Response(JSON.stringify({ error: 'Missing API key' }), {
|
| 387 |
-
status: 401,
|
| 388 |
-
headers: {
|
| 389 |
-
'Content-Type': 'application/json',
|
| 390 |
-
'Access-Control-Allow-Origin': '*',
|
| 391 |
-
},
|
| 392 |
-
});
|
| 393 |
-
}
|
| 394 |
-
|
| 395 |
-
const apiKey = authorization.replace('Bearer ', '').trim();
|
| 396 |
-
if (apiKey !== VALID_API_KEY) {
|
| 397 |
-
return new Response(JSON.stringify({ error: 'Invalid API key' }), {
|
| 398 |
-
status: 401,
|
| 399 |
-
headers: {
|
| 400 |
-
'Content-Type': 'application/json',
|
| 401 |
-
'Access-Control-Allow-Origin': '*',
|
| 402 |
-
},
|
| 403 |
-
});
|
| 404 |
-
}
|
| 405 |
-
|
| 406 |
-
return null;
|
| 407 |
-
}
|
| 408 |
-
|
| 409 |
-
async function handleRequest(request: Request) {
|
| 410 |
-
const url = new URL(request.url);
|
| 411 |
-
|
| 412 |
-
// 添加根路径处理
|
| 413 |
-
if (request.method === 'GET' && url.pathname === '/') {
|
| 414 |
-
return new Response("it's work!", {
|
| 415 |
-
headers: {
|
| 416 |
-
'Content-Type': 'text/plain',
|
| 417 |
-
'Access-Control-Allow-Origin': '*',
|
| 418 |
-
},
|
| 419 |
-
});
|
| 420 |
-
}
|
| 421 |
-
|
| 422 |
-
if (request.method === 'OPTIONS') {
|
| 423 |
-
return new Response(null, {
|
| 424 |
-
headers: {
|
| 425 |
-
'Access-Control-Allow-Origin': '*',
|
| 426 |
-
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
|
| 427 |
-
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
|
| 428 |
-
},
|
| 429 |
-
});
|
| 430 |
-
}
|
| 431 |
-
|
| 432 |
-
// 验证 API 密钥(除了 OPTIONS 请求)
|
| 433 |
-
const authError = verifyApiKey(request);
|
| 434 |
-
if (authError) return authError;
|
| 435 |
-
|
| 436 |
-
if (request.method === 'GET' && url.pathname === '/v1/models') {
|
| 437 |
-
const currentTime = Math.floor(Date.now() / 1000);
|
| 438 |
-
return new Response(JSON.stringify({
|
| 439 |
-
object: 'list',
|
| 440 |
-
data: [
|
| 441 |
-
// Original models
|
| 442 |
-
{
|
| 443 |
-
id: 'DeepSeek-R1',
|
| 444 |
-
object: 'model',
|
| 445 |
-
created: currentTime,
|
| 446 |
-
owned_by: 'library'
|
| 447 |
-
},
|
| 448 |
-
{
|
| 449 |
-
id: 'DeepSeek-V3',
|
| 450 |
-
object: 'model',
|
| 451 |
-
created: currentTime,
|
| 452 |
-
owned_by: 'library'
|
| 453 |
-
},
|
| 454 |
-
{
|
| 455 |
-
id: 'Doubao',
|
| 456 |
-
object: 'model',
|
| 457 |
-
created: currentTime,
|
| 458 |
-
owned_by: 'library'
|
| 459 |
-
},
|
| 460 |
-
{
|
| 461 |
-
id: 'Qwen',
|
| 462 |
-
object: 'model',
|
| 463 |
-
created: currentTime,
|
| 464 |
-
owned_by: 'library'
|
| 465 |
-
},
|
| 466 |
-
{
|
| 467 |
-
id: 'Glm3',
|
| 468 |
-
object: 'model',
|
| 469 |
-
created: currentTime,
|
| 470 |
-
owned_by: 'library'
|
| 471 |
-
},
|
| 472 |
-
{
|
| 473 |
-
id: 'Moonshot_v1',
|
| 474 |
-
object: 'model',
|
| 475 |
-
created: currentTime,
|
| 476 |
-
owned_by: 'library'
|
| 477 |
-
},
|
| 478 |
-
// Search-enabled models
|
| 479 |
-
{
|
| 480 |
-
id: 'DeepSeek-R1-Search',
|
| 481 |
-
object: 'model',
|
| 482 |
-
created: currentTime,
|
| 483 |
-
owned_by: 'library',
|
| 484 |
-
features: ['online_search']
|
| 485 |
-
},
|
| 486 |
-
{
|
| 487 |
-
id: 'DeepSeek-V3-Search',
|
| 488 |
-
object: 'model',
|
| 489 |
-
created: currentTime,
|
| 490 |
-
owned_by: 'library',
|
| 491 |
-
features: ['online_search']
|
| 492 |
-
},
|
| 493 |
-
{
|
| 494 |
-
id: 'Doubao-Search',
|
| 495 |
-
object: 'model',
|
| 496 |
-
created: currentTime,
|
| 497 |
-
owned_by: 'library',
|
| 498 |
-
features: ['online_search']
|
| 499 |
-
},
|
| 500 |
-
{
|
| 501 |
-
id: 'Qwen-Search',
|
| 502 |
-
object: 'model',
|
| 503 |
-
created: currentTime,
|
| 504 |
-
owned_by: 'library',
|
| 505 |
-
features: ['online_search']
|
| 506 |
-
},
|
| 507 |
-
{
|
| 508 |
-
id: 'Glm3-Search',
|
| 509 |
-
object: 'model',
|
| 510 |
-
created: currentTime,
|
| 511 |
-
owned_by: 'library',
|
| 512 |
-
features: ['online_search']
|
| 513 |
-
},
|
| 514 |
-
{
|
| 515 |
-
id: 'Moonshot_v1-Search',
|
| 516 |
-
object: 'model',
|
| 517 |
-
created: currentTime,
|
| 518 |
-
owned_by: 'library',
|
| 519 |
-
features: ['online_search']
|
| 520 |
-
}
|
| 521 |
-
]
|
| 522 |
-
}), {
|
| 523 |
-
headers: {
|
| 524 |
-
'Content-Type': 'application/json',
|
| 525 |
-
'Access-Control-Allow-Origin': '*',
|
| 526 |
-
},
|
| 527 |
-
});
|
| 528 |
-
}
|
| 529 |
-
|
| 530 |
-
if (request.method === 'POST' && url.pathname === '/v1/chat/completions') {
|
| 531 |
-
const body = await request.json();
|
| 532 |
-
const isStream = body.stream || false;
|
| 533 |
-
|
| 534 |
-
if (isStream) {
|
| 535 |
-
const stream = new ReadableStream({
|
| 536 |
-
async start(controller) {
|
| 537 |
-
try {
|
| 538 |
-
for await (const chunk of pipe.pipe(body)) {
|
| 539 |
-
controller.enqueue(new TextEncoder().encode(`data: ${JSON.stringify(chunk)}\n\n`));
|
| 540 |
-
}
|
| 541 |
-
controller.enqueue(new TextEncoder().encode('data: [DONE]\n\n'));
|
| 542 |
-
controller.close();
|
| 543 |
-
} catch (e) {
|
| 544 |
-
console.error('Error in stream:', e);
|
| 545 |
-
controller.error(e);
|
| 546 |
-
}
|
| 547 |
-
},
|
| 548 |
-
});
|
| 549 |
-
|
| 550 |
-
return new Response(stream, {
|
| 551 |
-
headers: {
|
| 552 |
-
'Content-Type': 'text/event-stream',
|
| 553 |
-
'Cache-Control': 'no-cache',
|
| 554 |
-
'Connection': 'keep-alive',
|
| 555 |
-
'Access-Control-Allow-Origin': '*',
|
| 556 |
-
},
|
| 557 |
-
});
|
| 558 |
-
}
|
| 559 |
-
|
| 560 |
-
if (!isStream) {
|
| 561 |
-
let content = '';
|
| 562 |
-
let meta = null;
|
| 563 |
-
let thinking_content: string[] = [];
|
| 564 |
-
let is_thinking = false;
|
| 565 |
-
|
| 566 |
-
try {
|
| 567 |
-
for await (const chunk of pipe.pipe(body)) {
|
| 568 |
-
if (chunk.choices?.[0]?.delta?.content) {
|
| 569 |
-
const content_chunk = chunk.choices[0].delta.content;
|
| 570 |
-
if (content_chunk === '<think>\n\n') {
|
| 571 |
-
is_thinking = true;
|
| 572 |
-
} else if (content_chunk === '\n</think>\n\n') {
|
| 573 |
-
is_thinking = false;
|
| 574 |
-
} else if (is_thinking) {
|
| 575 |
-
thinking_content.push(content_chunk);
|
| 576 |
-
} else {
|
| 577 |
-
content += content_chunk;
|
| 578 |
-
}
|
| 579 |
-
}
|
| 580 |
-
if (chunk.choices?.[0]?.delta?.meta) {
|
| 581 |
-
meta = chunk.choices[0].delta.meta;
|
| 582 |
-
}
|
| 583 |
-
}
|
| 584 |
-
|
| 585 |
-
// 处理思考内容
|
| 586 |
-
const reasoningContent = thinking_content.join('');
|
| 587 |
-
|
| 588 |
-
return new Response(JSON.stringify({
|
| 589 |
-
id: crypto.randomUUID(),
|
| 590 |
-
object: 'chat.completion',
|
| 591 |
-
created: Math.floor(Date.now() / 1000),
|
| 592 |
-
model: body.model,
|
| 593 |
-
choices: [{
|
| 594 |
-
message: {
|
| 595 |
-
role: 'assistant',
|
| 596 |
-
reasoning_content: reasoningContent ? `<think>\n${reasoningContent}\n</think>` : '',
|
| 597 |
-
content: content.trim(),
|
| 598 |
-
meta: meta
|
| 599 |
-
},
|
| 600 |
-
finish_reason: 'stop'
|
| 601 |
-
}]
|
| 602 |
-
} as NonStreamResponse), {
|
| 603 |
-
headers: {
|
| 604 |
-
'Content-Type': 'application/json',
|
| 605 |
-
'Access-Control-Allow-Origin': '*',
|
| 606 |
-
},
|
| 607 |
-
});
|
| 608 |
-
} catch (e) {
|
| 609 |
-
console.error('Error processing chat request:', e);
|
| 610 |
-
return new Response(JSON.stringify({ error: 'Internal Server Error' }), {
|
| 611 |
-
status: 500,
|
| 612 |
-
headers: {
|
| 613 |
-
'Content-Type': 'application/json',
|
| 614 |
-
'Access-Control-Allow-Origin': '*',
|
| 615 |
-
},
|
| 616 |
-
});
|
| 617 |
-
}
|
| 618 |
-
}
|
| 619 |
-
}
|
| 620 |
-
|
| 621 |
-
return new Response('Not Found', { status: 404 });
|
| 622 |
-
}
|
| 623 |
-
|
| 624 |
-
serve(handleRequest, { port: 7860 });
|
| 625 |
-
|
| 626 |
-
interface Message {
|
| 627 |
-
role: string;
|
| 628 |
-
content: string;
|
| 629 |
-
}
|
| 630 |
-
|
| 631 |
-
interface ChatRequest {
|
| 632 |
-
model: string;
|
| 633 |
-
messages: Message[];
|
| 634 |
-
stream: boolean;
|
| 635 |
-
temperature?: number;
|
| 636 |
-
top_p?: number;
|
| 637 |
-
n?: number;
|
| 638 |
-
max_tokens?: number;
|
| 639 |
-
presence_penalty?: number;
|
| 640 |
-
frequency_penalty?: number;
|
| 641 |
-
user?: string;
|
| 642 |
-
}
|
| 643 |
-
|
| 644 |
-
interface DeltaContent {
|
| 645 |
-
content?: string;
|
| 646 |
-
meta?: {
|
| 647 |
-
device_id: string;
|
| 648 |
-
conversation_id: string;
|
| 649 |
-
};
|
| 650 |
-
}
|
| 651 |
-
|
| 652 |
-
interface Choice {
|
| 653 |
-
delta: DeltaContent;
|
| 654 |
-
finish_reason: string | null;
|
| 655 |
-
}
|
| 656 |
-
|
| 657 |
-
interface StreamResponse {
|
| 658 |
-
choices?: Choice[];
|
| 659 |
-
error?: string;
|
| 660 |
-
}
|
| 661 |
-
|
| 662 |
-
interface NonStreamResponse {
|
| 663 |
-
id: string;
|
| 664 |
-
object: string;
|
| 665 |
-
created: number;
|
| 666 |
-
model: string;
|
| 667 |
-
choices: Array<{
|
| 668 |
-
message: {
|
| 669 |
-
role: string;
|
| 670 |
-
reasoning_content: string;
|
| 671 |
-
content: string;
|
| 672 |
-
meta: {
|
| 673 |
-
device_id: string;
|
| 674 |
-
conversation_id: string;
|
| 675 |
-
};
|
| 676 |
-
};
|
| 677 |
-
finish_reason: string;
|
| 678 |
-
}>;
|
| 679 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
requirements.txt
DELETED
|
@@ -1,8 +0,0 @@
|
|
| 1 |
-
fastapi
|
| 2 |
-
pydantic
|
| 3 |
-
httpx[http2]
|
| 4 |
-
uvicorn
|
| 5 |
-
uuid
|
| 6 |
-
python-dotenv
|
| 7 |
-
fake-useragent
|
| 8 |
-
httpx[socks]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
version.py
DELETED
|
@@ -1 +0,0 @@
|
|
| 1 |
-
VERSION = '2025.05.10.0001'
|
|
|
|
|
|