| import gradio as gr |
| import json |
| import os |
| import uuid |
| import time |
| import re |
| from openai import OpenAI |
| from huggingface_hub import HfApi, InferenceClient |
| from datasets import Dataset, load_dataset |
| from game.player import create_player, create_player_from_npc |
| from game.turn_engine import play_turn |
| from data.save_system import save_to_hf, load_from_hf, load_and_enter_game |
| from game.vlm_engine import extract_scene_data, generate_entity_image, generate_player_avatar |
| from game.model_config import get_available_models, get_model_display_name, LANGUAGE_ROUTING |
| from game.image_model_config import get_available_image_models, get_image_model_display_name |
| from game.constants import TRAIT_CHOICES, ASPIRATION_CHOICES, TRAIT_INFO, ASPIRATION_INFO |
| from game.npc_registry import get_playable_npcs |
| from systems.reputation_system import format_reputation_for_ui |
| from systems.inventory_system import get_inventory_markdown, get_inventory_images |
| from systems.relationship_system import get_relationships_markdown |
| from systems.house_points_system import format_house_points_for_ui |
| from systems.spell_system import get_known_spells_markdown |
|
|
| print("--- DEBUG INFO [APP]: APP STARTING...") |
|
|
| TEXT_MODEL_NAME = "meta-llama/Meta-Llama-3-8B-Instruct" |
|
|
| |
| |
|
|
| token = os.environ.get("HF_TOKEN") |
|
|
| if token is None: |
| raise ValueError("HF_TOKEN not set") |
|
|
| api = HfApi(token=token) |
| username = api.whoami()["name"] |
|
|
| text_client = OpenAI( |
| base_url="https://router.huggingface.co/v1", |
| api_key=token |
| ) |
|
|
| image_client = InferenceClient( |
| provider="fal-ai", |
| api_key=token |
| ) |
|
|
| DATASET_REPO = f"{username}/ai-rpg-saves" |
|
|
| |
| |
| |
|
|
| prompt_file_path = "prompt/default_system.txt" |
| if os.path.exists(prompt_file_path): |
| with open(prompt_file_path, "r") as f: |
| SYSTEM_PROMPT = f.read() |
| else: |
| print("--- DEBUG ERROR [APP]:SYSTEM_PROMPT FILE DOES NOT EXIST") |
|
|
| |
| ALL_MODELS = get_available_models() |
| |
| IMAGE_ALL_MODELS = get_available_image_models() |
| |
| |
| |
| HOUSE_EMOJIS = { |
| "Gryffindor": "🦁", |
| "Slytherin": "🐍", |
| "Ravenclaw": "🦅", |
| "Hufflepuff": "🦡", |
| } |
|
|
| PLAYABLE_NPCS = get_playable_npcs() |
|
|
| CANONICAL_CHOICES = [ |
| ( |
| f"{HOUSE_EMOJIS.get(npc['house'], '✨')} {npc['name']} — {npc['house']}", |
| npc_id, |
| ) |
| for npc_id, npc in PLAYABLE_NPCS.items() |
| ] |
| |
| |
| |
| with gr.Blocks(theme=gr.themes.Soft()) as demo: |
| |
| player_state = gr.State("{}") |
| remaining_points = gr.State(10) |
|
|
| |
| with gr.Column(visible=True) as char_screen: |
| gr.Markdown("## Create Your Character") |
|
|
| name = gr.Textbox(label="Name") |
| gender = gr.Radio(["Male", "Female", "Non-binary"], label="Gender") |
| appearance = gr.Textbox( |
| label="Appearance (Optional)", |
| placeholder="e.g. hooded, scarred face, glowing cybernetic eye" |
| ) |
| language = gr.Dropdown( |
| choices=["English", "Deutsch", "Español", "Français", "Türkçe"], |
| value="English", |
| label="Game Language" |
| ) |
|
|
| |
| with gr.Row(): |
| gen_avatar_btn = gr.Button("🎲 Generate Avatar", variant="secondary") |
|
|
| |
| avatar = gr.Image(type="filepath", label="Avatar (Upload or Generate)") |
|
|
| |
| with gr.Row(): |
| start_btn = gr.Button("Continue", variant="primary") |
| load_char_btn = gr.Button("📂 Load Saved Game", variant="secondary") |
|
|
| gr.Markdown("---") |
| play_as_tab_btn = gr.Button( |
| "🎭 Or: Play as a Canonical Character", |
| variant="secondary", |
| size="lg", |
| ) |
|
|
| |
| with gr.Column(visible=False) as play_as_screen: |
| gr.Markdown("## 🎭 Play as a Hogwarts Student") |
| gr.Markdown( |
| "*Step into the shoes of a canonical character. " |
| "Stats, traits, and aspirations are pre-defined — jump straight into the story.*" |
| ) |
|
|
| canonical_selector = gr.Radio( |
| choices=CANONICAL_CHOICES, |
| label="Choose your character", |
| value=None, |
| ) |
| canonical_preview = gr.Markdown( |
| "*Select a character above to see their profile.*" |
| ) |
|
|
| canonical_language = gr.Dropdown( |
| choices=["English", "Deutsch", "Español", "Français", "Türkçe"], |
| value="English", |
| label="Game Language", |
| ) |
|
|
| with gr.Row(): |
| back_btn = gr.Button("← Create Character", variant="secondary") |
| play_as_begin = gr.Button("▶ Begin Adventure", variant="primary") |
| |
| with gr.Column(visible=False) as stat_screen: |
| gr.Markdown("## Distribute 10 Points") |
| remaining_text = gr.Markdown("### Points: 10") |
| |
| stats = {} |
| buttons_pozitive = {} |
| buttons_negative = {} |
| |
| stat_list = ["strength", "agility", "intelligence", "charisma", "willpower"] |
| |
| for s in stat_list: |
| with gr.Row(): |
| stats[s] = gr.Number(value=5, label=s.capitalize(), interactive=False) |
| buttons_pozitive[s] = gr.Button(f"+ {s}") |
| buttons_negative[s] = gr.Button(f"- {s}") |
| |
| gr.Markdown("---") |
| gr.Markdown("### 🏰 Hogwarts Placement") |
| gr.Markdown("*You are a transfer student from Durmstrang. Choose your house and year.*") |
|
|
| house_select = gr.Radio( |
| choices=["Gryffindor", "Hufflepuff", "Ravenclaw", "Slytherin"], |
| label="House", |
| value="Gryffindor", |
| interactive=True |
| ) |
| year_select = gr.Radio( |
| choices=[3, 4, 5, 6], |
| label="Year", |
| value=3, |
| interactive=True |
| ) |
| |
| finish_stats = gr.Button("Continue →", variant="primary") |
|
|
| |
| with gr.Column(visible=False) as trait_screen: |
| gr.Markdown("## ✨ Define Your Identity") |
| gr.Markdown("*These choices will shape your entire adventure at Hogwarts.*") |
|
|
| with gr.Row(): |
| |
| with gr.Column(): |
| gr.Markdown("### 🎭 Traits — Choose 3") |
| gr.Markdown( |
| "*Traits define your personality and affect dialogue, spells, and relationships.*" |
| ) |
| trait_selector = gr.CheckboxGroup( |
| choices=TRAIT_CHOICES, label="", value=[] |
| ) |
| trait_count_md = gr.Markdown("**0 / 3 selected**") |
| trait_desc_md = gr.Markdown("*Select traits above to see what they do.*") |
|
|
| |
| with gr.Column(): |
| gr.Markdown("### 🎯 Aspirations — Choose 2") |
| gr.Markdown( |
| "*Your life goals. Completing one unlocks a permanent special ability.*" |
| ) |
| aspiration_selector = gr.CheckboxGroup( |
| choices=ASPIRATION_CHOICES, label="", value=[] |
| ) |
| aspiration_count_md = gr.Markdown("**0 / 2 selected**") |
| aspiration_desc_md = gr.Markdown("*Select aspirations above to see their rewards.*") |
|
|
| trait_error_md = gr.Markdown("", visible=False) |
| begin_btn = gr.Button("⚡ Begin Your Hogwarts Adventure", variant="primary", size="lg") |
|
|
| |
| with gr.Column(visible=False) as game_screen: |
| with gr.Row(): |
| |
| |
| with gr.Column(scale=1): |
| with gr.Tabs(): |
| |
| |
| with gr.TabItem("👤 Profile"): |
| with gr.Row(): |
| profile_pic = gr.Image( |
| height=120, width=120, |
| interactive=False, show_label=False |
| ) |
| profile_info = gr.Markdown("### Player Name") |
|
|
| gr.Markdown("---") |
| gr.Markdown("#### 🌐 Reputation") |
| reputation_text = gr.Markdown("🫥 **Unassuming** (0)") |
|
|
| gr.Markdown("---") |
| gr.Markdown("#### 🏠 House Points") |
| house_points_text = gr.Markdown("🏠 **+0** House Points — 😐 Unproven") |
|
|
| gr.Markdown("---") |
| gr.Markdown("#### Alignment") |
| light_dark_slider = gr.Slider(-100, 100, value=0, label="Dark ↔ Light", interactive=False) |
| order_chaos_slider = gr.Slider(-100, 100, value=0, label="Chaos ↔ Order", interactive=False) |
|
|
| |
| with gr.TabItem("📊 Stats"): |
| stat_display = gr.Markdown("Loading stats...") |
|
|
| |
| with gr.TabItem("🎒 Inventory"): |
| inventory_md = gr.Markdown(label="Inventory") |
| inventory_gallery = gr.Gallery(label="Items", columns=3, height="auto") |
| |
|
|
| |
| with gr.TabItem("📖 Journal"): |
| journal_display = gr.Markdown("*Your story has just begun... No entries yet.*") |
|
|
| |
| with gr.TabItem("💞 Relations"): |
| relations_display = gr.Markdown( |
| "*You haven't gotten close to anyone yet. Relationships form as " |
| "you meet and interact with people at Hogwarts.*" |
| ) |
|
|
| |
| with gr.TabItem("✨ Spells"): |
| known_spells_display = gr.Markdown( |
| "*You haven't learned any spells yet. Attend a class, study a book, " |
| "or ask someone to teach you one.*" |
| ) |
|
|
| gr.Markdown("---") |
|
|
| |
| gr.Markdown("#### 🤖 LLM Model") |
| gr.Markdown( |
| "*When 'Auto' is selected, the best model for your game language is chosen automatically.*", |
| elem_id="model_hint" |
| ) |
| model_selector = gr.Dropdown( |
| choices=["Auto (Language Routing)"] + ALL_MODELS, |
| value="Auto (Language Routing)", |
| label="Active Model", |
| interactive=True, |
| ) |
|
|
| |
| gr.Markdown("#### 🎨 Image Model") |
| image_model_selector = gr.Dropdown( |
| choices=["Auto (Recommended)"] + IMAGE_ALL_MODELS, |
| value="Auto (Recommended)", |
| label="Active Image Model", |
| interactive=True, |
| ) |
|
|
| |
| gr.Markdown("---") |
| with gr.Row(): |
| save_btn = gr.Button("Save ☁️") |
| load_btn = gr.Button("Load ⬇️") |
| save_status = gr.Markdown() |
|
|
| |
| with gr.Column(scale=3): |
| scene_image = gr.Image(label="Current Scene", height=300, interactive=False) |
|
|
| chatbot = gr.Chatbot(height=400, show_label=False, type="tuples") |
| |
| with gr.Row(): |
| choice1 = gr.Button("1", variant="secondary") |
| choice2 = gr.Button("2", variant="secondary") |
| choice3 = gr.Button("3", variant="secondary") |
| choice4 = gr.Button("4", variant="secondary") |
| choice5 = gr.Button("5", variant="secondary") |
| |
| with gr.Row(): |
| custom_input = gr.Textbox( |
| label="What do you do?", |
| placeholder="Type your action here...", scale=4 |
| ) |
| send_btn = gr.Button("Send Action", variant="primary", scale=1) |
|
|
| |
| with gr.Column(scale=1): |
| gr.Markdown("### 👁️ Current Encounter") |
| npc_image = gr.Image(label="NPC", height=200, interactive=False) |
| |
| gr.Markdown("### 🎁 Loot / Item") |
| item_image = gr.Image(label="Discovered Item", height=200, interactive=False) |
|
|
| |
| |
| |
| ui_components = [choice1, choice2, choice3, choice4, choice5, custom_input, send_btn] |
|
|
| def lock_ui(): |
| |
| return [gr.update(interactive=False) for _ in ui_components] |
|
|
| def unlock_ui(): |
| |
| updates = [gr.update(interactive=True) for _ in ui_components] |
| updates[5] = gr.update(interactive=True, value="") |
| return updates |
|
|
| |
| def handle_avatar_generation(n, g, app, img_model_sel): |
| |
| if not n: |
| n = "Unknown Drifter" |
|
|
| img_override = _resolve_image_override(img_model_sel) |
| |
| avatar_path = generate_player_avatar(image_client, n, g, app, model_override=img_override) |
| return avatar_path |
|
|
| |
| |
| |
|
|
| def update_canonical_preview(npc_id): |
| """Updates the character preview card when a canonical NPC is selected.""" |
| if not npc_id: |
| return "*Select a character above to see their profile.*" |
| npc = PLAYABLE_NPCS.get(npc_id) |
| if not npc: |
| return "*Character not found.*" |
|
|
| house_emoji = HOUSE_EMOJIS.get(npc.get("house", ""), "✨") |
| traits_str = " · ".join(npc.get("traits", [])) |
| asps_str = " · ".join(npc.get("aspirations", [])) |
| stats = npc.get("starting_stats", {}) |
|
|
| return ( |
| f"### {house_emoji} {npc['name']}\n" |
| f"**{npc['house']} · Year {npc['canonical_year']}**\n\n" |
| f"*{npc.get('play_as_description', '')}*\n\n" |
| f"**Traits:** {traits_str} \n" |
| f"**Aspirations:** {asps_str} \n\n" |
| f"**STR** {stats.get('strength',5)} · " |
| f"**AGI** {stats.get('agility',5)} · " |
| f"**INT** {stats.get('intelligence',5)} · " |
| f"**CHA** {stats.get('charisma',5)} · " |
| f"**WIL** {stats.get('willpower',5)}" |
| ) |
|
|
| def go_game_canonical(npc_id, lang): |
| """ |
| Creates the player state from a canonical NPC and prepares the game screen. |
| Skips stat distribution and trait selection entirely. |
| Returns 13 UI updates matching GO_CANONICAL_OUTPUTS. |
| """ |
| if not npc_id: |
| return tuple([gr.update()] * 13) |
|
|
| npc = PLAYABLE_NPCS.get(npc_id) |
| if not npc: |
| return tuple([gr.update()] * 13) |
|
|
| state = create_player_from_npc(npc_id, npc, lang) |
|
|
| traits_str = " · ".join(state.get("traits", [])) |
| asps_str = " · ".join(state.get("aspirations", [])) |
| stats_md = ( |
| f"**House:** {state['house']} · **Year:** {state['year']} \n" |
| f"**Traits:** {traits_str} \n" |
| f"**Aspirations:** {asps_str} \n\n---\n" |
| f"**Strength:** {state['strength']} \n" |
| f"**Agility:** {state['agility']} \n" |
| f"**Intelligence:** {state['intelligence']} \n" |
| f"**Charisma:** {state['charisma']} \n" |
| f"**Willpower:** {state['willpower']} " |
| ) |
|
|
| return ( |
| json.dumps(state), |
| gr.update(visible=False), |
| gr.update(visible=True), |
| state.get("avatar"), |
| f"### {state['name']}\n*Year {state['year']} · {state['house']}*", |
| state["light_dark"], |
| state["order_chaos"], |
| format_reputation_for_ui(0), |
| format_house_points_for_ui(0), |
| stats_md, |
| None, |
| get_inventory_markdown(state), |
| get_inventory_images(state), |
| get_relationships_markdown(state), |
| get_known_spells_markdown(state), |
| ) |
|
|
| |
| gen_avatar_btn.click( |
| handle_avatar_generation, |
| inputs=[name, gender, appearance, image_model_selector], |
| outputs=avatar, |
| api_name=False |
| ) |
|
|
| |
| def go_stats(n, g, a, l): |
| print(f"\n--- DEBUG INFO [APP]: Creating player: {n} | Gender: {g} | Lang: {l} ---") |
| state = create_player(n, g, a, l) |
| return json.dumps(state), gr.update(visible=False), gr.update(visible=True) |
|
|
| start_btn.click( |
| go_stats, |
| inputs=[name, gender, avatar, language], |
| outputs=[player_state, char_screen, stat_screen], |
| api_name=False |
| ) |
| |
| def load_char_wrapper(): |
| return load_and_enter_game(DATASET_REPO, from_char_screen=True) |
| |
| load_char_btn.click( |
| load_char_wrapper, |
| inputs=[], |
| outputs= [ |
| player_state, chatbot, |
| char_screen, stat_screen, game_screen, |
| profile_pic, profile_info, |
| light_dark_slider, order_chaos_slider, |
| reputation_text, house_points_text, |
| stat_display, scene_image, model_selector, image_model_selector, |
| inventory_md, inventory_gallery, relations_display, known_spells_display, |
| ], |
| api_name=False |
| ) |
|
|
| |
| for s in stats: |
| def make_callback_pozitive(stat_name): |
| def handler(state_json, remaining): |
| state = json.loads(state_json) |
| if remaining <= 0: |
| return state_json, remaining, f"### Points: {remaining}", state[stat_name] |
| |
| state[stat_name] += 1 |
| remaining -= 1 |
| return json.dumps(state), remaining, f"### Points: {remaining}", state[stat_name] |
| return handler |
| |
| def make_callback_negative(stat_name): |
| def handler(state_json, remaining): |
| state = json.loads(state_json) |
| if state[stat_name] <= 0: |
| return state_json, remaining, f"### Points: {remaining}", state[stat_name] |
| |
| state[stat_name] -= 1 |
| remaining += 1 |
| return json.dumps(state), remaining, f"### Points: {remaining}", state[stat_name] |
| return handler |
| |
| buttons_pozitive[s].click( |
| make_callback_pozitive(s), |
| inputs=[player_state, remaining_points], |
| outputs=[player_state, remaining_points, remaining_text, stats[s]], |
| api_name=False |
| ) |
| |
| buttons_negative[s].click( |
| make_callback_negative(s), |
| inputs=[player_state, remaining_points], |
| outputs=[player_state, remaining_points, remaining_text, stats[s]], |
| api_name=False |
| ) |
|
|
| |
| def go_to_trait_screen(state_json, house_val, year_val): |
| state = json.loads(state_json) |
| state["house"] = house_val |
| state["year"] = int(year_val) |
| return json.dumps(state), gr.update(visible=False), gr.update(visible=True) |
| |
| finish_stats.click( |
| go_to_trait_screen, |
| inputs=[player_state, house_select, year_select], |
| outputs=[player_state, stat_screen, trait_screen], |
| api_name=False |
| ) |
| |
| |
| def update_trait_display(selected): |
| count = len(selected) |
| count_text = f"**{count} / 3 selected**" + (" ✅" if count == 3 else "") |
| desc = ( |
| "\n\n".join( |
| f"**{TRAIT_INFO[n]['emoji']} {n}** — {TRAIT_INFO[n]['short']}" |
| for n in selected if n in TRAIT_INFO |
| ) if selected else "*Select traits above to see what they do.*" |
| ) |
| return count_text, desc |
| |
| def update_aspiration_display(selected): |
| count = len(selected) |
| count_text = f"**{count} / 2 selected**" + (" ✅" if count == 2 else "") |
| desc = ( |
| "\n\n".join( |
| f"**{ASPIRATION_INFO[n]['emoji']} {n}** — {ASPIRATION_INFO[n]['short']}" |
| for n in selected if n in ASPIRATION_INFO |
| ) if selected else "*Select aspirations above to see their rewards.*" |
| ) |
| return count_text, desc |
| |
| trait_selector.change( |
| update_trait_display, |
| inputs=[trait_selector], |
| outputs=[trait_count_md, trait_desc_md], |
| api_name=False |
| ) |
| aspiration_selector.change( |
| update_aspiration_display, |
| inputs=[aspiration_selector], |
| outputs=[aspiration_count_md, aspiration_desc_md], |
| api_name=False |
| ) |
| |
| |
| def confirm_traits_and_start(state_json, traits, aspirations): |
| errors = [] |
| if len(traits) != 3: |
| errors.append(f"⚠️ Select **exactly 3 traits** ({len(traits)} selected).") |
| if len(aspirations) != 2: |
| errors.append(f"⚠️ Select **exactly 2 aspirations** ({len(aspirations)} selected).") |
| |
| if errors: |
| |
| return ( |
| state_json, |
| gr.update(), gr.update(), |
| gr.update(), gr.update(), |
| gr.update(), gr.update(), |
| gr.update(), gr.update(), gr.update(), |
| gr.update(value="\n\n".join(errors), visible=True), |
| ) |
| |
| state = json.loads(state_json) |
| state["traits"] = traits |
| state["aspirations"] = aspirations |
| |
| stats_md = ( |
| f"**House:** {state['house']} · **Year:** {state['year']} \n" |
| f"**Traits:** {' · '.join(traits)} \n" |
| f"**Aspirations:** {' · '.join(aspirations)} \n\n---\n" |
| f"**Strength:** {state['strength']} \n" |
| f"**Agility:** {state['agility']} \n" |
| f"**Intelligence:** {state['intelligence']} \n" |
| f"**Charisma:** {state['charisma']} \n" |
| f"**Willpower:** {state['willpower']} " |
| ) |
| |
| return ( |
| json.dumps(state), |
| gr.update(visible=False), |
| gr.update(visible=True), |
| state["avatar"], |
| f"### {state['name']}\n*Year {state['year']} · {state['house']}*", |
| state["light_dark"], |
| state["order_chaos"], |
| stats_md, |
| None, |
| gr.update(visible=False), |
| ) |
|
|
| |
| def _resolve_override(model_selector_value): |
| """ |
| 'Auto' seçiliyse None döner (model_config dil routing'i devreye girer). |
| Belirli bir model seçilmişse o model ID'sini döner. |
| """ |
| if model_selector_value == "Auto (Language Routing)": |
| return None |
| return model_selector_value |
|
|
| def _resolve_image_override(image_model_selector_value): |
| """ |
| 'Auto' seçiliyse None döner (image_model_config'teki varsayılan devreye girer). |
| Belirli bir görsel model seçilmişse o model ID'sini döner. |
| """ |
| if image_model_selector_value == "Auto (Recommended)": |
| return None |
| return image_model_selector_value |
|
|
| def play_turn_wrapper(user_input, history, state_json, model_sel, image_model_sel): |
| print(f"\n--- DEBUG INFO [APP]: Turn triggered! Action: '{user_input}' ---") |
| override = _resolve_override(model_sel) |
| image_override = _resolve_image_override(image_model_sel) |
| yield from play_turn( |
| user_input, |
| history, |
| state_json, |
| text_client, |
| image_client, |
| TEXT_MODEL_NAME, |
| SYSTEM_PROMPT, |
| model_override=override, |
| image_model_override = image_override |
| ) |
| |
| def first_turn(history, state_json, model_sel, image_model_sel): |
| state = json.loads(state_json) |
| |
| if len(state.get("traits", [])) != 3 or len(state.get("aspirations", [])) != 2: |
| print("--- DEBUG INFO [APP]: first_turn guard — traits not confirmed, skipping.") |
| yield ( |
| history, state_json, |
| state.get("light_dark", 0), state.get("order_chaos", 0), |
| gr.update(), |
| gr.update(), gr.update(), gr.update(), |
| gr.update(), gr.update(), |
| gr.update(), |
| gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), |
| ) |
| return |
| lang = state.get("language", "English") |
|
|
| if state.get("is_canonical_character"): |
| |
| name = state.get("name", "") |
| house = state.get("house", "") |
| year = state.get("year", 3) |
| canonical_openings = { |
| "English": ( |
| f"Start the adventure. I am {name}, a Year {year} {house} student at Hogwarts. " |
| f"This is a peaceful school year — no grand threat. Begin with a natural, " |
| f"typical morning scene that fits exactly who I am. Give me 5 choices." |
| ), |
| "Deutsch": ( |
| f"Starte das Abenteuer. Ich bin {name}, Schüler/in in {house}, {year}. Jahrgang in Hogwarts. " |
| f"Es ist ein ruhiges Schuljahr. Beginne mit einer typischen Morgenszene, die zu " |
| f"meinem Charakter passt. Gib mir 5 Optionen." |
| ), |
| "Español": ( |
| f"Comienza la aventura. Soy {name}, estudiante de {house} en el año {year} en Hogwarts. " |
| f"Es un año tranquilo. Empieza con una escena matutina típica para mi personaje. " |
| f"Dame 5 opciones." |
| ), |
| "Français": ( |
| f"Commence l'aventure. Je suis {name}, étudiant(e) en {house} en {year}e année à Poudlard. " |
| f"C'est une année tranquille. Commence par une scène matinale typique pour mon personnage. " |
| f"Donne-moi 5 choix." |
| ), |
| "Türkçe": ( |
| f"Maceraya başla. Ben {name}'yim, Hogwarts'ta {year}. sınıf {house} öğrencisiyim. " |
| f"Huzurlu bir okul yılı. Karakterime tamamen uygun tipik bir sabah sahnesiyle başla. " |
| f"Bana 5 seçenek sun." |
| ), |
| } |
| opening = canonical_openings.get(lang, canonical_openings["English"]) |
|
|
| else: |
| |
| house = state.get("house", "Gryffindor") |
| year = state.get("year", 3) |
| opening_prompts = { |
| "Deutsch": "Starte das Abenteuer. Beschreibe meine Umgebung und gib mir 5 Optionen.", |
| "Español": "Comienza la aventura. Describe mi entorno y dame 5 opciones.", |
| "Français": "Commence l'aventure. Décris mon environnement et donne-moi 5 choix.", |
| "Türkçe": "Maceraya başla. Çevremdeki ortamı anlat ve bana 5 seçenek sun.", |
| } |
| |
| opening = opening_prompts.get( |
| lang, |
| f"Start the adventure. I am a transfer student from Durmstrang, newly sorted into " |
| f"{house} in Year {year} at Hogwarts. Begin with the Sorting Hat ceremony confirming " |
| f"my placement, and my first steps into the {house} common room. Give me 5 choices." |
| ) |
| override = _resolve_override(model_sel) |
| image_override = _resolve_image_override(image_model_sel) |
| yield from play_turn( |
| opening, |
| history, |
| state_json, |
| text_client, |
| image_client, |
| TEXT_MODEL_NAME, |
| SYSTEM_PROMPT, |
| model_override=override, |
| image_model_override=image_override |
| ) |
|
|
| |
| |
| |
| |
| TURN_OUTPUTS = [ |
| chatbot, player_state, |
| light_dark_slider, order_chaos_slider, |
| reputation_text, |
| house_points_text, |
| scene_image, npc_image, item_image, |
| inventory_md, inventory_gallery, |
| relations_display, |
| known_spells_display, |
| choice1, choice2, choice3, choice4, choice5, |
| ] |
|
|
| |
| |
| |
| |
| |
| play_as_tab_btn.click( |
| lambda: (gr.update(visible=False), gr.update(visible=True)), |
| outputs=[char_screen, play_as_screen], |
| api_name=False, |
| ) |
|
|
| |
| back_btn.click( |
| lambda: (gr.update(visible=True), gr.update(visible=False)), |
| outputs=[char_screen, play_as_screen], |
| api_name=False, |
| ) |
|
|
| |
| canonical_selector.change( |
| update_canonical_preview, |
| inputs=[canonical_selector], |
| outputs=[canonical_preview], |
| api_name=False, |
| ) |
|
|
| |
| GO_CANONICAL_OUTPUTS = [ |
| player_state, play_as_screen, game_screen, |
| profile_pic, profile_info, |
| light_dark_slider, order_chaos_slider, |
| reputation_text, house_points_text, |
| stat_display, |
| scene_image, inventory_md, inventory_gallery, |
| relations_display, known_spells_display, |
| ] |
|
|
| play_as_begin.click( |
| go_game_canonical, |
| inputs=[canonical_selector, canonical_language], |
| outputs=GO_CANONICAL_OUTPUTS, |
| api_name=False, |
| ).then( |
| lock_ui, |
| outputs=ui_components, |
| api_name=False, |
| ).then( |
| first_turn, |
| inputs=[chatbot, player_state, model_selector, image_model_selector], |
| outputs=TURN_OUTPUTS, |
| api_name=False, |
| ).then( |
| unlock_ui, |
| outputs=ui_components, |
| api_name=False, |
| ) |
| |
| |
| begin_btn.click( |
| confirm_traits_and_start, |
| inputs=[player_state, trait_selector, aspiration_selector], |
| outputs=[ |
| player_state, |
| trait_screen, game_screen, |
| profile_pic, profile_info, |
| light_dark_slider, order_chaos_slider, |
| stats_md_out := stat_display, scene_image, |
| trait_error_md, |
| ], |
| api_name=False |
| ).then( |
| lock_ui, |
| outputs=ui_components, |
| api_name=False |
| ).then( |
| first_turn, |
| inputs=[chatbot, player_state, model_selector, image_model_selector], |
| outputs=TURN_OUTPUTS, |
| api_name=False |
| ).then( |
| unlock_ui, |
| outputs=ui_components, |
| api_name=False |
| ) |
|
|
| |
| send_btn.click( |
| lock_ui, |
| outputs=ui_components, |
| api_name=False |
| ).then( |
| play_turn_wrapper, |
| inputs=[custom_input, chatbot, player_state, model_selector, image_model_selector], |
| outputs=TURN_OUTPUTS, |
| api_name=False |
| ).then( |
| unlock_ui, |
| outputs=ui_components, |
| api_name=False |
| ) |
|
|
| |
| choice_outputs = [chatbot, player_state, light_dark_slider, order_chaos_slider, scene_image, npc_image, item_image] |
| choice_buttons = [choice1, choice2, choice3, choice4, choice5] |
|
|
| for btn in choice_buttons: |
| btn.click( |
| lock_ui, |
| outputs=ui_components, |
| api_name=False |
| ).then( |
| play_turn_wrapper, |
| |
| inputs=[btn, chatbot, player_state, model_selector, image_model_selector], |
| outputs=TURN_OUTPUTS, |
| api_name=False |
| ).then( |
| unlock_ui, |
| outputs=ui_components, |
| api_name=False |
| ) |
|
|
| |
| def save_wrapper(state_json, history_list, model_sel, image_model_sel): |
| state = json.loads(state_json) |
| state["saved_model"] = model_sel |
| state["saved_image_model"] = image_model_sel |
| return save_to_hf(DATASET_REPO, json.dumps(state), json.dumps(history_list)) |
|
|
| def load_ingame_wrapper(): |
| return load_and_enter_game(DATASET_REPO, from_char_screen=False) |
|
|
| save_btn.click( |
| save_wrapper, |
| inputs=[player_state, chatbot, model_selector, image_model_selector], |
| outputs=save_status, |
| api_name=False |
| ) |
|
|
| load_btn.click( |
| load_ingame_wrapper, |
| inputs=[], |
| outputs= [ |
| player_state, chatbot, |
| char_screen, stat_screen, game_screen, |
| profile_pic, profile_info, |
| light_dark_slider, order_chaos_slider, |
| reputation_text, house_points_text, |
| stat_display, scene_image, model_selector, image_model_selector, |
| inventory_md, inventory_gallery, relations_display, known_spells_display, |
| ], |
| api_name=False |
| ) |
|
|
| demo.launch(server_name="0.0.0.0", server_port=7860, show_error=True) |