xixi2333 commited on
Commit
c6e0a5a
·
1 Parent(s): 098a67c
Files changed (8) hide show
  1. Dockerfile +10 -0
  2. README.md +3 -3
  3. app.py +273 -0
  4. chat.txt +146 -0
  5. func.py +160 -0
  6. requirements.txt +9 -0
  7. templates/login.html +20 -0
  8. templates/manage.html +33 -0
Dockerfile ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.9-slim
2
+
3
+ WORKDIR /app
4
+
5
+ COPY requirements.txt .
6
+ RUN pip install --no-cache-dir -r requirements.txt
7
+
8
+ COPY . .
9
+
10
+ CMD ["python", "app.py"]
README.md CHANGED
@@ -1,7 +1,7 @@
1
  ---
2
- title: My 2
3
- emoji: 📊
4
- colorFrom: yellow
5
  colorTo: yellow
6
  sdk: docker
7
  pinned: false
 
1
  ---
2
+ title: my_2
3
+ emoji: 🔥
4
+ colorFrom: green
5
  colorTo: yellow
6
  sdk: docker
7
  pinned: false
app.py ADDED
@@ -0,0 +1,273 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, request, jsonify, Response, stream_with_context, render_template, session, redirect, url_for
2
+ from apscheduler.schedulers.background import BackgroundScheduler
3
+ import google.generativeai as genai
4
+ import json
5
+ from datetime import datetime
6
+ import os
7
+ from termcolor import colored
8
+ import logging
9
+ import func
10
+ import requests
11
+ import time
12
+
13
+ os.environ['TZ'] = 'Asia/Shanghai'
14
+ app = Flask(__name__)
15
+ if not os.environ.get('TERM'):
16
+ os.environ['TERM'] = 'xterm'
17
+ app.secret_key = os.urandom(24)
18
+
19
+ ProxyPasswords = os.environ.get('ProxyPasswords').split(',')
20
+
21
+ formatter = logging.Formatter('%(message)s')
22
+ ADMIN_PASSWORD = os.environ.get('AdminPassword')
23
+
24
+ logger = logging.getLogger(__name__)
25
+ logger.setLevel(logging.INFO)
26
+
27
+ handler = logging.StreamHandler()
28
+ handler.setFormatter(formatter)
29
+
30
+ logger.addHandler(handler)
31
+
32
+ safety_settings = [
33
+ {
34
+ "category": "HARM_CATEGORY_HARASSMENT",
35
+ "threshold": "BLOCK_NONE"
36
+ },
37
+ {
38
+ "category": "HARM_CATEGORY_HATE_SPEECH",
39
+ "threshold": "BLOCK_NONE"
40
+ },
41
+ {
42
+ "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",
43
+ "threshold": "BLOCK_NONE"
44
+ },
45
+ {
46
+ "category": "HARM_CATEGORY_DANGEROUS_CONTENT",
47
+ "threshold": "BLOCK_NONE"
48
+ },
49
+ ]
50
+ class APIKeyManager:
51
+ def __init__(self):
52
+ self.api_keys = os.environ.get('API_KEYS').split(',')
53
+ self.current_index = 0
54
+
55
+
56
+ def get_available_key(self):
57
+ if self.current_index >= len(self.api_keys):
58
+ self.current_index = 0
59
+ current_key = self.api_keys[self.current_index]
60
+ self.current_index += 1
61
+ return current_key
62
+
63
+ key_manager = APIKeyManager()
64
+ current_api_key = key_manager.get_available_key()
65
+ logger.info(f"Current API key: {current_api_key[:11]}...")
66
+ genai.configure(api_key=current_api_key)
67
+ models = genai.list_models()
68
+ logger.info("Available models:")
69
+ for model in models:
70
+ logger.info(f"- {model.name}: {model.supported_generation_methods}")
71
+ GEMINI_MODELS = [
72
+ {"id": "gemini-pro"},
73
+ {"id": "gemini-pro-vision"},
74
+ {"id": "gemini-1.0-pro"},
75
+ {"id": "gemini-1.0-pro-vision"},
76
+ {"id": "gemini-1.5-pro-002"},
77
+ {"id": "gemini-exp-1114"},
78
+ {"id": "gemini-exp-1121"},
79
+ {"id": "gemini-exp-1206"},
80
+ {"id": "gemini-2.0-flash-exp"},
81
+ {"id": "gemini-2.0-flash-thinking-exp-1219"},
82
+ {"id": "gemini-2.0-flash-thinking-exp-01-21"},
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
+
269
+ if __name__ == '__main__':
270
+ scheduler = BackgroundScheduler()
271
+ # 设置定时任务,每 12 小时执行一次 keep_alive 函数
272
+ scheduler.start()
273
+ app.run(debug=True, host='0.0.0.0', port=int(os.environ.get('PORT', 7860)))
chat.txt ADDED
@@ -0,0 +1,146 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @app.route('/hf/v1/chat/completions', methods=['POST'])
2
+ def chat_completions():
3
+ global current_api_key
4
+ is_authenticated, auth_error, status_code = authenticate_request(request)
5
+ if not is_authenticated:
6
+ return auth_error if auth_error else jsonify({'error': 'Unauthorized'}), status_code if status_code else 401
7
+ try:
8
+
9
+ request_data = request.get_json()
10
+
11
+ messages = request_data.get('messages', [])
12
+ model = request_data.get('model', 'gemini-exp-1206')
13
+ temperature = request_data.get('temperature', 1)
14
+ max_tokens = request_data.get('max_tokens', 8192)
15
+ stream = request_data.get('stream', False)
16
+
17
+ logger.info(colored(f"\n{model} [r] -> {current_api_key[:11]}...", 'yellow'))
18
+ # 将 OpenAI 格式的消息转换为 Gemini 格式
19
+ gemini_history = []
20
+ for message in messages:
21
+ role = message.get('role')
22
+ content = message.get('content')
23
+ if role == 'system':
24
+ gemini_history.append({"role": "user", "parts": [content]})
25
+ elif role == 'user':
26
+ gemini_history.append({"role": "user", "parts": [content]})
27
+ elif role == 'assistant':
28
+ gemini_history.append({"role": "model", "parts": [content]})
29
+
30
+ user_message = gemini_history[-1]['parts'][0] if gemini_history else messages[-1]['content']
31
+ gemini_history = gemini_history[:-1] if gemini_history else []
32
+ genai.configure(api_key=current_api_key)
33
+ # 调用 Gemini API
34
+ generation_config = {
35
+ "temperature": temperature,
36
+ "max_output_tokens": max_tokens
37
+ }
38
+
39
+ gen_model = genai.GenerativeModel(
40
+ model_name=model,
41
+ generation_config=generation_config,
42
+ safety_settings=safety_settings
43
+ )
44
+
45
+
46
+ if stream:
47
+ # 流式响应
48
+ if gemini_history:
49
+ chat_session = gen_model.start_chat(history=gemini_history)
50
+ response = chat_session.send_message(user_message, stream=True)
51
+ else:
52
+ response = gen_model.generate_content(user_message, stream=True)
53
+
54
+ def generate():
55
+ try:
56
+ for chunk in response:
57
+ if chunk.text:
58
+ data = {
59
+ 'choices': [
60
+ {
61
+ 'delta': {
62
+ 'content': chunk.text
63
+ },
64
+ 'finish_reason': None,
65
+ 'index': 0
66
+ }
67
+ ],
68
+ 'object': 'chat.completion.chunk'
69
+ }
70
+
71
+ yield f"data: {json.dumps(data)}\n\n"
72
+ data = {
73
+ 'choices': [
74
+ {
75
+ 'delta': {},
76
+ 'finish_reason': 'stop',
77
+ 'index': 0
78
+ }
79
+ ],
80
+ 'object': 'chat.completion.chunk'
81
+ }
82
+
83
+ yield f"data: {json.dumps(data)}\n\n"
84
+ except Exception as e:
85
+ logger.error(f"Error during streaming: {str(e)}")
86
+
87
+ data = {
88
+ 'error': {
89
+ 'message': str(e),
90
+ 'type': 'internal_server_error'
91
+ }
92
+ }
93
+ yield f"data: {json.dumps(data)}\n\n"
94
+
95
+ return Response(stream_with_context(generate()), mimetype='text/event-stream')
96
+ else:
97
+
98
+ # 调用 API
99
+ if gemini_history:
100
+ chat_session = gen_model.start_chat(history=gemini_history)
101
+ response = chat_session.send_message(user_message)
102
+ else:
103
+ response = gen_model.generate_content(user_message)
104
+
105
+ try:
106
+ text_content = response.candidates[0].content.parts[0].text
107
+
108
+ except (AttributeError, IndexError, TypeError) as e:
109
+ logger.error(colored(f"Error getting text content: {str(e)}",'red'))
110
+
111
+ text_content = "Error: Unable to get text content."
112
+
113
+ response_data = {
114
+ 'id': 'chatcmpl-xxxxxxxxxxxx',
115
+ 'object': 'chat.completion',
116
+ 'created': int(datetime.now().timestamp()),
117
+ 'model': model,
118
+ 'choices': [{
119
+ 'index': 0,
120
+ 'message': {
121
+ 'role': 'assistant',
122
+ 'content': text_content
123
+ },
124
+ 'finish_reason': 'stop'
125
+ }],
126
+ 'usage':{
127
+ 'prompt_tokens': 0,
128
+ 'completion_tokens': 0,
129
+ 'total_tokens': 0
130
+ }
131
+ }
132
+ logger.info(colored(f"Generation Success", 'green'))
133
+ return jsonify(response_data)
134
+
135
+ except Exception as e:
136
+ logger.error(f"Error in chat completions: {str(e)}")
137
+
138
+ return jsonify({
139
+ 'error': {
140
+ 'message': str(e),
141
+ 'type': 'invalid_request_error'
142
+ }
143
+ }), 500
144
+ finally:
145
+ current_api_key = key_manager.get_available_key()
146
+ logger.info(colored(f"API KEY Switched -> {current_api_key[:11]}...", 'aqua'))
func.py ADDED
@@ -0,0 +1,160 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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:
93
+ role = message.get('role')
94
+ content = message.get('content')
95
+
96
+ if isinstance(content, str):
97
+ if role == 'system':
98
+ gemini_history.append({"role": "user", "parts": [content]})
99
+ elif role == 'user':
100
+ gemini_history.append({"role": "user", "parts": [content]})
101
+ elif role == 'assistant':
102
+ gemini_history.append({"role": "model", "parts": [content]})
103
+ else:
104
+ errors.append(f"Invalid role: {role}")
105
+ elif isinstance(content, list):
106
+ parts = []
107
+ for item in content:
108
+ if item.get('type') == 'text':
109
+ parts.append({"text": item.get('text')})
110
+ elif item.get('type') == 'image_url':
111
+ image_data = item.get('image_url', {}).get('url', '')
112
+ if image_data.startswith('data:image/'):
113
+
114
+ try:
115
+ mime_type, base64_data = image_data.split(';')[0].split(':')[1], image_data.split(',')[1]
116
+ parts.append({
117
+ "inline_data": {
118
+ "mime_type": mime_type,
119
+ "data": base64_data
120
+ }
121
+ })
122
+ except (IndexError, ValueError):
123
+ errors.append(f"Invalid data URI for image: {image_data}")
124
+ else:
125
+ errors.append(f"Invalid image URL format for item: {item}")
126
+ elif item.get('type') == 'file_url':
127
+ file_data = item.get('file_url', {}).get('url', '')
128
+ if file_data.startswith('data:'):
129
+
130
+ try:
131
+ mime_type, base64_data = file_data.split(';')[0].split(':')[1], file_data.split(',')[1]
132
+ parts.append({
133
+ "inline_data": {
134
+ "mime_type": mime_type,
135
+ "data": base64_data
136
+ }
137
+ })
138
+ except (IndexError, ValueError):
139
+ errors.append(f"Invalid data URI for file: {file_data}")
140
+ else:
141
+ errors.append(f"Invalid file URL format for item: {item}")
142
+
143
+ if parts:
144
+ if role in ['user', 'system']:
145
+ gemini_history.append({"role": "user", "parts": parts})
146
+ elif role == 'assistant':
147
+ gemini_history.append({"role": "model", "parts": parts})
148
+ else:
149
+ errors.append(f"Invalid role: {role}")
150
+
151
+ if gemini_history:
152
+ user_message = gemini_history[-1]
153
+ gemini_history = gemini_history[:-1]
154
+ else:
155
+ user_message = {"role": "user", "parts": [""]}
156
+
157
+ if errors:
158
+ return gemini_history, user_message, (jsonify({'error': errors}), 400)
159
+ else:
160
+ return gemini_history, user_message, None
requirements.txt ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ Flask==2.0.3
2
+ Flask-CORS==3.0.10
3
+ requests==2.26.0
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>