PierreBrunelle's picture
Update app.py
f2699d6 verified
raw
history blame
15 kB
import gradio as gr
import pixeltable as pxt
import numpy as np
from datetime import datetime
from pixeltable.functions.huggingface import sentence_transformer
from pixeltable.functions import openai
import os
import getpass
import re
# Set up OpenAI API key
if 'OPENAI_API_KEY' not in os.environ:
os.environ['OPENAI_API_KEY'] = getpass.getpass('Enter your OpenAI API key: ')
# Initialize Pixeltable
pxt.drop_dir('ai_rpg', force=True)
pxt.create_dir('ai_rpg')
@pxt.udf
def generate_messages(genre: str, player_name: str, initial_scenario: str, player_input: str, turn_number: int) -> list[dict]:
return [
{
'role': 'system',
'content': f"""You are the game master for a {genre} RPG. The player's name is {player_name}.
Provide your response in two clearly separated sections using exactly this format:
STORY: [Your engaging narrative response to the player's action]
OPTIONS:
1. [A dialogue option]
2. [A random action they could take]
3. [A unique or unexpected choice]"""
},
{
'role': 'user',
'content': f"Current scenario: {initial_scenario}\n"
f"Player's action: {player_input}\n"
f"Turn number: {turn_number}\n\n"
"Provide the story response and options:"
}
]
@pxt.udf
def get_story(response: str) -> str:
"""Extract just the story part from the response"""
parts = response.split("OPTIONS:")
if len(parts) != 2:
return response
story = parts[0].replace("STORY:", "").strip()
return story
@pxt.udf
def get_options(response: str) -> list[str]:
"""Extract the options from the response"""
parts = response.split("OPTIONS:")
if len(parts) != 2:
return ["Continue...", "Take another action", "Try something else"]
options = re.findall(r'\d\.\s*(.*?)(?=\d\.|$)', parts[1], re.DOTALL)
options = [opt.strip() for opt in options[:3]]
while len(options) < 3:
options.append("Take another action...")
return options
# Create a single table for all game data
interactions = pxt.create_table(
'ai_rpg.interactions',
{
'session_id': pxt.String,
'player_name': pxt.String,
'genre': pxt.String,
'initial_scenario': pxt.String,
'turn_number': pxt.Int,
'player_input': pxt.String,
'timestamp': pxt.Timestamp,
}
)
# Add computed columns for AI responses
interactions['messages'] = generate_messages(
interactions.genre,
interactions.player_name,
interactions.initial_scenario,
interactions.player_input,
interactions.turn_number
)
interactions['ai_response'] = openai.chat_completions(
messages=interactions.messages,
model='gpt-4o-mini-2024-07-18',
max_tokens=500,
temperature=0.8
)
interactions['full_response'] = interactions.ai_response.choices[0].message.content
interactions['story_text'] = get_story(interactions.full_response)
interactions['options'] = get_options(interactions.full_response)
class RPGGame:
def __init__(self):
self.current_session_id = None
self.turn_number = 0
def start_game(self, player_name: str, genre: str, scenario: str) -> tuple[str, str, list[str]]:
session_id = f"session_{datetime.now().strftime('%Y%m%d%H%M%S')}_{player_name}"
self.current_session_id = session_id
self.turn_number = 0
interactions.insert([{
'session_id': session_id,
'player_name': player_name,
'genre': genre,
'initial_scenario': scenario,
'turn_number': 0,
'player_input': "Game starts",
'timestamp': datetime.now()
}])
result = interactions.select(
interactions.story_text,
interactions.options
).where(
(interactions.session_id == session_id) &
(interactions.turn_number == 0)
).collect()
return session_id, result['story_text'][0], result['options'][0]
def process_action(self, action: str) -> tuple[str, list[str]]:
if not self.current_session_id:
return "No active game session. Please start a new game.", []
self.turn_number += 1
prev_turn = interactions.select(
interactions.player_name,
interactions.genre,
interactions.initial_scenario
).where(
(interactions.session_id == self.current_session_id) &
(interactions.turn_number == 0)
).collect()
interactions.insert([{
'session_id': self.current_session_id,
'player_name': prev_turn['player_name'][0],
'genre': prev_turn['genre'][0],
'initial_scenario': prev_turn['initial_scenario'][0],
'turn_number': self.turn_number,
'player_input': action,
'timestamp': datetime.now()
}])
result = interactions.select(
interactions.story_text,
interactions.options
).where(
(interactions.session_id == self.current_session_id) &
(interactions.turn_number == self.turn_number)
).collect()
return result['story_text'][0], result['options'][0]
def create_interface():
game = RPGGame()
with gr.Blocks(theme=gr.themes.Base()) as demo:
gr.Markdown(
"""
<div style="margin-bottom: 20px;">
<h1 style="margin-bottom: 0.5em;">🎲 AI RPG Adventure</h1>
<p>An interactive RPG experience from Pixeltable and powered by OpenAI! Get started with an example below.</p>
</div>
"""
)
with gr.Row():
with gr.Column():
with gr.Accordion("🎯 What does it do?", open=False):
gr.Markdown("""
This AI RPG Adventure demonstrates Pixeltable's capabilities:
1. 🎮 Creates dynamic, AI-driven interactive stories
2. 🔄 Maintains game state and history using Pixeltable tables
3. 💭 Generates contextual options based on player actions
4. 🤖 Uses LLMs to create engaging narratives
5. 📊 Tracks and displays game progression
""")
with gr.Column():
with gr.Accordion("🛠️ How does it work?", open=False):
gr.Markdown("""
The app leverages several Pixeltable features:
1. 📦 **Data Management**: Uses Pixeltable tables to store game state,
player actions, and AI responses
2. 🤖 **AI Integration**: Seamlessly connects with language models
for story generation and response processing
3. 🔄 **State Tracking**: Maintains session history and player
choices using Pixeltable's computed columns
4. ⚙️ **Custom Processing**: Uses Pixeltable UDFs to handle game
logic and AI prompt generation
5. 🎯 **Interactive Flow**: Processes player choices and generates
contextual responses in real-time
""")
with gr.Row():
with gr.Column():
player_name = gr.Textbox(
label="👤 Your Character's Name",
placeholder="Enter your character's name..."
)
genre = gr.Dropdown(
choices=[
"🧙‍♂️ Fantasy",
"🚀 Sci-Fi",
"👻 Horror",
"🔍 Mystery",
"🌋 Post-Apocalyptic",
"🤖 Cyberpunk",
"⚙️ Steampunk"
],
label="🎭 Choose Your Genre"
)
scenario = gr.Textbox(
label="📖 Starting Scenario",
lines=3,
placeholder="Describe the initial setting and situation..."
)
start_button = gr.Button("🎮 Begin Adventure", variant="primary")
with gr.Column():
story_display = gr.Textbox(
label="📜 Story",
lines=8,
interactive=False
)
gr.Markdown("### 🎯 Choose Your Action")
action_input = gr.Radio(
choices=[],
label="🎲 Select your next action:",
interactive=True
)
submit_action = gr.Button("⚡ Take Action", variant="secondary")
gr.Markdown("### 💫 Example Adventures")
gr.Examples(
examples=[
["Eldric", "🧙‍♂️ Fantasy", "You find yourself in an ancient forest clearing, standing before a mysterious glowing portal. Your journey begins..."],
["Commander Nova", "🚀 Sci-Fi", "Aboard the starship Nebula, alarms blare as unknown entities approach. The fate of the crew rests in your hands..."],
["Detective Blake", "🔍 Mystery", "In the fog-shrouded streets of Victorian London, a peculiar letter arrives at your doorstep..."],
],
inputs=[player_name, genre, scenario]
)
history_df = gr.Dataframe(
headers=["📅 Turn", "🎯 Player Action", "💬 Game Response"],
label="📚 Adventure History",
wrap=True,
row_count=5,
col_count=(3, "fixed")
)
def start_new_game(name, genre_choice, scenario_text):
if not name or not genre_choice or not scenario_text:
return "Please fill in all fields before starting.", [], []
try:
_, initial_story, initial_options = game.start_game(name, genre_choice, scenario_text)
history_df = interactions.select(
turn=interactions.turn_number,
action=interactions.player_input,
response=interactions.story_text
).where(
interactions.session_id == game.current_session_id
).order_by(
interactions.turn_number
).collect().to_pandas()
history_data = [
[str(row['turn']), row['action'], row['response']]
for _, row in history_df.iterrows()
]
return initial_story, gr.Radio(choices=initial_options, interactive=True), history_data
except Exception as e:
return f"Error starting game: {str(e)}", [], []
def process_player_action(action_choice):
try:
if not action_choice:
return "Please select an action to continue.", [], []
story, options = game.process_action(action_choice)
history_df = interactions.select(
turn=interactions.turn_number,
action=interactions.player_input,
response=interactions.story_text
).where(
interactions.session_id == game.current_session_id
).order_by(
interactions.turn_number
).collect().to_pandas()
history_data = [
[str(row['turn']), row['action'], row['response']]
for _, row in history_df.iterrows()
]
return story, gr.Radio(choices=options, interactive=True), history_data
except Exception as e:
return f"Error: {str(e)}", [], []
start_button.click(
start_new_game,
inputs=[player_name, genre, scenario],
outputs=[story_display, action_input, history_df]
)
submit_action.click(
process_player_action,
inputs=[action_input],
outputs=[story_display, action_input, history_df]
)
gr.HTML(
"""
<div style="margin-top: 2rem; padding-top: 1rem; border-top: 1px solid #e5e7eb;">
<div style="display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 1rem;">
<div style="flex: 1;">
<h4 style="margin: 0; color: #374151;">🚀 Built with Pixeltable</h4>
<p style="margin: 0.5rem 0; color: #6b7280;">
Open Source AI Data infrastructure.
</p>
</div>
<div style="flex: 1;">
<h4 style="margin: 0; color: #374151;">🔗 Resources</h4>
<div style="display: flex; gap: 1.5rem; margin-top: 0.5rem;">
<a href="https://github.com/pixeltable/pixeltable" target="_blank" style="color: #4F46E5; text-decoration: none; display: flex; align-items: center; gap: 0.25rem;">
💻 GitHub
</a>
<a href="https://docs.pixeltable.com" target="_blank" style="color: #4F46E5; text-decoration: none; display: flex; align-items: center; gap: 0.25rem;">
📚 Documentation
</a>
<a href="https://huggingface.co/Pixeltable" target="_blank" style="color: #4F46E5; text-decoration: none; display: flex; align-items: center; gap: 0.25rem;">
🤗 Hugging Face
</a>
</div>
</div>
</div>
<p style="margin: 1rem 0 0; text-align: center; color: #9CA3AF; font-size: 0.875rem;">
This work is licensed under the Apache License 2.0. You can freely use, modify, and distribute this code, provided you include appropriate attribution and maintain the original license notice.
</p>
</div>
"""
)
return demo
if __name__ == "__main__":
demo = create_interface()
demo.launch()