Spaces:
Paused
Paused
| # Gradio_11Labs_Enhanced_with_Proxy.py | |
| # ============================================================================ | |
| # 2025-07-09 – *Proxy + Full Voice Manager + UX Tweaks + Privacy Protection* | |
| # --------------------------------------------------------------------------- | |
| # ✦ Proxy Tab: bulk add/test, auto/manual assign (≤3 keys/proxy), delete bad | |
| # ✦ Batch Tab: refresh-all, live token count, total credit, verify key-proxy | |
| # – "🌀 Tạo giọng nói" sits *above* textbox | |
| # – auto-pick checkbox at bottom | |
| # ✦ API-Key Tab: shows proxy **host** only (no creds/port) | |
| # ✦ Voice Tab: restored original full manager (add/edit/delete/reset/export) | |
| # ✦ Privacy: Hide sensitive info (API keys, proxy credentials, voice IDs) | |
| # ✦ Session: Use gr.State for per-browser isolation, auto-clear after session ends | |
| # --------------------------------------------------------------------------- | |
| import gradio as gr | |
| import os, json, time, urllib.parse, requests, random | |
| from datetime import datetime | |
| import pandas as pd | |
| from dotenv import load_dotenv | |
| from elevenlabs.client import ElevenLabs | |
| import tempfile | |
| import uuid | |
| from functools import wraps | |
| def log_exception(e, context=""): | |
| import traceback | |
| tb = traceback.format_exc() | |
| msg = f"❌ Lỗi ở {context}: {str(e)}\n{tb}" | |
| print(msg) | |
| return msg | |
| # === .env === | |
| load_dotenv() | |
| DEFAULT_MODEL = os.getenv("ELEVENLABS_MODEL_ID", "eleven_multilingual_v2") | |
| DEFAULT_FORMAT = os.getenv("ELEVENLABS_OUTPUT_FORMAT", "mp3_44100") | |
| # === Default Data === | |
| def load_default_voices(): | |
| """Load default voices from voices.json""" | |
| try: | |
| with open("voices.json", "r", encoding="utf-8") as f: | |
| return json.load(f) | |
| except: | |
| return {} | |
| def load_default_proxies(): | |
| """Load default proxies from proxies.json""" | |
| try: | |
| with open("proxies.json", "r", encoding="utf-8") as f: | |
| return json.load(f) | |
| except: | |
| return {} | |
| DEFAULT_VOICE_SETTINGS = { | |
| "speed": 1.0, | |
| "stability": 0.5, | |
| "similarity_boost": 0.75, | |
| "style_exaggeration": 0.0, | |
| "use_speaker_boost": True, | |
| } | |
| MODELS = ["eleven_monolingual_v1", "eleven_multilingual_v1", "eleven_multilingual_v2"] | |
| # === Session Management === | |
| def session_wrapper(func): | |
| """Decorator to ensure session data access""" | |
| def wrapper(*args, **kwargs): | |
| return func(*args, **kwargs) | |
| return wrapper | |
| # === Privacy helpers === | |
| def mask_api_key(key): | |
| """Mask API key: show first 4 and last 4 chars""" | |
| if not key or len(key) < 8: | |
| return key | |
| return f"{key[:4]}...{key[-4:]}" | |
| def mask_voice_id(voice_id): | |
| """Mask Voice ID: show first 3 and last 3 chars""" | |
| if not voice_id or len(voice_id) < 6: | |
| return voice_id | |
| return f"{voice_id[:3]}...{voice_id[-3:]}" | |
| def mask_proxy_url(url): | |
| """Mask credentials in proxy URL""" | |
| try: | |
| parsed = urllib.parse.urlparse(url) | |
| if parsed.username and parsed.password: | |
| masked_netloc = f"***:***@{parsed.hostname}" | |
| if parsed.port: | |
| masked_netloc += f":{parsed.port}" | |
| return urllib.parse.urlunparse(( | |
| parsed.scheme, masked_netloc, parsed.path, | |
| parsed.params, parsed.query, parsed.fragment | |
| )) | |
| return url | |
| except: | |
| return url | |
| def create_key_display_map(api_keys): | |
| """Create mapping between real API key and display name""" | |
| display_map = {} | |
| reverse_map = {} | |
| for i, key in enumerate(api_keys.keys(), 1): | |
| display_name = f"Key-{i:02d} ({mask_api_key(key)})" | |
| display_map[key] = display_name | |
| reverse_map[display_name] = key | |
| return display_map, reverse_map | |
| def get_key_choices_for_display(api_keys): | |
| """Get list of masked API keys for display""" | |
| display_map, _ = create_key_display_map(api_keys) | |
| return list(display_map.values()) | |
| def get_real_key_from_display(display_name, api_keys): | |
| """Get real API key from display name""" | |
| _, reverse_map = create_key_display_map(api_keys) | |
| return reverse_map.get(display_name, display_name) | |
| def create_proxy_display_map(proxies): | |
| """Create mapping between real proxy URL and display name""" | |
| display_map = {} | |
| reverse_map = {} | |
| for i, url in enumerate(proxies.keys(), 1): | |
| display_name = f"Proxy-{i:02d} ({mask_proxy_url(url)})" | |
| display_map[url] = display_name | |
| reverse_map[display_name] = url | |
| return display_map, reverse_map | |
| def get_proxy_choices_for_display(proxies): | |
| """Get list of masked proxies for display""" | |
| display_map, _ = create_proxy_display_map(proxies) | |
| return list(display_map.values()) | |
| def get_real_proxy_from_display(display_name, proxies): | |
| """Get real proxy URL from display name""" | |
| _, reverse_map = create_proxy_display_map(proxies) | |
| return reverse_map.get(display_name, display_name) | |
| # === Generic helpers === | |
| def safe_get_default(choices, default_func): | |
| """Safely get default value that exists in choices""" | |
| if not choices: | |
| return None | |
| default = default_func() | |
| return default if default in choices else choices[0] | |
| # === ElevenLabs === | |
| def get_client(api_key): | |
| return ElevenLabs(api_key=api_key) | |
| def get_api_usage(api_key, bypass_proxy=False, proxies=None): | |
| import urllib3 | |
| urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) | |
| proxy_url = None if bypass_proxy else get_proxy_of_key(api_key, proxies) | |
| proxies_dict = {"http": proxy_url, "https": proxy_url} if proxy_url else None | |
| try: | |
| r = requests.get( | |
| "https://api.elevenlabs.io/v1/user/subscription", | |
| headers={"xi-api-key": api_key}, | |
| proxies=proxies_dict, | |
| timeout=4, | |
| verify=False if proxy_url else True | |
| ) | |
| if r.status_code == 200: | |
| d = r.json() | |
| return { | |
| "status": "✅ OK", | |
| "used": d.get("character_count", 0), | |
| "limit": d.get("character_limit", 0), | |
| "tier": d.get("tier", ""), | |
| "remaining": d.get("character_limit", 0) - d.get("character_count", 0), | |
| } | |
| return {"status": f"❌ {r.status_code}"} | |
| except Exception as e: | |
| return {"status": f"⚠️ {str(e).split(' ')[0]}"} | |
| def total_credit(api_keys): | |
| return sum(v.get("remaining", 0) for v in api_keys.values()) | |
| # === Proxy helpers === | |
| def proxy_host(url: str): | |
| try: | |
| return urllib.parse.urlsplit(url).hostname or "" | |
| except: | |
| return "" | |
| def format_proxy_table(proxies): | |
| rows = [] | |
| for url, info in proxies.items(): | |
| masked_url = mask_proxy_url(url) | |
| masked_keys = [mask_api_key(k) for k in info.get("assigned_keys", [])] | |
| sample_keys = ", ".join(masked_keys[:3]) + ("…" if len(masked_keys) > 3 else "") | |
| rows.append([ | |
| masked_url, | |
| info.get("status", "-"), | |
| info.get("latency", "-"), | |
| len(info.get("assigned_keys", [])), | |
| sample_keys, | |
| info.get("last_checked", "-"), | |
| ]) | |
| return pd.DataFrame(rows, columns=["Proxy", "Status", "Latency (ms)", "#Keys", "Sample Keys", "Last check"]) | |
| def test_proxy_once(url: str, timeout=3): # giảm timeout từ 6 -> 3 | |
| import urllib3 | |
| urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) | |
| proxies = {"http": url, "https": url} | |
| t0 = time.time() | |
| try: | |
| r = requests.get("https://api.ipify.org?format=json", proxies=proxies, timeout=timeout, verify=False) | |
| if r.status_code == 200: | |
| return {"status": "✅ OK", "latency": int((time.time()-t0)*1000)} | |
| except Exception as e: | |
| return {"status": f"⚠️ {str(e).split(' ')[0]}", "latency": None} | |
| return {"status": "❌ Failed", "latency": None} | |
| def add_and_test_proxies(text, proxies_state): | |
| try: | |
| proxies = proxies_state.copy() | |
| added = 0 | |
| for line in text.strip().splitlines(): | |
| p = line.strip() | |
| if not p: continue | |
| if p not in proxies: | |
| proxies[p] = {"assigned_keys": []} | |
| added += 1 | |
| proxies[p].update(test_proxy_once(p)) | |
| proxies[p]["last_checked"] = datetime.utcnow().isoformat(timespec="seconds") | |
| return format_proxy_table(proxies), f"✅ Đã thêm/kiểm tra {added} proxy.", proxies | |
| except Exception as e: | |
| msg = log_exception(e, "add_and_test_proxies") | |
| return None, msg, proxies_state | |
| def refresh_proxy_status(proxies_state): | |
| proxies = proxies_state.copy() | |
| for url in proxies: | |
| proxies[url].update(test_proxy_once(url)) | |
| proxies[url]["last_checked"] = datetime.utcnow().isoformat(timespec="seconds") | |
| return format_proxy_table(proxies), "🔄 Đã refresh proxy.", proxies | |
| def get_proxy_of_key(key, proxies): | |
| for url, info in proxies.items(): | |
| if key in info.get("assigned_keys", []): | |
| return url | |
| return "" | |
| def assign_proxy_to_key(proxy_display, api_key_display, proxies_state, api_keys_state): | |
| proxy_url = get_real_proxy_from_display(proxy_display, proxies_state) | |
| api_key = get_real_key_from_display(api_key_display, api_keys_state) | |
| proxies = proxies_state.copy() | |
| keys = api_keys_state.copy() | |
| if proxy_url not in proxies: | |
| return format_proxy_table(proxies), "❌ Proxy không tồn tại!", proxies, keys | |
| status = proxies[proxy_url].get("status", "") | |
| if not (status.startswith("✅") or "HTTPSConnectionPool" in status): | |
| return format_proxy_table(proxies), "❌ Proxy không hoạt động!", proxies, keys | |
| if api_key not in keys: | |
| return format_proxy_table(proxies), "❌ API Key không tồn tại!", proxies, keys | |
| for info in proxies.values(): | |
| if api_key in info.get("assigned_keys", []): | |
| info["assigned_keys"].remove(api_key) | |
| proxies[proxy_url].setdefault("assigned_keys", []).append(api_key) | |
| return format_proxy_table(proxies), "✅ Đã gắn key.", proxies, keys | |
| def smart_proxy_assignment(proxies_state, api_keys_state): | |
| proxies = proxies_state.copy() | |
| keys = api_keys_state.copy() | |
| active_proxies = [] | |
| for url, info in proxies.items(): | |
| status = info.get("status", "") | |
| if status.startswith("✅") or "HTTPSConnectionPool" in status: | |
| active_proxies.append((url, info)) | |
| if not active_proxies: | |
| return [], list(keys.keys()), "⚠️ Không gắn proxy – Đang dùng IP thật (Vẫn ổn nếu xài dưới 3 Key/ngày).", proxies | |
| for url, info in proxies.items(): | |
| info["assigned_keys"] = [] | |
| key_list = list(keys.keys()) | |
| random.shuffle(key_list) | |
| random.shuffle(active_proxies) | |
| num_keys = len(key_list) | |
| num_proxies = len(active_proxies) | |
| assigned_keys = [] | |
| unassigned_keys = [] | |
| if num_proxies >= num_keys: | |
| for i, key in enumerate(key_list): | |
| active_proxies[i][1]["assigned_keys"].append(key) | |
| assigned_keys.append(key) | |
| message = f"✅ Gắn 1:1, {len(assigned_keys)} key được gắn với {len(assigned_keys)} proxy, dư {num_proxies - num_keys} proxy." | |
| elif num_keys < 3 * num_proxies: | |
| keys_per_proxy_base = num_keys // num_proxies | |
| extra_keys = num_keys % num_proxies | |
| key_index = 0 | |
| proxies_with_extra = 0 | |
| proxies_normal = 0 | |
| for i, (url, info) in enumerate(active_proxies): | |
| keys_for_this_proxy = keys_per_proxy_base + (1 if i < extra_keys else 0) | |
| for _ in range(keys_for_this_proxy): | |
| if key_index < len(key_list): | |
| info["assigned_keys"].append(key_list[key_index]) | |
| assigned_keys.append(key_list[key_index]) | |
| key_index += 1 | |
| if keys_for_this_proxy > keys_per_proxy_base: | |
| proxies_with_extra += 1 | |
| else: | |
| proxies_normal += 1 | |
| if extra_keys > 0: | |
| message = f"✅ Phân bổ hợp lý: {proxies_with_extra} proxy nhận {keys_per_proxy_base + 1} key, {proxies_normal} proxy nhận {keys_per_proxy_base} key." | |
| else: | |
| message = f"✅ Phân bổ đều: mỗi proxy nhận {keys_per_proxy_base} key." | |
| else: | |
| max_assignable = 3 * num_proxies | |
| keys_to_assign = key_list[:max_assignable] | |
| unassigned_keys = key_list[max_assignable:] | |
| key_index = 0 | |
| for url, info in active_proxies: | |
| for _ in range(3): | |
| if key_index < len(keys_to_assign): | |
| info["assigned_keys"].append(keys_to_assign[key_index]) | |
| assigned_keys.append(keys_to_assign[key_index]) | |
| key_index += 1 | |
| message = f"✅ Mỗi proxy gắn 3 key, {len(assigned_keys)}/{num_keys} key được gắn." | |
| if unassigned_keys: | |
| message += f" ⚠️ {len(unassigned_keys)} key chưa gắn: {', '.join([mask_api_key(k) for k in unassigned_keys[:3]])}{'...' if len(unassigned_keys) > 3 else ''}" | |
| return assigned_keys, unassigned_keys, message, proxies | |
| def auto_assign(proxies_state, api_keys_state): | |
| proxy_table, _, proxies = refresh_proxy_status(proxies_state) | |
| assigned_keys, unassigned_keys, message, proxies = smart_proxy_assignment(proxies, api_keys_state) | |
| return proxy_table, message, proxies | |
| def delete_bad(proxies_state): | |
| proxies = proxies_state.copy() | |
| rem = [] | |
| for u, i in list(proxies.items()): | |
| bad = i.get("status", "").startswith(("❌", "⚠️")) | |
| if bad: | |
| rem.append(u) | |
| proxies.pop(u) | |
| return format_proxy_table(proxies), f"🗑️ Đã xoá {len(rem)} proxy lỗi.", proxies | |
| def filter_bad_proxies(proxies_state): | |
| proxies = proxies_state.copy() | |
| bad_proxies = {} | |
| for url, info in proxies.items(): | |
| is_bad = info.get("status", "").startswith(("❌", "⚠️")) | |
| if is_bad: | |
| bad_proxies[url] = info | |
| return format_proxy_table(bad_proxies) | |
| # === Voice helpers === | |
| def get_voice_list(voices_state): | |
| return list(voices_state.keys()) | |
| def get_default_voice(voices_state): | |
| lst = get_voice_list(voices_state) | |
| return lst[0] if lst else None | |
| def save_voice(name, voice_id, current_voice, voices_state): | |
| try: | |
| if not name or not voice_id: | |
| return "❌ Cần nhập tên và ID!", get_voice_list(voices_state), get_voice_list(voices_state), current_voice, voices_state | |
| voices = voices_state.copy() | |
| for n, v in voices.items(): | |
| if v.get("voice_id") == voice_id and n != name: | |
| return f"❌ Voice ID đã tồn tại ở '{n}'.", get_voice_list(voices), get_voice_list(voices), current_voice, voices | |
| voices[name] = {"voice_id": voice_id, "settings": DEFAULT_VOICE_SETTINGS.copy()} | |
| vl = get_voice_list(voices) | |
| return f"✅ Đã lưu '{name}'", vl, vl, name, voices | |
| except Exception as e: | |
| msg = log_exception(e, "save_voice") | |
| return msg, get_voice_list(voices_state), get_voice_list(voices_state), current_voice, voices_state | |
| def load_voice_for_edit(name, voices_state): | |
| voices = voices_state.copy() | |
| v = voices.get(name, {}) | |
| cfg = v.get("settings", DEFAULT_VOICE_SETTINGS.copy()) | |
| if not name: | |
| return "", "", *DEFAULT_VOICE_SETTINGS.values() | |
| voice_id = v.get("voice_id", "") | |
| masked_voice_id = mask_voice_id(voice_id) | |
| return name, masked_voice_id, cfg["speed"], cfg["stability"], cfg["similarity_boost"], cfg["style_exaggeration"], cfg["use_speaker_boost"] | |
| def delete_voice(name, confirm, cur, voices_state): | |
| if not name: | |
| return "❌ Chọn voice!", get_voice_list(voices_state), get_voice_list(voices_state), cur, voices_state | |
| if not confirm: | |
| return "⚠️ Hãy tick vào ô xác nhận xoá!", get_voice_list(voices_state), get_voice_list(voices_state), cur, voices_state | |
| voices = voices_state.copy() | |
| protected_voice_ids = { | |
| "TxGEqnHWrfWFTfGW9XjX", "TX3LPaxmHKxFdv7VOQHJ", "ErXwobaYiN019PkySvjV", | |
| "iP95p4xoKVk53GoZ742B", "VR6AewLTigWG4xSOukaG", "pNInz6obpgDQGcFmaJgB", | |
| "nPczCjzI2devNBz1zQrb", "CwhRBWXzGAHq8TQ4Fs17", "5Q0t7uMcjvnagumLfvZi", | |
| "29vD33N1CtxCmqQRPOHJ", "flq6f7yk4E4fJM5XTYuZ", "t0jbNlBVZ17f02VDIeMI", | |
| "pqHfZKP75CvOlQylNhV4", "LcfcDJNUP1GQjkzn1xUU", "z9fAnlkpzviPz146aGWa", | |
| "pMsXgVXv3BLzUgSXRplE", "XrExE9yKIg1WjnnlVkGX", "SAz9YHcvj6GT2YYXdXww", | |
| "N2lVS1w4EtoT3dr4eOWO", "2EiwWnXFnvU5JabPnv8n", "p28fY1cl6tovhD2M4WEH", | |
| "eC5XQ2bYx6LQHFG29bNv" | |
| } | |
| voice_info = voices.get(name) | |
| if voice_info and voice_info.get("voice_id") in protected_voice_ids: | |
| return "❌ Không được xoá voice mặc định!", get_voice_list(voices), get_voice_list(voices), cur, voices | |
| if name in voices: | |
| voices.pop(name) | |
| lst = get_voice_list(voices) | |
| new = lst[0] if lst else None | |
| return f"🗑️ Đã xoá '{name}'", lst, lst, new, voices | |
| return "❌ Không tìm thấy voice!", get_voice_list(voices), get_voice_list(voices), cur, voices | |
| def reset_voice(name, confirm, cur, voices_state): | |
| if not name: | |
| return "❌ Chọn voice!", get_voice_list(voices_state), get_voice_list(voices_state), cur, voices_state | |
| if not confirm: | |
| return "⚠️ Hãy tick vào ô xác nhận reset!", get_voice_list(voices_state), get_voice_list(voices_state), cur, voices_state | |
| voices = voices_state.copy() | |
| if name in voices: | |
| voices[name]["settings"] = DEFAULT_VOICE_SETTINGS.copy() | |
| return f"✅ Đã reset '{name}'", get_voice_list(voices), get_voice_list(voices), name, voices | |
| return "❌ Không tìm thấy voice!", get_voice_list(voices), get_voice_list(voices), cur, voices | |
| def update_voice_cfg(name, speed, stab, sim, exag, boost, cur, voices_state): | |
| if not name: | |
| return "❌ Chọn voice!", get_voice_list(voices_state), get_voice_list(voices_state), cur, voices_state | |
| voices = voices_state.copy() | |
| voices.setdefault(name, {"voice_id": "", "settings": DEFAULT_VOICE_SETTINGS.copy()}) | |
| voices[name]["settings"] = { | |
| "speed": speed, | |
| "stability": stab, | |
| "similarity_boost": sim, | |
| "style_exaggeration": exag, | |
| "use_speaker_boost": boost | |
| } | |
| return f"✅ Đã cập nhật '{name}'", get_voice_list(voices), get_voice_list(voices), name, voices | |
| def voice_table(voices_state): | |
| voices = voices_state.copy() | |
| data = [[n, mask_voice_id(v.get("voice_id", "")), "✅" if v.get("settings") else "❌"] for n, v in voices.items()] | |
| return pd.DataFrame(data, columns=["Tên Voice", "Voice ID", "Đã cấu hình"]) | |
| # === API-Key helpers === | |
| def dataframe_with_keys(api_keys_state, proxies_state): | |
| keys = api_keys_state.copy() | |
| proxies = proxies_state.copy() | |
| rows = [] | |
| for k, v in keys.items(): | |
| masked_key = mask_api_key(k) | |
| rows.append([masked_key, v.get("status", ""), v.get("used", 0), v.get("limit", 0), v.get("tier", ""), v.get("remaining", 0), proxy_host(get_proxy_of_key(k, proxies))]) | |
| df = pd.DataFrame(rows, columns=["API Key", "Status", "Used", "Limit", "Tier", "Remaining", "Proxy Host"]) | |
| df = df.sort_values("Remaining", ascending=True) | |
| return df | |
| def get_sorted_keys_by_credit(api_keys_state): | |
| keys = api_keys_state.copy() | |
| if not keys: | |
| return [] | |
| sorted_keys = sorted(keys.items(), key=lambda x: x[1].get("remaining", 0)) | |
| return [k for k, v in sorted_keys] | |
| def lowest_key(api_keys_state): | |
| keys = api_keys_state.copy() | |
| if not keys: | |
| return None | |
| return sorted(keys.items(), key=lambda x: x[1].get("remaining", float("inf")))[0][0] | |
| def save_and_show_keys(text, api_keys_state, proxies_state): | |
| # --- Copy state hiện tại --- | |
| keys = api_keys_state.copy() | |
| proxies = proxies_state.copy() | |
| # --- Đọc input, thu list new_keys --- | |
| new_keys = [] | |
| for line in text.strip().splitlines(): | |
| k = line.strip() | |
| if k and k not in keys: | |
| new_keys.append(k) | |
| # --- Nhánh 1: nếu không có key mới --- | |
| if not new_keys: | |
| df = dataframe_with_keys(keys, proxies) | |
| message = "ℹ️ Không có key mới nào." | |
| choices = get_key_choices_for_display(keys) | |
| lowest = choices[0] if choices else None | |
| return ( | |
| # Tab 2 outputs: key_df, key_dd, key_del_dd, key_sel, status_out, api_keys_state, total_credit_txt | |
| df, | |
| gr.update(choices=choices, value=lowest), # dropdown “Chọn API Key” | |
| gr.update(choices=choices, value=None), # dropdown “Chọn API Key để xoá” | |
| gr.update(choices=choices, value=None), # dropdown “API Key” | |
| message, | |
| keys, | |
| f"Tổng credit: {total_credit(keys):,}" | |
| ) | |
| # --- Nhánh 2: có new_keys -> thêm vào state --- | |
| added = len(new_keys) | |
| for k in new_keys: | |
| keys[k] = {"status": "⏳ Chưa kiểm tra", "remaining": 0} | |
| # Gán proxy và kiểm tra usage cho các key mới | |
| assigned_keys, unassigned_keys, assign_message, proxies = smart_proxy_assignment(proxies, keys) | |
| checked = 0 | |
| for k in new_keys: | |
| if k in assigned_keys: | |
| keys[k] = get_api_usage(k, proxies=proxies) | |
| checked += 1 | |
| else: | |
| if not proxies: | |
| keys[k] = get_api_usage(k, bypass_proxy=True, proxies=proxies) | |
| checked += 1 | |
| else: | |
| keys[k]["status"] = "⚠️ Chưa gắn proxy" | |
| active_proxies = [ | |
| p for p in proxies.values() | |
| if p.get("status", "").startswith("✅") or "HTTPSConnectionPool" in p.get("status", "") | |
| ] | |
| if not active_proxies: | |
| assign_message += " ⚠️ Không có proxy – Đang dùng IP thật (Vẫn ổn nếu xài dưới 3 key/ngày)" | |
| # Build kết quả | |
| df = dataframe_with_keys(keys, proxies) | |
| if not active_proxies: | |
| assign_message = "⚠️ Không gắn proxy – Đang dùng IP thật (Vẫn ổn nếu xài dưới 3 Key/ngày)" | |
| message = f"✅ Thêm {added} key mới, kiểm tra {checked} key. {assign_message}" | |
| choices = get_key_choices_for_display(keys) | |
| lowest = choices[0] if choices else None | |
| return ( | |
| # Tab 2 outputs: key_df, key_dd, key_del_dd, key_sel, status_out, api_keys_state, total_credit_txt | |
| df, | |
| gr.update(choices=choices, value=lowest), # dropdown “Chọn API Key” | |
| gr.update(choices=choices, value=None), # dropdown “Chọn API Key để xoá” | |
| gr.update(choices=choices, value=None), # dropdown “API Key” | |
| message, | |
| keys, | |
| f"Tổng credit: {total_credit(keys):,}" | |
| ) | |
| def refresh_keys(api_keys_state, proxies_state): | |
| keys = api_keys_state.copy() | |
| for k in keys: | |
| keys[k] = get_api_usage(k, proxies=proxies_state) | |
| df = dataframe_with_keys(keys, proxies_state) | |
| choices = get_key_choices_for_display(keys) | |
| lowest = choices[0] if choices else None | |
| return ( | |
| df, | |
| gr.update(choices=choices, value=lowest), # an toàn ngay cả khi choices = [] | |
| f"Tổng credit: {total_credit(keys):,}", | |
| gr.update(choices=choices, value=None), | |
| keys | |
| ) | |
| def filter_api_keys_by_credit(threshold, api_keys_state, proxies_state): | |
| keys = api_keys_state.copy() | |
| filtered = {k: v for k, v in keys.items() if v.get("remaining", 0) < threshold} | |
| rows = [] | |
| for k, v in filtered.items(): | |
| masked_key = mask_api_key(k) | |
| rows.append([masked_key, v.get("status", ""), v.get("used", 0), v.get("limit", 0), v.get("tier", ""), v.get("remaining", 0), proxy_host(get_proxy_of_key(k, proxies_state))]) | |
| df = pd.DataFrame(rows, columns=["API Key", "Status", "Used", "Limit", "Tier", "Remaining", "Proxy Host"]) | |
| return df | |
| def remove_insufficient_keys(threshold, api_keys_state, proxies_state): | |
| keys = api_keys_state.copy() | |
| filtered = {k: v for k, v in keys.items() if v.get("remaining", 0) >= threshold} | |
| choices = get_key_choices_for_display(filtered) | |
| lowest = choices[0] if choices else None | |
| return ( | |
| dataframe_with_keys(filtered, proxies_state), | |
| gr.update(choices=choices, value=lowest), | |
| gr.update(choices=choices, value=None), | |
| gr.update(choices=choices, value=None), | |
| filtered | |
| ) | |
| def key_has_proxy(k, proxies_state): | |
| return bool(get_proxy_of_key(k, proxies_state)) | |
| def tts_from_text(text, voice, model, fmt, key_display, auto, bypass_proxy, voices_state, api_keys_state, proxies_state): | |
| if not text.strip(): | |
| return None, "Nội dung trống!", "", api_keys_state | |
| keys = api_keys_state.copy() | |
| proxies = proxies_state.copy() | |
| voices = voices_state.copy() | |
| tokens = len(text) | |
| import urllib3 | |
| urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) | |
| if auto: | |
| if bypass_proxy: | |
| c = [(k, v["remaining"]) for k, v in keys.items() if v.get("remaining", 0) >= tokens] | |
| else: | |
| c = [(k, v["remaining"]) for k, v in keys.items() if v.get("remaining", 0) >= tokens and key_has_proxy(k, proxies)] | |
| if not c: | |
| c = [(k, v["remaining"]) for k, v in keys.items() if v.get("remaining", 0) >= tokens] | |
| bypass_proxy = True # chuyển sang dùng IP thật | |
| if not c: | |
| return None, "❌ Không có key đủ credit", "", keys | |
| api_key = sorted(c, key=lambda x: x[1])[0][0] | |
| else: | |
| if not key_display: | |
| return None, "❌ Chưa chọn key", "", keys | |
| api_key = get_real_key_from_display(key_display, keys) | |
| if not bypass_proxy and not key_has_proxy(api_key, proxies): | |
| return None, "❌ Key chưa gắn proxy", "", keys | |
| proxy_url = None if bypass_proxy else get_proxy_of_key(api_key, proxies) | |
| try: | |
| info = voices.get(voice, {}) | |
| if not info: | |
| return None, "❌ Voice không tồn tại", "", keys | |
| payload = { | |
| "text": text, | |
| "voice_settings": info.get("settings", DEFAULT_VOICE_SETTINGS), | |
| "model_id": model | |
| } | |
| headers = { | |
| "Accept": "audio/mpeg", | |
| "Content-Type": "application/json", | |
| "xi-api-key": api_key | |
| } | |
| url = f"https://api.elevenlabs.io/v1/text-to-speech/{info.get('voice_id')}" | |
| proxies_dict = None | |
| if not bypass_proxy and proxy_url: | |
| proxies_dict = {"http": proxy_url, "https": proxy_url} | |
| response = requests.post( | |
| url, | |
| json=payload, | |
| headers=headers, | |
| proxies=proxies_dict, | |
| timeout=30, | |
| verify=False if proxies_dict else True | |
| ) | |
| if response.status_code != 200: | |
| error_detail = response.text | |
| if response.status_code == 401 and "detected_unusual_activity" in error_detail: | |
| return None, f"❌ Key {mask_api_key(api_key)} bị chặn 'unusual activity'.", "", keys | |
| else: | |
| return None, f"❌ API Error {response.status_code}: {error_detail[:100]}", "", keys | |
| ext = fmt.split('_')[0] if '_' in fmt else fmt | |
| timestamp = int(time.time()) | |
| unique_id = str(uuid.uuid4())[:8] | |
| filename = f"tts_{timestamp}_{unique_id}.{ext}" | |
| temp_dir = tempfile.gettempdir() | |
| file_path = os.path.join(temp_dir, filename) | |
| with open(file_path, 'wb') as f: | |
| f.write(response.content) | |
| if not os.path.exists(file_path): | |
| return None, "❌ Không thể tạo file audio", "", keys | |
| keys[api_key] = get_api_usage(api_key, bypass_proxy, proxies) | |
| proxy_status = "🔓 Direct" if bypass_proxy or not proxy_url else f"🛡️ Proxy" | |
| success_msg = f"✅ Tạo {tokens} ký tự bằng key {mask_api_key(api_key)} ({proxy_status})" | |
| credit_msg = f"Tổng credit: {total_credit(keys):,}" | |
| return file_path, success_msg, credit_msg, keys | |
| except Exception as e: | |
| error_msg = str(e) | |
| if "ProxyError" in error_msg or "ConnectError" in error_msg: | |
| return None, f"❌ Lỗi kết nối proxy: {mask_proxy_url(proxy_url)}", "", keys | |
| else: | |
| return None, f"❌ Lỗi: {error_msg[:100]}", "", keys | |
| def verify_key_proxy(key_display, api_keys_state, proxies_state): | |
| api_key = get_real_key_from_display(key_display, api_keys_state) | |
| proxy = get_proxy_of_key(api_key, proxies_state) | |
| if not proxy: | |
| return "🔴 Key chưa gắn proxy!" | |
| proxies = {"http": proxy, "https": proxy} | |
| try: | |
| r = requests.get("https://api.elevenlabs.io/v1/user/subscription", headers={"xi-api-key": api_key}, proxies=proxies, timeout=8) | |
| ip = requests.get("https://api.ipify.org?format=json", proxies=proxies, timeout=6).json().get("ip", "-") | |
| return f"{'✅' if r.status_code == 200 else '❌'} ElevenLabs {r.status_code} | IP via proxy: {ip}" | |
| except Exception as e: | |
| return f"❌ Lỗi: {str(e).split(' ')[0]}" | |
| # === Refresh-all === | |
| def refresh_all(voices_state, api_keys_state, proxies_state): | |
| voices = voices_state.copy() | |
| keys = api_keys_state.copy() | |
| proxies = proxies_state.copy() | |
| voice_list = get_voice_list(voices) | |
| key_df = dataframe_with_keys(keys, proxies) | |
| proxy_df = format_proxy_table(proxies) | |
| sorted_keys = get_key_choices_for_display(keys) | |
| proxy_list = get_proxy_choices_for_display(proxies) | |
| total_credit_msg = f"Tổng credit: {total_credit(keys):,}" | |
| lowest = sorted_keys[0] if sorted_keys else None | |
| return ( | |
| voice_list, | |
| voice_list, | |
| gr.update(choices=sorted_keys, value=lowest), | |
| gr.update(choices=sorted_keys, value=None), | |
| gr.update(choices=sorted_keys, value=None), | |
| proxy_list, | |
| total_credit_msg, | |
| key_df, | |
| proxy_df, | |
| voices, | |
| keys, | |
| proxies, | |
| ) | |
| def refresh_keys_complete(api_keys_state, proxies_state): | |
| keys = api_keys_state.copy() | |
| for k in keys: | |
| keys[k] = get_api_usage(k, proxies=proxies_state) | |
| key_df = dataframe_with_keys(keys, proxies_state) | |
| sorted_keys = get_key_choices_for_display(keys) | |
| total_credit_msg = f"Tổng credit: {total_credit(keys):,}" | |
| lowest = sorted_keys[0] if sorted_keys else None | |
| return ( | |
| key_df, | |
| gr.update(choices=sorted_keys, value=lowest), # “Chọn API Key” | |
| gr.update(choices=sorted_keys, value=None), # “Chọn API Key để xoá” | |
| gr.update(choices=sorted_keys, value=None), # “API Key” | |
| total_credit_msg, | |
| lowest, | |
| keys | |
| ) | |
| def refresh_proxies_complete(proxies_state, api_keys_state): | |
| proxies = proxies_state.copy() | |
| for url in proxies: | |
| proxies[url].update(test_proxy_once(url)) | |
| proxies[url]["last_checked"] = datetime.utcnow().isoformat(timespec="seconds") | |
| keys = api_keys_state.copy() | |
| key_df = dataframe_with_keys(keys, proxies) | |
| proxy_df = format_proxy_table(proxies) | |
| proxy_list = get_proxy_choices_for_display(proxies) | |
| return proxy_df, "🔄 Đã refresh proxy.", proxy_list, key_df, proxies | |
| def refresh_voices_complete(voices_state): | |
| voices = voices_state.copy() | |
| voice_list = get_voice_list(voices) | |
| voice_df = voice_table(voices) | |
| return voice_list, voice_list, voice_df, voices | |
| # === UI === | |
| with gr.Blocks() as demo: | |
| voices_state = gr.State(load_default_voices()) | |
| api_keys_state = gr.State({}) | |
| proxies_state = gr.State(load_default_proxies()) | |
| gr.Markdown(""" | |
| > 🟢 **Công cụ này hoàn toàn MIỄN PHÍ cho tất cả mọi người.** | |
| > ❌ Vui lòng KHÔNG rao bán, đổi tên hay thương mại hoá công cụ. | |
| > 🛡️ Mọi dữ liệu (API key, proxy) chỉ lưu tạm trong phiên, tự xóa khi đóng trình duyệt. | |
| """) | |
| gr.Markdown("# 🎙️ ElevenLabs TTS + Proxy Manager") | |
| with gr.Tabs(): | |
| with gr.Tab("1. Xử lý Batch"): | |
| with gr.Row(): | |
| voice_dd = gr.Dropdown(choices=get_voice_list(voices_state.value), value=get_default_voice(voices_state.value), label="Chọn Voice", allow_custom_value=True) | |
| model_dd = gr.Dropdown(choices=MODELS, value=DEFAULT_MODEL, label="Model") | |
| fmt_dd = gr.Dropdown(choices=["mp3_44100", "wav"], value=DEFAULT_FORMAT, label="Output") | |
| with gr.Row(): | |
| key_dd = gr.Dropdown(choices=get_key_choices_for_display(api_keys_state.value), value=None, label="Chọn API Key", allow_custom_value=True) | |
| key_credit = gr.Text(label="Credit hiện còn", interactive=False) | |
| total_credit_txt = gr.Text(label="Tổng Credit", interactive=False) | |
| def update_key_credit(display_key, api_keys_state): | |
| if not display_key: | |
| return "-" | |
| if isinstance(display_key, list): | |
| display_key = display_key[0] if display_key else "" | |
| real_key = get_real_key_from_display(display_key, api_keys_state) | |
| remaining = api_keys_state.get(real_key, {}).get('remaining', '-') | |
| if isinstance(remaining, (int, float)): | |
| return f"{remaining:,}" | |
| return str(remaining) | |
| key_dd.change(update_key_credit, [key_dd, api_keys_state], key_credit) | |
| refresh_all_btn = gr.Button("🔄 Refresh All") | |
| verify_btn = gr.Button("⚡ Kiểm tra Proxy của API Key") | |
| status_out = gr.Text(label="Trạng thái") | |
| input_txt = gr.Textbox(lines=6, label="Nội dung") | |
| token_info = gr.Text(label="Tổng ký tự") | |
| input_txt.change(lambda t: f"{len(t)} ký tự", input_txt, token_info) | |
| auto_cb = gr.Checkbox(value=True, label="Tự động chọn API Key có gắn proxy") | |
| bypass_proxy_cb = gr.Checkbox(value=False, label="🔓 Tạm thời không dùng proxy (bypass)") | |
| generate_btn = gr.Button("🌀 Tạo giọng nói") | |
| audio_out = gr.Audio(label="Kết quả", type="filepath") | |
| with gr.Tab("2. Quản lý API Key"): | |
| api_in = gr.Textbox(lines=4, label="Nhập API Key (mỗi dòng)") | |
| save_key_btn = gr.Button("📅 Lưu & Kiểm tra") | |
| refresh_key_btn = gr.Button("🔄 Refresh danh sách") | |
| key_df = gr.Dataframe(label="Danh sách API Key", interactive=False) | |
| gr.Markdown("### Xoá API Key thủ công") | |
| key_del_dd = gr.Dropdown(choices=get_key_choices_for_display(api_keys_state.value), value=None, label="Chọn API Key để xoá", allow_custom_value=True) | |
| key_del_btn = gr.Button("🗑️ Xoá API Key đã chọn") | |
| gr.Markdown("### Lọc API Key theo Credit") | |
| filter_input = gr.Number(label="Lọc các API Key có số credit dưới", value=100) | |
| filter_btn = gr.Button("🔍 Lọc API Key") | |
| remove_low_btn = gr.Button("❌ Xoá các key không đủ credit") | |
| with gr.Tab("3. Quản lý Voice ID"): | |
| with gr.Row(): | |
| v_name = gr.Textbox(label="Tên Voice") | |
| v_id = gr.Textbox(label="Voice ID") | |
| v_select = gr.Dropdown(choices=get_voice_list(voices_state.value), value=get_default_voice(voices_state.value), label="Chọn Voice để sửa", allow_custom_value=True) | |
| voice_status = gr.Textbox(label="Trạng thái", interactive=False) | |
| with gr.Row(): | |
| save_voice_btn = gr.Button("💾 Lưu Voice mới") | |
| with gr.Row(): | |
| del_voice_btn = gr.Button("🗑️ Xoá Voice đang chọn") | |
| with gr.Row(): | |
| speed_sl = gr.Slider(0.70, 1.20, DEFAULT_VOICE_SETTINGS["speed"], step=0.01, label="Speed") | |
| stab_sl = gr.Slider(0, 1, DEFAULT_VOICE_SETTINGS["stability"], step=0.05, label="Stability") | |
| sim_sl = gr.Slider(0, 1, DEFAULT_VOICE_SETTINGS["similarity_boost"], step=0.05, label="Similarity") | |
| ex_sl = gr.Slider(0, 1, DEFAULT_VOICE_SETTINGS["style_exaggeration"], step=0.05, label="Exaggeration") | |
| boost_cb = gr.Checkbox(value=True, label="Speaker Boost") | |
| upd_cfg_btn = gr.Button("💾 Lưu cấu hình Voice") | |
| reset_cfg_btn = gr.Button("↻ Reset cấu hình Voice") | |
| refresh_v_btn = gr.Button("🔄 Làm mới danh sách Voice") | |
| with gr.Row(): | |
| reset_confirm_cb = gr.Checkbox(value=False, label="Đồng ý reset về mặc định") | |
| gr.HTML("") | |
| delete_confirm_cb = gr.Checkbox(value=False, label="Đồng ý xoá voice") | |
| voice_df = gr.Dataframe(label="Danh sách Voice", interactive=False) | |
| with gr.Tab("4. Quản lý Proxy"): | |
| proxy_in = gr.Textbox(lines=4, label="Nhập proxy (mỗi dòng | http://user:pass@host:port)") | |
| add_p_btn = gr.Button("💾 Lưu & Kiểm tra") | |
| refresh_p_btn = gr.Button("🔄 Refresh trạng thái") | |
| proxy_df = gr.Dataframe(label="Danh sách Proxy", interactive=False) | |
| p_status = gr.Text(label="Trạng thái") | |
| gr.Markdown("### Xoá Proxy thủ công") | |
| proxy_del_dd = gr.Dropdown(choices=get_proxy_choices_for_display(proxies_state.value), value=None, label="Chọn Proxy để xoá", allow_custom_value=True) | |
| proxy_del_btn = gr.Button("🗑️ Xoá Proxy đã chọn") | |
| gr.Markdown("### Gắn Proxy với API Key") | |
| with gr.Row(): | |
| proxy_sel = gr.Dropdown(choices=get_proxy_choices_for_display(proxies_state.value), value=None, label="Proxy", allow_custom_value=True) | |
| key_sel = gr.Dropdown(choices=get_key_choices_for_display(api_keys_state.value), value=None, label="API Key", allow_custom_value=True) | |
| assign_btn = gr.Button("↔️ Gắn thủ công") | |
| auto_btn = gr.Button("🤖 Gắn tự động thông minh") | |
| filter_bad_btn = gr.Button("🔍 Lọc Proxy lỗi") | |
| del_bad_btn = gr.Button("🗑️ Xoá Proxy lỗi") | |
| # Events setup | |
| def delete_api_key_manual(display_key, api_keys_state, proxies_state): | |
| if not display_key: | |
| return dataframe_with_keys(api_keys_state, proxies_state), get_key_choices_for_display(api_keys_state), get_key_choices_for_display(api_keys_state), get_key_choices_for_display(api_keys_state), "❌ Chọn API Key để xoá!", api_keys_state | |
| real_key = get_real_key_from_display(display_key, api_keys_state) | |
| keys = api_keys_state.copy() | |
| if real_key in keys: | |
| del keys[real_key] | |
| key_list = get_key_choices_for_display(keys) | |
| return ( | |
| dataframe_with_keys(keys, proxies_state), | |
| gr.update(choices=key_list, value=key_list[0]), # key_dd | |
| gr.update(choices=key_list, value=None), # key_del_dd | |
| gr.update(choices=key_list, value=None), # key_sel | |
| f"🗑️ Đã xoá key: {display_key}", | |
| keys | |
| ) | |
| def delete_proxy_manual(display_proxy, proxies_state): | |
| if not display_proxy: | |
| return format_proxy_table(proxies_state), get_proxy_choices_for_display(proxies_state), "❌ Chọn Proxy để xoá!", proxies_state | |
| real_proxy = get_real_proxy_from_display(display_proxy, proxies_state) | |
| proxies = proxies_state.copy() | |
| if real_proxy in proxies: | |
| del proxies[real_proxy] | |
| proxy_list = get_proxy_choices_for_display(proxies) | |
| return format_proxy_table(proxies), proxy_list, f"🗑️ Đã xoá proxy: {display_proxy}", proxies | |
| def update_proxy_and_sync(text, proxies_state): | |
| proxy_table, message, proxies = add_and_test_proxies(text, proxies_state) | |
| proxy_choices = get_proxy_choices_for_display(proxies) | |
| return proxy_table, message, proxy_choices, proxies | |
| def auto_assign_and_sync(proxies_state, api_keys_state): | |
| proxy_df_result, message, proxies = auto_assign(proxies_state, api_keys_state) | |
| proxies_choices = get_proxy_choices_for_display(proxies) | |
| sorted_keys = get_key_choices_for_display(api_keys_state) | |
| api_key_df = dataframe_with_keys(api_keys_state, proxies) | |
| return proxy_df_result, message, proxies_choices, sorted_keys, api_key_df, proxies | |
| def assign_manual_and_sync(proxy_display, key_display, proxies_state, api_keys_state): | |
| proxy_df_result, message, proxies, keys = assign_proxy_to_key(proxy_display, key_display, proxies_state, api_keys_state) | |
| api_key_df = dataframe_with_keys(keys, proxies) | |
| return proxy_df_result, message, api_key_df, proxies, keys | |
| def delete_bad_and_sync(proxies_state): | |
| proxy_table, message, proxies = delete_bad(proxies_state) | |
| proxies_choices = get_proxy_choices_for_display(proxies) | |
| return proxy_table, message, proxies_choices, proxies | |
| # Gắn sự kiện | |
| key_del_btn.click( | |
| delete_api_key_manual, | |
| [key_del_dd, api_keys_state, proxies_state], | |
| [key_df, key_dd, key_del_dd, key_sel, status_out, api_keys_state] | |
| ) | |
| proxy_del_btn.click( | |
| delete_proxy_manual, | |
| [proxy_del_dd, proxies_state], | |
| [proxy_df, proxy_del_dd, p_status, proxies_state] | |
| ) | |
| save_key_btn.click( | |
| fn=save_and_show_keys, | |
| inputs=[api_in, api_keys_state, proxies_state], | |
| outputs=[key_df, key_dd, key_del_dd, key_sel, status_out, api_keys_state, total_credit_txt] | |
| ) | |
| refresh_key_btn.click( | |
| refresh_keys, | |
| [api_keys_state, proxies_state], | |
| [key_df, key_dd, total_credit_txt, key_sel, api_keys_state] | |
| ) | |
| filter_btn.click( | |
| filter_api_keys_by_credit, | |
| [filter_input, api_keys_state, proxies_state], | |
| key_df | |
| ) | |
| remove_low_btn.click( | |
| remove_insufficient_keys, | |
| [filter_input, api_keys_state, proxies_state], | |
| [key_df, key_dd, key_del_dd, key_sel, api_keys_state] | |
| ) | |
| refresh_all_btn.click( | |
| refresh_all, | |
| [voices_state, api_keys_state, proxies_state], | |
| [voice_dd, v_select, key_dd, key_del_dd, key_sel, proxy_sel, total_credit_txt, key_df, proxy_df, voices_state, api_keys_state, proxies_state] | |
| ) | |
| verify_btn.click( | |
| verify_key_proxy, | |
| [key_dd, api_keys_state, proxies_state], | |
| status_out | |
| ) | |
| generate_btn.click( | |
| tts_from_text, | |
| [input_txt, voice_dd, model_dd, fmt_dd, key_dd, auto_cb, bypass_proxy_cb, voices_state, api_keys_state, proxies_state], | |
| [audio_out, status_out, total_credit_txt, api_keys_state] | |
| ) | |
| def save_voice_and_refresh(name, voice_id, current_voice, voices_state): | |
| status, v_select_choices, voice_dd_choices, selected_voice, new_voices_state = save_voice( | |
| name, voice_id, current_voice, voices_state | |
| ) | |
| voice_df_data = voice_table(new_voices_state) | |
| voice_dd_update = gr.update(choices=get_voice_list(new_voices_state), value=get_default_voice(new_voices_state)) | |
| return status, v_select_choices, voice_dd_update, selected_voice, new_voices_state, voice_df_data | |
| save_voice_btn.click( | |
| save_voice_and_refresh, | |
| [v_name, v_id, v_select, voices_state], | |
| [voice_status, v_select, voice_dd, v_select, voices_state, voice_df] | |
| ) | |
| v_select.change( | |
| load_voice_for_edit, | |
| [v_select, voices_state], | |
| [v_name, v_id, speed_sl, stab_sl, sim_sl, ex_sl, boost_cb] | |
| ) | |
| def update_voice_cfg_and_refresh(name, speed, stab, sim, exag, boost, cur, voices_state): | |
| status, v_select_choices, voice_dd_choices, selected_voice, new_voices_state = update_voice_cfg( | |
| name, speed, stab, sim, exag, boost, cur, voices_state | |
| ) | |
| voice_df_data = voice_table(new_voices_state) | |
| return status, v_select_choices, voice_dd_choices, selected_voice, new_voices_state, voice_df_data | |
| upd_cfg_btn.click( | |
| update_voice_cfg_and_refresh, | |
| [v_select, speed_sl, stab_sl, sim_sl, ex_sl, boost_cb, v_select, voices_state], | |
| [voice_status, v_select, voice_dd, v_select, voices_state, voice_df] | |
| ) | |
| def reset_voice_and_refresh(name, confirm, cur, voices_state): | |
| status, v_select_choices, voice_dd_choices, selected_voice, new_voices_state = reset_voice( | |
| name, confirm, cur, voices_state | |
| ) | |
| voice_df_data = voice_table(new_voices_state) | |
| voice_dd_update = gr.update(choices=get_voice_list(new_voices_state), value=get_default_voice(new_voices_state)) | |
| return status, v_select_choices, voice_dd_update, selected_voice, new_voices_state, voice_df_data | |
| reset_cfg_btn.click( | |
| reset_voice_and_refresh, | |
| [v_select, reset_confirm_cb, v_select, voices_state], | |
| [voice_status, v_select, voice_dd, v_select, voices_state, voice_df] | |
| ) | |
| refresh_v_btn.click( | |
| refresh_voices_complete, | |
| voices_state, | |
| [v_select, voice_dd, voice_df, voices_state] | |
| ) | |
| def del_voice_and_refresh(name, confirm, cur, voices_state): | |
| status, v_select_choices, voice_dd_choices, selected_voice, new_voices_state = delete_voice( | |
| name, confirm, cur, voices_state | |
| ) | |
| voice_df_data = voice_table(new_voices_state) | |
| voice_dd_update = gr.update(choices=get_voice_list(new_voices_state), value=get_default_voice(new_voices_state)) | |
| return status, v_select_choices, voice_dd_update, selected_voice, new_voices_state, voice_df_data | |
| del_voice_btn.click( | |
| del_voice_and_refresh, | |
| [v_select, delete_confirm_cb, v_select, voices_state], | |
| [voice_status, v_select, voice_dd, v_select, voices_state, voice_df] | |
| ) | |
| def add_proxy_and_refresh(text, proxies_state): | |
| proxy_table, message, new_proxies_state = add_and_test_proxies(text, proxies_state) | |
| proxy_sel_update = gr.update(choices=get_proxy_choices_for_display(new_proxies_state), value=None) | |
| proxy_del_dd_update = gr.update(choices=get_proxy_choices_for_display(new_proxies_state), value=None) | |
| return proxy_table, message, proxy_sel_update, new_proxies_state, proxy_del_dd_update | |
| add_p_btn.click( | |
| add_proxy_and_refresh, | |
| [proxy_in, proxies_state], | |
| [proxy_df, p_status, proxy_sel, proxies_state, proxy_del_dd] | |
| ) | |
| refresh_p_btn.click( | |
| refresh_proxies_complete, | |
| [proxies_state, api_keys_state], | |
| [proxy_df, p_status, proxy_sel, key_df, proxies_state] | |
| ) | |
| assign_btn.click( | |
| assign_manual_and_sync, | |
| [proxy_sel, key_sel, proxies_state, api_keys_state], | |
| [proxy_df, p_status, key_df, proxies_state, api_keys_state] | |
| ) | |
| auto_btn.click( | |
| auto_assign_and_sync, | |
| [proxies_state, api_keys_state], | |
| [proxy_df, p_status, proxy_sel, key_sel, key_df, proxies_state] | |
| ) | |
| filter_bad_btn.click( | |
| filter_bad_proxies, | |
| proxies_state, | |
| proxy_df | |
| ) | |
| del_bad_btn.click( | |
| delete_bad_and_sync, | |
| proxies_state, | |
| [proxy_df, p_status, proxy_sel, proxies_state] | |
| ) | |
| demo.load( | |
| voice_table, | |
| voices_state, | |
| voice_df | |
| ) | |
| demo.load( | |
| dataframe_with_keys, | |
| [api_keys_state, proxies_state], | |
| key_df | |
| ) | |
| demo.load( | |
| format_proxy_table, | |
| proxies_state, | |
| proxy_df | |
| ) | |
| demo.load( | |
| lambda api_keys: f"Tổng credit: {total_credit(api_keys):,}", | |
| api_keys_state, | |
| total_credit_txt | |
| ) | |
| demo.launch(ssr_mode=False) |