cereeenn120's picture
Add house points system phase one
6da7ff4
Raw
History Blame Contribute Delete
37 kB
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"
#IMAGE_MODEL_NAME = "black-forest-labs/FLUX.1-schnell"
#IMAGE_ROUTER_URL = f"https://router.huggingface.co/hf-inference/models/{IMAGE_MODEL_NAME}"
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"
# ----------------------------
# SYSTEM PROMPT
# ----------------------------
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")
# Dropdown için llm model listesini model_config'den al (hardcode yok)
ALL_MODELS = get_available_models()
# Dropdown için image model listesini de registry'den al
IMAGE_ALL_MODELS = get_available_image_models()
# ----------------------------
# CANONICAL CHARACTER CONSTANTS
# ----------------------------
HOUSE_EMOJIS = {
"Gryffindor": "🦁",
"Slytherin": "🐍",
"Ravenclaw": "🦅",
"Hufflepuff": "🦡",
}
PLAYABLE_NPCS = get_playable_npcs() # {npc_id: npc_data}
CANONICAL_CHOICES = [
(
f"{HOUSE_EMOJIS.get(npc['house'], '✨')} {npc['name']}{npc['house']}",
npc_id,
)
for npc_id, npc in PLAYABLE_NPCS.items()
]
# ----------------------------
# UI
# ----------------------------
with gr.Blocks(theme=gr.themes.Soft()) as demo:
player_state = gr.State("{}")
remaining_points = gr.State(10)
# ================== SCREEN 1 — CHARACTER ==================
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"
)
# YENİ: Avatar Generate Butonu
with gr.Row():
gen_avatar_btn = gr.Button("🎲 Generate Avatar", variant="secondary")
# Mevcut avatar yükleme alanı. type="filepath" olduğu için üretilen görselin yolunu da kolayca gösterebiliriz.
avatar = gr.Image(type="filepath", label="Avatar (Upload or Generate)")
#start_btn = gr.Button("Continue")
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",
)
# ================== SCREEN 1b — PLAY AS ==================
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")
# ================== SCREEN 2 — STATS ==================
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")
# ================== SCREEN 3 — TRAITS & ASPIRATIONS ==================
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():
# --- Traits column ---
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.*")
# --- Aspirations column ---
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")
# ================== SCREEN 4 — GAME ==================
with gr.Column(visible=False) as game_screen:
with gr.Row():
# --- SOL PANEL: KARAKTER KAĞIDI VE SİSTEMLER (scale=1) ---
with gr.Column(scale=1):
with gr.Tabs():
# 1. SEKME: PROFİL VE ALIGNMENT
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)
# 2. SEKME: STATLAR
with gr.TabItem("📊 Stats"):
stat_display = gr.Markdown("Loading stats...")
# 3. SEKME: ENVANTER
with gr.TabItem("🎒 Inventory"):
inventory_md = gr.Markdown(label="Inventory")
inventory_gallery = gr.Gallery(label="Items", columns=3, height="auto")
#inventory_display = gr.Markdown("*Your pockets are currently empty...*\n\n**Credits:** 0 🪙")
# 4. SEKME: JOURNAL
with gr.TabItem("📖 Journal"):
journal_display = gr.Markdown("*Your story has just begun... No entries yet.*")
# 5. SEKME: RELATIONS
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.*"
)
# 6. SEKME: SPELLS
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("---")
# YENİ: Model Seçici — model_config'deki tüm modeller buraya otomatik yükleniyor
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,
)
# YENİ: Image Model Seçici — image_model_config'deki tüm modeller otomatik yükleniyor
gr.Markdown("#### 🎨 Image Model")
image_model_selector = gr.Dropdown(
choices=["Auto (Recommended)"] + IMAGE_ALL_MODELS,
value="Auto (Recommended)",
label="Active Image Model",
interactive=True,
)
# KAYIT SİSTEMİ (Sol panelin en altında)
gr.Markdown("---")
with gr.Row():
save_btn = gr.Button("Save ☁️")
load_btn = gr.Button("Load ⬇️")
save_status = gr.Markdown()
# --- ORTA PANEL: HİKAYE VE AKSİYON (scale=3) ---
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)
# --- SAĞ PANEL: NPC VE EŞYALAR (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)
# ----------------------------
# LOGIC FUNCTIONS
# ----------------------------
ui_components = [choice1, choice2, choice3, choice4, choice5, custom_input, send_btn]
def lock_ui():
# İşlem başlarken her şeyi pasif (tıklanamaz) hale getir
return [gr.update(interactive=False) for _ in ui_components]
def unlock_ui():
# İşlem bitince her şeyi aktif hale getir ve Textbox'ın içini temizle (index 5)
updates = [gr.update(interactive=True) for _ in ui_components]
updates[5] = gr.update(interactive=True, value="") # clear text input
return updates
# Avatar
def handle_avatar_generation(n, g, app, img_model_sel):
# UI'ı kilitleyip üretimi başlatıyoruz
if not n:
n = "Unknown Drifter" # İsim girilmemişse varsayılan
img_override = _resolve_image_override(img_model_sel)
# image_client zaten app.py'de tanımlı
avatar_path = generate_player_avatar(image_client, n, g, app, model_override=img_override)
return avatar_path
# ----------------------------
# CANONICAL CHARACTER HELPERS
# ----------------------------
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), # player_state
gr.update(visible=False), # play_as_screen → hide
gr.update(visible=True), # game_screen → show
state.get("avatar"), # profile_pic
f"### {state['name']}\n*Year {state['year']} · {state['house']}*", # profile_info
state["light_dark"], # light_dark_slider
state["order_chaos"], # order_chaos_slider
format_reputation_for_ui(0), # reputation_text
format_house_points_for_ui(0), # house_points_text
stats_md, # stat_display
None, # scene_image (cleared)
get_inventory_markdown(state), # inventory_md
get_inventory_images(state), # inventory_gallery
get_relationships_markdown(state), # relations_display
get_known_spells_markdown(state), # known_spells_display
)
# Butona tıklandığında loading animasyonu ile birlikte görseli üret ve Image component'ine bas
gen_avatar_btn.click(
handle_avatar_generation,
inputs=[name, gender, appearance, image_model_selector],
outputs=avatar,
api_name=False
)
# ---- Screen 1 → Screen 2 ----
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=[], # ← DATASET_REPO geçme! Wrapper içinde kullanılıyor
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
)
# ---- Stat +/- buttons ----
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
)
# ---- Screen 2 → Screen 3 ----
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
)
# ---- Trait / aspiration live preview ----
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
)
# ---- Screen 3 → Screen 4 ----
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:
# Validation failed — stay on trait_screen, show errors, don't change anything else
return (
state_json,
gr.update(), gr.update(), # trait_screen, game_screen unchanged
gr.update(), gr.update(), # profile_pic, profile_info unchanged
gr.update(), gr.update(), # sliders unchanged
gr.update(), gr.update(), gr.update(), # stat_display, scene_image, reputation unchanged
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), # trait_screen → hide
gr.update(visible=True), # game_screen → show
state["avatar"], # profile_pic
f"### {state['name']}\n*Year {state['year']} · {state['house']}*",
state["light_dark"],
state["order_chaos"],
stats_md,
None, # scene_image (cleared; first scene arrives via first_turn)
gr.update(visible=False), # hide any previous error
)
# ---- Gameplay helpers ----
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)
# Guard: don't run if trait/aspiration selection was invalid (validation failed)
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(), # reputation_text
gr.update(), gr.update(), gr.update(), # scene, npc, item
gr.update(), gr.update(), # inventory_md, inventory_gallery
gr.update(), # relations_display ← EKSİKTİ
gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), # 5 buton
)
return
lang = state.get("language", "English")
if state.get("is_canonical_character"):
# --- Canonical character opening ---
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:
# --- Custom (Durmstrang transfer) opening ---
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 (shared list — used by all play_turn event chains)
# Order must match turn_engine.py play_turn yield exactly.
# ----------------------------
TURN_OUTPUTS = [
chatbot, player_state,
light_dark_slider, order_chaos_slider,
reputation_text, # ← NEW (position 5)
house_points_text,
scene_image, npc_image, item_image,
inventory_md, inventory_gallery,
relations_display, # ← NEW (Relations tab)
known_spells_display, # ← NEW (Spells tab)
choice1, choice2, choice3, choice4, choice5,
]
# ----------------------------
# EVENT CHAINS
# ----------------------------
# ---- PLAY AS: char_screen → play_as_screen ----
play_as_tab_btn.click(
lambda: (gr.update(visible=False), gr.update(visible=True)),
outputs=[char_screen, play_as_screen],
api_name=False,
)
# ---- PLAY AS: play_as_screen → char_screen (back) ----
back_btn.click(
lambda: (gr.update(visible=True), gr.update(visible=False)),
outputs=[char_screen, play_as_screen],
api_name=False,
)
# ---- PLAY AS: NPC selector → live preview ----
canonical_selector.change(
update_canonical_preview,
inputs=[canonical_selector],
outputs=[canonical_preview],
api_name=False,
)
# ---- PLAY AS: Begin → game_screen (skip stat/trait screens) ----
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,
)
# Screen 3 → begin adventure
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
)
# --- SERBEST METİN GİRİŞİ ---
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], # model_selector eklendi
outputs=TURN_OUTPUTS,
api_name=False
).then(
unlock_ui,
outputs=ui_components,
api_name=False
)
# --- SEÇENEK BUTONLARI ---
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,
# Butonun kendisini inputs'a ekliyoruz, böylece metni gönderilir
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
)
# Save / Load
def save_wrapper(state_json, history_list, model_sel, image_model_sel):
state = json.loads(state_json)
state["saved_model"] = model_sel # "Auto (Language Routing)" veya seçilen model ID
state["saved_image_model"] = image_model_sel # "Auto (Recommended)" veya seçilen image model ID
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)