feat: implement core UI tabs, API backend management, and initial service infrastructure.

This commit is contained in:
tinix-ai
2026-03-18 08:40:54 +07:00
parent 78cf08823d
commit bde89de5ce
16 changed files with 898 additions and 258 deletions
+28 -6
View File
@@ -3,6 +3,7 @@ from datetime import datetime
from locales.i18n import t
from services.project_manager import ProjectManager, list_project_titles
from services.novel_generator import Chapter
from services.style_manager import StyleManager
from core.state import app_state
import logging
@@ -40,9 +41,20 @@ def build_continue_tab():
memory_type = gr.Radio(label="Cách nhớ ngữ cảnh", choices=["Toàn văn", "Tóm tắt"], value="Toàn văn")
with gr.Column(scale=1):
memory_chapters = gr.Number(label="Số chương ghi nhớ", value=3, minimum=1, maximum=20, step=1)
with gr.Column(scale=1):
use_reflection_checkbox = gr.Checkbox(label="Bật chế độ Tự kiểm duyệt (Self-Reflection)", value=False, info="AI tự đọc lại và sửa lỗi nháp. Tốn gấp đôi thời gian & Token.")
with gr.Row():
continue_target_words = gr.Number(label=t("rewrite.target_words"), value=3000, minimum=100, maximum=50000, step=100)
style_choices = StyleManager.get_style_names()
continue_style_dropdown = gr.Dropdown(
choices=style_choices,
label="Phong cách viết",
value=style_choices[0] if style_choices else None,
interactive=True
)
continue_custom_prompt = gr.Textbox(label=t("create.custom_prompt_label"), placeholder=t("create.custom_prompt_placeholder"), scale=2)
with gr.Accordion("🚀 3. Sáng tác tiếp", open=False):
@@ -141,12 +153,16 @@ def build_continue_tab():
return ch.content or ""
return ""
def on_continue_generate(project_title, ch_num, ch_title, ch_desc, target_words, custom_prompt, mem_type, mem_chaps):
def on_continue_generate(project_title, ch_num, ch_title, ch_desc, target_words, custom_prompt, mem_type, mem_chaps, use_reflection, selected_style):
yield "⏳ Đang chuẩn bị dữ liệu...", "", gr.update(interactive=False), gr.update(), gr.update()
if not app_state.current_project:
yield f"{t('continue_tab.no_project_selected')}", "", gr.update(interactive=True), gr.update(), gr.update()
return
gen = app_state.get_generator()
if selected_style:
gen.config.generation.writing_style = selected_style
gen = app_state.get_generator()
project = app_state.current_project
@@ -192,7 +208,8 @@ def build_continue_tab():
world_setting=project.world_setting,
plot_idea=project.plot_idea, genre=project.genre,
sub_genres=project.sub_genres,
previous_content=prev_content, context_summary=context_summary, custom_prompt=custom_prompt
previous_content=prev_content, context_summary=context_summary, custom_prompt=custom_prompt,
use_reflection=use_reflection
)
if content:
@@ -228,12 +245,16 @@ def build_continue_tab():
else:
yield f"{msg}", "", gr.update(interactive=True), gr.update(), gr.update()
def on_continue_auto_generate(project_title, target_words, custom_prompt, mem_type, mem_chaps, progress=gr.Progress()):
def on_continue_auto_generate(project_title, target_words, custom_prompt, mem_type, mem_chaps, use_reflection, selected_style, progress=gr.Progress()):
yield "⏳ Đang chuẩn bị dữ liệu...", "", gr.update(interactive=False), gr.update(interactive=False), gr.update(), gr.update()
if not app_state.current_project:
yield f"{t('continue_tab.no_project_selected')}", "", gr.update(interactive=True), gr.update(interactive=True), gr.update(), gr.update()
return
gen = app_state.get_generator()
if selected_style:
gen.config.generation.writing_style = selected_style
gen = app_state.get_generator()
project = app_state.current_project
app_state.is_generating = True
@@ -292,7 +313,8 @@ def build_continue_tab():
world_setting=project.world_setting,
plot_idea=project.plot_idea, genre=project.genre,
sub_genres=project.sub_genres,
previous_content=prev_content, context_summary=context_summary, custom_prompt=custom_prompt
previous_content=prev_content, context_summary=context_summary, custom_prompt=custom_prompt,
use_reflection=use_reflection
)
if content:
@@ -347,12 +369,12 @@ def build_continue_tab():
continue_generate_btn.click(
fn=on_continue_generate,
inputs=[continue_project_selector, continue_chapter_num, continue_chapter_title,
continue_chapter_desc, continue_target_words, continue_custom_prompt, memory_type, memory_chapters],
continue_chapter_desc, continue_target_words, continue_custom_prompt, memory_type, memory_chapters, use_reflection_checkbox, continue_style_dropdown],
outputs=[continue_status, continue_content_display, continue_generate_btn, continue_outline_display, continue_chapter_selector]
)
continue_auto_btn.click(
fn=on_continue_auto_generate,
inputs=[continue_project_selector, continue_target_words, continue_custom_prompt, memory_type, memory_chapters],
inputs=[continue_project_selector, continue_target_words, continue_custom_prompt, memory_type, memory_chapters, use_reflection_checkbox, continue_style_dropdown],
outputs=[continue_status, continue_content_display, continue_auto_btn, continue_generate_btn, continue_outline_display, continue_chapter_selector],
show_progress="full"
)
+19 -3
View File
@@ -9,6 +9,7 @@ from services.novel_generator import OutlineParser
from services.project_manager import ProjectManager
from services.genre_manager import GenreManager
from services.sub_genre_manager import SubGenreManager
from services.style_manager import StyleManager
from core.state import app_state
import logging
@@ -36,6 +37,14 @@ def build_create_tab():
multiselect=True,
interactive=True
)
style_choices = StyleManager.get_style_names()
create_style_dropdown = gr.Dropdown(
choices=style_choices,
label="Phong cách viết",
value=style_choices[0] if style_choices else None,
interactive=True
)
with gr.Column(scale=2):
title_input = gr.Textbox(
@@ -115,6 +124,8 @@ def build_create_tab():
memory_type = gr.Radio(label="Cách nhớ ngữ cảnh", choices=["Toàn văn", "Tóm tắt"], value="Toàn văn")
with gr.Column(scale=1):
memory_chapters = gr.Number(label="Số chương ghi nhớ", value=3, minimum=1, maximum=20, step=1)
with gr.Column(scale=1):
use_reflection_checkbox = gr.Checkbox(label="Bật chế độ Tự kiểm duyệt (Self-Reflection)", value=False, info="AI tự đọc lại và sửa lỗi nháp. Tốn gấp đôi thời gian & Token.")
with gr.Row():
auto_generate_btn = gr.Button(t("create.start_gen_btn"), variant="primary", size="lg", interactive=False)
stop_btn = gr.Button(t("create.pause_gen_btn"), variant="stop", size="lg", interactive=False)
@@ -200,9 +211,13 @@ def build_create_tab():
)
return content, msg
def on_auto_generate(title, genre, sub_genres, char_setting, world_setting, plot_idea, outline_text, mem_type, mem_chaps, progress=gr.Progress()):
def on_auto_generate(title, genre, sub_genres, char_setting, world_setting, plot_idea, outline_text, mem_type, mem_chaps, use_reflection, selected_style, progress=gr.Progress()):
"""Tự động tạo toàn bộ tiểu thuyết"""
gen = app_state.get_generator()
# Cập nhật style vào config hiện tại
if selected_style:
gen.config.generation.writing_style = selected_style
chapters, parse_msg = OutlineParser.parse(outline_text)
if not chapters:
@@ -273,7 +288,8 @@ def build_create_tab():
chapter_desc=chapter.desc, novel_title=title,
character_setting=char_setting, world_setting=world_setting,
plot_idea=plot_idea, genre=genre, sub_genres=sub_genres,
previous_content=prev_content, context_summary=context_summary
previous_content=prev_content, context_summary=context_summary,
use_reflection=use_reflection
)
if content:
@@ -340,7 +356,7 @@ def build_create_tab():
)
auto_generate_btn.click(
fn=on_auto_generate,
inputs=[title_input, genre_dropdown, sub_genre_dropdown, character_input, world_input, plot_input, outline_output, memory_type, memory_chapters],
inputs=[title_input, genre_dropdown, sub_genre_dropdown, character_input, world_input, plot_input, outline_output, memory_type, memory_chapters, use_reflection_checkbox, create_style_dropdown],
outputs=[generation_progress, chapter_selector]
)
chapter_selector.change(
+8 -6
View File
@@ -25,6 +25,8 @@ def build_polish_tab():
with gr.Row():
polish_btn = gr.Button(t("polish.start_polish"), variant="primary")
polish_suggest_btn = gr.Button(t("polish.polish_suggest_btn"), variant="secondary")
use_reflection_checkbox = gr.Checkbox(label="Bật chế độ Tự kiểm duyệt (Self-Reflection)", value=False, info="AI báo lỗi và viết lại cho hoàn chỉnh hơn.")
polish_status = gr.Textbox(label=t("polish.polish_status"), interactive=False)
polish_output = gr.Textbox(label=t("polish.polished_text"), lines=15, interactive=True)
@@ -38,24 +40,24 @@ def build_polish_tab():
except Exception as e:
return f"{str(e)}"
def on_polish(text, custom_req):
def on_polish(text, custom_req, use_reflection):
yield "⏳ Đang gọi AI xử lý... Vui lòng chờ.", gr.update(), gr.update(interactive=False)
gen = app_state.get_generator()
content, msg = gen.polish_text(text, custom_requirements=custom_req)
content, msg = gen.polish_text(text, custom_requirements=custom_req, use_reflection=use_reflection)
if content:
yield f"{msg}", content, gr.update(interactive=True)
else:
yield f"{msg}", gr.update(), gr.update(interactive=True)
def on_polish_suggest(text, custom_req):
def on_polish_suggest(text, custom_req, use_reflection):
yield "⏳ Đang gọi AI xử lý... Vui lòng chờ.", gr.update(), gr.update(interactive=False)
gen = app_state.get_generator()
content, msg = gen.polish_and_suggest(text, custom_requirements=custom_req)
content, msg = gen.polish_and_suggest(text, custom_requirements=custom_req, use_reflection=use_reflection)
if content:
yield f"{msg}", content, gr.update(interactive=True)
else:
yield f"{msg}", gr.update(), gr.update(interactive=True)
polish_file_input.change(fn=on_polish_file_upload, inputs=[polish_file_input], outputs=[polish_input])
polish_btn.click(fn=on_polish, inputs=[polish_input, polish_custom_req], outputs=[polish_status, polish_output, polish_btn])
polish_suggest_btn.click(fn=on_polish_suggest, inputs=[polish_input, polish_custom_req], outputs=[polish_status, polish_output, polish_suggest_btn])
polish_btn.click(fn=on_polish, inputs=[polish_input, polish_custom_req, use_reflection_checkbox], outputs=[polish_status, polish_output, polish_btn])
polish_suggest_btn.click(fn=on_polish_suggest, inputs=[polish_input, polish_custom_req, use_reflection_checkbox], outputs=[polish_status, polish_output, polish_suggest_btn])
+2 -3
View File
@@ -13,9 +13,7 @@ def build_projects_tab():
headers=["ID", t("ui.col_project_name"), t("ui.col_type"), t("ui.col_created_at"), t("ui.col_chapters")],
interactive=False
)
with gr.Row():
refresh_projects_btn = gr.Button(t("projects.refresh_btn"))
delete_project_btn = gr.Button(t("projects.delete_btn"), variant="stop")
refresh_projects_btn = gr.Button(t("projects.refresh_btn"))
gr.Markdown(f"#### {t('projects.delete_header')}")
delete_project_selector = gr.Dropdown(
@@ -23,6 +21,7 @@ def build_projects_tab():
label=t("projects.delete_select_project"),
interactive=True
)
delete_project_btn = gr.Button(t("projects.delete_btn"), variant="stop")
project_manage_status = gr.Textbox(label=t("projects.status_label"), interactive=False)
def on_refresh_projects():
+28 -12
View File
@@ -1,6 +1,8 @@
import gradio as gr
from locales.i18n import t
from services.novel_generator import get_preset_templates
from services.genre_manager import GenreManager
from services.style_manager import StyleManager
from utils.file_parser import parse_novel_file
from core.state import app_state
import logging
@@ -12,19 +14,33 @@ def build_rewrite_tab():
gr.Markdown(f"### {t('rewrite.header')}")
rewrite_file_input = gr.File(label=t("rewrite.upload_file"), file_types=[".txt", ".docx", ".md", ".pdf"])
genre_choices = GenreManager.get_genre_names()
with gr.Row():
rewrite_genre = gr.Dropdown(
choices=genre_choices,
label=t("create.genre_label"),
value=genre_choices[0] if genre_choices else None,
interactive=True,
scale=1
)
style_choices = StyleManager.get_style_names()
rewrite_style = gr.Dropdown(
choices=style_choices,
label=t("rewrite.preset_style"),
value=style_choices[0] if style_choices else None,
interactive=True,
scale=1
)
use_reflection_checkbox = gr.Checkbox(label="Bật chế độ Tự kiểm duyệt", value=False, info="AI báo lỗi và viết lại cho hoàn chỉnh hơn.", scale=1)
rewrite_input = gr.Textbox(
label=t("polish.original_text"),
lines=10, placeholder="Dán nội dung cần viết lại..."
)
preset_choices = list(get_preset_templates().keys())
rewrite_style = gr.Dropdown(
choices=preset_choices,
label=t("rewrite.preset_style"),
value=preset_choices[0] if preset_choices else None,
interactive=True
)
rewrite_btn = gr.Button(t("rewrite.start_rewrite"), variant="primary")
rewrite_status = gr.Textbox(label=t("rewrite.parse_status"), interactive=False)
rewrite_output = gr.Textbox(label=t("rewrite.full_rewritten"), lines=15, interactive=True)
@@ -38,15 +54,15 @@ def build_rewrite_tab():
except Exception as e:
return f"{str(e)}"
def on_rewrite(text, style):
def on_rewrite(text, genre, style, use_reflection):
yield "⏳ Đang gọi AI xử lý... Vui lòng chờ.", gr.update(), gr.update(interactive=False)
instructions = get_preset_templates().get(style, "")
instructions = f"Thể loại: {genre}\nPhong cách thiết lập: {style}"
gen = app_state.get_generator()
content, msg = gen.rewrite_paragraph(text, instructions)
content, msg = gen.rewrite_paragraph(text, instructions, use_reflection=use_reflection)
if content:
yield f"{msg}", content, gr.update(interactive=True)
else:
yield f"{msg}", gr.update(), gr.update(interactive=True)
rewrite_file_input.change(fn=on_file_upload, inputs=[rewrite_file_input], outputs=[rewrite_input])
rewrite_btn.click(fn=on_rewrite, inputs=[rewrite_input, rewrite_style], outputs=[rewrite_status, rewrite_output, rewrite_btn])
rewrite_btn.click(fn=on_rewrite, inputs=[rewrite_input, rewrite_genre, rewrite_style, use_reflection_checkbox], outputs=[rewrite_status, rewrite_output, rewrite_btn])
+271 -101
View File
@@ -6,6 +6,7 @@ from services.api_client import get_api_client, reinit_api_client
from services.novel_generator import get_cache_size, list_generation_caches, clear_generation_cache
from services.genre_manager import GenreManager
from services.sub_genre_manager import SubGenreManager
from services.style_manager import StyleManager
from core.state import app_state
def build_settings_tab():
@@ -15,15 +16,13 @@ def build_settings_tab():
with gr.Tabs():
# Sub-tab: Quản lý giao diện API
with gr.Tab(t("settings.tab_backends")):
gr.Markdown(f"#### {t('settings.backends_header')}")
backends_display = gr.Markdown("...")
gr.Markdown(f"### {t('settings.backends_header')}")
def get_backends_display():
try:
result = ConfigAPIManager.list_backends()
if result["success"] and result["data"]:
lines = [f"### 📋 {len(result['data'])} backend(s)\n"]
lines = [f"#### 📋 {len(result['data'])} backend(s)\n"]
for b in result["data"]:
enabled = "" if b.get("enabled") else ""
default = "" if b.get("is_default") else ""
@@ -37,52 +36,69 @@ def build_settings_tab():
except Exception as e:
return f"{str(e)}"
gr.Markdown("---")
gr.Markdown(f"#### {t('settings.add_backend_header')}")
provider_names = [API_PROVIDERS[k]["name"] for k in API_PROVIDERS]
with gr.Row():
api_provider_dropdown = gr.Dropdown(
choices=provider_names,
label=t("settings.provider_label"),
info=t("settings.provider_info"),
interactive=True
)
# Cột trái: Quản lý danh sách
with gr.Column(scale=1):
with gr.Group():
backends_display = gr.Markdown("...")
api_refresh_btn = gr.Button(t("settings.refresh_list"), size="sm")
with gr.Group():
gr.Markdown(f"#### {t('settings.test_manage_header')}")
with gr.Row():
test_name_input = gr.Textbox(label=t("settings.test_backend_name"), placeholder=t("settings.test_backend_placeholder"), scale=2)
api_test_btn = gr.Button(t("settings.test_btn"), variant="secondary", scale=1)
test_result = gr.Textbox(label=t("settings.test_result"), interactive=False)
with gr.Row():
api_name_input = gr.Textbox(label=t("settings.backend_name"), placeholder=t("settings.backend_name_placeholder"))
api_type_dropdown = gr.Dropdown(
choices=ConfigAPIManager.get_backend_types(),
value="openai", label=t("settings.backend_type"), interactive=True
)
gr.Markdown("---")
with gr.Row():
delete_name_input = gr.Textbox(label=t("settings.delete_backend_name"), placeholder=t("settings.delete_backend_placeholder"), scale=2)
api_delete_btn = gr.Button(t("settings.delete_btn"), variant="stop", scale=1)
with gr.Row():
api_url_input = gr.Textbox(label=t("settings.base_url"), placeholder=t("settings.base_url_placeholder"))
api_key_input = gr.Textbox(label=t("settings.api_key"), placeholder=t("settings.api_key_placeholder"), type="password")
# Cột phải: Form thay đổi
with gr.Column(scale=2):
with gr.Group():
gr.Markdown(f"#### Thay đổi cấu hình ({t('settings.add_backend_header')})")
provider_names = [API_PROVIDERS[k]["name"] for k in API_PROVIDERS]
with gr.Row():
manage_backend_select = gr.Dropdown(
choices=[b['name'] for b in ConfigAPIManager.list_backends().get("data", [])],
label="Chọn cấu hình để chỉnh sửa (bỏ trống để thêm mới)",
interactive=True,
scale=4
)
manage_backend_btn = gr.Button("Chọn", variant="secondary", scale=1)
with gr.Row():
api_model_input = gr.Textbox(label=t("settings.model_name"), placeholder=t("settings.model_name_placeholder"))
api_timeout_input = gr.Slider(minimum=5, maximum=600, value=120, step=5, label=t("settings.timeout"))
with gr.Row():
api_provider_dropdown = gr.Dropdown(
choices=provider_names,
label=t("settings.provider_label"),
info=t("settings.provider_info"),
interactive=True
)
with gr.Row():
api_save_btn = gr.Button(t("settings.add_btn"), variant="primary")
api_refresh_btn = gr.Button(t("settings.refresh_list"), size="sm")
with gr.Row():
api_name_input = gr.Textbox(label=t("settings.backend_name"), placeholder=t("settings.backend_name_placeholder"))
api_type_dropdown = gr.Dropdown(
choices=ConfigAPIManager.get_backend_types(),
value="openai", label=t("settings.backend_type"), interactive=True
)
api_status = gr.Textbox(label=t("settings.operation_result"), interactive=False)
with gr.Row():
api_url_input = gr.Textbox(label=t("settings.base_url"), placeholder=t("settings.base_url_placeholder"))
api_key_input = gr.Textbox(label=t("settings.api_key"), placeholder=t("settings.api_key_placeholder"), type="text")
gr.Markdown("---")
gr.Markdown(f"#### {t('settings.test_manage_header')}")
with gr.Row():
api_model_input = gr.Textbox(label=t("settings.model_name"), placeholder=t("settings.model_name_placeholder"))
api_timeout_input = gr.Slider(minimum=5, maximum=600, value=120, step=5, label=t("settings.timeout"))
with gr.Row():
test_name_input = gr.Textbox(label=t("settings.test_backend_name"), placeholder=t("settings.test_backend_placeholder"))
api_test_btn = gr.Button(t("settings.test_btn"), variant="secondary")
with gr.Row():
api_save_btn = gr.Button(t("settings.add_btn"), variant="primary")
api_update_btn = gr.Button("Cập nhật thay đổi", variant="secondary")
with gr.Row():
delete_name_input = gr.Textbox(label=t("settings.delete_backend_name"), placeholder=t("settings.delete_backend_placeholder"))
api_delete_btn = gr.Button(t("settings.delete_btn"), variant="stop")
test_result = gr.Textbox(label=t("settings.test_result"), interactive=False)
api_status = gr.Textbox(label=t("settings.operation_result"), interactive=False)
def on_provider_select(provider_name):
for key, info in API_PROVIDERS.items():
@@ -96,52 +112,136 @@ def build_settings_tab():
yield result["message"]
def on_api_save(name, btype, url, key, model, timeout):
if not name.strip():
return "❌ Vui lòng nhập tên giao diện", get_backends_display(), gr.update()
result = ConfigAPIManager.add_backend(name, btype, url, key, model, int(timeout))
reinit_api_client()
app_state.generator = None
return result["message"], get_backends_display()
new_choices = [b['name'] for b in ConfigAPIManager.list_backends().get("data", [])]
return result["message"], get_backends_display(), gr.update(choices=new_choices)
def on_api_update(old_name, new_name, btype, url, key, model, timeout):
if not old_name:
return "❌ Vui lòng chọn một cấu hình để chỉnh sửa trước", get_backends_display(), gr.update()
if not new_name.strip():
return "❌ Vui lòng nhập tên giao diện", get_backends_display(), gr.update()
# Handle updating the existing backend with new details
# If name was changed, it requires updating that as well but the update_backend method modifies existing object's properties in place based on its previous name
result = ConfigAPIManager.update_backend(
old_name,
name=new_name.strip(),
type=btype,
base_url=url,
api_key=key,
model=model,
timeout=int(timeout)
)
reinit_api_client()
app_state.generator = None
new_choices = [b['name'] for b in ConfigAPIManager.list_backends().get("data", [])]
# if name changed, update the dropdown selected value to the new name, otherwise clear it
new_val = new_name.strip() if result["success"] else old_name
return result["message"], get_backends_display(), gr.update(choices=new_choices, value=new_val)
def on_backend_select(selected_name):
print(f"DEBUG: on_backend_select triggered with: {selected_name}")
if not selected_name:
return gr.update(value=""), gr.update(value="openai"), gr.update(value=""), gr.update(value=""), gr.update(value=""), gr.update(value=120)
backends = ConfigAPIManager.list_backends().get("data", [])
for b in backends:
if b["name"] == selected_name:
return (
gr.update(value=b.get("name", "")),
gr.update(value=b.get("type", "openai")),
gr.update(value=b.get("base_url", "")),
gr.update(value=b.get("api_key", "")),
gr.update(value=b.get("model", "")),
gr.update(value=b.get("timeout", 120))
)
return gr.update(value=""), gr.update(value="openai"), gr.update(value=""), gr.update(value=""), gr.update(value=""), gr.update(value=120)
def on_api_delete(name):
result = ConfigAPIManager.delete_backend(name)
reinit_api_client()
app_state.generator = None
return result["message"], get_backends_display()
new_choices = [b['name'] for b in ConfigAPIManager.list_backends().get("data", [])]
return result["message"], get_backends_display(), gr.update(choices=new_choices)
api_provider_dropdown.change(
fn=on_provider_select,
inputs=[api_provider_dropdown],
outputs=[api_url_input, api_model_input, api_name_input]
)
manage_backend_btn.click(
fn=on_backend_select,
inputs=[manage_backend_select],
outputs=[api_name_input, api_type_dropdown, api_url_input, api_key_input, api_model_input, api_timeout_input]
)
api_save_btn.click(
fn=on_api_save,
inputs=[api_name_input, api_type_dropdown, api_url_input, api_key_input, api_model_input, api_timeout_input],
outputs=[api_status, backends_display]
outputs=[api_status, backends_display, manage_backend_select]
)
api_update_btn.click(
fn=on_api_update,
inputs=[manage_backend_select, api_name_input, api_type_dropdown, api_url_input, api_key_input, api_model_input, api_timeout_input],
outputs=[api_status, backends_display, manage_backend_select]
)
api_test_btn.click(fn=on_api_test, inputs=[test_name_input], outputs=[test_result])
api_delete_btn.click(fn=on_api_delete, inputs=[delete_name_input], outputs=[api_status, backends_display])
api_refresh_btn.click(fn=lambda: get_backends_display(), outputs=[backends_display])
api_delete_btn.click(fn=on_api_delete, inputs=[delete_name_input], outputs=[api_status, backends_display, manage_backend_select])
def force_refresh_backends():
choices = [b['name'] for b in ConfigAPIManager.list_backends().get("data", [])]
return get_backends_display(), gr.update(choices=choices)
api_refresh_btn.click(fn=force_refresh_backends, outputs=[backends_display, manage_backend_select])
# Sub-tab: Tham số sinh
with gr.Tab(t("settings.tab_params")):
gr.Markdown(f"#### {t('settings.params_header')}")
with gr.Group():
gr.Markdown(f"#### {t('settings.params_header')}")
config = get_config()
gen_config = config.generation
config = get_config()
gen_config = config.generation
param_temperature = gr.Slider(minimum=0.1, maximum=2.0, value=gen_config.temperature, step=0.1, label=t("settings.temperature_label"), info=t("settings.temperature_info"))
param_top_p = gr.Slider(minimum=0.1, maximum=1.0, value=gen_config.top_p, step=0.05, label="Top P", info=t("settings.top_p_info"))
param_max_tokens = gr.Slider(minimum=100, maximum=100000, value=gen_config.max_tokens, step=100, label="Max Tokens", info=t("settings.max_tokens_info"))
param_chapter_words = gr.Slider(minimum=500, maximum=65536, value=gen_config.chapter_target_words, step=500, label=t("settings.chapter_target_words"))
with gr.Row():
param_temperature = gr.Slider(minimum=0.1, maximum=2.0, value=gen_config.temperature, step=0.1, label=t("settings.temperature_label"), info=t("settings.temperature_info"))
param_top_p = gr.Slider(minimum=0.1, maximum=1.0, value=gen_config.top_p, step=0.05, label="Top P", info=t("settings.top_p_info"))
save_params_btn = gr.Button(t("settings.save_params_btn"), variant="primary")
params_status = gr.Textbox(label=t("settings.save_status"), interactive=False)
with gr.Row():
param_max_tokens = gr.Slider(minimum=100, maximum=100000, value=gen_config.max_tokens, step=100, label="Max Tokens", info=t("settings.max_tokens_info"))
param_chapter_words = gr.Slider(minimum=500, maximum=65536, value=gen_config.chapter_target_words, step=500, label=t("settings.chapter_target_words"))
def on_save_params(temp, top_p, max_tokens, chapter_words):
with gr.Row():
param_writing_style = gr.Dropdown(choices=StyleManager.get_style_names(), value=gen_config.writing_style, label=t("settings.writing_style"), allow_custom_value=True)
param_writing_tone = gr.Dropdown(choices=t("settings.tones"), value=gen_config.writing_tone, label=t("settings.tone_label"), allow_custom_value=True)
with gr.Row():
param_char_dev = gr.Dropdown(choices=t("settings.char_dev_options"), value=gen_config.character_development, label=t("settings.char_dev_label"), allow_custom_value=True)
param_plot_complexity = gr.Dropdown(choices=t("settings.plot_complexity_options"), value=gen_config.plot_complexity, label=t("settings.plot_complexity_label"), allow_custom_value=True)
save_params_btn = gr.Button(t("settings.save_params_btn"), variant="primary")
params_status = gr.Textbox(label=t("settings.save_status"), interactive=False)
def on_save_params(temp, top_p, max_tokens, chapter_words, style, tone, chardev, plotcomp):
cfg = get_config()
success, msg = cfg.update_generation_config(
temperature=temp, top_p=top_p,
max_tokens=int(max_tokens),
chapter_target_words=int(chapter_words)
chapter_target_words=int(chapter_words),
writing_style=style,
writing_tone=tone,
character_development=chardev,
plot_complexity=plotcomp
)
if success:
app_state.generator = None
@@ -150,32 +250,33 @@ def build_settings_tab():
save_params_btn.click(
fn=on_save_params,
inputs=[param_temperature, param_top_p, param_max_tokens, param_chapter_words],
inputs=[param_temperature, param_top_p, param_max_tokens, param_chapter_words, param_writing_style, param_writing_tone, param_char_dev, param_plot_complexity],
outputs=[params_status]
)
# Sub-tab: Bộ nhớ đệm
with gr.Tab(t("settings.tab_cache")):
gr.Markdown(f"#### {t('settings.cache_header')}")
with gr.Group():
gr.Markdown(f"#### {t('settings.cache_header')}")
cache_info_display = gr.Markdown(t("ui.no_cache"))
cache_info_display = gr.Markdown(t("ui.no_cache"))
def get_cache_info():
try:
api_client = get_api_client()
stats = api_client.get_cache_stats()
gen_caches = list_generation_caches()
gen_size_val = get_cache_size()
def get_cache_info():
try:
api_client = get_api_client()
stats = api_client.get_cache_stats()
gen_caches = list_generation_caches()
gen_size_val = get_cache_size()
return f"### Thống kê bộ nhớ đệm\n- **API Cache**: {stats['total_entries']}/{stats['max_size']} ({stats['usage_rate']:.1f}%)\n- **Generation Cache**: {len(gen_caches)} files ({gen_size_val / 1024:.1f} KB)"
except Exception as e:
return f"{str(e)}"
return f"### Thống kê bộ nhớ đệm\n- **API Cache**: {stats['total_entries']}/{stats['max_size']} ({stats['usage_rate']:.1f}%)\n- **Generation Cache**: {len(gen_caches)} files ({gen_size_val / 1024:.1f} KB)"
except Exception as e:
return f"{str(e)}"
with gr.Row():
refresh_cache_btn = gr.Button(t("settings.refresh_cache"), size="sm")
clear_all_cache_btn = gr.Button(t("settings.clear_all"), variant="stop")
with gr.Row():
refresh_cache_btn = gr.Button(t("settings.refresh_cache"), size="sm")
clear_all_cache_btn = gr.Button(t("settings.clear_all"), variant="stop")
cache_op_status = gr.Textbox(label=t("settings.cache_op_status"), interactive=False)
cache_op_status = gr.Textbox(label=t("settings.cache_op_status"), interactive=False)
def on_clear_all_cache():
try:
@@ -191,22 +292,26 @@ def build_settings_tab():
# Sub-tab: Quản lý thể loại
with gr.Tab(t("settings.tab_genre")):
gr.Markdown(f"#### {t('settings.genre_desc')}")
genre_select = gr.Dropdown(
choices=GenreManager.get_genre_names(),
label=t("settings.genre_select"),
interactive=True
)
genre_name_input = gr.Textbox(label=t("settings.genre_name"), placeholder=t("settings.genre_name_placeholder"))
genre_desc_input = gr.Textbox(label=t("settings.genre_description"), placeholder=t("settings.genre_description_placeholder"), lines=3)
with gr.Row():
genre_add_btn = gr.Button(t("settings.genre_add_btn"), variant="primary")
genre_update_btn = gr.Button(t("settings.genre_update_btn"), variant="secondary")
genre_delete_btn = gr.Button(t("settings.genre_delete_btn"), variant="stop")
with gr.Column(scale=1):
gr.Markdown(f"#### {t('settings.genre_desc')}")
genre_select = gr.Dropdown(
choices=GenreManager.get_genre_names(),
label=t("settings.genre_select"),
interactive=True
)
genre_op_status = gr.Textbox(label=t("settings.genre_op_status"), interactive=False)
with gr.Column(scale=2):
with gr.Group():
genre_name_input = gr.Textbox(label=t("settings.genre_name"), placeholder=t("settings.genre_name_placeholder"))
genre_desc_input = gr.Textbox(label=t("settings.genre_description"), placeholder=t("settings.genre_description_placeholder"), lines=3)
with gr.Row():
genre_add_btn = gr.Button(t("settings.genre_add_btn"), variant="primary")
genre_update_btn = gr.Button(t("settings.genre_update_btn"), variant="secondary")
genre_delete_btn = gr.Button(t("settings.genre_delete_btn"), variant="stop")
genre_op_status = gr.Textbox(label=t("settings.genre_op_status"), interactive=False)
def on_genre_select(name):
if name:
@@ -245,22 +350,26 @@ def build_settings_tab():
# Sub-tab: Quản lý chủ đề con
with gr.Tab(t("settings.tab_sub_genre")):
gr.Markdown(f"#### {t('settings.sub_genre_desc')}")
sub_genre_select = gr.Dropdown(
choices=SubGenreManager.get_sub_genre_names(),
label=t("settings.sub_genre_select"),
interactive=True
)
sub_genre_name_input = gr.Textbox(label=t("settings.sub_genre_name"), placeholder=t("settings.sub_genre_name_placeholder"))
sub_genre_desc_input = gr.Textbox(label=t("settings.sub_genre_description"), placeholder=t("settings.sub_genre_description_placeholder"), lines=3)
with gr.Row():
sub_genre_add_btn = gr.Button(t("settings.sub_genre_add_btn"), variant="primary")
sub_genre_update_btn = gr.Button(t("settings.sub_genre_update_btn"), variant="secondary")
sub_genre_delete_btn = gr.Button(t("settings.sub_genre_delete_btn"), variant="stop")
with gr.Column(scale=1):
gr.Markdown(f"#### {t('settings.sub_genre_desc')}")
sub_genre_select = gr.Dropdown(
choices=SubGenreManager.get_sub_genre_names(),
label=t("settings.sub_genre_select"),
interactive=True
)
sub_genre_op_status = gr.Textbox(label=t("settings.sub_genre_op_status"), interactive=False)
with gr.Column(scale=2):
with gr.Group():
sub_genre_name_input = gr.Textbox(label=t("settings.sub_genre_name"), placeholder=t("settings.sub_genre_name_placeholder"))
sub_genre_desc_input = gr.Textbox(label=t("settings.sub_genre_description"), placeholder=t("settings.sub_genre_description_placeholder"), lines=3)
with gr.Row():
sub_genre_add_btn = gr.Button(t("settings.sub_genre_add_btn"), variant="primary")
sub_genre_update_btn = gr.Button(t("settings.sub_genre_update_btn"), variant="secondary")
sub_genre_delete_btn = gr.Button(t("settings.sub_genre_delete_btn"), variant="stop")
sub_genre_op_status = gr.Textbox(label=t("settings.sub_genre_op_status"), interactive=False)
def on_sub_genre_select(name):
if name:
@@ -296,3 +405,64 @@ def build_settings_tab():
sub_genre_add_btn.click(fn=on_sub_genre_add, inputs=[sub_genre_name_input, sub_genre_desc_input], outputs=[sub_genre_op_status, sub_genre_select])
sub_genre_update_btn.click(fn=on_sub_genre_update, inputs=[sub_genre_select, sub_genre_name_input, sub_genre_desc_input], outputs=[sub_genre_op_status, sub_genre_select])
sub_genre_delete_btn.click(fn=on_sub_genre_delete, inputs=[sub_genre_select], outputs=[sub_genre_op_status, sub_genre_select])
# Sub-tab: Quản lý phong cách viết
with gr.Tab("Quản lý phong cách viết"):
with gr.Row():
with gr.Column(scale=1):
gr.Markdown("#### Quản lý danh sách các phong cách viết để AI hiểu và áp dụng")
style_select = gr.Dropdown(
choices=StyleManager.get_style_names(),
label="Chọn phong cách",
interactive=True
)
with gr.Column(scale=2):
with gr.Group():
style_name_input = gr.Textbox(label="Tên phong cách", placeholder="Ví dụ: Mượt mà tự nhiên...")
style_desc_input = gr.Textbox(label="Mô tả phong cách", placeholder="Hướng dẫn chi tiết cách viết...", lines=3)
with gr.Row():
style_add_btn = gr.Button("Thêm phong cách", variant="primary")
style_update_btn = gr.Button("Cập nhật", variant="secondary")
style_delete_btn = gr.Button("Xóa", variant="stop")
style_op_status = gr.Textbox(label="Trạng thái thao tác", interactive=False)
def on_style_select(name):
if name:
desc = StyleManager.get_style_description(name)
return name, desc or ""
return "", ""
def on_style_add(name, desc):
if not name.strip():
return "❌ Tên phong cách không được trống", gr.update()
success = StyleManager.add_style(name.strip(), desc.strip())
if success:
new_choices = StyleManager.get_style_names()
return "✅ Đã thêm phong cách mới", gr.update(choices=new_choices)
return "❌ Tên phong cách đã tồn tại hoặc lỗi", gr.update()
def on_style_update(old_name, new_name, desc):
if not old_name:
return "❌ Vui lòng chọn phong cách", gr.update()
success = StyleManager.update_style(old_name, new_name.strip(), desc.strip())
if success:
new_choices = StyleManager.get_style_names()
return "✅ Đã cập nhật phong cách", gr.update(choices=new_choices)
return "❌ Cập nhật thất bại", gr.update()
def on_style_delete(name):
if not name:
return "❌ Vui lòng chọn phong cách", gr.update()
success = StyleManager.delete_style(name)
if success:
new_choices = StyleManager.get_style_names()
return "✅ Đã xóa phong cách", gr.update(choices=new_choices)
return "❌ Xóa thất bại", gr.update()
style_select.change(fn=on_style_select, inputs=[style_select], outputs=[style_name_input, style_desc_input])
style_add_btn.click(fn=on_style_add, inputs=[style_name_input, style_desc_input], outputs=[style_op_status, style_select])
style_update_btn.click(fn=on_style_update, inputs=[style_select, style_name_input, style_desc_input], outputs=[style_op_status, style_select])
style_delete_btn.click(fn=on_style_delete, inputs=[style_select], outputs=[style_op_status, style_select])