Moonfanz commited on
Commit
c3009b9
·
verified ·
1 Parent(s): 0d399a4

Upload 6 files

Browse files
Files changed (5) hide show
  1. app.py +208 -275
  2. func.py +66 -49
  3. requirements.txt +2 -1
  4. templates/login.html +20 -0
  5. templates/manage.html +33 -0
app.py CHANGED
@@ -1,335 +1,272 @@
1
- from flask import Flask, request, jsonify, Response, stream_with_context, render_template_string
2
- from google.generativeai.types import generation_types
3
- from google.api_core.exceptions import InvalidArgument, ResourceExhausted, ServiceUnavailable, InternalServerError, Aborted
4
  import google.generativeai as genai
5
  import json
 
6
  import os
 
7
  import logging
8
  import func
9
- from datetime import datetime, timedelta
10
- from apscheduler.schedulers.background import BackgroundScheduler
11
- import time
12
  import requests
13
- from collections import deque
14
-
15
 
16
  os.environ['TZ'] = 'Asia/Shanghai'
17
-
18
  app = Flask(__name__)
19
-
 
20
  app.secret_key = os.urandom(24)
21
 
 
22
 
23
  formatter = logging.Formatter('%(message)s')
 
 
24
  logger = logging.getLogger(__name__)
25
- logger.setLevel(logging.INFO)
 
26
  handler = logging.StreamHandler()
27
  handler.setFormatter(formatter)
28
- logger.addHandler(handler)
29
-
30
- MAX_RETRIES = int(os.environ.get('MaxRetries', 3))
31
- MAX_REQUESTS = int(os.environ.get('MaxRequests', 2))
32
- LIMIT_WINDOW = int(os.environ.get('LimitWindow', 60))
33
- RETRY_DELAY = 1
34
- MAX_RETRY_DELAY = 16
35
-
36
- request_counts = {}
37
-
38
- api_key_blacklist = set()
39
- api_key_blacklist_duration = 60
40
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
  class APIKeyManager:
42
  def __init__(self):
43
- self.api_keys = os.environ.get('KeyArray').split(',')
44
- self.current_index = 0
45
-
 
46
  def get_available_key(self):
47
- num_keys = len(self.api_keys)
48
- for _ in range(num_keys):
49
- if self.current_index >= num_keys:
50
- self.current_index = 0
51
- current_key = self.api_keys[self.current_index]
52
- self.current_index += 1
53
-
54
- if current_key not in api_key_blacklist:
55
- return current_key
56
-
57
- logger.error("所有API key都已耗尽或被暂时禁用,请重新配置或稍后重试")
58
- return None
59
-
60
- def show_all_keys(self):
61
- logger.info(f"当前可用API key个数: {len(self.api_keys)} ")
62
- for i, api_key in enumerate(self.api_keys):
63
- logger.info(f"API Key{i}: {api_key[:11]}...")
64
-
65
- def blacklist_key(self, key):
66
- logger.warning(f"{key[:11]} → 暂时禁用 {api_key_blacklist_duration} 秒")
67
- api_key_blacklist.add(key)
68
-
69
- scheduler.add_job(lambda: api_key_blacklist.discard(key), 'date', run_date=datetime.now() + timedelta(seconds=api_key_blacklist_duration))
70
 
71
  key_manager = APIKeyManager()
72
- key_manager.show_all_keys()
73
  current_api_key = key_manager.get_available_key()
74
-
75
- def switch_api_key():
76
- global current_api_key
77
- key = key_manager.get_available_key()
78
- if key:
79
- current_api_key = key
80
- logger.info(f"API key 替换为 → {current_api_key[:11]}...")
81
- else:
82
- logger.error("API key 替换失败,所有API key都已耗尽或被暂时禁用,请重新配置或稍后重试")
83
-
84
- logger.info(f"当前 API key: {current_api_key[:11]}...")
85
-
86
  GEMINI_MODELS = [
 
 
 
 
87
  {"id": "gemini-1.5-pro-002"},
88
- {"id": "gemini-1.5-pro-latest"},
89
- {"id": "gemini-1.5-pro-exp-0827"},
90
- {"id": "learnlm-1.5-pro-experimental"},
91
  {"id": "gemini-exp-1114"},
92
  {"id": "gemini-exp-1121"},
93
  {"id": "gemini-exp-1206"},
94
  {"id": "gemini-2.0-flash-exp"},
95
  {"id": "gemini-2.0-flash-thinking-exp-1219"},
96
- {"id": "gemini-2.0-pro-exp"}
97
  ]
98
 
99
- @app.route('/')
100
- def index():
101
- main_content = "Moonfanz Reminiproxy"
102
- html_template = """
103
- <!DOCTYPE html>
104
- <html>
105
- <head>
106
- <meta charset="utf-8">
107
- <script>
108
- function copyToClipboard(text) {
109
- var textarea = document.createElement("textarea");
110
- textarea.textContent = text;
111
- textarea.style.position = "fixed";
112
- document.body.appendChild(textarea);
113
- textarea.select();
114
- try {
115
- return document.execCommand("copy");
116
- } catch (ex) {
117
- console.warn("Copy to clipboard failed.", ex);
118
- return false;
119
- } finally {
120
- document.body.removeChild(textarea);
121
- }
122
- }
123
- function copyLink(event) {
124
- event.preventDefault();
125
- const url = new URL(window.location.href);
126
- const link = url.protocol + '//' + url.host + '/hf/v1';
127
- copyToClipboard(link);
128
- alert('链接已复制: ' + link);
129
- }
130
- </script>
131
- </head>
132
- <body>
133
- {{ main_content }}<br/><br/>完全开源、免费且禁止商用<br/><br/>点击复制反向代理: <a href="v1" onclick="copyLink(event)">Copy Link</a><br/>聊天来源选择"自定义(兼容 OpenAI)"<br/>将复制的网址填入到自定义端点<br/>将设置password填入自定义API秘钥<br/><br/><br/>
134
- </body>
135
- </html>
136
- """
137
- return render_template_string(html_template, main_content=main_content)
138
-
139
- def is_within_rate_limit(api_key):
140
- now = datetime.now()
141
- if api_key not in request_counts:
142
- request_counts[api_key] = deque()
143
-
144
- while request_counts[api_key] and request_counts[api_key][0] < now - timedelta(seconds=LIMIT_WINDOW):
145
- request_counts[api_key].popleft()
146
-
147
- if len(request_counts[api_key]) >= MAX_REQUESTS:
148
- earliest_request_time = request_counts[api_key][0]
149
- wait_time = (earliest_request_time + timedelta(seconds=LIMIT_WINDOW)) - now
150
- return False, wait_time.total_seconds()
151
- else:
152
- return True, 0
153
-
154
- def increment_request_count(api_key):
155
- now = datetime.now()
156
- if api_key not in request_counts:
157
- request_counts[api_key] = deque()
158
- request_counts[api_key].append(now)
159
-
160
- def handle_api_error(error, attempt, stream=False):
161
- if attempt > MAX_RETRIES:
162
- logger.error(f"{MAX_RETRIES} 次尝试后仍然失败,请修改预设或输入")
163
- return False, jsonify({
164
- 'error': {
165
- 'message': f"{MAX_RETRIES} 次尝试后仍然失败,请修改预设或输入",
166
- 'type': 'max_retries_exceeded'
167
- }
168
- })
169
-
170
- if isinstance(error, InvalidArgument):
171
- logger.error(f"{current_api_key[:11]} → 无效,可能已过期或被删除")
172
- key_manager.blacklist_key(current_api_key)
173
- switch_api_key()
174
- return False, None
175
-
176
- elif isinstance(error, (ResourceExhausted, Aborted, InternalServerError, ServiceUnavailable)):
177
- delay = min(RETRY_DELAY * (2 ** attempt), MAX_RETRY_DELAY)
178
- if isinstance(error, ResourceExhausted):
179
- logger.warning(f"{current_api_key[:11]} → 429 官方资源耗尽 → {delay} 秒后重试...")
180
  else:
181
- logger.warning(f"{current_api_key[:11]} → 未知错误↙ {delay} 秒后重试...\n{type(error).__name__}\n")
182
- time.sleep(delay)
183
- if isinstance(error, (ResourceExhausted)):
184
- key_manager.blacklist_key(current_api_key)
185
- switch_api_key()
186
- return False, None
187
-
188
- elif isinstance(error, generation_types.StopCandidateException):
189
- logger.warning(f"AI输出内容被Gemini官方阻挡,代理没有得到有效回复")
190
- switch_api_key()
191
- return False, None
192
-
193
- else:
194
- logger.error(f"未知错误↙\n {error}")
195
- return False, None
 
 
 
 
 
 
 
 
 
 
196
 
197
  @app.route('/hf/v1/chat/completions', methods=['POST'])
198
  def chat_completions():
 
199
  is_authenticated, auth_error, status_code = func.authenticate_request(request)
200
  if not is_authenticated:
201
- return auth_error if auth_error else jsonify({'error': '未授权'}), status_code if status_code else 401
202
-
203
- request_data = request.get_json()
204
- messages = request_data.get('messages', [])
205
- model = request_data.get('model', 'gemini-2.0-flash-exp')
206
- temperature = request_data.get('temperature', 1)
207
- max_tokens = request_data.get('max_tokens', 8192)
208
- stream = request_data.get('stream', False)
209
-
210
- logger.info(f"\n{model} [r] → {current_api_key[:11]}...")
211
-
212
- gemini_history, user_message, error_response = func.process_messages_for_gemini(messages)
213
-
214
- if error_response:
215
- logger.error(f"处理输入消息时出错↙\n {error_response}")
216
- return jsonify(error_response), 400
217
-
218
- def do_request(current_api_key, attempt):
219
- isok, time = is_within_rate_limit(current_api_key)
220
- if not isok:
221
- logger.warning(f"{current_api_key[:11]} → 暂时超过限额,该API key将在 {time} 秒后启用...")
222
- switch_api_key()
223
- return False, None
224
-
225
- increment_request_count(current_api_key)
 
 
 
226
 
227
- gen_model = func.get_gen_model(current_api_key, model, temperature, max_tokens)
 
 
 
 
228
 
229
- try:
 
 
230
  if gemini_history:
231
  chat_session = gen_model.start_chat(history=gemini_history)
232
- response = chat_session.send_message(user_message, stream=stream)
233
  else:
234
- response = gen_model.generate_content(user_message, stream=stream)
235
- return True, response
236
- except Exception as e:
237
- return handle_api_error(e, attempt, stream)
238
-
239
- def generate(response):
240
- try:
241
- for chunk in response:
242
- if chunk.text:
 
 
 
 
 
 
 
 
 
 
 
243
  data = {
244
  'choices': [
245
  {
246
- 'delta': {
247
- 'content': chunk.text
248
- },
249
- 'finish_reason': None,
250
  'index': 0
251
  }
252
  ],
253
  'object': 'chat.completion.chunk'
254
  }
 
 
 
 
 
 
 
 
 
 
 
255
  yield f"data: {json.dumps(data)}\n\n"
256
 
257
- yield "data: [DONE]\n\n"
258
- logger.info(f"200!")
259
-
260
- except Exception as e:
261
- switch_api_key()
262
- logger.error(f"流式输出时截断,请关闭流式输出或修改你的输入")
263
- error_data = {
264
- 'error': {
265
- 'message': '流式输出时截断,请关闭流式输出或修改你的输入',
266
- 'type': 'internal_server_error'
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
267
  }
268
  }
269
- yield f"data: {json.dumps(error_data)}\n\n"
270
- yield "data: [DONE]\n\n"
271
 
272
- attempt = 0
273
- success = False
274
- response = None
275
-
276
- while attempt < MAX_RETRIES and not success:
277
- attempt += 1
278
- logger.info(f"第 {attempt}/{MAX_RETRIES} 次尝试 ...")
279
- success, response = do_request(current_api_key, attempt)
280
-
281
- if not success:
282
- logger.error(f" {MAX_RETRIES} 次尝试均失败,请调整配置或向Moonfanz反馈")
283
- response = {
284
  'error': {
285
- 'message': f' {MAX_RETRIES} 次尝试均失败,请调整配置或向Moonfanz反馈',
286
- 'type': 'internal_server_error'
287
- }
288
- }
289
- return jsonify(response), 500 if response is not None else 503
290
-
291
- if stream:
292
- return Response(stream_with_context(generate(response)), mimetype='text/event-stream')
293
- else:
294
- try:
295
- text_content = response.text
296
- except (AttributeError, IndexError, TypeError) as e:
297
- logger.error(f"处理AI返回消息时出错↙\n{e}")
298
- return jsonify({
299
- 'error': {
300
- 'message': '处理AI返回消息时出错',
301
- 'type': 'response_processing_error'
302
- }
303
- }), 500
304
-
305
- response_data = {
306
- 'id': 'chatcmpl-xxxxxxxxxxxx',
307
- 'object': 'chat.completion',
308
- 'created': int(datetime.now().timestamp()),
309
- 'model': model,
310
- 'choices': [{
311
- 'index': 0,
312
- 'message': {
313
- 'role': 'assistant',
314
- 'content': text_content
315
- },
316
- 'finish_reason': 'stop'
317
- }],
318
- 'usage': {
319
- 'prompt_tokens': 0,
320
- 'completion_tokens': 0,
321
- 'total_tokens': 0
322
  }
323
- }
324
- logger.info(f"200!")
325
- return jsonify(response_data)
 
326
 
327
  @app.route('/hf/v1/models', methods=['GET'])
328
  def list_models():
 
 
 
329
  response = {"object": "list", "data": GEMINI_MODELS}
330
  return jsonify(response)
331
 
332
  def keep_alive():
 
333
  try:
334
  response = requests.get("http://127.0.0.1:7860/", timeout=10)
335
  response.raise_for_status()
@@ -339,12 +276,8 @@ def keep_alive():
339
 
340
  if __name__ == '__main__':
341
  scheduler = BackgroundScheduler()
342
-
343
- scheduler.add_job(keep_alive, 'interval', hours=12)
 
344
  scheduler.start()
345
-
346
- logger.info(f"最大尝试次数/MaxRetries: {MAX_RETRIES}")
347
- logger.info(f"最大请求次数/MaxRequests: {MAX_REQUESTS}")
348
- logger.info(f"请求限额窗口/LimitWindow: {LIMIT_WINDOW} 秒")
349
-
350
- app.run(debug=True, host='0.0.0.0', port=int(os.environ.get('PORT', 7860)))
 
1
+
2
+ from flask import Flask, request, jsonify, Response, stream_with_context, render_template, session, redirect, url_for
3
+ from apscheduler.schedulers.background import BackgroundScheduler
4
  import google.generativeai as genai
5
  import json
6
+ from datetime import datetime
7
  import os
8
+ from termcolor import colored
9
  import logging
10
  import func
 
 
 
11
  import requests
12
+ import time
 
13
 
14
  os.environ['TZ'] = 'Asia/Shanghai'
 
15
  app = Flask(__name__)
16
+ if not os.environ.get('TERM'):
17
+ os.environ['TERM'] = 'xterm'
18
  app.secret_key = os.urandom(24)
19
 
20
+ ProxyPasswords = os.environ.get('ProxyPasswords').split(',')
21
 
22
  formatter = logging.Formatter('%(message)s')
23
+ ADMIN_PASSWORD = os.environ.get('AdminPassword')
24
+
25
  logger = logging.getLogger(__name__)
26
+ logger.setLevel(logging.INFO)
27
+
28
  handler = logging.StreamHandler()
29
  handler.setFormatter(formatter)
 
 
 
 
 
 
 
 
 
 
 
 
30
 
31
+ logger.addHandler(handler)
32
+
33
+ safety_settings = [
34
+ {
35
+ "category": "HARM_CATEGORY_HARASSMENT",
36
+ "threshold": "BLOCK_NONE"
37
+ },
38
+ {
39
+ "category": "HARM_CATEGORY_HATE_SPEECH",
40
+ "threshold": "BLOCK_NONE"
41
+ },
42
+ {
43
+ "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",
44
+ "threshold": "BLOCK_NONE"
45
+ },
46
+ {
47
+ "category": "HARM_CATEGORY_DANGEROUS_CONTENT",
48
+ "threshold": "BLOCK_NONE"
49
+ },
50
+ ]
51
  class APIKeyManager:
52
  def __init__(self):
53
+ self.api_keys = os.environ.get('API_KEYS').split(',')
54
+ self.current_index = 0
55
+
56
+
57
  def get_available_key(self):
58
+ if self.current_index >= len(self.api_keys):
59
+ self.current_index = 0
60
+ current_key = self.api_keys[self.current_index]
61
+ self.current_index += 1
62
+ return current_key
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
 
64
  key_manager = APIKeyManager()
 
65
  current_api_key = key_manager.get_available_key()
66
+ logger.info(f"Current API key: {current_api_key[:11]}...")
67
+ genai.configure(api_key=current_api_key)
68
+ models = genai.list_models()
69
+ logger.info("Available models:")
70
+ for model in models:
71
+ logger.info(f"- {model.name}: {model.supported_generation_methods}")
 
 
 
 
 
 
72
  GEMINI_MODELS = [
73
+ {"id": "gemini-pro"},
74
+ {"id": "gemini-pro-vision"},
75
+ {"id": "gemini-1.0-pro"},
76
+ {"id": "gemini-1.0-pro-vision"},
77
  {"id": "gemini-1.5-pro-002"},
 
 
 
78
  {"id": "gemini-exp-1114"},
79
  {"id": "gemini-exp-1121"},
80
  {"id": "gemini-exp-1206"},
81
  {"id": "gemini-2.0-flash-exp"},
82
  {"id": "gemini-2.0-flash-thinking-exp-1219"},
83
+ {"id": "gemini-2.0-pro-exp"},
84
  ]
85
 
86
+ @app.route("/", methods=["GET", "POST"])
87
+ def login():
88
+ if request.method == "POST":
89
+ password = request.form.get("password")
90
+ if password == ADMIN_PASSWORD:
91
+ session["logged_in"] = True
92
+ return redirect(url_for("manage_keys"))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
93
  else:
94
+ return render_template("login.html", error="Incorrect password")
95
+ return render_template("login.html", error=None)
96
+
97
+ @app.route("/manage", methods=["GET", "POST"])
98
+ def manage_keys():
99
+ if not session.get("logged_in"):
100
+ return redirect(url_for("login"))
101
+
102
+ if request.method == "POST":
103
+ action = request.form.get("action")
104
+ if action == "add":
105
+ new_key = request.form.get("new_key")
106
+ if new_key:
107
+ ProxyPasswords.append(new_key)
108
+ elif action == "delete":
109
+ key_to_delete = request.form.get("key_to_delete")
110
+ if key_to_delete in ProxyPasswords:
111
+ ProxyPasswords.remove(key_to_delete)
112
+
113
+ return render_template("manage.html", keys=ProxyPasswords)
114
+
115
+ @app.route("/logout")
116
+ def logout():
117
+ session.pop("logged_in", None)
118
+ return redirect(url_for("login"))
119
 
120
  @app.route('/hf/v1/chat/completions', methods=['POST'])
121
  def chat_completions():
122
+ global current_api_key
123
  is_authenticated, auth_error, status_code = func.authenticate_request(request)
124
  if not is_authenticated:
125
+ return auth_error if auth_error else jsonify({'error': 'Unauthorized'}), status_code if status_code else 401
126
+ try:
127
+ request_data = request.get_json()
128
+ r_data = func.sanitize_request_data(request_data)
129
+ r_data = json.dumps(r_data, indent=4, ensure_ascii=False).replace('\\n', '\n')
130
+ os.system('cls' if os.name == 'nt' else 'clear')
131
+ logger.info(r_data)
132
+ messages = request_data.get('messages', [])
133
+ model = request_data.get('model', 'gemini-exp-1206')
134
+ temperature = request_data.get('temperature', 1)
135
+ max_tokens = request_data.get('max_tokens', 8192)
136
+ stream = request_data.get('stream', False)
137
+
138
+ logger.info(colored(f"\n{model} [r] -> {current_api_key[:11]}...", 'yellow'))
139
+
140
+ # OpenAI 格式的消息转换为 Gemini 格式
141
+ gemini_history, user_message, error_response = func.process_messages_for_gemini(messages)
142
+
143
+ if error_response:
144
+ # 处理错误
145
+ print(error_response)
146
+
147
+ genai.configure(api_key=current_api_key)
148
+
149
+ generation_config = {
150
+ "temperature": temperature,
151
+ "max_output_tokens": max_tokens
152
+ }
153
 
154
+ gen_model = genai.GenerativeModel(
155
+ model_name=model,
156
+ generation_config=generation_config,
157
+ safety_settings=safety_settings
158
+ )
159
 
160
+
161
+ if stream:
162
+ # 流式响应
163
  if gemini_history:
164
  chat_session = gen_model.start_chat(history=gemini_history)
165
+ response = chat_session.send_message(user_message, stream=True)
166
  else:
167
+ response = gen_model.generate_content(user_message, stream=True)
168
+
169
+ def generate():
170
+ try:
171
+ for chunk in response:
172
+ if chunk.text:
173
+ data = {
174
+ 'choices': [
175
+ {
176
+ 'delta': {
177
+ 'content': chunk.text
178
+ },
179
+ 'finish_reason': None,
180
+ 'index': 0
181
+ }
182
+ ],
183
+ 'object': 'chat.completion.chunk'
184
+ }
185
+
186
+ yield f"data: {json.dumps(data)}\n\n"
187
  data = {
188
  'choices': [
189
  {
190
+ 'delta': {},
191
+ 'finish_reason': 'stop',
 
 
192
  'index': 0
193
  }
194
  ],
195
  'object': 'chat.completion.chunk'
196
  }
197
+
198
+ yield f"data: {json.dumps(data)}\n\n"
199
+ except Exception as e:
200
+ logger.error(f"Error during streaming: {str(e)}")
201
+
202
+ data = {
203
+ 'error': {
204
+ 'message': str(e),
205
+ 'type': 'internal_server_error'
206
+ }
207
+ }
208
  yield f"data: {json.dumps(data)}\n\n"
209
 
210
+ return Response(stream_with_context(generate()), mimetype='text/event-stream')
211
+ else:
212
+ # 调用 API
213
+ if gemini_history:
214
+ chat_session = gen_model.start_chat(history=gemini_history)
215
+ response = chat_session.send_message(user_message)
216
+ else:
217
+ response = gen_model.generate_content(user_message)
218
+ try:
219
+ text_content = response.candidates[0].content.parts[0].text
220
+
221
+ except (AttributeError, IndexError, TypeError) as e:
222
+ logger.error(colored(f"Error getting text content: {str(e)}",'red'))
223
+
224
+ text_content = "Error: Unable to get text content."
225
+
226
+ response_data = {
227
+ 'id': 'chatcmpl-xxxxxxxxxxxx',
228
+ 'object': 'chat.completion',
229
+ 'created': int(datetime.now().timestamp()),
230
+ 'model': model,
231
+ 'choices': [{
232
+ 'index': 0,
233
+ 'message': {
234
+ 'role': 'assistant',
235
+ 'content': text_content
236
+ },
237
+ 'finish_reason': 'stop'
238
+ }],
239
+ 'usage':{
240
+ 'prompt_tokens': 0,
241
+ 'completion_tokens': 0,
242
+ 'total_tokens': 0
243
  }
244
  }
245
+ logger.info(colored(f"Generation Success", 'green'))
246
+ return jsonify(response_data)
247
 
248
+ except Exception as e:
249
+ logger.error(f"Error in chat completions: {str(e)}")
250
+ return jsonify({
 
 
 
 
 
 
 
 
 
251
  'error': {
252
+ 'message': str(e),
253
+ 'type': 'invalid_request_error'
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
254
  }
255
+ }), 500
256
+ finally:
257
+ current_api_key = key_manager.get_available_key()
258
+ logger.info(colored(f"API KEY Switched -> {current_api_key[:11]}...", 'aqua'))
259
 
260
  @app.route('/hf/v1/models', methods=['GET'])
261
  def list_models():
262
+ is_authenticated, auth_error, status_code = func.authenticate_request(request)
263
+ if not is_authenticated:
264
+ return auth_error if auth_error else jsonify({'error': 'Unauthorized'}), status_code if status_code else 401
265
  response = {"object": "list", "data": GEMINI_MODELS}
266
  return jsonify(response)
267
 
268
  def keep_alive():
269
+ """ 定期向应用自身发送请求,保持活跃 """
270
  try:
271
  response = requests.get("http://127.0.0.1:7860/", timeout=10)
272
  response.raise_for_status()
 
276
 
277
  if __name__ == '__main__':
278
  scheduler = BackgroundScheduler()
279
+ # 设置定时任务,每 12 小时执行一次 keep_alive 函数
280
+ scheduler.add_job(keep_alive, 'interval', hours = 12)
281
+
282
  scheduler.start()
283
+ app.run(debug=True, host='0.0.0.0', port=int(os.environ.get('PORT', 7860)))
 
 
 
 
 
func.py CHANGED
@@ -1,75 +1,92 @@
1
  from io import BytesIO
2
- import base64
3
- from PIL import Image
4
  from flask import jsonify
5
  import logging
6
  import json
7
  import re
8
- import os
9
- import requests
10
- import google.generativeai as genai
11
  logger = logging.getLogger(__name__)
12
 
13
-
14
- request_counts = {}
15
-
16
- # 核心优势
17
- safety_settings = [
18
- {
19
- "category": "HARM_CATEGORY_HARASSMENT",
20
- "threshold": "BLOCK_NONE"
21
- },
22
- {
23
- "category": "HARM_CATEGORY_HATE_SPEECH",
24
- "threshold": "BLOCK_NONE"
25
- },
26
- {
27
- "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",
28
- "threshold": "BLOCK_NONE"
29
- },
30
- {
31
- "category": "HARM_CATEGORY_DANGEROUS_CONTENT",
32
- "threshold": "BLOCK_NONE"
33
- },
34
- ]
35
-
36
- password = os.environ['password']
37
 
38
  def authenticate_request(request):
39
  auth_header = request.headers.get('Authorization')
40
 
41
  if not auth_header:
42
- return False, jsonify({'error': '缺少Authorization请求头'}), 401
43
-
44
  try:
45
- auth_type, pass_word = auth_header.split(' ', 1)
46
  except ValueError:
47
- return False, jsonify({'error': 'Authorization请求头格式错误'}), 401
48
 
49
  if auth_type.lower() != 'bearer':
50
- return False, jsonify({'error': 'Authorization类型必须为Bearer'}), 401
51
 
52
- if pass_word != password:
53
- return False, jsonify({'error': '未授权'}), 401
54
 
55
  return True, None, None
56
 
57
- def get_gen_model(api_key, model, temperature, max_tokens):
58
- genai.configure(api_key=api_key)
59
-
60
- generation_config = {
61
- "temperature": temperature,
62
- "max_output_tokens": max_tokens
63
- }
64
-
65
- gen_model = genai.GenerativeModel(
66
- model_name=model,
67
- generation_config=generation_config,
68
- safety_settings=safety_settings
 
 
 
 
 
 
 
 
 
 
 
 
69
  )
70
- return gen_model
 
71
 
72
  def process_messages_for_gemini(messages):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
73
  gemini_history = []
74
  errors = []
75
  for message in messages:
 
1
  from io import BytesIO
2
+ import os
 
3
  from flask import jsonify
4
  import logging
5
  import json
6
  import re
 
 
 
7
  logger = logging.getLogger(__name__)
8
 
9
+ ProxyPasswords = os.environ.get('ProxyPasswords').split(',')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
 
11
  def authenticate_request(request):
12
  auth_header = request.headers.get('Authorization')
13
 
14
  if not auth_header:
15
+ return False, jsonify({'error': 'Authorization header is missing'}), 401
16
+
17
  try:
18
+ auth_type, api_key = auth_header.split(' ', 1)
19
  except ValueError:
20
+ return False, jsonify({'error': 'Invalid Authorization header format'}), 401
21
 
22
  if auth_type.lower() != 'bearer':
23
+ return False, jsonify({'error': 'Authorization type must be Bearer'}), 401
24
 
25
+ if api_key not in ProxyPasswords:
26
+ return False, jsonify({'error': 'Unauthorized'}), 401
27
 
28
  return True, None, None
29
 
30
+ def sanitize_request_data(request_data):
31
+ """
32
+ 从请求数据中删除base64编码的数据。
33
+
34
+ Args:
35
+ request_data: 包含可能存在base64数据的字典。
36
+
37
+ Returns:
38
+ 清理后的字典,其中base64数据被替换为"[Base64 Data Omitted]"。
39
+ """
40
+
41
+
42
+ def replace_base64(match):
43
+ # 替换base64数据为提示信息
44
+ return '"[Base64 Data Omitted]"'
45
+
46
+
47
+ request_data_str = json.dumps(request_data)
48
+
49
+ # 使用正则表达式匹配base64数据,并替换为提示信息
50
+ sanitized_request_data_str = re.sub(
51
+ r'"(data:[^;]+;base64,)[^"]+"',
52
+ replace_base64,
53
+ request_data_str
54
  )
55
+
56
+ return json.loads(sanitized_request_data_str)
57
 
58
  def process_messages_for_gemini(messages):
59
+ """
60
+ 将通用的对话消息格式转换为Gemini API所需的格式
61
+ 这个函数处理消息列表并将其转换为Gemini API兼容的格式。它支持文本、图片和文件内容的处理。
62
+ 参数:
63
+ messages (list): 包含对话消息的列表。每条消息应该是一个字典,包含'role'和'content'字段。
64
+ - role: 可以是 'system', 'user' 或 'assistant'
65
+ - content: 可以是字符串或包含多个内容项的列表
66
+ 返回:
67
+ tuple: 包含三个元素:
68
+ - gemini_history (list): 转换后的历史消息列表
69
+ - user_message (dict): 最新的用户消息
70
+ - error (tuple or None): 如果有错误,返回错误响应;否则返回None
71
+ 错误处理:
72
+ - 检查角色是否有效
73
+ - 验证图片URL格式
74
+ - 验证文件URL格式
75
+ 示例消息格式:
76
+ 文本消息:
77
+ {
78
+ 'role': 'user',
79
+ 'content': '你好'
80
+ }
81
+ 多模态消息:
82
+ {
83
+ 'role': 'user',
84
+ 'content': [
85
+ {'type': 'text', 'text': '这是什么图片?'},
86
+ {'type': 'image_url', 'image_url': {'url': 'data:image/jpeg;base64,...'}}
87
+ ]
88
+ }
89
+ """
90
  gemini_history = []
91
  errors = []
92
  for message in messages:
requirements.txt CHANGED
@@ -4,5 +4,6 @@ requests==2.26.0
4
  Werkzeug==2.0.3
5
  google==3.0.0
6
  google-generativeai==0.8.3
 
7
  pillow==10.4.0
8
- apscheduler
 
4
  Werkzeug==2.0.3
5
  google==3.0.0
6
  google-generativeai==0.8.3
7
+ termcolor==2.5.0
8
  pillow==10.4.0
9
+ apscheduler
templates/login.html ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html>
3
+
4
+ <head>
5
+ <title>Login</title>
6
+ </head>
7
+
8
+ <body>
9
+ <h1>Admin Login</h1>
10
+ {% if error %}
11
+ <p style="color: red;">{{ error }}</p>
12
+ {% endif %}
13
+ <form method="POST">
14
+ <label for="password">Password:</label>
15
+ <input type="password" name="password" id="password" required>
16
+ <button type="submit">Login</button>
17
+ </form>
18
+ </body>
19
+
20
+ </html>
templates/manage.html ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html>
3
+
4
+ <head>
5
+ <title>Manage API Keys</title>
6
+ </head>
7
+
8
+ <body>
9
+ <h1>Manage Hugging Face API Keys</h1>
10
+ <h2>Current Keys:</h2>
11
+ <ul>
12
+ {% for key in keys %}
13
+ <li>
14
+ {{ key }}
15
+ <form method="POST" style="display: inline;">
16
+ <input type="hidden" name="action" value="delete">
17
+ <input type="hidden" name="key_to_delete" value="{{ key }}">
18
+ <button type="submit">Delete</button>
19
+ </form>
20
+ </li>
21
+ {% endfor %}
22
+ </ul>
23
+ <h2>Add New Key:</h2>
24
+ <form method="POST">
25
+ <input type="hidden" name="action" value="add">
26
+ <input type="text" name="new_key" placeholder="Enter new API key">
27
+ <button type="submit">Add</button>
28
+ </form>
29
+ <br>
30
+ <a href="{{ url_for('logout') }}">Logout</a>
31
+ </body>
32
+
33
+ </html>