Spaces:
Paused
Paused
| import gradio as gr | |
| import os | |
| import requests | |
| import json | |
| from dotenv import load_dotenv | |
| from elevenlabs.client import ElevenLabs | |
| from elevenlabs import save | |
| import pandas as pd | |
| # === Load .env === | |
| load_dotenv() | |
| DEFAULT_MODEL = os.getenv("ELEVENLABS_MODEL_ID", "eleven_multilingual_v2") | |
| DEFAULT_FORMAT = os.getenv("ELEVENLABS_OUTPUT_FORMAT", "mp3_44100") | |
| # === Data files === | |
| API_KEY_FILE = "api_keys.json" | |
| VOICE_FILE = "voices.json" | |
| MODELS = ["eleven_monolingual_v1", "eleven_multilingual_v1", "eleven_multilingual_v2"] | |
| # Cấu hình mặc định cho voice mới | |
| DEFAULT_VOICE_SETTINGS = { | |
| "speed": 1.0, # 0.70 - 1.20 | |
| "stability": 0.5, # 0.0 - 1.0 (50%) | |
| "similarity_boost": 0.75, # 0.0 - 1.0 (75%) | |
| "style_exaggeration": 0.0, # 0.0 - 1.0 (0%) | |
| "use_speaker_boost": True | |
| } | |
| # Danh sách voice ID mặc định | |
| DEFAULT_VOICES = { | |
| "Josh": "TxGEqnHWrfWFTfGW9XjX", | |
| "Liam": "TX3LPaxmHKxFdv7VOQHJ", | |
| "Antoni": "ErXwobaYiN019PkySvjV", | |
| "Chris": "iP95p4xoKVk53GoZ742B", | |
| "Arnold": "VR6AewLTigWG4xSOukaG", | |
| "Adam": "pNInz6obpgDQGcFmaJgB", | |
| "Brian": "nPczCjzI2devNBz1zQrb", | |
| "Roger": "CwhRBWXzGAHq8TQ4Fs17", | |
| "Paul": "5Q0t7uMcjvnagumLfvZi", | |
| "Drew": "29vD33N1CtxCmqQRPOHJ", | |
| "Michael": "f1q6f7yk4E4fJM5XTYuZ", | |
| "Jessie": "tOjbN1BVZ17f02VDIeMI", | |
| "Bill": "pqH3ZKP75CvO1Qy1NhV4", | |
| "Emily": "LcfcDJNUP1GQjkzn1xUU", | |
| "Glinda": "z9fAnlkpzviPz146aGWa", | |
| "Serena": "pMsXgVXv3BLzUgSXRplE", | |
| "Matilda": "XrExE9yKIg1WjnnlVkGX", | |
| "River": "SAz9YHcvj6GT2YYXdXww", | |
| "Callum": "N21VS1w4EtoT3dr4e0W0", | |
| "Clyde": "2EiwWnXFnvU5JabPnv8n" | |
| } | |
| # === JSON helpers === | |
| def load_json(file): | |
| if not os.path.exists(file): | |
| return {} | |
| with open(file, 'r') as f: | |
| return json.load(f) | |
| def save_json(file, data): | |
| with open(file, 'w') as f: | |
| json.dump(data, f, indent=2) | |
| # Khởi tạo danh sách voice mặc định khi chưa có file voices.json | |
| def initialize_default_voices(): | |
| if not os.path.exists(VOICE_FILE) or os.path.getsize(VOICE_FILE) == 0: | |
| voices = {} | |
| for name, voice_id in DEFAULT_VOICES.items(): | |
| voices[name] = { | |
| "voice_id": voice_id, | |
| "settings": DEFAULT_VOICE_SETTINGS.copy() | |
| } | |
| save_json(VOICE_FILE, voices) | |
| return load_json(VOICE_FILE) | |
| # === ElevenLabs API === | |
| def get_client(api_key): | |
| return ElevenLabs(api_key=api_key) | |
| def get_api_usage(api_key): | |
| try: | |
| headers = {"xi-api-key": api_key} | |
| r = requests.get("https://api.elevenlabs.io/v1/user/subscription", headers=headers) | |
| if r.status_code == 200: | |
| data = r.json() | |
| return { | |
| "status": "✅ OK", | |
| "used": data.get("character_count", 0), | |
| "limit": data.get("character_limit", 0), | |
| "tier": data.get("tier", ""), | |
| "remaining": data.get("character_limit", 0) - data.get("character_count", 0) | |
| } | |
| return {"status": f"❌ {r.status_code}"} | |
| except Exception as e: | |
| return {"status": f"⚠️ {str(e)}"} | |
| def total_credit(api_keys_state): | |
| return sum([v.get("remaining", 0) for v in api_keys_state.values()]) | |
| # === TTS === | |
| def tts_from_text(text, voice_name, model, output_format, api_key_choice, auto_mode, api_keys_state): | |
| if not text.strip(): | |
| return "", "Văn bản trống!", f"Tổng credit còn lại: {total_credit(api_keys_state):,}" | |
| # Cập nhật lại api_keys_state từ file để có thông tin mới nhất | |
| api_keys_state = load_json(API_KEY_FILE) | |
| token_count = len(text) | |
| key_to_use = None | |
| if auto_mode: | |
| candidates = [(k, v['remaining']) for k, v in api_keys_state.items() if v.get("remaining", 0) >= token_count] | |
| if not candidates: | |
| return "", "❌ Không có API Key nào đủ credit.", f"Tổng credit còn lại: {total_credit(api_keys_state):,}" | |
| key_to_use = sorted(candidates, key=lambda x: x[1])[0][0] | |
| else: | |
| key_to_use = api_key_choice | |
| client = get_client(key_to_use) | |
| voices = load_json(VOICE_FILE) | |
| voice_info = voices.get(voice_name, {}) | |
| voice_id = voice_info.get("voice_id", "") | |
| settings = voice_info.get("settings", {}) | |
| if not voice_id: | |
| return "", "❌ Không tìm thấy Voice ID.", f"Tổng credit còn lại: {total_credit(api_keys_state):,}" | |
| audio = client.generate( | |
| text=text, | |
| voice=voice_id, | |
| model=model, | |
| output_format=output_format, | |
| voice_settings=settings if settings else None | |
| ) | |
| output_path = f"output.{output_format.split('_')[0]}" | |
| save(audio, output_path) | |
| # Cập nhật thông tin API key sau khi sử dụng | |
| updated_key_info = get_api_usage(key_to_use) | |
| api_keys_state[key_to_use] = updated_key_info | |
| save_json(API_KEY_FILE, api_keys_state) | |
| return output_path, f"✅ Đã tạo giọng nói với {token_count} ký tự.", f"Tổng credit còn lại: {total_credit(api_keys_state):,}" | |
| # === Refresh === | |
| def refresh_all_dropdowns(): | |
| # Đảm bảo rằng đã khởi tạo danh sách voice mặc định | |
| initialize_default_voices() | |
| voices = list(load_json(VOICE_FILE).keys()) | |
| # Cập nhật thông tin credit cho tất cả API keys | |
| api_keys = load_json(API_KEY_FILE) | |
| for key in list(api_keys.keys()): | |
| api_keys[key] = get_api_usage(key) | |
| save_json(API_KEY_FILE, api_keys) | |
| keys = list(api_keys.keys()) | |
| # Tìm API key có ít credit nhất | |
| default_key = get_lowest_credit_key(api_keys) | |
| return voices, keys, f"Tổng credit còn lại: {total_credit(api_keys):,}", default_key | |
| def get_lowest_credit_key(api_keys): | |
| if not api_keys: | |
| return None | |
| # Sắp xếp các API key theo số credit còn lại (tăng dần) | |
| sorted_keys = sorted(api_keys.items(), key=lambda x: x[1].get('remaining', float('inf'))) | |
| # Trả về key có ít credit nhất | |
| return sorted_keys[0][0] if sorted_keys else None | |
| def remove_insufficient_keys(threshold): | |
| keys = load_json(API_KEY_FILE) | |
| filtered = {k: v for k, v in keys.items() if v.get("remaining", 0) >= threshold} | |
| save_json(API_KEY_FILE, filtered) | |
| api_keys = list(filtered.keys()) | |
| default_key = get_lowest_credit_key(filtered) | |
| return pd.DataFrame.from_dict(filtered, orient="index").reset_index().rename(columns={"index": "API Key"}), api_keys, default_key | |
| def filter_api_keys_by_credit(threshold): | |
| keys = load_json(API_KEY_FILE) | |
| filtered = {k: v for k, v in keys.items() if v.get("remaining", 0) < threshold} | |
| df = pd.DataFrame.from_dict(filtered, orient="index") | |
| df.reset_index(inplace=True) | |
| df.columns = ["API Key", "Status", "Used", "Limit", "Tier", "Remaining"][:len(df.columns)] | |
| return df | |
| # === Voice Save & Management === | |
| def save_voice(name, voice_id, current_voice): | |
| if not name or not voice_id: | |
| return "❌ Cần nhập tên Voice và Voice ID!", get_voice_list(), get_voice_list(), current_voice | |
| voices = load_json(VOICE_FILE) | |
| # Kiểm tra xem Voice ID đã tồn tại chưa (trường hợp ghi đè) | |
| for existing_name, voice_data in voices.items(): | |
| if voice_data.get("voice_id") == voice_id and existing_name != name: | |
| return f"❌ Voice ID đã tồn tại cho voice '{existing_name}'. Vui lòng sử dụng tên này hoặc chọn Voice ID khác.", get_voice_list(), get_voice_list(), current_voice | |
| # Cấu hình mặc định cho voice mới | |
| voices[name] = { | |
| "voice_id": voice_id, | |
| "settings": DEFAULT_VOICE_SETTINGS.copy() | |
| } | |
| save_json(VOICE_FILE, voices) | |
| # Lấy danh sách voice mới và trả về tên voice vừa tạo | |
| voice_list = get_voice_list() | |
| return f"✅ Đã lưu Voice '{name}'", voice_list, voice_list, name | |
| def load_voice_for_edit(name): | |
| if not name: | |
| return "", "", DEFAULT_VOICE_SETTINGS["speed"], DEFAULT_VOICE_SETTINGS["stability"], \ | |
| DEFAULT_VOICE_SETTINGS["similarity_boost"], DEFAULT_VOICE_SETTINGS["style_exaggeration"], \ | |
| DEFAULT_VOICE_SETTINGS["use_speaker_boost"] | |
| voices = load_json(VOICE_FILE) | |
| v = voices.get(name, {}) | |
| cfg = v.get("settings", DEFAULT_VOICE_SETTINGS.copy()) | |
| return ( | |
| name, | |
| v.get("voice_id", ""), | |
| cfg.get("speed", DEFAULT_VOICE_SETTINGS["speed"]), | |
| cfg.get("stability", DEFAULT_VOICE_SETTINGS["stability"]), | |
| cfg.get("similarity_boost", DEFAULT_VOICE_SETTINGS["similarity_boost"]), | |
| cfg.get("style_exaggeration", DEFAULT_VOICE_SETTINGS["style_exaggeration"]), | |
| cfg.get("use_speaker_boost", DEFAULT_VOICE_SETTINGS["use_speaker_boost"]) | |
| ) | |
| def delete_voice(name, current_voice): | |
| if not name: | |
| return "❌ Vui lòng chọn Voice để xóa", get_voice_list(), get_voice_list(), current_voice | |
| voices = load_json(VOICE_FILE) | |
| if name in voices: | |
| del voices[name] | |
| save_json(VOICE_FILE, voices) | |
| voice_list = get_voice_list() | |
| new_current = voice_list[0] if voice_list else None | |
| return f"✅ Đã xóa Voice '{name}'", voice_list, voice_list, new_current | |
| return f"❌ Không tìm thấy Voice '{name}'", get_voice_list(), get_voice_list(), current_voice | |
| def delete_all_voices(confirm_delete): | |
| if not confirm_delete: | |
| return "❌ Vui lòng đánh dấu vào ô xác nhận để xóa tất cả Voice", [], [] | |
| voices = load_json(VOICE_FILE) | |
| if not voices: | |
| return "❌ Không có Voice nào để xóa", [], [] | |
| # Xóa tất cả voices | |
| save_json(VOICE_FILE, {}) | |
| return "✅ Đã xóa tất cả Voice", [], [] | |
| def voice_table(): | |
| voices = load_json(VOICE_FILE) | |
| rows = [[k, v.get("voice_id", ""), "✅" if v.get("settings") else "❌"] for k, v in voices.items()] | |
| return pd.DataFrame(rows, columns=["Tên Voice", "Voice ID", "Đã cấu hình"]) | |
| def reset_voice_settings(name, current_voice): | |
| if not name: | |
| return "❌ Vui lòng chọn Voice để reset cấu hình", get_voice_list(), get_voice_list(), current_voice | |
| voices = load_json(VOICE_FILE) | |
| if name not in voices: | |
| return f"❌ Không tìm thấy Voice '{name}'", get_voice_list(), get_voice_list(), current_voice | |
| # Lấy voice_id hiện tại | |
| voice_id = voices[name].get("voice_id", "") | |
| # Reset cấu hình về mặc định | |
| voices[name] = { | |
| "voice_id": voice_id, | |
| "settings": DEFAULT_VOICE_SETTINGS.copy() | |
| } | |
| save_json(VOICE_FILE, voices) | |
| return f"✅ Đã reset cấu hình Voice '{name}' về mặc định", get_voice_list(), get_voice_list(), name | |
| def update_voice_config(name, speed, stability, similarity, exaggeration, boost, current_voice): | |
| if not name: | |
| return "❌ Vui lòng chọn Voice để cập nhật cấu hình", get_voice_list(), get_voice_list(), current_voice | |
| voices = load_json(VOICE_FILE) | |
| if name not in voices: | |
| return f"❌ Không tìm thấy Voice '{name}'", get_voice_list(), get_voice_list(), current_voice | |
| # Cập nhật cấu hình | |
| voices[name]["settings"] = { | |
| "stability": stability, | |
| "similarity_boost": similarity, | |
| "style_exaggeration": exaggeration, | |
| "use_speaker_boost": boost, | |
| "speed": speed | |
| } | |
| save_json(VOICE_FILE, voices) | |
| return f"✅ Đã cập nhật cấu hình Voice '{name}'", get_voice_list(), get_voice_list(), name | |
| def get_voice_list(): | |
| # Đảm bảo đã khởi tạo voice mặc định | |
| initialize_default_voices() | |
| voices = load_json(VOICE_FILE) | |
| voice_list = list(voices.keys()) | |
| return voice_list if voice_list else [] | |
| def get_default_voice(): | |
| # Đảm bảo đã khởi tạo voice mặc định | |
| initialize_default_voices() | |
| voices = load_json(VOICE_FILE) | |
| voice_list = list(voices.keys()) | |
| return voice_list[0] if voice_list else None | |
| # === API key management === | |
| def save_and_show_keys(text): | |
| # Lấy các API key hiện có | |
| existing_keys = load_json(API_KEY_FILE) | |
| # Thêm hoặc cập nhật các API key mới | |
| for key in text.strip().splitlines(): | |
| key = key.strip() | |
| if key and key not in existing_keys: | |
| existing_keys[key] = get_api_usage(key) | |
| save_json(API_KEY_FILE, existing_keys) | |
| df = pd.DataFrame.from_dict(existing_keys, orient="index") | |
| df.reset_index(inplace=True) | |
| df.columns = ["API Key", "Status", "Used", "Limit", "Tier", "Remaining"][:len(df.columns)] | |
| api_keys = list(existing_keys.keys()) | |
| default_key = get_lowest_credit_key(existing_keys) | |
| return df, api_keys, default_key | |
| def refresh_keys(): | |
| # Cập nhật thông tin credit cho tất cả API keys | |
| keys = load_json(API_KEY_FILE) | |
| for key in list(keys.keys()): | |
| keys[key] = get_api_usage(key) | |
| save_json(API_KEY_FILE, keys) | |
| df = pd.DataFrame.from_dict(keys, orient="index") | |
| df.reset_index(inplace=True) | |
| df.columns = ["API Key", "Status", "Used", "Limit", "Tier", "Remaining"][:len(df.columns)] | |
| default_key = get_lowest_credit_key(keys) | |
| return df, list(keys.keys()), f"Tổng credit còn lại: {total_credit(keys):,}", default_key | |
| # === UI === | |
| with gr.Blocks() as demo: | |
| gr.Markdown("# 🎙️ ElevenLabs TTS Interface") | |
| gr.Markdown("### Tạo giọng nói từ văn bản với ElevenLabs API") | |
| gr.Markdown("Vui lòng nhập API Key của bạn trong tab 'Quản lý API Key'") | |
| api_keys_state = gr.State(load_json(API_KEY_FILE)) | |
| with gr.Tabs(): | |
| with gr.Tab("1. Xử lý Batch"): | |
| with gr.Row(): | |
| voice_dropdown = gr.Dropdown(choices=get_voice_list(), value=get_default_voice(), label="Chọn Voice") | |
| model_dropdown = gr.Dropdown(choices=MODELS, value=DEFAULT_MODEL, label="Chọn Model") | |
| output_format = gr.Dropdown(choices=["mp3_44100", "wav"], value=DEFAULT_FORMAT, label="Output Format") | |
| with gr.Row(): | |
| api_keys_list = gr.Dropdown(choices=list(load_json(API_KEY_FILE).keys()), label="Chọn API Key (thủ công)") | |
| api_key_credit = gr.Text(label="Credit của API Key", interactive=False) | |
| total_credit_label = gr.Text(label="Tổng Credit", interactive=False) | |
| def update_api_key_credit(key): | |
| keys = load_json(API_KEY_FILE) | |
| return f"{keys.get(key, {}).get('remaining', 0):,}" if key in keys else "-" | |
| api_keys_list.change(fn=update_api_key_credit, inputs=api_keys_list, outputs=api_key_credit) | |
| refresh_btn = gr.Button("🔄 Refresh") | |
| refresh_btn.click(fn=refresh_all_dropdowns, outputs=[voice_dropdown, api_keys_list, total_credit_label, api_keys_list]) | |
| input_text = gr.Textbox(lines=6, label="Nhập văn bản") | |
| token_info = gr.Text(label="Tổng token") | |
| input_text.change(fn=lambda txt: f"{len(txt)} ký tự", inputs=input_text, outputs=token_info) | |
| result_audio = gr.Audio(label="Kết quả", type="filepath") | |
| status = gr.Text(label="Trạng thái") | |
| auto_mode = gr.Checkbox(label="Tự động chọn API Key đủ credit", value=True) | |
| generate_btn = gr.Button("🌀 Tạo giọng nói") | |
| generate_btn.click( | |
| fn=tts_from_text, | |
| inputs=[input_text, voice_dropdown, model_dropdown, output_format, api_keys_list, auto_mode, api_keys_state], | |
| outputs=[result_audio, status, total_credit_label] | |
| ) | |
| with gr.Tab("2. Quản lý API Key"): | |
| gr.Markdown("### Thêm API Key của bạn") | |
| gr.Markdown("Nhập API key của ElevenLabs tại đây. Mỗi key trên một dòng.") | |
| multi_api_input = gr.Textbox(lines=5, label="Nhập API Key (mỗi dòng 1 key)") | |
| save_btn = gr.Button("💾 Lưu & Kiểm tra") | |
| key_status_table = gr.Dataframe(headers=["API Key", "Status", "Used", "Limit", "Tier", "Remaining"], label="Danh sách API Key") | |
| refresh_keys_btn = gr.Button("🔄 Refresh danh sách") | |
| filter_input = gr.Number(label="Lọc các API Key dưới bao nhiêu credit") | |
| filter_btn = gr.Button("🔍 Lọc") | |
| remove_low_btn = gr.Button("❌ Xoá các key không đủ credit") | |
| save_btn.click( | |
| fn=save_and_show_keys, | |
| inputs=multi_api_input, | |
| outputs=[key_status_table, api_keys_list, api_keys_list] | |
| ) | |
| refresh_keys_btn.click( | |
| fn=refresh_keys, | |
| outputs=[key_status_table, api_keys_list, total_credit_label, api_keys_list] | |
| ) | |
| filter_btn.click(fn=filter_api_keys_by_credit, inputs=filter_input, outputs=key_status_table) | |
| remove_low_btn.click(fn=remove_insufficient_keys, inputs=filter_input, outputs=[key_status_table, api_keys_list, api_keys_list]) | |
| with gr.Tab("3. Quản lý Voice ID"): | |
| gr.Markdown("### Thêm Voice ID mới") | |
| gr.Markdown("Bạn có thể tìm Voice ID mới trên ElevenLabs và thêm vào đây.") | |
| with gr.Row(): | |
| voice_name = gr.Textbox(label="Tên Voice", scale=1) | |
| voice_id_box = gr.Textbox(label="Voice ID", scale=2) | |
| voice_select = gr.Dropdown(choices=get_voice_list(), value=None, label="Chọn Voice để sửa", interactive=True, scale=2) | |
| voice_status = gr.Textbox(label="Trạng thái", interactive=False) | |
| with gr.Row(): | |
| save_voice_btn = gr.Button("💾 Lưu Voice", scale=1) | |
| save_config_btn = gr.Button("💾 Lưu cấu hình Voice", scale=1) | |
| with gr.Row(): | |
| delete_voice_btn = gr.Button("❌ Xoá Voice đang chọn", variant="secondary", scale=1) | |
| reset_config_btn = gr.Button("↻ Reset cấu hình Voice", variant="primary", scale=1) | |
| gr.Markdown("### Cấu hình Voice") | |
| speed_slider = gr.Slider(0.70, 1.20, DEFAULT_VOICE_SETTINGS["speed"], step=0.01, label="Tốc độ (Speed): Chậm 0.70 - Nhanh 1.20") | |
| stability_slider = gr.Slider(0.0, 1.0, DEFAULT_VOICE_SETTINGS["stability"], label="Độ ổn định (Stability): 0-100% - Cao = Giọng đều đặn, Thấp = Giọng biểu cảm hơn") | |
| similarity_slider = gr.Slider(0.0, 1.0, DEFAULT_VOICE_SETTINGS["similarity_boost"], label="Độ tương đồng (Similarity): 0-100% - Cao = Giống giọng gốc, Thấp = Đa dạng hơn") | |
| exaggeration_slider = gr.Slider(0.0, 1.0, DEFAULT_VOICE_SETTINGS["style_exaggeration"], label="Phóng đại phong cách (Style Exaggeration): 0-100% - Cao = Phóng đại phong cách, Thấp = Tự nhiên") | |
| boost_checkbox = gr.Checkbox(label="Tăng cường giọng nói (Speaker Boost): Giọng rõ và trong hơn", value=DEFAULT_VOICE_SETTINGS["use_speaker_boost"]) | |
| gr.Markdown("## 📄 Danh sách Voice đã lưu") | |
| voice_table_refresh = gr.Button("🔄 Làm mới danh sách") | |
| voice_table_view = gr.Dataframe(label="Voice List", interactive=False) | |
| # Phần xóa tất cả voices - chuyển xuống dưới cùng | |
| gr.Markdown("### Xóa tất cả Voice") | |
| with gr.Row(): | |
| delete_confirm = gr.Checkbox(label="Xác nhận xóa tất cả Voice", value=False) | |
| delete_all_btn = gr.Button("❌ Xoá tất cả Voice", variant="stop") | |
| # Voice management event handlers | |
| save_voice_btn.click( | |
| fn=save_voice, | |
| inputs=[voice_name, voice_id_box, voice_select], | |
| outputs=[voice_status] | |
| ).then( | |
| fn=get_voice_list, | |
| outputs=[voice_select, voice_dropdown] | |
| ).then( | |
| fn=voice_table, | |
| outputs=voice_table_view | |
| ).then( | |
| fn=lambda name: name, | |
| inputs=[voice_name], | |
| outputs=[voice_select] | |
| ) | |
| voice_select.change( | |
| fn=load_voice_for_edit, | |
| inputs=[voice_select], | |
| outputs=[voice_name, voice_id_box, speed_slider, stability_slider, similarity_slider, exaggeration_slider, boost_checkbox] | |
| ) | |
| # "Reset cấu hình Voice" button | |
| reset_config_btn.click( | |
| fn=reset_voice_settings, | |
| inputs=[voice_select, voice_select], | |
| outputs=[voice_status, voice_select, voice_dropdown, voice_select] | |
| ).then( | |
| fn=voice_table, | |
| outputs=voice_table_view | |
| ).then( | |
| fn=lambda name: load_voice_for_edit(name), | |
| inputs=[voice_select], | |
| outputs=[voice_name, voice_id_box, speed_slider, stability_slider, similarity_slider, exaggeration_slider, boost_checkbox] | |
| ) | |
| # "Xoá Voice đang chọn" button | |
| delete_voice_btn.click( | |
| fn=delete_voice, | |
| inputs=[voice_select, voice_select], | |
| outputs=[voice_status, voice_select, voice_dropdown, voice_select] | |
| ).then( | |
| fn=voice_table, | |
| outputs=voice_table_view | |
| ).then( | |
| fn=lambda name: load_voice_for_edit(name) if name else ("", "", DEFAULT_VOICE_SETTINGS["speed"], DEFAULT_VOICE_SETTINGS["stability"], DEFAULT_VOICE_SETTINGS["similarity_boost"], DEFAULT_VOICE_SETTINGS["style_exaggeration"], DEFAULT_VOICE_SETTINGS["use_speaker_boost"]), | |
| inputs=[voice_select], | |
| outputs=[voice_name, voice_id_box, speed_slider, stability_slider, similarity_slider, exaggeration_slider, boost_checkbox] | |
| ) | |
| # "Xoá tất cả Voice" button - thêm tham số confirm_delete | |
| delete_all_btn.click( | |
| fn=delete_all_voices, | |
| inputs=[delete_confirm], | |
| outputs=[voice_status, voice_select, voice_dropdown] | |
| ).then( | |
| fn=voice_table, | |
| outputs=voice_table_view | |
| ).then( | |
| # Xóa giá trị trong các trường nhập | |
| fn=lambda: ("", "", DEFAULT_VOICE_SETTINGS["speed"], DEFAULT_VOICE_SETTINGS["stability"], DEFAULT_VOICE_SETTINGS["similarity_boost"], DEFAULT_VOICE_SETTINGS["style_exaggeration"], DEFAULT_VOICE_SETTINGS["use_speaker_boost"]), | |
| outputs=[voice_name, voice_id_box, speed_slider, stability_slider, similarity_slider, exaggeration_slider, boost_checkbox] | |
| ) | |
| # "Lưu cấu hình Voice" button | |
| save_config_btn.click( | |
| fn=update_voice_config, | |
| inputs=[voice_select, speed_slider, stability_slider, similarity_slider, exaggeration_slider, boost_checkbox, voice_select], | |
| outputs=[ |