sonygod/flash2
0
1from fastapi import FastAPI, HTTPException, UploadFile, File, Form2from fastapi.middleware.cors import CORSMiddleware3from fastapi.responses import HTMLResponse4from pydantic import BaseModel5from fastapi import FastAPI, HTTPException, Request6from asyncio import TimeoutError7import asyncio8from typing import Optional9import requests10import uvicorn11import shutil12import datetime # Add this import13import logging14from logging.handlers import RotatingFileHandler15import time16from typing import List, Dict, Optional17import json18import os19import psutil20import sys21from typing import Dict22import tempfile23import re24import random25import aiohttp 26 27from fastapi.templating import Jinja2Templates28from fastapi.staticfiles import StaticFiles29import ollama30app = FastAPI()31 32# Add USER_AGENTS constant33USER_AGENTS = [34 "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36",35 "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",36 "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36"37]38# Configure logging39# Configure detailed logging40logging.basicConfig(41 level=logging.INFO,42 format='%(asctime)s - %(levelname)s - %(message)s'43)44logger = logging.getLogger(__name__)45# Request counter46request_counter = {47 "analyze": 0,48 "compareAnalyze": 0,49 "total": 050}51 52# Add CORS middleware53# Add CORS middleware with both HTTP and HTTPS54app.add_middleware(55 CORSMiddleware,56 allow_origins=[57 "http://*",58 "https://*"59 ],60 allow_credentials=True,61 allow_methods=["*"],62 allow_headers=["*"],63)64 65class AskRequest(BaseModel):66 prompt: str67 model: str = "GEMINI"68 69 70@app.get("/")71async def health_check():72 return {73 "health": "ok",74 "timestamp": datetime.datetime.now().isoformat(),75 "service": "AI API Forwarding Service",76 "version": "1.0"77 }78 79 80@app.post("/ask")81async def forward_ask(request: AskRequest):82 request_counter["total"] += 183 try:84 response = requests.post(85 "http://s5.serv00.com:9081/ask",86 headers={'Content-Type': 'application/json'},87 json=request.dict()88 )89 return response.json()90 except Exception as e:91 raise HTTPException(status_code=500, detail=str(e))92 93@app.post("/analyze")94async def forward_analyze(image: UploadFile = File(...), model: str = Form(...)):95 request_counter["analyze"] += 196 request_counter["total"] += 197 try:98 files = {'image': (image.filename, image.file, image.content_type)}99 data = {'model': model}100 response = requests.post(101 "http://s5.serv00.com:9081/analyze",102 files=files,103 data=data104 )105 return response.json()106 except Exception as e:107 raise HTTPException(status_code=500, detail=str(e))108 109@app.post("/compareAnalyze")110async def forward_compare_analyze(image: UploadFile = File(...)):111 request_counter["compareAnalyze"] += 1112 request_counter["total"] += 1113 try:114 files = {'image': (image.filename, image.file, image.content_type)}115 response = requests.post(116 "http://s5.serv00.com:9081/compareAnalyze",117 files=files118 )119 return response.json()120 except Exception as e:121 raise HTTPException(status_code=500, detail=str(e))122 123@app.get("/status")124async def forward_status():125 start_time = time.time()126 logger.info(f"Received status request at {datetime.datetime.now()}")127 logger.info(f"Current request counter: {request_counter}")128 129 try:130 logger.info("Attempting to contact upstream server...")131 response = requests.get("http://s5.serv00.com:9081/status")132 elapsed_time = time.time() - start_time133 134 logger.info(f"Upstream server responded in {elapsed_time:.2f} seconds")135 logger.info(f"Response status code: {response.status_code}")136 logger.info(f"Response content: {response.text[:200]}...")137 138 return response.json()139 except Exception as e:140 logger.error(f"Error occurred: {str(e)}")141 logger.error(f"Error type: {type(e).__name__}")142 return {143 "status": "running",144 "requests": request_counter,145 "error": str(e),146 "timestamp": datetime.datetime.now().isoformat()147 }148 149@app.get("/check", response_class=HTMLResponse)150async def forward_check():151 try:152 response = requests.get("http://s5.serv00.com:9081/check")153 return response.text154 except Exception as e:155 raise HTTPException(status_code=500, detail=str(e))156 157 158 159# Add new models160class Translation(BaseModel):161 translation: str162 type: str163 164class Phrase(BaseModel):165 phrase: str166 translation: str167 168class Word(BaseModel):169 word: str170 translations: List[dict] # Changed to accept dictionary format171 phrases: List[Phrase] = [] # Made optional with default empty list172 level: str = "" # Add level field with default empty string173 174# Add global word map175word_map: Dict[str, Word] = {}176 177def get_level_from_filename(filename: str) -> str:178 # Extract level from filenames like "1-初中-顺序.json"179 match = re.match(r'\d+-(.+?)-顺序\.json', filename)180 return match.group(1) if match else "unknown"181# Add initialization function182def init_word_map():183 current_dir = os.path.dirname(os.path.abspath(__file__))184 json_dir = os.path.join(current_dir, "json")185 stats = {186 "total_words": 0,187 "total_files": 0,188 "file_stats": {}189 }190 191 try:192 for filename in os.listdir(json_dir):193 if filename.endswith('.json'):194 try:195 level = get_level_from_filename(filename)196 with open(os.path.join(json_dir, filename), 'r', encoding='utf-8') as f:197 words = json.load(f)198 word_count = len(words)199 stats["total_words"] += word_count200 stats["total_files"] += 1201 stats["file_stats"][filename] = word_count202 for word_data in words:203 # Convert legacy format to new format204 if 'translations' not in word_data:205 word_data['translations'] = [{206 'translation': word_data.get('translation', ''),207 'type': word_data.get('type', '')208 }]209 if 'phrases' not in word_data:210 word_data['phrases'] = []211 212 word_data['level'] = level213 word = Word(**word_data)214 word_map[word.word.lower()] = word215 logger.info(f"Loaded {filename}: {word_count} words")216 except Exception as e:217 logger.error(f"Error loading {filename}: {str(e)}")218 continue219 220 logger.info(f"Dictionary initialization complete:")221 logger.info(f"Total files processed: {stats['total_files']}")222 logger.info(f"Total words loaded: {stats['total_words']}")223 return stats224 except Exception as e:225 logger.error(f"Fatal error in init_word_map: {str(e)}")226 return stats227 228 229# Add cache configuration230# Update cache file location231CACHE_DIR = os.path.join(tempfile.gettempdir(), "flash_api_cache")232CACHE_FILE = os.path.join(CACHE_DIR, "ai_translation_cache.json")233ai_cache: Dict[str, dict] = {}234 235# Load cache on startup236def save_cache():237 try:238 # Create cache directory if it doesn't exist239 os.makedirs(CACHE_DIR, exist_ok=True)240 241 with open(CACHE_FILE, 'w', encoding='utf-8') as f:242 json.dump(ai_cache, f, ensure_ascii=False, indent=2)243 logger.info(f"Cache saved to: {CACHE_FILE}")244 except PermissionError as pe:245 logger.error(f"Permission denied writing to cache: {pe}")246 except Exception as e:247 logger.error(f"Error saving cache: {e}")248 249def load_cache():250 global ai_cache251 try:252 if os.path.exists(CACHE_FILE):253 with open(CACHE_FILE, 'r', encoding='utf-8') as f:254 ai_cache = json.load(f)255 logger.info(f"Loaded {len(ai_cache)} cached translations from: {CACHE_FILE}")256 except Exception as e:257 logger.error(f"Error loading cache: {e}")258 ai_cache = {}259 260# Add translate endpoint261@app.get("/translate/{word}")262async def translate_word(word: str):263 start_time = time.time()264 logger.info(f"Translation request received for word: {word}")265 266 try:267 word = word.lower().strip()268 logger.debug(f"Processed word: {word}")269 270 # Check word map271 # if word in word_map:272 # logger.info(f"Word found in map: {word}")273 # word_data = word_map[word]274 # logger.debug(f"Word data: {word_data}")275 276 # # Format all translations277 # translations_text = []278 # for trans in word_data.translations:279 # translation = trans['translation']280 # type_info = trans['type']281 # translations_text.append(f"({type_info}) {translation}")282 283 # # Join translations with separators284 # translations_combined = " | ".join(translations_text)285 # logger.debug(f"Combined translations: {translations_combined}")286 287 # # Handle examples288 # examples = []289 # if word_data.phrases:290 # examples = [f"{p.phrase}: {p.translation}" for p in word_data.phrases[:3]]291 # logger.debug(f"Examples found: {examples}")292 293 # # Build response with proper formatting294 # formatted_response = f"{word} [{word_data.level}]: {translations_combined}"295 # if examples:296 # formatted_response += f"\n\n例句:\n{chr(10).join(examples)}"297 298 # elapsed = time.time() - start_time299 # logger.info(f"Word map translation completed in {elapsed:.2f}s")300 301 # return {302 # "status": 200,303 # "data": {304 # "response": formatted_response,305 # "word": word,306 # "level": word_data.level, # Add level info here307 # "translations": word_data.translations,308 # "examples": examples309 # }310 # }311 312 # Check AI cache313 # if word in ai_cache:314 # logger.info(f"Word found in AI cache: {word}")315 # elapsed = time.time() - start_time316 # logger.info(f"Cache hit completed in {elapsed:.2f}s")317 # return ai_cache[word]318 319 # Fallback to AI translation320 logger.info("Word not found in cache, calling AI API")321 # Fallback to AI translation322 logger.info("Word not found in map, falling back to AI translation")323 try:324 request = AskRequest(325 prompt=f'''翻译以下英文326 {word}327 每行一个 格式参考,不要任何md格式,分别要有音标,单词属性(名词,动词,形容词),中文翻译,英文解析,例句,近义词,反义词,词性328 格式参考:329 hello:/həˈləʊ/| n. vt. int.|你好,问候语,|例句:Hello, how are you? 你好,你好吗?|近义词:hi, hey, |反义词:sick, bad.''',330 model="GEMINI"331 )332 logger.debug(f"AI Request: {request}")333 334 result = await forward_ask(request)335 336 # Cache the result337 #ai_cache[word] = result338 #save_cache()339 logger.debug(f"AI Response: {result}")340 341 elapsed = time.time() - start_time342 logger.info(f"AI translation completed in {elapsed:.2f}s")343 return result344 345 except Exception as e:346 logger.error(f"AI translation error: {str(e)}", exc_info=True)347 raise HTTPException(status_code=500, detail=str(e))348 349 except Exception as e:350 logger.error(f"Translation error: {str(e)}", exc_info=True)351 raise HTTPException(status_code=500, detail=str(e))352 353# Add cleanup functions354def cleanup_temp_files():355 try:356 # Clean temp directory357 temp_dir = os.path.join(tempfile.gettempdir(), "flash_api_cache")358 if os.path.exists(temp_dir):359 shutil.rmtree(temp_dir)360 logger.info(f"Cleaned up temp directory: {temp_dir}")361 except Exception as e:362 logger.error(f"Error cleaning temp files: {e}")363 364def cleanup_cache():365 global ai_cache366 ai_cache = {}367 logger.info("Cache cleared")368 369 370# Initialize word map on startup371@app.on_event("startup")372async def startup_event():373 #init_word_map()// waste of memory374 #load_cache()375 cleanup_temp_files()376 cleanup_cache()377 logger.info(f"Memory usage after init: {get_memory_usage()}")378 379@app.on_event("shutdown")380async def shutdown_event():381 # Cleanup on shutdown382 cleanup_temp_files()383 cleanup_cache()384 logger.info("Application shutdown cleanup complete")385def get_memory_usage():386 process = psutil.Process()387 memory_info = process.memory_info()388 389 # Get system memory info390 system = psutil.virtual_memory()391 392 return {393 "process": {394 "rss": f"{memory_info.rss / 1024 / 1024:.2f} MB",395 "rss_percent": f"{memory_info.rss / system.total * 100:.2f}%",396 "vms": f"{memory_info.vms / 1024 / 1024:.2f} MB",397 "vms_percent": f"{memory_info.vms / system.total * 100:.2f}%"398 },399 "system": {400 "total": f"{system.total / 1024 / 1024:.2f} MB",401 "available": f"{system.available / 1024 / 1024:.2f} MB",402 "used_percent": f"{system.percent:.2f}%"403 },404 "word_map": {405 "entries": len(word_map),406 "memory": f"{sys.getsizeof(word_map) / 1024 / 1024:.2f} MB",407 "memory_percent": f"{sys.getsizeof(word_map) / system.total * 100:.4f}%"408 }409 }410 411@app.get("/memory")412async def memory_status():413 return get_memory_usage()414 415# Add new endpoint416@app.get("/proxy")417async def proxy_request(url: str, request: Request):418 try:419 # Get random user agent420 user_agent = random.choice(USER_AGENTS)421 422 #print url423 424 logger.info(f"Proxy request received for: {url}")425 426 # Prepare headers427 headers = {428 'User-Agent': user_agent,429 'Accept': 'application/json, text/plain, */*',430 'Accept-Language': 'en-US,en;q=0.9',431 'Origin': 'https://www.youtube.com',432 'Referer': 'https://www.youtube.com/',433 'Sec-Fetch-Dest': 'empty',434 'Sec-Fetch-Mode': 'cors',435 'Sec-Fetch-Site': 'same-site',436 'Connection': 'keep-alive'437 }438 439 # Set timeout440 timeout = aiohttp.ClientTimeout(total=10) # 10 seconds timeout441 442 async with aiohttp.ClientSession(timeout=timeout) as session:443 async with session.get(url, headers=headers) as response:444 # Check HTTP status445 if response.status != 200:446 raise HTTPException(447 status_code=response.status,448 detail=f"HTTP error: {response.status}"449 )450 451 # Parse JSON response452 data = await response.json()453 454 #print data's length455 456 logger.info(f"Received youtube subtile data: {len(data)} bytes")457 458 459 # Validate data format460 if not data or 'events' not in data:461 raise HTTPException(462 status_code=400,463 detail="Invalid subtitle data format"464 )465 466 return data467 468 except TimeoutError:469 raise HTTPException(status_code=408, detail="Request timeout")470 except Exception as e:471 logger.error(f"Proxy error: {str(e)}")472 raise HTTPException(status_code=500, detail=str(e))473 474templates = Jinja2Templates(directory="templates")475 476@app.get("/chat")477async def chat_page(request: Request):478 return templates.TemplateResponse("chat.html", {"request": request})479 480@app.post("/testchat")481async def test_chat(data: dict):482 try:483 response = ollama.chat(model='gemma:2b', messages=[484 {485 'role': 'user',486 'content': data['prompt']487 }488 ])489 return {"response": response['message']['content']}490 except Exception as e:491 raise HTTPException(status_code=500, detail=str(e))492if __name__ == "__main__":493 uvicorn.run(app, host="0.0.0.0", port=7860)