hzruo commited on
Commit
9153ff1
·
verified ·
1 Parent(s): a833778

Update Dockerfile

Browse files
Files changed (1) hide show
  1. Dockerfile +726 -55
Dockerfile CHANGED
@@ -1,55 +1,726 @@
1
- FROM python:3.11-slim
2
-
3
- WORKDIR /app
4
-
5
- # Install system dependencies
6
- RUN apt-get update && apt-get install -y \
7
- build-essential \
8
- curl \
9
- libcurl4-openssl-dev \
10
- libssl-dev \
11
- # Chrome dependencies
12
- wget \
13
- gnupg \
14
- fonts-liberation \
15
- libasound2 \
16
- libatk-bridge2.0-0 \
17
- libatk1.0-0 \
18
- libatspi2.0-0 \
19
- libcups2 \
20
- libdbus-1-3 \
21
- libdrm2 \
22
- libgbm1 \
23
- libgtk-3-0 \
24
- libnspr4 \
25
- libnss3 \
26
- libxcomposite1 \
27
- libxdamage1 \
28
- libxfixes3 \
29
- libxkbcommon0 \
30
- libxrandr2 \
31
- xdg-utils \
32
- # Install Chrome
33
- && wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \
34
- && echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list \
35
- && apt-get update \
36
- && apt-get install -y google-chrome-stable \
37
- && rm -rf /var/lib/apt/lists/*
38
-
39
- # Copy requirements first to leverage Docker cache
40
- COPY requirements.txt .
41
- RUN pip install --no-cache-dir -r requirements.txt
42
-
43
- # Copy the rest of the application
44
- COPY . .
45
-
46
- # Expose the port the app runs on
47
- EXPOSE 7860
48
-
49
- # Environment variables with defaults
50
- ENV OPENAI_API_KEY=None
51
- ENV ENVIRONMENT="production"
52
- ENV PORT=7860
53
-
54
- # Command to run the application
55
- CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "7860"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, Request, Depends, HTTPException
2
+ from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
3
+ from fastapi.responses import StreamingResponse, HTMLResponse
4
+ from fastapi.background import BackgroundTasks
5
+ from contextlib import asynccontextmanager
6
+ import requests
7
+ from curl_cffi import requests as cffi_requests
8
+ import uuid
9
+ import json
10
+ import time
11
+ from typing import Optional
12
+ import asyncio
13
+ import base64
14
+ import tempfile
15
+ import os
16
+ import re
17
+ import threading
18
+ from DrissionPage import ChromiumPage, ChromiumOptions
19
+ import logging
20
+ from dotenv import load_dotenv
21
+
22
+ # 加载环境变量
23
+ load_dotenv(override=True)
24
+
25
+ # 配置日志
26
+ logging.basicConfig(
27
+ level=logging.INFO, # 改为 INFO 级别
28
+ format='%(asctime)s - %(levelname)s - %(message)s'
29
+ )
30
+ logger = logging.getLogger(__name__)
31
+
32
+ # 修改全局数据存储
33
+ global_data = {
34
+ "cookie": None,
35
+ "cookies": None,
36
+ "last_update": 0,
37
+ "cookie_expires": 0 # 添加 cookie 过期时间
38
+ }
39
+
40
+ @asynccontextmanager
41
+ async def lifespan(app: FastAPI):
42
+ # 启动时获取 cookie
43
+ threading.Thread(target=get_cookie).start()
44
+ yield
45
+ # 关闭时清理资源
46
+ global_data["cookie"] = None
47
+ global_data["cookies"] = None
48
+ global_data["last_update"] = 0
49
+
50
+ app = FastAPI(lifespan=lifespan)
51
+ security = HTTPBearer()
52
+
53
+ # OpenAI API Key 配置,可以通过环境变量覆盖
54
+ OPENAI_API_KEY = os.getenv("OPENAI_API_KEY", None)
55
+ logger.info(f"OPENAI_API_KEY is set: {OPENAI_API_KEY is not None}")
56
+ logger.info(f"OPENAI_API_KEY value: {OPENAI_API_KEY}")
57
+
58
+ def get_cookie():
59
+ try:
60
+ logger.info("Starting cookie retrieval process...")
61
+
62
+ # 创建配置对象
63
+ options = ChromiumOptions()
64
+
65
+ # 设置浏览器路径
66
+ chrome_path = os.getenv('CHROME_PATH', '/usr/bin/google-chrome-stable')
67
+ logger.info(f"Using Chrome path: {chrome_path}")
68
+ options.set_browser_path(chrome_path)
69
+
70
+ # 设置用户数据目录
71
+ user_data_dir = '/tmp/chrome-data'
72
+ logger.info(f"Using user data directory: {user_data_dir}")
73
+ options.set_argument(f'--user-data-dir={user_data_dir}')
74
+
75
+ # 设置无头模式和其他参数
76
+ options.set_argument('--headless=new')
77
+ options.set_argument('--no-sandbox')
78
+ options.set_argument('--disable-dev-shm-usage')
79
+ options.set_argument('--disable-gpu')
80
+ options.set_argument('--disable-software-rasterizer')
81
+ options.set_argument('--disable-extensions')
82
+ options.set_argument('--disable-setuid-sandbox')
83
+ options.set_argument('--no-first-run')
84
+ options.set_argument('--no-zygote')
85
+ options.set_argument('--single-process')
86
+ options.set_argument('--remote-debugging-port=9222')
87
+ options.set_argument('--window-size=1920,1080')
88
+ options.set_argument('--user-agent=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')
89
+
90
+ logger.info("Creating ChromiumPage instance...")
91
+ page = ChromiumPage(addr_or_opts=options, show_log=True)
92
+
93
+ logger.info("Navigating to target website...")
94
+ page.get("https://chat.akash.network/")
95
+
96
+ logger.info("Waiting for page load...")
97
+ time.sleep(10)
98
+
99
+ logger.info("Getting cookies...")
100
+ cookies = page.cookies()
101
+ if not cookies:
102
+ logger.error("No cookies found")
103
+ page.quit()
104
+ return None
105
+
106
+ cookie_dict = {cookie['name']: cookie['value'] for cookie in cookies}
107
+ if 'cf_clearance' not in cookie_dict:
108
+ logger.error("cf_clearance cookie not found")
109
+ page.quit()
110
+ return None
111
+
112
+ cookie_str = '; '.join([f"{cookie['name']}={cookie['value']}" for cookie in cookies])
113
+ global_data["cookie"] = cookie_str
114
+ global_data["last_update"] = time.time()
115
+
116
+ expires = min([cookie.get('expires', float('inf')) for cookie in cookies])
117
+ if expires != float('inf'):
118
+ global_data["cookie_expires"] = expires
119
+ else:
120
+ global_data["cookie_expires"] = time.time() + 3600
121
+
122
+ logger.info("Successfully retrieved cookies")
123
+ page.quit()
124
+ return cookie_str
125
+
126
+ except Exception as e:
127
+ logger.error(f"Error fetching cookie: {str(e)}")
128
+ logger.error(f"Error type: {type(e)}")
129
+ import traceback
130
+ logger.error(f"Traceback: {traceback.format_exc()}")
131
+ return None
132
+
133
+ # 添加刷新 cookie 的函数
134
+ async def refresh_cookie():
135
+ logger.info("Refreshing cookie due to 401 error")
136
+ # 标记 cookie 为过期
137
+ global_data["cookie_expires"] = 0
138
+ # 获取新的 cookie
139
+ return get_cookie()
140
+
141
+ async def check_and_update_cookie(background_tasks: BackgroundTasks):
142
+ # 如果 cookie 不存在或已过期,则更新
143
+ current_time = time.time()
144
+ if not global_data["cookie"] or current_time >= global_data["cookie_expires"]:
145
+ logger.info("Cookie expired or not available, refreshing...")
146
+ background_tasks.add_task(get_cookie)
147
+ else:
148
+ logger.info("Using existing cookie")
149
+
150
+ async def get_api_key(credentials: HTTPAuthorizationCredentials = Depends(security)):
151
+ token = credentials.credentials
152
+ logger.info(f"Received token: {token}")
153
+
154
+ # 如果设置了 OPENAI_API_KEY,则需要验证
155
+ if OPENAI_API_KEY is not None:
156
+ # 去掉 Bearer 前缀后再比较
157
+ clean_token = token.replace("Bearer ", "") if token.startswith("Bearer ") else token
158
+ logger.info(f"Clean token: {clean_token}")
159
+ if clean_token != OPENAI_API_KEY:
160
+ logger.error(f"Token mismatch. Expected: {OPENAI_API_KEY}, Got: {clean_token}")
161
+ raise HTTPException(
162
+ status_code=401,
163
+ detail="Invalid API key"
164
+ )
165
+ logger.info("API key validation passed")
166
+
167
+ return True
168
+
169
+ async def validate_cookie(background_tasks: BackgroundTasks):
170
+ # 检查并更新 cookie(如果需要)
171
+ await check_and_update_cookie(background_tasks)
172
+
173
+ # 等待 cookie 初始化完成
174
+ max_wait = 30 # 最大等待时间(秒)
175
+ start_time = time.time()
176
+ while not global_data["cookie"] and time.time() - start_time < max_wait:
177
+ await asyncio.sleep(1)
178
+ logger.info("Waiting for cookie initialization...")
179
+
180
+ # 检查是否有有效的 cookie
181
+ if not global_data["cookie"]:
182
+ logger.error("Cookie not available after waiting")
183
+ raise HTTPException(
184
+ status_code=503,
185
+ detail="Service temporarily unavailable - Cookie not available"
186
+ )
187
+
188
+ logger.info("Cookie validation passed")
189
+ return global_data["cookie"]
190
+
191
+ async def check_image_status(session: requests.Session, job_id: str, headers: dict) -> Optional[str]:
192
+ """检查图片生成状态并获取生成的图片"""
193
+ max_retries = 30
194
+ for attempt in range(max_retries):
195
+ try:
196
+ print(f"\nAttempt {attempt + 1}/{max_retries} for job {job_id}")
197
+ response = session.get(
198
+ f'https://chat.akash.network/api/image-status?ids={job_id}',
199
+ headers=headers
200
+ )
201
+ print(f"Status response code: {response.status_code}")
202
+ status_data = response.json()
203
+
204
+ if status_data and isinstance(status_data, list) and len(status_data) > 0:
205
+ job_info = status_data[0]
206
+ status = job_info.get('status')
207
+ print(f"Job status: {status}")
208
+
209
+ # 只有当状态为 completed 时才处理结果
210
+ if status == "completed":
211
+ result = job_info.get("result")
212
+ if result and not result.startswith("Failed"):
213
+ print("Got valid result, attempting upload...")
214
+ image_url = await upload_to_xinyew(result, job_id)
215
+ if image_url:
216
+ print(f"Successfully uploaded image: {image_url}")
217
+ return image_url
218
+ print("Image upload failed")
219
+ return None
220
+ print("Invalid result received")
221
+ return None
222
+ elif status == "failed":
223
+ print(f"Job {job_id} failed")
224
+ return None
225
+
226
+ # 如果状态是其他(如 pending),继续等待
227
+ await asyncio.sleep(1)
228
+ continue
229
+
230
+ except Exception as e:
231
+ print(f"Error checking status: {e}")
232
+ return None
233
+
234
+ print(f"Timeout waiting for job {job_id}")
235
+ return None
236
+
237
+ @app.get("/", response_class=HTMLResponse)
238
+ async def health_check():
239
+ """Health check endpoint"""
240
+ status = {
241
+ "status": "ok",
242
+ "version": "1.0.0",
243
+ "cookie_status": {
244
+ "available": global_data["cookie"] is not None,
245
+ "last_update": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(global_data["last_update"])) if global_data["last_update"] > 0 else None,
246
+ "expires": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(global_data["cookie_expires"])) if global_data["cookie_expires"] > 0 else None
247
+ }
248
+ }
249
+
250
+ html = f"""
251
+ <!DOCTYPE html>
252
+ <html>
253
+ <head>
254
+ <title>Akash API Status</title>
255
+ <style>
256
+ body {{
257
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
258
+ line-height: 1.6;
259
+ margin: 0;
260
+ padding: 20px;
261
+ background-color: #f5f5f5;
262
+ }}
263
+ .container {{
264
+ max-width: 800px;
265
+ margin: 0 auto;
266
+ background-color: white;
267
+ padding: 20px;
268
+ border-radius: 8px;
269
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
270
+ }}
271
+ h1 {{
272
+ color: #333;
273
+ margin-top: 0;
274
+ }}
275
+ .status {{
276
+ display: inline-block;
277
+ padding: 4px 8px;
278
+ border-radius: 4px;
279
+ font-weight: bold;
280
+ background-color: #4CAF50;
281
+ color: white;
282
+ }}
283
+ .info {{
284
+ margin-top: 20px;
285
+ }}
286
+ .info-item {{
287
+ margin-bottom: 10px;
288
+ }}
289
+ .label {{
290
+ font-weight: bold;
291
+ color: #666;
292
+ }}
293
+ .value {{
294
+ color: #333;
295
+ }}
296
+ .cookie-status {{
297
+ margin-top: 20px;
298
+ padding: 15px;
299
+ background-color: #f8f9fa;
300
+ border-radius: 4px;
301
+ }}
302
+ .cookie-status .available {{
303
+ color: {"#4CAF50" if status["cookie_status"]["available"] else "#f44336"};
304
+ }}
305
+ </style>
306
+ </head>
307
+ <body>
308
+ <div class="container">
309
+ <h1>Akash API Status <span class="status">{status["status"]}</span></h1>
310
+
311
+ <div class="info">
312
+ <div class="info-item">
313
+ <span class="label">Version:</span>
314
+ <span class="value">{status["version"]}</span>
315
+ </div>
316
+ </div>
317
+
318
+ <div class="cookie-status">
319
+ <h2>Cookie Status</h2>
320
+ <div class="info-item">
321
+ <span class="label">Available:</span>
322
+ <span class="value available">{str(status["cookie_status"]["available"])}</span>
323
+ </div>
324
+ <div class="info-item">
325
+ <span class="label">Last Update:</span>
326
+ <span class="value">{status["cookie_status"]["last_update"] or "Never"}</span>
327
+ </div>
328
+ <div class="info-item">
329
+ <span class="label">Expires:</span>
330
+ <span class="value">{status["cookie_status"]["expires"] or "Unknown"}</span>
331
+ </div>
332
+ </div>
333
+ </div>
334
+ </body>
335
+ </html>
336
+ """
337
+
338
+ return html
339
+
340
+ @app.post("/v1/chat/completions")
341
+ async def chat_completions(
342
+ request: Request,
343
+ background_tasks: BackgroundTasks,
344
+ api_key: bool = Depends(get_api_key),
345
+ cookie: str = Depends(validate_cookie)
346
+ ):
347
+ try:
348
+ data = await request.json()
349
+
350
+ chat_id = str(uuid.uuid4()).replace('-', '')[:16]
351
+
352
+ akash_data = {
353
+ "id": chat_id,
354
+ "messages": data.get('messages', []),
355
+ "model": data.get('model', "DeepSeek-R1"),
356
+ "system": data.get('system_message', "You are a helpful assistant."),
357
+ "temperature": data.get('temperature', 0.6),
358
+ "topP": data.get('top_p', 0.95)
359
+ }
360
+
361
+ # 构建请求头
362
+ headers = {
363
+ "Content-Type": "application/json",
364
+ "User-Agent": "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",
365
+ "Accept": "*/*",
366
+ "Accept-Language": "en-US,en;q=0.9",
367
+ "Accept-Encoding": "gzip, deflate, br",
368
+ "Origin": "https://chat.akash.network",
369
+ "Referer": "https://chat.akash.network/",
370
+ "Sec-Fetch-Dest": "empty",
371
+ "Sec-Fetch-Mode": "cors",
372
+ "Sec-Fetch-Site": "same-origin",
373
+ "Connection": "keep-alive"
374
+ }
375
+
376
+ # 设置 Cookie
377
+ headers["Cookie"] = cookie
378
+
379
+ with requests.Session() as session:
380
+ response = session.post(
381
+ 'https://chat.akash.network/api/chat',
382
+ json=akash_data,
383
+ headers=headers,
384
+ stream=True
385
+ )
386
+
387
+ # 检查响应状态码,如果是 401,尝试刷新 cookie 并重试
388
+ if response.status_code == 401:
389
+ logger.info("Cookie expired, refreshing...")
390
+ new_cookie = await refresh_cookie()
391
+ if new_cookie:
392
+ headers["Cookie"] = new_cookie
393
+ response = session.post(
394
+ 'https://chat.akash.network/api/chat',
395
+ json=akash_data,
396
+ headers=headers,
397
+ stream=True
398
+ )
399
+
400
+ if response.status_code != 200:
401
+ logger.error(f"Akash API error: {response.text}")
402
+ raise HTTPException(
403
+ status_code=response.status_code,
404
+ detail=f"Akash API error: {response.text}"
405
+ )
406
+
407
+ def generate():
408
+ content_buffer = ""
409
+ for line in response.iter_lines():
410
+ if not line:
411
+ continue
412
+
413
+ try:
414
+ line_str = line.decode('utf-8')
415
+ msg_type, msg_data = line_str.split(':', 1)
416
+
417
+ if msg_type == '0':
418
+ if msg_data.startswith('"') and msg_data.endswith('"'):
419
+ msg_data = msg_data.replace('\\"', '"')
420
+ msg_data = msg_data[1:-1]
421
+ msg_data = msg_data.replace("\\n", "\n")
422
+
423
+ # 在处理消息时先判断模型类型
424
+ if data.get('model') == 'AkashGen' and "<image_generation>" in msg_data:
425
+ # 图片生成模型的特殊处理
426
+ async def process_and_send():
427
+ messages = await process_image_generation(msg_data, session, headers, chat_id)
428
+ if messages:
429
+ return messages
430
+ return None
431
+
432
+ # 创建新的事件循环
433
+ loop = asyncio.new_event_loop()
434
+ asyncio.set_event_loop(loop)
435
+ try:
436
+ result_messages = loop.run_until_complete(process_and_send())
437
+ finally:
438
+ loop.close()
439
+
440
+ if result_messages:
441
+ for message in result_messages:
442
+ yield f"data: {json.dumps(message)}\n\n"
443
+ continue
444
+
445
+ content_buffer += msg_data
446
+
447
+ chunk = {
448
+ "id": f"chatcmpl-{chat_id}",
449
+ "object": "chat.completion.chunk",
450
+ "created": int(time.time()),
451
+ "model": data.get('model'),
452
+ "choices": [{
453
+ "delta": {"content": msg_data},
454
+ "index": 0,
455
+ "finish_reason": None
456
+ }]
457
+ }
458
+ yield f"data: {json.dumps(chunk)}\n\n"
459
+
460
+ elif msg_type in ['e', 'd']:
461
+ chunk = {
462
+ "id": f"chatcmpl-{chat_id}",
463
+ "object": "chat.completion.chunk",
464
+ "created": int(time.time()),
465
+ "model": data.get('model'),
466
+ "choices": [{
467
+ "delta": {},
468
+ "index": 0,
469
+ "finish_reason": "stop"
470
+ }]
471
+ }
472
+ yield f"data: {json.dumps(chunk)}\n\n"
473
+ yield "data: [DONE]\n\n"
474
+ break
475
+
476
+ except Exception as e:
477
+ print(f"Error processing line: {e}")
478
+ continue
479
+
480
+ return StreamingResponse(
481
+ generate(),
482
+ media_type='text/event-stream',
483
+ headers={
484
+ 'Cache-Control': 'no-cache',
485
+ 'Connection': 'keep-alive',
486
+ 'Content-Type': 'text/event-stream'
487
+ }
488
+ )
489
+
490
+ except Exception as e:
491
+ print(f"Error in chat_completions: {e}")
492
+ import traceback
493
+ print(traceback.format_exc())
494
+ return {"error": str(e)}
495
+
496
+ @app.get("/v1/models")
497
+ async def list_models(
498
+ background_tasks: BackgroundTasks,
499
+ cookie: str = Depends(validate_cookie)
500
+ ):
501
+ try:
502
+ headers = {
503
+ "accept": "application/json",
504
+ "accept-language": "en-US,en;q=0.9",
505
+ "sec-ch-ua": '"Not_A Brand";v="8", "Chromium";v="120", "Google Chrome";v="120"',
506
+ "sec-ch-ua-mobile": "?0",
507
+ "sec-ch-ua-platform": '"macOS"',
508
+ "sec-fetch-dest": "document",
509
+ "sec-fetch-mode": "cors",
510
+ "sec-fetch-site": "same-origin",
511
+ "user-agent": "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",
512
+ "referer": "https://chat.akash.network/"
513
+ }
514
+
515
+ # 设置 Cookie
516
+ headers["Cookie"] = cookie
517
+
518
+ print(f"Using cookie: {headers.get('Cookie', 'None')}")
519
+ print("Sending request to get models...")
520
+
521
+ response = requests.get(
522
+ 'https://chat.akash.network/api/models',
523
+ headers=headers
524
+ )
525
+
526
+ print(f"Models response status: {response.status_code}")
527
+ print(f"Models response headers: {response.headers}")
528
+
529
+ if response.status_code == 401:
530
+ print("Authentication failed. Please check your API key.")
531
+ return {"error": "Authentication failed. Please check your API key."}
532
+
533
+ akash_response = response.json()
534
+
535
+ # 添加错误处理和调试信息
536
+ print(f"Akash API response: {akash_response}")
537
+
538
+ # 检查响应格式并适配
539
+ models_list = []
540
+ if isinstance(akash_response, list):
541
+ # 如果直接是列表
542
+ models_list = akash_response
543
+ elif isinstance(akash_response, dict):
544
+ # 如果是字典格式
545
+ models_list = akash_response.get("models", [])
546
+ else:
547
+ print(f"Unexpected response format: {type(akash_response)}")
548
+ models_list = []
549
+
550
+ # 转换为标准 OpenAI 格式
551
+ openai_models = {
552
+ "object": "list",
553
+ "data": [
554
+ {
555
+ "id": model["id"] if isinstance(model, dict) else model,
556
+ "object": "model",
557
+ "created": int(time.time()),
558
+ "owned_by": "akash",
559
+ "permission": [{
560
+ "id": f"modelperm-{model['id'] if isinstance(model, dict) else model}",
561
+ "object": "model_permission",
562
+ "created": int(time.time()),
563
+ "allow_create_engine": False,
564
+ "allow_sampling": True,
565
+ "allow_logprobs": True,
566
+ "allow_search_indices": False,
567
+ "allow_view": True,
568
+ "allow_fine_tuning": False,
569
+ "organization": "*",
570
+ "group": None,
571
+ "is_blocking": False
572
+ }]
573
+ } for model in models_list
574
+ ]
575
+ }
576
+
577
+ return openai_models
578
+
579
+ except Exception as e:
580
+ print(f"Error in list_models: {e}")
581
+ import traceback
582
+ print(traceback.format_exc())
583
+ return {"error": str(e)}
584
+
585
+ async def process_image_generation(msg_data: str, session: requests.Session, headers: dict, chat_id: str) -> Optional[list]:
586
+ """处理图片生成的逻辑,返回多个消息块"""
587
+ match = re.search(r"jobId='([^']+)' prompt='([^']+)' negative='([^']*)'", msg_data)
588
+ if match:
589
+ job_id, prompt, negative = match.groups()
590
+ print(f"Starting image generation process for job_id: {job_id}")
591
+
592
+ # 记录开始时间
593
+ start_time = time.time()
594
+
595
+ # 发送思考开始的消息
596
+ think_msg = "<think>\n"
597
+ think_msg += "🎨 Generating image...\n\n"
598
+ think_msg += f"Prompt: {prompt}\n"
599
+
600
+ # 检查图片状态和上传
601
+ result = await check_image_status(session, job_id, headers)
602
+
603
+ # 计算实际花费的时间
604
+ elapsed_time = time.time() - start_time
605
+
606
+ # 完成思考部分
607
+ think_msg += f"\n🤔 Thinking for {elapsed_time:.1f}s...\n"
608
+ think_msg += "</think>"
609
+
610
+ # 返回两个独立的消息块
611
+ messages = []
612
+
613
+ # 第一个消息块:思考过程
614
+ messages.append({
615
+ "id": f"chatcmpl-{chat_id}-think",
616
+ "object": "chat.completion.chunk",
617
+ "created": int(time.time()),
618
+ "model": "AkashGen",
619
+ "choices": [{
620
+ "delta": {"content": think_msg},
621
+ "index": 0,
622
+ "finish_reason": None
623
+ }]
624
+ })
625
+
626
+ # 第二个消息块:图片结果
627
+ if result:
628
+ image_msg = f"\n\n![Generated Image]({result})"
629
+ messages.append({
630
+ "id": f"chatcmpl-{chat_id}-image",
631
+ "object": "chat.completion.chunk",
632
+ "created": int(time.time()),
633
+ "model": "AkashGen",
634
+ "choices": [{
635
+ "delta": {"content": image_msg},
636
+ "index": 0,
637
+ "finish_reason": None
638
+ }]
639
+ })
640
+ else:
641
+ fail_msg = "\n\n*Image generation or upload failed.*"
642
+ messages.append({
643
+ "id": f"chatcmpl-{chat_id}-fail",
644
+ "object": "chat.completion.chunk",
645
+ "created": int(time.time()),
646
+ "model": "AkashGen",
647
+ "choices": [{
648
+ "delta": {"content": fail_msg},
649
+ "index": 0,
650
+ "finish_reason": None
651
+ }]
652
+ })
653
+
654
+ return messages
655
+ return None
656
+
657
+ async def upload_to_xinyew(image_base64: str, job_id: str) -> Optional[str]:
658
+ """上传图片到新野图床并返回URL"""
659
+ try:
660
+ print(f"\n=== Starting image upload for job {job_id} ===")
661
+ print(f"Base64 data length: {len(image_base64)}")
662
+
663
+ # 解码base64图片数据
664
+ try:
665
+ image_data = base64.b64decode(image_base64.split(',')[1] if ',' in image_base64 else image_base64)
666
+ print(f"Decoded image data length: {len(image_data)} bytes")
667
+ except Exception as e:
668
+ print(f"Error decoding base64: {e}")
669
+ print(f"First 100 chars of base64: {image_base64[:100]}...")
670
+ return None
671
+
672
+ # 创建临时文件
673
+ with tempfile.NamedTemporaryFile(suffix='.jpeg', delete=False) as temp_file:
674
+ temp_file.write(image_data)
675
+ temp_file_path = temp_file.name
676
+
677
+ try:
678
+ filename = f"{job_id}.jpeg"
679
+ print(f"Using filename: {filename}")
680
+
681
+ # 准备文件上传
682
+ files = {
683
+ 'file': (filename, open(temp_file_path, 'rb'), 'image/jpeg')
684
+ }
685
+
686
+ print("Sending request to xinyew.cn...")
687
+ response = requests.post(
688
+ 'https://api.xinyew.cn/api/jdtc',
689
+ files=files,
690
+ timeout=30
691
+ )
692
+
693
+ print(f"Upload response status: {response.status_code}")
694
+ if response.status_code == 200:
695
+ result = response.json()
696
+ print(f"Upload response: {result}")
697
+
698
+ if result.get('errno') == 0:
699
+ url = result.get('data', {}).get('url')
700
+ if url:
701
+ print(f"Successfully got image URL: {url}")
702
+ return url
703
+ print("No URL in response data")
704
+ else:
705
+ print(f"Upload failed: {result.get('message')}")
706
+ else:
707
+ print(f"Upload failed with status {response.status_code}")
708
+ print(f"Response content: {response.text}")
709
+ return None
710
+
711
+ finally:
712
+ # 清理临时文件
713
+ try:
714
+ os.unlink(temp_file_path)
715
+ except Exception as e:
716
+ print(f"Error removing temp file: {e}")
717
+
718
+ except Exception as e:
719
+ print(f"Error in upload_to_xinyew: {e}")
720
+ import traceback
721
+ print(traceback.format_exc())
722
+ return None
723
+
724
+ if __name__ == '__main__':
725
+ import uvicorn
726
+ uvicorn.run(app, host='0.0.0.0', port=9000)