| """ |
| Ars-Fabula Visual Novel β Gradio entrypoint. |
| |
| The VN UI is split across the `ui/` package: |
| ui.styles β CSS + onload JS |
| ui.media β data: URI image embedding |
| ui.bootstrap β constants, initial scene, engine construction |
| ui.screens β HTML builders for the canvas |
| ui.beats β response β paced display beats + screen render |
| ui.callbacks β Gradio event handlers (make_callbacks) |
| |
| This module only assembles the gr.Blocks layout, wires events, and launches. |
| |
| Run with: |
| python app.py # production (tries real model) |
| ARS_FABULA_BACKEND=mock python app.py # test mode (no server) |
| ARS_FABULA_VOICE=kokoro python app.py # voiced lines via Kokoro |
| """ |
| from __future__ import annotations |
| import os |
|
|
| |
| |
| |
| |
| ON_SPACES = bool(os.getenv("SPACE_ID")) |
| if ON_SPACES: |
| os.environ.setdefault("ARS_FABULA_BACKEND", "transformers") |
|
|
| import gradio as gr |
|
|
| from providers import get_background_provider |
| from ui.styles import CSS, ONLOAD_JS |
| from ui.bootstrap import make_initial_scene, DEFAULT_CAST, STATIC_DIR |
| from ui.screens import _title_screen_html |
| from ui.callbacks import make_callbacks |
|
|
|
|
| def create_ui(): |
| """Build and return the Gradio VN app.""" |
| bg_backend = os.getenv("ARS_FABULA_BG", "preset") |
| bg_provider = get_background_provider(bg_backend) |
| (on_start, on_choice, on_save, on_load, on_reset, on_advance, |
| on_free_submit, on_casting_confirm, on_casting_redo, |
| on_menu_load) = make_callbacks(bg_provider) |
|
|
| initial_scene = make_initial_scene() |
|
|
| with gr.Blocks(title="Ars-Fabula VN") as app: |
| |
| scene_state = gr.State(initial_scene) |
| engine_ref = gr.State(None) |
| cast_data = gr.State(DEFAULT_CAST) |
| prev_bg_state = gr.State("") |
| beats_state = gr.State([]) |
| beat_idx_state = gr.State(0) |
| turn_streaming = gr.State(False) |
| |
| casting_state = gr.State(None) |
|
|
| |
| |
| |
| |
| |
| with gr.Column(elem_id="stage"): |
| |
| vn_screen = gr.HTML(_title_screen_html()) |
|
|
| |
| |
| |
| |
| |
| |
| with gr.Row(elem_id="free-input-row"): |
| free_input = gr.Textbox( |
| scale=8, lines=1, max_lines=1, |
| placeholder="Your choiceβ¦", |
| label=None, show_label=False, container=False, |
| elem_id="free-input", |
| ) |
| free_submit = gr.Button("β€", scale=1, size="sm", |
| elem_id="free-submit-btn") |
|
|
| |
| |
| |
| |
| |
| |
| cs_n_0 = gr.Textbox(value="", visible="hidden", interactive=True, elem_id="cs-n-0") |
| cs_c_0 = gr.Textbox(value="", visible="hidden", interactive=True, elem_id="cs-c-0") |
| cs_t_0 = gr.Textbox(value="", visible="hidden", interactive=True, elem_id="cs-t-0") |
| cs_n_1 = gr.Textbox(value="", visible="hidden", interactive=True, elem_id="cs-n-1") |
| cs_c_1 = gr.Textbox(value="", visible="hidden", interactive=True, elem_id="cs-c-1") |
| cs_t_1 = gr.Textbox(value="", visible="hidden", interactive=True, elem_id="cs-t-1") |
| cs_n_2 = gr.Textbox(value="", visible="hidden", interactive=True, elem_id="cs-n-2") |
| cs_c_2 = gr.Textbox(value="", visible="hidden", interactive=True, elem_id="cs-c-2") |
| cs_t_2 = gr.Textbox(value="", visible="hidden", interactive=True, elem_id="cs-t-2") |
| cs_fast = gr.Checkbox(value=False, visible="hidden", elem_id="cs-fast") |
| generate_cast_btn = gr.Button("Generate", visible="hidden", |
| elem_id="generate-cast-btn") |
|
|
| |
| cast_panel = gr.HTML(visible=False) |
| scene_info = gr.Markdown(visible=False) |
| narration_log = gr.Textbox(visible=False, interactive=False) |
|
|
| |
| |
| voice_audio = gr.Audio( |
| value=None, autoplay=True, visible=True, |
| interactive=False, elem_id="voice-audio", container=False, |
| ) |
|
|
| |
| |
| |
| preset_box = gr.Textbox(value="generated", visible="hidden", |
| interactive=True, elem_id="preset-box") |
| player_name_box = gr.Textbox(value="Player", visible="hidden", |
| interactive=True, elem_id="player-name-box") |
| start_btn = gr.Button("Begin", visible="hidden", |
| elem_id="start-btn-hidden") |
| save_slot = gr.Number(value=0, minimum=0, maximum=4, step=1, |
| precision=0, visible="hidden", |
| elem_id="save-slot-hidden") |
| save_btn = gr.Button("Save", visible="hidden", elem_id="save-btn-hidden") |
| load_btn = gr.Button("Load", visible="hidden", elem_id="load-btn-hidden") |
| reset_btn = gr.Button("Reset", visible="hidden", elem_id="reset-btn-hidden") |
| save_status = gr.Markdown("", visible="hidden", |
| elem_id="save-status-hidden") |
| |
| |
| save_name_box = gr.Textbox(value="", visible="hidden", interactive=True, |
| elem_id="save-name-box") |
| slots_meta = gr.HTML("", visible="hidden", elem_id="slots-meta-hidden") |
| |
| menu_load_btn = gr.Button("Menu Load", visible="hidden", |
| elem_id="menu-load-btn") |
|
|
| choice_input = gr.Textbox(visible="hidden", interactive=True, |
| elem_id="choice-id-box") |
| choice_text_input = gr.Textbox(visible="hidden", interactive=True, |
| elem_id="choice-text-box") |
| submit_choice_btn = gr.Button("Submit Choice", visible="hidden", |
| elem_id="submit-choice-btn") |
| |
| |
| advance_btn = gr.Button("Advance", visible="hidden", |
| elem_id="advance-btn") |
|
|
| |
| free_choice_id = gr.Textbox(value="__free__", visible="hidden", |
| elem_id="free-choice-id") |
|
|
| |
| casting_prompt_box = gr.Textbox(visible="hidden", interactive=True, |
| elem_id="casting-prompt-box") |
| casting_desc_box = gr.Textbox(visible="hidden", interactive=True, |
| elem_id="casting-desc-box") |
| casting_confirm_btn = gr.Button("Confirm", visible="hidden", |
| elem_id="casting-confirm-btn") |
| casting_redo_btn = gr.Button("Redo", visible="hidden", |
| elem_id="casting-redo-btn") |
|
|
| main_outputs = [scene_state, engine_ref, vn_screen, cast_data, |
| cast_panel, narration_log, scene_info, |
| voice_audio, prev_bg_state] |
| |
| turn_outputs = main_outputs + [beats_state, beat_idx_state] |
| |
| start_outputs = turn_outputs + [casting_state] |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| _start_inputs = [preset_box, casting_state, player_name_box, |
| cs_n_0, cs_c_0, cs_t_0, |
| cs_n_1, cs_c_1, cs_t_1, |
| cs_n_2, cs_c_2, cs_t_2, |
| cs_fast] |
| start_btn.click(fn=on_start, inputs=_start_inputs, outputs=start_outputs, |
| show_progress="hidden") |
| generate_cast_btn.click(fn=on_start, inputs=_start_inputs, outputs=start_outputs, |
| show_progress="hidden") |
|
|
| submit_choice_btn.click( |
| fn=on_choice, |
| inputs=[choice_input, choice_text_input, scene_state, engine_ref, |
| prev_bg_state, narration_log], |
| outputs=turn_outputs + [turn_streaming], |
| show_progress="hidden", |
| ) |
|
|
| |
| |
| advance_btn.click( |
| fn=on_advance, |
| inputs=[beats_state, beat_idx_state, scene_state, prev_bg_state, |
| narration_log, turn_streaming], |
| outputs=[vn_screen, beat_idx_state, voice_audio, scene_info, prev_bg_state], |
| show_progress="hidden", |
| ) |
|
|
| save_btn.click(fn=on_save, |
| inputs=[save_slot, save_name_box, narration_log, |
| scene_state, engine_ref], |
| outputs=[save_status, slots_meta], show_progress="hidden") |
| load_btn.click(fn=on_load, |
| inputs=[save_slot, scene_state, engine_ref, prev_bg_state], |
| outputs=turn_outputs + [save_status], |
| show_progress="hidden") |
| menu_load_btn.click(fn=on_menu_load, inputs=[save_slot], |
| outputs=turn_outputs + [save_status], |
| show_progress="hidden") |
|
|
| reset_btn.click(fn=on_reset, outputs=turn_outputs, show_progress="hidden") |
|
|
| |
| casting_confirm_btn.click( |
| fn=on_casting_confirm, |
| inputs=[casting_state, casting_prompt_box, casting_desc_box, |
| scene_state, engine_ref, prev_bg_state], |
| outputs=start_outputs, |
| show_progress="hidden", |
| ) |
| |
| redo_outputs = [vn_screen, scene_info, casting_state, casting_prompt_box] |
| casting_redo_btn.click( |
| fn=on_casting_redo, |
| inputs=[casting_state, casting_prompt_box, casting_desc_box], |
| outputs=redo_outputs, |
| show_progress="hidden", |
| ) |
|
|
| |
| |
| |
| |
| free_outputs = turn_outputs + [turn_streaming, free_input] |
| free_submit.click( |
| fn=on_free_submit, |
| inputs=[free_input, scene_state, engine_ref, prev_bg_state, |
| narration_log], |
| outputs=free_outputs, |
| show_progress="hidden", |
| ) |
|
|
| return app |
|
|
|
|
| |
| |
| |
|
|
| if __name__ == "__main__": |
| import sys |
|
|
| port = int(os.getenv("GRADIO_PORT", "7861")) |
| if len(sys.argv) > 1: |
| try: |
| port = int(sys.argv[1]) |
| except ValueError: |
| pass |
|
|
| if ON_SPACES: |
| |
| |
| from model_client import preload_transformers |
| preload_transformers() |
| port = int(os.getenv("GRADIO_SERVER_PORT", "7860")) |
|
|
| app = create_ui() |
| print(f"\nπΈ Ars-Fabula VN starting on http://127.0.0.1:{port}") |
| print(f" Mock mode: {os.getenv('ARS_FABULA_BACKEND', 'server') == 'mock'}") |
| print(f" Voice: {os.getenv('ARS_FABULA_VOICE', 'mock')} Β· " |
| f"BG: {os.getenv('ARS_FABULA_BG', 'preset')}\n") |
| app.launch( |
| server_name="0.0.0.0" if ON_SPACES else "127.0.0.1", |
| server_port=port, |
| allowed_paths=[STATIC_DIR], |
| css=CSS, |
| js=ONLOAD_JS, |
| theme=gr.themes.Base( |
| primary_hue="amber", |
| neutral_hue="slate", |
| ), |
| |
| |
| |
| |
| |
| |
| |
| ssr_mode=False, |
| quiet=False, |
| share=False, |
| ) |
|
|