hzruo commited on
Commit
1e4bd6e
·
verified ·
1 Parent(s): ddf4eb6

Update main.py

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