Spaces:
Running
Running
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') | |
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:" | |
} | |
] | |
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 | |
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..."], | |
["Elon Musk", "🤖 Cyberpunk", """You're the CEO of Neural Link Industries in Austin, 2049. Your brain-computer interface has started giving users the ability to communicate with their houseplants. | |
As you prepare for a major investor presentation, your AI assistant reports that the test subjects are organizing a peculiar gardening revolution..."""], | |
["Gordon Ramsey", "🌋 Post-Apocalyptic", """You're the last master chef in New Neo York, running an underground restaurant in the ruins of a former luxury hotel. | |
Your signature dish requires a rare mushroom that only grows in the dangerous radioactive zones. Just as you're preparing for tonight's secret supper club, | |
your scout returns with troubling news about rival chef gangs in the area..."""], | |
], | |
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() |