semantrix_cond2 / app.py
Javierss
Clone old repo
b875699
raw
history blame
24.4 kB
# """
# This module defines a Gradio-based web application for the Semantrix game. The application allows users to play the game in either Spanish or English, using different embedding models for word similarity.
# Modules:
# gradio: Used for creating the web interface.
# json: Used for loading configuration files.
# game: Contains the Semantrix class for game logic.
# File Paths:
# config_file_path: Path to the configuration file.
# logo_path: Path to the logo image.
# logo_win_path: Path to the winning logo image.
# Functions:
# convert_to_markdown_centered(text):
# Converts text to a centered markdown format for displaying game history and last attempt.
# reset(difficulty, lang, model):
# Resets the game state based on the selected difficulty, language, and model.
# change(state, inp):
# Changes the game state by incrementing the state variable.
# update(state, radio, inp, hint):
# Updates the game state and UI components based on the current state and user inputs.
# Gradio Components:
# demo: The main Gradio Blocks component that contains the entire UI layout.
# header: A Markdown component for displaying the game header.
# state: A State component for tracking the current game state.
# difficulty: A State component for tracking the difficulty level.
# hint: A State component for tracking if a hint is provided.
# img: An Image component for displaying the game logo.
# ranking: A Markdown component for displaying the ranking.
# out: A Textbox component for displaying game messages.
# hint_out: A Textbox component for displaying hints.
# radio: A Radio component for user selections.
# inp: A Textbox component for user input.
# but: A Button component for several actions.
# give_up: A Button component for giving up.
# reload: A Button component for reloading the game.
# model: A Dropdown component for selecting the embedding model.
# lang: A Dropdown component for selecting the language.
# Events:
# inp.submit: Triggers the change function on input submission.
# but.click: Triggers the change function on button click.
# give_up.click: Triggers the change function on give up button click.
# radio.input: Triggers the change function on radio input.
# reload.click: Triggers the reset function on reload button click.
# demo.load: Triggers the reset function on demo load.
# lang[0].select: Triggers the reset function on language selection.
# model[0].select: Triggers the reset function on model selection.
# state.change: Triggers the update function on state change.
# Main:
# Launches the Gradio application if the script is run as the main module.
# """
import gradio as gr
import json
from datetime import datetime, date
from game import Semantrix, Model_class
from huggingface_hub import CommitScheduler
import os
import logging
# Configure logging
logging.basicConfig(
level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)
logger = logging.getLogger(__name__)
# File paths for configuration and images
base_path = os.path.dirname(os.path.abspath(__file__))
config_file_path = os.path.join(base_path, "config/lang.json")
logo_path = os.path.join(base_path, "config/images/logo.png")
logo_win_path = os.path.join(base_path, "config/images/logo_win.gif")
condition_config_path = os.path.join(base_path, "config/condition.json")
data_path = os.path.join(base_path, "data")
plays_path = os.path.join(data_path, "plays")
condition_name = "condition_2"
# Dynamically determine the condition name based on the folder name
# folder_name = os.path.basename(os.path.dirname(os.path.abspath(__file__)))
# condition = folder_name.split("_")[-1][-1]
# condition_name = "condition_" + condition
with open(condition_config_path, "r") as file:
condition_config = json.load(file)
model = condition_config[condition_name]["model"]
hints_enabled = condition_config[condition_name]["hints"]
# Loading the configuration file
with open(config_file_path, "r") as file:
Config_full = json.load(file)
scheduler = CommitScheduler(
repo_id="Jsevisal/semantrix_data_" + condition_name[-1],
repo_type="dataset",
folder_path=plays_path,
path_in_repo="data",
every=10,
)
lang = 0 # Language configuration flag (0 for Spanish, 1 for English)
# Setting the configuration based on the language flag
if lang == 1:
Config = Config_full["ENG"]["Game"]
Menu = Config_full["ENG"]["Menu"]
else:
Config = Config_full["SPA"]["Game"]
Menu = Config_full["SPA"]["Menu"]
# Function to convert text to centered markdown format
def convert_to_markdown_centered(text):
lines = text.strip().split("\n")
if not lines:
return ""
last_attempt = lines[0]
history_attempts = lines[2:12]
markdown = '<div align="center">\n\n'
markdown += "## " + Menu["Best_tries"] + "\n"
markdown += "<table>\n"
markdown += " <tr>\n"
markdown += " <th>" + Menu["N_try"] + "</th>\n"
markdown += " <th>" + Menu["Word"] + "</th>\n"
markdown += " <th>" + Menu["Score"] + "</th>\n"
markdown += " </tr>\n"
for line in history_attempts:
items = eval(line.strip())
markdown += " <tr>\n"
markdown += f" <td><strong>{items[0]}</strong></td>\n"
markdown += f" <td>{items[1]}</td>\n"
markdown += f" <td>{items[2]}</td>\n"
markdown += " </tr>\n"
markdown += "</table>\n\n"
last_items = eval(last_attempt)
markdown += f"## " + Menu["Last_try"] + "\n"
markdown += (
f"**{last_items[0]}:** {last_items[1]} - "
+ Menu["Score"]
+ f": {last_items[2]}\n\n"
)
markdown += "---\n\n"
markdown += "</div>"
return markdown
#
with gr.Blocks(
theme="ocean",
) as demo:
# Initializing state variables to manage the internal state of the application
state = gr.State(-1) # State variable to track the current game state
difficulty = gr.State(-1) # State variable to track the difficulty level
hint = gr.State(False) # State variable to track if the hint is provided
secret_word_used = gr.BrowserState(
0
) # State variable to track the number of secret words used
# Initializing the game using the Semantrix class with default parameters
game_instances = {}
sessions_to_remove = {}
cooldown_time = 120 # Time in seconds to remove the session from the game_instances
model_class = Model_class(lang=0, model_type=model)
# Creating a Markdown component to display the header
header = gr.Markdown(
"""
<p style="text-align:center"> """
+ Menu["Header"]
+ """ </p>
"""
)
# Function to reset the game
def reset(reload, request: gr.Request):
global Config, game, Menu, model, lang # Declare global variables to modify them within the function
if reload:
game_instances[request.session_hash] = Semantrix(
lang=0, model_type=model, session_hash=request.session_hash
)
game_instances[request.session_hash].reset_game() # Reset the game state
logger.info("New session detected: %s", request.session_hash)
logger.info("Game instances: %s", game_instances)
# Define the initial output components for the UI
output = [
-1,
gr.Textbox(visible=False),
gr.Textbox(visible=False),
gr.Image(logo_path, visible=True, interactive=False),
gr.Button(Menu["Start"], visible=True, variant="secondary"),
gr.Radio(visible=False),
gr.Textbox(visible=False),
gr.Button(visible=False),
gr.Markdown(
"""
<p style="text-align:center"> """
+ Menu["Header"]
+ """ </p>
"""
),
gr.Button(visible=False),
]
# Return the initial output components
return output
def remove_game_instance(timer_tick=False, request: gr.Request | None = None):
request = None if timer_tick else request
if request is not None:
logger.info("Session on inactivity timer: %s", request.session_hash)
sessions_to_remove[request.session_hash] = datetime.now()
if len(sessions_to_remove.items()) > 0:
for session_hash, timestamp in list(sessions_to_remove.items()):
if (datetime.now() - timestamp).seconds > cooldown_time:
del sessions_to_remove[session_hash]
session_id = game_instances[session_hash].get_session_id()
del game_instances[session_hash]
# Delete ranking file if it exists
ranking_file = os.path.join(
data_path, f"rankings/ranking_{session_hash}.txt"
)
if os.path.exists(ranking_file):
os.remove(ranking_file)
# Delete plays files if it exists
session_files = [
f for f in os.listdir(plays_path) if f.startswith(session_id)
]
for file_name in session_files:
with open(os.path.join(plays_path, file_name), "r") as file:
os.remove(os.path.join(plays_path, file_name))
logger.info("Deleted session: %s", session_hash)
# Function to change the state of the game
def change(state, inp):
# Increment the state by 1
state = state + 1
# Return the updated state and input component
return [state, inp]
# Function to update the game state based on the current state of the game
def update(state, radio, inp, hint, secret_word_used, request: gr.Request):
global difficulty, hints_enabled
# Define the difficulty state
dif_state = 3
# Initialize the output component list with the current state
state_int = state
output = [state]
if request.session_hash in sessions_to_remove:
sessions_to_remove.pop(request.session_hash)
logger.info("Session saved: %s", request.session_hash)
# Define UI components for the initial state
if state_int == -1:
output.extend(
[
gr.Button(Menu["Start"], visible=True),
gr.Radio(label="", visible=False),
gr.Textbox(
Config[list(Config.keys())[state_int]], visible=False, label=""
),
gr.Button(Menu["Give_up"], visible=False),
gr.Textbox(visible=False),
gr.Image(interactive=False, visible=True),
gr.Textbox(visible=False),
gr.Button(visible=False),
gr.Markdown(visible=False),
gr.Button(visible=False),
secret_word_used,
]
)
# Define UI components for the first state, ask the user if they want to know the rules
elif state_int == 1:
output.extend(
[
gr.Button(visible=False),
gr.Radio(
[Menu["Yes"], Menu["No"]], value=None, label="", visible=True
),
gr.Textbox(
Config[list(Config.keys())[state_int]], visible=True, label=""
),
gr.Button(Menu["Give_up"], visible=False),
gr.Textbox(visible=False),
gr.Image(interactive=False, visible=False),
gr.Textbox(visible=False),
gr.Button(visible=False),
gr.Markdown(visible=False),
gr.Button(visible=False),
secret_word_used,
]
)
# Define UI components for the second state, Depending on the answer, show the rules or keep going
elif state_int == 2:
if radio == Menu["No"]:
output = [
dif_state,
gr.Button("Introducir", visible=True),
gr.Radio(visible=False),
gr.Textbox(
Config[list(Config.keys())[state_int]], visible=True, label=""
),
gr.Button(Menu["Give_up"], visible=False),
gr.Textbox(visible=False),
gr.Image(interactive=False, visible=False),
gr.Textbox(visible=False),
gr.Button(visible=False),
gr.Markdown(visible=False),
gr.Button(visible=False),
secret_word_used,
]
else:
output.extend(
[
gr.Button(Menu["Next"], visible=True),
gr.Radio(visible=False),
gr.Textbox(
Config[list(Config.keys())[state_int]],
visible=True,
label="",
),
gr.Button(Menu["Give_up"], visible=False),
gr.Textbox(visible=False),
gr.Image(interactive=False, visible=False),
gr.Textbox(visible=False),
gr.Button(visible=False),
gr.Markdown(visible=False),
gr.Button(visible=False),
secret_word_used,
]
)
# Define UI components for the difficulty state, ask the user to select the difficulty level
elif state_int == dif_state:
output.extend(
[
gr.Button(Menu["Start"], visible=True, variant="primary"),
gr.Radio(visible=False),
gr.Textbox(
Config[list(Config.keys())[state_int]], visible=True, label=""
),
gr.Button(Menu["Give_up"], visible=False),
gr.Textbox(visible=False),
gr.Image(interactive=False, visible=False),
gr.Textbox(visible=False),
gr.Button(visible=False),
gr.Markdown(visible=False),
gr.Button(visible=False),
secret_word_used,
]
)
# Define UI components for the difficulty state + 2, play the game based on the selected difficulty level and prepare the game for the word guessing
elif state_int == dif_state + 1:
game_instances[request.session_hash].prepare_game(
secret_word_used, 2 if hints_enabled else 4
)
output.extend(
[
gr.Button(Menu["Send"], visible=True, variant="primary"),
gr.Radio(label="", visible=False),
gr.Textbox(visible=False, label=""),
gr.Button(visible=False, variant="stop"),
gr.Textbox(
value="",
visible=True,
autofocus=True,
placeholder=Menu["New_word"],
),
gr.Image(interactive=False, visible=False),
gr.Textbox(visible=False),
gr.Button(visible=False),
gr.Markdown(visible=False),
gr.Button(visible=False),
secret_word_used + 1,
]
)
# Define UI components for the state greater than the difficulty state + 2, play the game and provide feedback based on the user input
elif state_int > dif_state + 1:
# Send the user input to the game and get the feedback from the game
feed = game_instances[request.session_hash].play_game(inp, model_class)
# Check if the feedback contains the ranking information and process it
feedback_trim = feed.split("[rank]")
if len(feedback_trim) > 1:
ranking_vis = True
ranking_md = convert_to_markdown_centered(feedback_trim[1])
else:
ranking_vis = False
ranking_md = ""
# Check if the feedback contains a hint, win, or lose message
feedback = feedback_trim[0].split("[hint]")
win = feedback_trim[0].split("[win]")
lose = feedback_trim[0].split("[lose]")
# Check if the feedback contains a hint message
if len(feedback) > 1:
hint = True
hint_out = feedback[1]
feedback = feedback[0]
else:
hint = False
feedback = feedback[0]
# Check if the feedback contains a win or lose message and process it
if len(win) > 1 or len(lose) > 1:
# Check if the user won the game
won = True if len(win) > 1 else False
# Get the curiosity message from the game
curiosity = game_instances[request.session_hash].curiosity()
# Define the output components for the win or lose state
output.extend(
[
gr.Button(Menu["Send"], visible=False, variant="primary"),
gr.Radio(label="", visible=False),
gr.Textbox(win[1] if won else lose[1], visible=True, label=""),
gr.Button(visible=False, variant="stop"),
gr.Textbox(
value="", visible=False, placeholder=Menu["New_word"]
),
gr.Image(
logo_win_path if won else logo_path,
interactive=False,
visible=True,
),
gr.Textbox(curiosity, visible=True, label=Menu["Curiosity"]),
gr.Button(Menu["Play_again"], variant="primary", visible=True),
gr.Markdown(visible=False),
gr.Button(visible=True),
secret_word_used,
]
)
return output
# Define the output components for the feedback and keep playing
output.extend(
[
gr.Button(Menu["Send"], visible=True, variant="primary"),
gr.Radio(label="", visible=False),
gr.Textbox(feedback, visible=True, label=""),
gr.Button(visible=True, variant="stop"),
gr.Textbox(value="", visible=True, placeholder=Menu["New_word"]),
gr.Image(logo_path, interactive=False, visible=False),
gr.Textbox(hint_out if hint else "", visible=hint, label="Pista"),
gr.Button(visible=False),
gr.Markdown(ranking_md, visible=ranking_vis),
gr.Button(visible=False),
secret_word_used,
]
)
# Define UI components for the rest of the states, state for showing basic text to the user
else:
output.extend(
[
gr.Button(Menu["Next"], visible=True),
gr.Radio(label="", visible=False),
gr.Textbox(
Config[list(Config.keys())[state_int]], visible=True, label=""
),
gr.Button("Pista", visible=False),
gr.Textbox(visible=False),
gr.Image(interactive=False, visible=False),
gr.Textbox(visible=False),
gr.Button(visible=False),
gr.Markdown(visible=False),
gr.Button(visible=False),
secret_word_used,
]
)
# Return the output components
return output
def commit_data(request: gr.Request):
session_id = game_instances[request.session_hash].get_session_id()
session_files = [f for f in os.listdir(plays_path) if f.startswith(session_id)]
combined_data = []
for file_name in session_files:
with open(os.path.join(plays_path, file_name), "r") as file:
combined_data.append(json.load(file))
os.remove(os.path.join(plays_path, file_name))
combined_file_path = os.path.join(plays_path, f"{session_id}.json")
with open(combined_file_path, "w") as combined_file:
json.dump(combined_data, combined_file, indent=4)
scheduler.push_to_hub()
if os.path.exists(combined_file_path):
os.remove(combined_file_path)
return [
gr.Button(visible=False),
gr.Textbox(visible=False),
gr.Textbox(visible=False),
gr.Button(
"Rellenar cuestionario",
variant="primary",
link="https://docs.google.com/forms/d/e/1FAIpQLSdGkCMsjk4r5Zvm1hZuebbPRK5LVc_aivbb8e9WrblX_yTHvg/viewform?usp=pp_url&entry.1459162177="
+ session_id,
visible=True,
),
]
# Define the UI layout for the gam
img = gr.Image(logo_path, height=430, interactive=False, visible=True)
ranking = gr.Markdown(visible=False)
with gr.Row():
out = gr.Textbox(visible=False, placeholder=Config[list(Config.keys())[0]])
hint_out = gr.Textbox(visible=False)
radio = gr.Radio(visible=False)
with gr.Row():
inp = gr.Textbox(visible=False, interactive=True, label="")
but = gr.Button(Menu["Start"])
give_up = gr.Button("Pista", visible=False)
reload = gr.Button(Menu["Play_again"], visible=False)
finish = gr.Button(
"Terminar",
variant="stop",
# link="https://docs.google.com/forms/d/e/1FAIpQLSd0z8nI4hhOSR83yPIw_bR3KkSt25Lsq0ZXG1pZnkldeoceqA/viewform?usp=pp_url&entry.327829192=Condici%C3%B3n+1",
visible=False,
)
timer_delete = gr.Timer(value=30)
# Define the UI events for the game
# Define events that trigger the game state change
timer_delete.tick(remove_game_instance, inputs=[gr.State(True)])
inp.submit(
change,
inputs=[state, inp],
outputs=[state, inp],
concurrency_limit=5,
)
but.click(
change,
inputs=[state, inp],
outputs=[state, inp],
concurrency_limit=5,
)
give_up.click(
change,
inputs=[
state,
gr.Textbox("give_up", visible=False, interactive=True, label=""),
],
outputs=[state, inp],
concurrency_limit=5,
)
radio.input(
change,
inputs=[state, inp],
outputs=[state, inp],
concurrency_limit=5,
)
finish.click(
commit_data, outputs=[reload, out, hint_out, finish]
) # Define events that trigger the game reset
reload.click(
reset,
inputs=[gr.State(False)],
outputs=[state, out, inp, img, but, radio, hint_out, reload, header, finish],
)
demo.load(
reset,
inputs=[gr.State(True)],
outputs=[state, out, inp, img, but, radio, hint_out, reload, header, finish],
)
demo.unload(remove_game_instance)
# Define events that trigger the game state update
state.change(
update,
inputs=[state, radio, inp, hint, secret_word_used],
outputs=[
state,
but,
radio,
out,
give_up,
inp,
img,
hint_out,
reload,
ranking,
finish,
secret_word_used,
],
)
if __name__ == "__main__":
demo.launch(debug=True)