sonygod commited on
Commit
1ba7fb7
·
1 Parent(s): 3afb208
Files changed (3) hide show
  1. Dockerfile +14 -0
  2. app.py +471 -0
  3. requirements.txt +7 -0
Dockerfile ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.9-slim
2
+
3
+ WORKDIR /app
4
+
5
+ COPY ./requirements.txt /app/requirements.txt
6
+ COPY ./app.py /app/app.py
7
+
8
+
9
+ RUN pip install --no-cache-dir -r requirements.txt
10
+
11
+
12
+ EXPOSE 7860
13
+
14
+ CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
app.py ADDED
@@ -0,0 +1,471 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, HTTPException, UploadFile, File, Form
2
+ from fastapi.middleware.cors import CORSMiddleware
3
+ from fastapi.responses import HTMLResponse
4
+ from pydantic import BaseModel
5
+ from fastapi import FastAPI, HTTPException, Request
6
+ from asyncio import TimeoutError
7
+ import asyncio
8
+ from typing import Optional
9
+ import requests
10
+ import uvicorn
11
+ import shutil
12
+ import datetime # Add this import
13
+ import logging
14
+ from logging.handlers import RotatingFileHandler
15
+ import time
16
+ from typing import List, Dict, Optional
17
+ import json
18
+ import os
19
+ import psutil
20
+ import sys
21
+ from typing import Dict
22
+ import tempfile
23
+ import re
24
+ import random
25
+ import aiohttp
26
+ app = FastAPI()
27
+
28
+ # Add USER_AGENTS constant
29
+ USER_AGENTS = [
30
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36",
31
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36",
32
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36"
33
+ ]
34
+ # Configure logging
35
+ # Configure detailed logging
36
+ logging.basicConfig(
37
+ level=logging.INFO,
38
+ format='%(asctime)s - %(levelname)s - %(message)s'
39
+ )
40
+ logger = logging.getLogger(__name__)
41
+ # Request counter
42
+ request_counter = {
43
+ "analyze": 0,
44
+ "compareAnalyze": 0,
45
+ "total": 0
46
+ }
47
+
48
+ # Add CORS middleware
49
+ # Add CORS middleware with both HTTP and HTTPS
50
+ app.add_middleware(
51
+ CORSMiddleware,
52
+ allow_origins=[
53
+ "http://*",
54
+ "https://*"
55
+ ],
56
+ allow_credentials=True,
57
+ allow_methods=["*"],
58
+ allow_headers=["*"],
59
+ )
60
+
61
+ class AskRequest(BaseModel):
62
+ prompt: str
63
+ model: str = "GEMINI"
64
+
65
+
66
+ @app.get("/")
67
+ async def health_check():
68
+ return {
69
+ "health": "ok",
70
+ "timestamp": datetime.datetime.now().isoformat(),
71
+ "service": "AI API Forwarding Service",
72
+ "version": "1.0"
73
+ }
74
+
75
+
76
+ @app.post("/ask")
77
+ async def forward_ask(request: AskRequest):
78
+ request_counter["total"] += 1
79
+ try:
80
+ response = requests.post(
81
+ "http://s5.serv00.com:9081/ask",
82
+ headers={'Content-Type': 'application/json'},
83
+ json=request.dict()
84
+ )
85
+ return response.json()
86
+ except Exception as e:
87
+ raise HTTPException(status_code=500, detail=str(e))
88
+
89
+ @app.post("/analyze")
90
+ async def forward_analyze(image: UploadFile = File(...), model: str = Form(...)):
91
+ request_counter["analyze"] += 1
92
+ request_counter["total"] += 1
93
+ try:
94
+ files = {'image': (image.filename, image.file, image.content_type)}
95
+ data = {'model': model}
96
+ response = requests.post(
97
+ "http://s5.serv00.com:9081/analyze",
98
+ files=files,
99
+ data=data
100
+ )
101
+ return response.json()
102
+ except Exception as e:
103
+ raise HTTPException(status_code=500, detail=str(e))
104
+
105
+ @app.post("/compareAnalyze")
106
+ async def forward_compare_analyze(image: UploadFile = File(...)):
107
+ request_counter["compareAnalyze"] += 1
108
+ request_counter["total"] += 1
109
+ try:
110
+ files = {'image': (image.filename, image.file, image.content_type)}
111
+ response = requests.post(
112
+ "http://s5.serv00.com:9081/compareAnalyze",
113
+ files=files
114
+ )
115
+ return response.json()
116
+ except Exception as e:
117
+ raise HTTPException(status_code=500, detail=str(e))
118
+
119
+ @app.get("/status")
120
+ async def forward_status():
121
+ start_time = time.time()
122
+ logger.info(f"Received status request at {datetime.datetime.now()}")
123
+ logger.info(f"Current request counter: {request_counter}")
124
+
125
+ try:
126
+ logger.info("Attempting to contact upstream server...")
127
+ response = requests.get("http://s5.serv00.com:9081/status")
128
+ elapsed_time = time.time() - start_time
129
+
130
+ logger.info(f"Upstream server responded in {elapsed_time:.2f} seconds")
131
+ logger.info(f"Response status code: {response.status_code}")
132
+ logger.info(f"Response content: {response.text[:200]}...")
133
+
134
+ return response.json()
135
+ except Exception as e:
136
+ logger.error(f"Error occurred: {str(e)}")
137
+ logger.error(f"Error type: {type(e).__name__}")
138
+ return {
139
+ "status": "running",
140
+ "requests": request_counter,
141
+ "error": str(e),
142
+ "timestamp": datetime.datetime.now().isoformat()
143
+ }
144
+
145
+ @app.get("/check", response_class=HTMLResponse)
146
+ async def forward_check():
147
+ try:
148
+ response = requests.get("http://s5.serv00.com:9081/check")
149
+ return response.text
150
+ except Exception as e:
151
+ raise HTTPException(status_code=500, detail=str(e))
152
+
153
+
154
+
155
+ # Add new models
156
+ class Translation(BaseModel):
157
+ translation: str
158
+ type: str
159
+
160
+ class Phrase(BaseModel):
161
+ phrase: str
162
+ translation: str
163
+
164
+ class Word(BaseModel):
165
+ word: str
166
+ translations: List[dict] # Changed to accept dictionary format
167
+ phrases: List[Phrase] = [] # Made optional with default empty list
168
+ level: str = "" # Add level field with default empty string
169
+
170
+ # Add global word map
171
+ word_map: Dict[str, Word] = {}
172
+
173
+ def get_level_from_filename(filename: str) -> str:
174
+ # Extract level from filenames like "1-初中-顺序.json"
175
+ match = re.match(r'\d+-(.+?)-顺序\.json', filename)
176
+ return match.group(1) if match else "unknown"
177
+ # Add initialization function
178
+ def init_word_map():
179
+ current_dir = os.path.dirname(os.path.abspath(__file__))
180
+ json_dir = os.path.join(current_dir, "json")
181
+ stats = {
182
+ "total_words": 0,
183
+ "total_files": 0,
184
+ "file_stats": {}
185
+ }
186
+
187
+ try:
188
+ for filename in os.listdir(json_dir):
189
+ if filename.endswith('.json'):
190
+ try:
191
+ level = get_level_from_filename(filename)
192
+ with open(os.path.join(json_dir, filename), 'r', encoding='utf-8') as f:
193
+ words = json.load(f)
194
+ word_count = len(words)
195
+ stats["total_words"] += word_count
196
+ stats["total_files"] += 1
197
+ stats["file_stats"][filename] = word_count
198
+ for word_data in words:
199
+ # Convert legacy format to new format
200
+ if 'translations' not in word_data:
201
+ word_data['translations'] = [{
202
+ 'translation': word_data.get('translation', ''),
203
+ 'type': word_data.get('type', '')
204
+ }]
205
+ if 'phrases' not in word_data:
206
+ word_data['phrases'] = []
207
+
208
+ word_data['level'] = level
209
+ word = Word(**word_data)
210
+ word_map[word.word.lower()] = word
211
+ logger.info(f"Loaded {filename}: {word_count} words")
212
+ except Exception as e:
213
+ logger.error(f"Error loading {filename}: {str(e)}")
214
+ continue
215
+
216
+ logger.info(f"Dictionary initialization complete:")
217
+ logger.info(f"Total files processed: {stats['total_files']}")
218
+ logger.info(f"Total words loaded: {stats['total_words']}")
219
+ return stats
220
+ except Exception as e:
221
+ logger.error(f"Fatal error in init_word_map: {str(e)}")
222
+ return stats
223
+
224
+
225
+ # Add cache configuration
226
+ # Update cache file location
227
+ CACHE_DIR = os.path.join(tempfile.gettempdir(), "flash_api_cache")
228
+ CACHE_FILE = os.path.join(CACHE_DIR, "ai_translation_cache.json")
229
+ ai_cache: Dict[str, dict] = {}
230
+
231
+ # Load cache on startup
232
+ def save_cache():
233
+ try:
234
+ # Create cache directory if it doesn't exist
235
+ os.makedirs(CACHE_DIR, exist_ok=True)
236
+
237
+ with open(CACHE_FILE, 'w', encoding='utf-8') as f:
238
+ json.dump(ai_cache, f, ensure_ascii=False, indent=2)
239
+ logger.info(f"Cache saved to: {CACHE_FILE}")
240
+ except PermissionError as pe:
241
+ logger.error(f"Permission denied writing to cache: {pe}")
242
+ except Exception as e:
243
+ logger.error(f"Error saving cache: {e}")
244
+
245
+ def load_cache():
246
+ global ai_cache
247
+ try:
248
+ if os.path.exists(CACHE_FILE):
249
+ with open(CACHE_FILE, 'r', encoding='utf-8') as f:
250
+ ai_cache = json.load(f)
251
+ logger.info(f"Loaded {len(ai_cache)} cached translations from: {CACHE_FILE}")
252
+ except Exception as e:
253
+ logger.error(f"Error loading cache: {e}")
254
+ ai_cache = {}
255
+
256
+ # Add translate endpoint
257
+ @app.get("/translate/{word}")
258
+ async def translate_word(word: str):
259
+ start_time = time.time()
260
+ logger.info(f"Translation request received for word: {word}")
261
+
262
+ try:
263
+ word = word.lower().strip()
264
+ logger.debug(f"Processed word: {word}")
265
+
266
+ # Check word map
267
+ # if word in word_map:
268
+ # logger.info(f"Word found in map: {word}")
269
+ # word_data = word_map[word]
270
+ # logger.debug(f"Word data: {word_data}")
271
+
272
+ # # Format all translations
273
+ # translations_text = []
274
+ # for trans in word_data.translations:
275
+ # translation = trans['translation']
276
+ # type_info = trans['type']
277
+ # translations_text.append(f"({type_info}) {translation}")
278
+
279
+ # # Join translations with separators
280
+ # translations_combined = " | ".join(translations_text)
281
+ # logger.debug(f"Combined translations: {translations_combined}")
282
+
283
+ # # Handle examples
284
+ # examples = []
285
+ # if word_data.phrases:
286
+ # examples = [f"{p.phrase}: {p.translation}" for p in word_data.phrases[:3]]
287
+ # logger.debug(f"Examples found: {examples}")
288
+
289
+ # # Build response with proper formatting
290
+ # formatted_response = f"{word} [{word_data.level}]: {translations_combined}"
291
+ # if examples:
292
+ # formatted_response += f"\n\n例句:\n{chr(10).join(examples)}"
293
+
294
+ # elapsed = time.time() - start_time
295
+ # logger.info(f"Word map translation completed in {elapsed:.2f}s")
296
+
297
+ # return {
298
+ # "status": 200,
299
+ # "data": {
300
+ # "response": formatted_response,
301
+ # "word": word,
302
+ # "level": word_data.level, # Add level info here
303
+ # "translations": word_data.translations,
304
+ # "examples": examples
305
+ # }
306
+ # }
307
+
308
+ # Check AI cache
309
+ # if word in ai_cache:
310
+ # logger.info(f"Word found in AI cache: {word}")
311
+ # elapsed = time.time() - start_time
312
+ # logger.info(f"Cache hit completed in {elapsed:.2f}s")
313
+ # return ai_cache[word]
314
+
315
+ # Fallback to AI translation
316
+ logger.info("Word not found in cache, calling AI API")
317
+ # Fallback to AI translation
318
+ logger.info("Word not found in map, falling back to AI translation")
319
+ try:
320
+ request = AskRequest(
321
+ prompt=f'''翻译以下英文
322
+ {word}
323
+ 每行一个 格式参考,不要任何md格式,分别要有音标,单词属性(名词,动词,形容词),中文翻译,英文解析,例句,近义词,反义词,词性
324
+ 格式参考:
325
+ hello:/həˈləʊ/| n. vt. int.|你好,问候语,|例句:Hello, how are you? 你好,你好吗?|近义词:hi, hey, |反义词:sick, bad.''',
326
+ model="GEMINI"
327
+ )
328
+ logger.debug(f"AI Request: {request}")
329
+
330
+ result = await forward_ask(request)
331
+
332
+ # Cache the result
333
+ #ai_cache[word] = result
334
+ #save_cache()
335
+ logger.debug(f"AI Response: {result}")
336
+
337
+ elapsed = time.time() - start_time
338
+ logger.info(f"AI translation completed in {elapsed:.2f}s")
339
+ return result
340
+
341
+ except Exception as e:
342
+ logger.error(f"AI translation error: {str(e)}", exc_info=True)
343
+ raise HTTPException(status_code=500, detail=str(e))
344
+
345
+ except Exception as e:
346
+ logger.error(f"Translation error: {str(e)}", exc_info=True)
347
+ raise HTTPException(status_code=500, detail=str(e))
348
+
349
+ # Add cleanup functions
350
+ def cleanup_temp_files():
351
+ try:
352
+ # Clean temp directory
353
+ temp_dir = os.path.join(tempfile.gettempdir(), "flash_api_cache")
354
+ if os.path.exists(temp_dir):
355
+ shutil.rmtree(temp_dir)
356
+ logger.info(f"Cleaned up temp directory: {temp_dir}")
357
+ except Exception as e:
358
+ logger.error(f"Error cleaning temp files: {e}")
359
+
360
+ def cleanup_cache():
361
+ global ai_cache
362
+ ai_cache = {}
363
+ logger.info("Cache cleared")
364
+
365
+
366
+ # Initialize word map on startup
367
+ @app.on_event("startup")
368
+ async def startup_event():
369
+ #init_word_map()// waste of memory
370
+ #load_cache()
371
+ cleanup_temp_files()
372
+ cleanup_cache()
373
+ logger.info(f"Memory usage after init: {get_memory_usage()}")
374
+
375
+ @app.on_event("shutdown")
376
+ async def shutdown_event():
377
+ # Cleanup on shutdown
378
+ cleanup_temp_files()
379
+ cleanup_cache()
380
+ logger.info("Application shutdown cleanup complete")
381
+ def get_memory_usage():
382
+ process = psutil.Process()
383
+ memory_info = process.memory_info()
384
+
385
+ # Get system memory info
386
+ system = psutil.virtual_memory()
387
+
388
+ return {
389
+ "process": {
390
+ "rss": f"{memory_info.rss / 1024 / 1024:.2f} MB",
391
+ "rss_percent": f"{memory_info.rss / system.total * 100:.2f}%",
392
+ "vms": f"{memory_info.vms / 1024 / 1024:.2f} MB",
393
+ "vms_percent": f"{memory_info.vms / system.total * 100:.2f}%"
394
+ },
395
+ "system": {
396
+ "total": f"{system.total / 1024 / 1024:.2f} MB",
397
+ "available": f"{system.available / 1024 / 1024:.2f} MB",
398
+ "used_percent": f"{system.percent:.2f}%"
399
+ },
400
+ "word_map": {
401
+ "entries": len(word_map),
402
+ "memory": f"{sys.getsizeof(word_map) / 1024 / 1024:.2f} MB",
403
+ "memory_percent": f"{sys.getsizeof(word_map) / system.total * 100:.4f}%"
404
+ }
405
+ }
406
+
407
+ @app.get("/memory")
408
+ async def memory_status():
409
+ return get_memory_usage()
410
+
411
+ # Add new endpoint
412
+ @app.get("/proxy")
413
+ async def proxy_request(url: str, request: Request):
414
+ try:
415
+ # Get random user agent
416
+ user_agent = random.choice(USER_AGENTS)
417
+
418
+ #print url
419
+
420
+ logger.info(f"Proxy request received for: {url}")
421
+
422
+ # Prepare headers
423
+ headers = {
424
+ 'User-Agent': user_agent,
425
+ 'Accept': 'application/json, text/plain, */*',
426
+ 'Accept-Language': 'en-US,en;q=0.9',
427
+ 'Origin': 'https://www.youtube.com',
428
+ 'Referer': 'https://www.youtube.com/',
429
+ 'Sec-Fetch-Dest': 'empty',
430
+ 'Sec-Fetch-Mode': 'cors',
431
+ 'Sec-Fetch-Site': 'same-site',
432
+ 'Connection': 'keep-alive'
433
+ }
434
+
435
+ # Set timeout
436
+ timeout = aiohttp.ClientTimeout(total=10) # 10 seconds timeout
437
+
438
+ async with aiohttp.ClientSession(timeout=timeout) as session:
439
+ async with session.get(url, headers=headers) as response:
440
+ # Check HTTP status
441
+ if response.status != 200:
442
+ raise HTTPException(
443
+ status_code=response.status,
444
+ detail=f"HTTP error: {response.status}"
445
+ )
446
+
447
+ # Parse JSON response
448
+ data = await response.json()
449
+
450
+ #print data's length
451
+
452
+ logger.info(f"Received youtube subtile data: {len(data)} bytes")
453
+
454
+
455
+ # Validate data format
456
+ if not data or 'events' not in data:
457
+ raise HTTPException(
458
+ status_code=400,
459
+ detail="Invalid subtitle data format"
460
+ )
461
+
462
+ return data
463
+
464
+ except TimeoutError:
465
+ raise HTTPException(status_code=408, detail="Request timeout")
466
+ except Exception as e:
467
+ logger.error(f"Proxy error: {str(e)}")
468
+ raise HTTPException(status_code=500, detail=str(e))
469
+
470
+ if __name__ == "__main__":
471
+ uvicorn.run(app, host="0.0.0.0", port=7860)
requirements.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ fastapi
2
+ uvicorn
3
+ requests
4
+ python-multipart
5
+ pydantic
6
+ psutil
7
+ aiohttp>=3.8.0