File size: 8,641 Bytes
8e2391b
dd7ea47
 
bc7679f
7b3b0be
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e436ee8
e67dc65
4a03fb5
 
37b2308
8dc8ce9
 
00bba9a
dd0d837
 
 
 
8dc8ce9
394eebf
92e1442
30dc47c
dd0d837
30dc47c
dd0d837
30dc47c
 
 
4a03fb5
 
3435a4d
7b3b0be
 
0f15f5c
 
 
 
 
 
 
 
364c005
394eebf
37b2308
364c005
8326d73
0f15f5c
dd0d837
34af201
364c005
34af201
 
 
 
 
 
 
 
 
 
8326d73
34af201
9ffd2f1
364c005
dd0d837
364c005
 
 
 
0f15f5c
364c005
 
 
dd7ea47
34af201
 
 
 
 
 
 
 
 
 
 
 
bc7679f
1dc43c7
dd7ea47
92e1442
da663a0
8326d73
 
 
 
 
 
 
 
 
 
 
 
 
a9e0c2a
8326d73
cca8473
34af201
 
dd7ea47
7b3b0be
 
 
 
 
 
 
 
36a7ff5
7b3b0be
 
dd7ea47
dd0d837
34af201
 
92e1442
8326d73
 
 
 
dd0d837
aae96c1
dd0d837
30dc47c
8326d73
30dc47c
dd7ea47
7b3b0be
8326d73
92e1442
dd7ea47
7b3b0be
aae96c1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
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"<div style='display: flex; gap: 8px; align-items: center; margin-bottom: 10px;'><b style='color: {s_theme['accent']}; font-size: 12px;'>{label} {status_label}:</b>"
        
        if not keys:
            html += "<span style='color: #ef4444; font-size: 10px;'>αžšαž€αž˜αž·αž“αžƒαžΎαž‰ Key</span>"
        else:
            for k in keys:
                state = key_status_registry.get(k, "ready")
                color = "#22c55e" if state == "ready" else "#ef4444"
                html += f"<div style='width: 10px; height: 10px; background: {color}; border-radius: 50%; box-shadow: 0 0 5px {color};'></div>"
        return html + "</div>"

    @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"<h1 style='text-align: center;'>🎬 {s_theme['title']}</h1>")
    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)