import os import requests import gradio as gr import re from datetime import datetime # === ១. បន្ថែម Logic សម្រាប់ប្តូររូបរាង (Theme) តាមរដូវកាល === def get_seasonal_config(): month = datetime.now().month # រដូវរងា (វិច្ឆិកា, ធ្នូ, មករា) - ពណ៌ខៀវត្រជាក់ if month in [11, 12, 1]: return { "bg": "#0f172a", "accent": "#38bdf8", "btn": "#0369a1", "title": "❄️ WINTER FROST EDITION" } # រដូវក្ដៅ (មីនា, មេសា, ឧសភា) - ពណ៌មាស/ទឹកក្រូច elif month in [3, 4, 5]: return { "bg": "#1c1917", "accent": "#fbbf24", "btn": "#b45309", "title": "☀️ SUMMER BLAZE EDITION" } # រដូវភ្លៀង/ស្លឹកឈើជ្រុះ (ផ្សេងៗ) - ពណ៌បៃតងស្រស់ else: return { "bg": "#064e3b", "accent": "#10b981", "btn": "#047857", "title": "🌿 RAINY FOREST EDITION" } # ទាញយកការកំណត់ពណ៌តាមខែបច្ចុប្បន្ន s_theme = get_seasonal_config() # Registry for Key Status key_status_registry = {} class AIService: @staticmethod def get_all_keys(prefix): env_name = prefix.rstrip('_') key = os.environ.get(env_name) if key: return [key] return [] @staticmethod def get_status_html(engine, target_lang=None): if "Gemini" in engine: prefix = "GEMINI_API_KEY" elif "Llama" in engine: prefix = "GROQ_API_KEY" else: prefix = "SEA_LION_API_KEY" keys = AIService.get_all_keys(prefix) label = engine.split(" ")[-1] status_label = "ស្ថានភាព/Status" # ប្រើពណ៌ accent តាមរដូវកាលសម្រាប់ Label html = f"
{label} {status_label}:" if not keys: html += "រកមិនឃើញ Key" else: for k in keys: state = key_status_registry.get(k, "ready") color = "#22c55e" if state == "ready" else "#ef4444" html += f"
" return html + "
" @staticmethod def call_api(engine, prompt): temp = 0.85 if "Gemini" in engine: keys = AIService.get_all_keys("GEMINI_API_KEY") models = ["gemini-3-flash", "gemini-2.5-flash", "gemini-1.5-flash"] for key in keys: for model_name in models: try: url = f"https://generativelanguage.googleapis.com/v1beta/models/{model_name}:generateContent?key={key}" res = requests.post(url, json={ "contents": [{"parts": [{"text": prompt}]}], "generationConfig": {"temperature": temp} }, timeout=30) if res.status_code == 200: key_status_registry[key] = "ready" return res.json()['candidates'][0]['content']['parts'][0]['text'].strip() except: continue key_status_registry[key] = "dead" elif "Llama" in engine: keys = AIService.get_all_keys("GROQ_API_KEY") for key in keys: try: res = requests.post("https://api.groq.com/openai/v1/chat/completions", headers={"Authorization": f"Bearer {key}"}, json={"model": "llama-3.3-70b-versatile", "messages": [{"role": "user", "content": prompt}], "temperature": temp}, timeout=30) if res.status_code == 200: key_status_registry[key] = "ready" return res.json()['choices'][0]['message']['content'].strip() except: continue else: # SEA-LION keys = AIService.get_all_keys("SEA_LION_API_KEY") for key in keys: try: res = requests.post("https://api.sea-lion.ai/v1/chat/completions", headers={"Authorization": f"Bearer {key}"}, json={"model": "aisingapore/Gemma-SEA-LION-v4-27B-IT", "messages": [{"role": "user", "content": prompt}], "temperature": 0.7}, timeout=60) if res.status_code == 200: key_status_registry[key] = "ready" return res.json()['choices'][0]['message']['content'].strip() except: continue return None def translator_hub(text, target_lang, engine): if not text.strip(): return "", AIService.get_status_html(engine) lang_name = re.sub(r'[^\w\s]', '', target_lang).strip() instruction = f"""Translate the content into {lang_name} naturally. STRICT RULES: 1. OUTPUT ONLY the translated text. NO explanations or notes. 2. If the input is SRT format, strictly preserve the timing and numbering. 3. CONTEXT & EMOTION: Understand the scene. Use natural slang and idioms (Modern/Classic). 4. PRONOUNS: - Family: បង, ប្អូន, បង, អូន, ម៉ាក់/ម៉ែ, ប៉ា/ពុក, កូន, ពូ, មីង... - Couples/Home: ប្តី, ប្រពន្ធ, បង, អូន... - Conflict/Enemies: យើង, អញ, អ្ហែង, ឯង... (Allow swear words and insults if in context). 5. VARIATION: If asked again, try to find better synonyms or more natural flow.""" prompt = f"{instruction}\n\nCONTENT TO TRANSLATE:\n{text}" result = AIService.call_api(engine, prompt) if result: return re.sub(r'```[a-zA-Z]*\n?|```', '', result).strip(), AIService.get_status_html(engine) return "❌ Error: API Key ឬ Model មិនដំណើរការ", AIService.get_status_html(engine) # === ២. កែសម្រួល CSS ឱ្យដើរតាម Dynamic Theme === custom_css = f""" body, .gradio-container {{ background-color: {s_theme['bg']} !important; color: white !important; }} .btn-trans {{ background-color: {s_theme['btn']} !important; color: white !important; border: 1px solid {s_theme['accent']} !important; }} .btn-clear {{ background-color: #475569 !important; color: white !important; }} h1 {{ color: {s_theme['accent']} !important; text-shadow: 0 0 10px {s_theme['accent']}55; }} .fieldset, .input-label {{ color: {s_theme['accent']} !important; }} """ with gr.Blocks(title=f"SRT Pro - {s_theme['title']}", css=custom_css) as demo: gr.Markdown(f"

🎬 {s_theme['title']}

") with gr.Row(): with gr.Column(): lang_opt = gr.Dropdown(["🇰🇭 Khmer", "🇺🇸 English", "🇨🇳 Chinese", "🇹🇭 Thai"], value="🇰🇭 Khmer", label="Language") engine_opt = gr.Radio(["💎 Gemini", "🦙 Llama", "🦁 SEA-LION"], value="💎 Gemini", label="Model Engine") status_ui = gr.HTML(AIService.get_status_html("💎 Gemini")) input_box = gr.Textbox(label="Original Content", lines=10, placeholder="បញ្ចូលអត្ថបទ ឬ SRT នៅទីនេះ...") with gr.Row(): btn_clear = gr.Button("🗑️ Clear", elem_classes="btn-clear") btn_trans = gr.Button("⚡ Translate", variant="primary", elem_classes="btn-trans") with gr.Column(): output_box = gr.Textbox(label="Result", lines=20, interactive=False) btn_copy = gr.Button("📋 Copy Result") # Events engine_opt.change(AIService.get_status_html, inputs=[engine_opt], outputs=[status_ui]) btn_trans.click(translator_hub, [input_box, lang_opt, engine_opt], [output_box, status_ui]) btn_copy.click(None, output_box, js="(v) => {{ navigator.clipboard.writeText(v); alert('✅ ចម្លងរួចរាល់!'); }}") btn_clear.click(lambda: ("", ""), None, [input_box, output_box]) if __name__ == "__main__": # Launch កម្មវិធី demo.launch(server_name="0.0.0.0", server_port=7860, css=custom_css, ssr_mode=False)