import os import pandas as pd from datetime import datetime from fastapi import FastAPI, HTTPException, BackgroundTasks from fastapi.middleware.cors import CORSMiddleware from pydantic import BaseModel from openai import AsyncOpenAI from dotenv import load_dotenv load_dotenv() app = FastAPI(title="PurePolyglot Hybrid Backend", version="1.0.0") # Enable CORS for the Vite SPA app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # Attempt Qwen first, fallback to Groq QWEN_API_KEY = os.getenv("QWEN_API_KEY") QWEN_BASE_URL = os.getenv("QWEN_BASE_URL", "https://dashscope-intl.aliyuncs.com/compatible-mode/v1") QWEN_MODEL_NAME = os.getenv("QWEN_MODEL_NAME", "qwen3-coder-80b-instruct") GROQ_API_KEY = os.getenv("GROQ_API_KEY") if QWEN_API_KEY and QWEN_API_KEY != "your-api-key-here": client = AsyncOpenAI(api_key=QWEN_API_KEY, base_url=QWEN_BASE_URL) MODEL_NAME = QWEN_MODEL_NAME NODE_TYPE = "Qwen Hybrid Node" elif GROQ_API_KEY: client = AsyncOpenAI(api_key=GROQ_API_KEY, base_url="https://api.groq.com/openai/v1") MODEL_NAME = "llama-3.3-70b-versatile" NODE_TYPE = "Groq Hybrid Node" else: client = None MODEL_NAME = None NODE_TYPE = "Offline" class TranslationRequest(BaseModel): text: str source_language: str = "Unknown" source_dialect: str = "Standard" target_language: str target_dialect: str user_key: str = "Polyglot Player" class TranslationResponse(BaseModel): original_text: str translated_text: str target_dialect: str node: str def log_to_pending_queue(request: TranslationRequest, translation: str): """Background task to log translations to pending_approvals.csv""" pending_file = "/app/pending_approvals.csv" if os.path.exists("/app") else "pending_approvals.csv" new_entry = { "User": request.user_key, "Data_Origin": "Game: Polyglot Chat", "Utterance": request.text, "Dialect": request.target_dialect, "Clarification": translation, "Clarification_Source": NODE_TYPE, "Tone": "Neutral / Conversational", "Context": f"Translated from {request.source_language} ({request.source_dialect})", "Pragmatic_Analysis": "", "Audio": "", "Timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), "Chain_ID": "", "Approvers": "", "Language": request.target_language } try: if os.path.exists(pending_file): df = pd.read_csv(pending_file) else: df = pd.DataFrame(columns=new_entry.keys()) df = pd.concat([df, pd.DataFrame([new_entry])], ignore_index=True) df.to_csv(pending_file, index=False) print(f"✅ Logged to {pending_file} for peer review.") # Sync to Hugging Face PureChain_Dataset if HF_TOKEN is present from huggingface_hub import HfApi hf_token = os.environ.get("HF_TOKEN") if hf_token: api = HfApi(token=hf_token) api.upload_file( path_or_fileobj=pending_file, path_in_repo="pending_approvals.csv", repo_id="toecm/PureChain_Dataset", repo_type="dataset", commit_message="🔄 Auto-sync: Polyglot Chat text translation added to pending approvals queue" ) print("☁️ Synced Polyglot Chat text translation entry to HF PureChain_Dataset.") except Exception as e: print(f"Failed to save translation to pending queue: {e}") @app.post("/api/translate", response_model=TranslationResponse) async def translate_text(request: TranslationRequest, background_tasks: BackgroundTasks): if not client: raise HTTPException(status_code=500, detail="No LLM API key configured (neither Qwen nor Groq).") system_prompt = ( f"You are an expert polyglot interpreter specializing in deep cultural and linguistic dialects.\n" f"Translate the following text from {request.source_language} ({request.source_dialect}) " f"into {request.target_language} ({request.target_dialect}).\n" f"Output ONLY the raw translated string. Do not include quotes, explanations, or thinking traces." ) try: response = await client.chat.completions.create( model=MODEL_NAME, messages=[ {"role": "system", "content": system_prompt}, {"role": "user", "content": request.text} ], temperature=0.3, max_tokens=256 ) translated_text = response.choices[0].message.content.strip() # Log to CSV in the background background_tasks.add_task(log_to_pending_queue, request, translated_text) return TranslationResponse( original_text=request.text, translated_text=translated_text, target_dialect=f"{request.target_language} ({request.target_dialect})", node=NODE_TYPE ) except Exception as e: print(f"Error calling {NODE_TYPE} API: {e}") raise HTTPException(status_code=500, detail=str(e)) @app.get("/") async def root(): return {"message": f"PurePolyglot Hybrid Backend Online ({NODE_TYPE})"} if __name__ == "__main__": import uvicorn uvicorn.run("api:app", host="0.0.0.0", port=8000, reload=True)