PierreBrunelle commited on
Commit
4b6484b
·
verified ·
1 Parent(s): aee324a

Upload 6 files

Browse files
interface/__init__.py ADDED
File without changes
interface/app.py ADDED
@@ -0,0 +1,245 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ from src.game_logic import RPGGame
3
+ from src.database import interactions
4
+
5
+ def create_interface():
6
+ game = RPGGame()
7
+
8
+ with gr.Blocks(theme=gr.themes.Base()) as demo:
9
+ # Header with title and description
10
+ gr.Markdown(
11
+ """
12
+ <div style="margin-bottom: 20px;">
13
+ <h1 style="margin-bottom: 0.5em;">🎲 AI RPG Adventure</h1>
14
+ <p>An interactive RPG experience from Pixeltable and powered by OpenAI! Get started with an example below.</p>
15
+ </div>
16
+ """
17
+ )
18
+
19
+ # Side-by-side accordions
20
+ with gr.Row():
21
+ with gr.Column():
22
+ with gr.Accordion("🎯 What does it do?", open=False):
23
+ gr.Markdown("""
24
+ This AI RPG Adventure demonstrates Pixeltable's capabilities:
25
+
26
+ - 🎮 Creates dynamic, AI-driven interactive stories
27
+ - 🔄 Maintains game state and history using Pixeltable tables
28
+ - 💭 Generates contextual options based on player actions
29
+ - 📝 Processes natural language inputs for custom actions
30
+ - 🤖 Uses LLMs to create engaging narratives
31
+ - 📊 Tracks and displays game progression
32
+
33
+ Perfect for understanding how Pixeltable can manage complex,
34
+ stateful applications with AI integration! ✨
35
+ """)
36
+
37
+ with gr.Column():
38
+ with gr.Accordion("🛠️ How does it work?", open=False):
39
+ gr.Markdown("""
40
+ The app leverages several Pixeltable features:
41
+
42
+ 1. 📦 **Data Management**: Uses Pixeltable tables to store game state,
43
+ player actions, and AI responses
44
+
45
+ 2. 🤖 **AI Integration**: Seamlessly connects with language models
46
+ for story generation and response processing
47
+
48
+ 3. 🔄 **State Tracking**: Maintains session history and player
49
+ choices using Pixeltable's computed columns
50
+
51
+ 4. ⚙️ **Custom Processing**: Uses Pixeltable UDFs to handle game
52
+ logic and AI prompt generation
53
+
54
+ 5. 🎯 **Interactive Flow**: Processes player inputs and generates
55
+ contextual responses in real-time
56
+ """)
57
+
58
+ with gr.Row():
59
+ # Setup column
60
+ with gr.Column():
61
+ player_name = gr.Textbox(
62
+ label="👤 Your Character's Name",
63
+ placeholder="Enter your character's name..."
64
+ )
65
+ genre = gr.Dropdown(
66
+ choices=[
67
+ "🧙‍♂️ Fantasy",
68
+ "🚀 Sci-Fi",
69
+ "👻 Horror",
70
+ "🔍 Mystery",
71
+ "🌋 Post-Apocalyptic",
72
+ "🤖 Cyberpunk",
73
+ "⚙️ Steampunk"
74
+ ],
75
+ label="🎭 Choose Your Genre"
76
+ )
77
+ scenario = gr.Textbox(
78
+ label="📖 Starting Scenario",
79
+ lines=3,
80
+ placeholder="Describe the initial setting and situation..."
81
+ )
82
+ start_button = gr.Button("🎮 Begin Adventure", variant="primary")
83
+
84
+ # Game interaction column
85
+ with gr.Column():
86
+ story_display = gr.Textbox(
87
+ label="📜 Story",
88
+ lines=8,
89
+ interactive=False
90
+ )
91
+
92
+ gr.Markdown("### 🎯 Choose Your Action")
93
+
94
+ with gr.Row():
95
+ with gr.Column():
96
+ action_input = gr.Radio(
97
+ choices=[],
98
+ label="🎲 Select an action or write your own below:",
99
+ interactive=True
100
+ )
101
+ custom_action = gr.Textbox(
102
+ label="✨ Custom Action",
103
+ placeholder="Write your own action here...",
104
+ lines=2
105
+ )
106
+ submit_action = gr.Button("⚡ Take Action", variant="secondary")
107
+
108
+ # History display
109
+ history_df = gr.Dataframe(
110
+ headers=["📅 Turn", "🎯 Player Action", "💬 Game Response"],
111
+ label="📚 Adventure History",
112
+ wrap=True,
113
+ row_count=5,
114
+ col_count=(3, "fixed")
115
+ )
116
+ def start_new_game(name, genre_choice, scenario_text):
117
+ if not name or not genre_choice or not scenario_text:
118
+ return "Please fill in all fields before starting.", [], "", []
119
+
120
+ try:
121
+ _, initial_response = game.start_game(name, genre_choice, scenario_text)
122
+
123
+ # Get options from the initial response
124
+ options = interactions.select(interactions.options).where(
125
+ (interactions.session_id == game.current_session_id) &
126
+ (interactions.turn_number == 0)
127
+ ).collect()['options'][0]
128
+
129
+ # Get initial history
130
+ history_df = interactions.select(
131
+ turn=interactions.turn_number,
132
+ action=interactions.player_input,
133
+ response=interactions.story_text
134
+ ).where(
135
+ interactions.session_id == game.current_session_id
136
+ ).order_by(
137
+ interactions.turn_number
138
+ ).collect().to_pandas()
139
+
140
+ history_data = [
141
+ [str(row['turn']), row['action'], row['response']]
142
+ for _, row in history_df.iterrows()
143
+ ]
144
+
145
+ return initial_response, gr.Radio(choices=options, interactive=True), "", history_data
146
+ except Exception as e:
147
+ return f"Error starting game: {str(e)}", [], "", []
148
+
149
+ def process_player_action(action_choice, custom_action):
150
+ try:
151
+ # Use custom action if provided, otherwise use selected choice
152
+ action = custom_action if custom_action else action_choice
153
+ if not action:
154
+ return "Please either select an action or write your own.", [], "", []
155
+
156
+ response = game.process_action(action)
157
+
158
+ # Get new options
159
+ options = interactions.select(interactions.options).where(
160
+ (interactions.session_id == game.current_session_id) &
161
+ (interactions.turn_number == game.turn_number)
162
+ ).collect()['options'][0]
163
+
164
+ # Get updated history
165
+ history_df = interactions.select(
166
+ turn=interactions.turn_number,
167
+ action=interactions.player_input,
168
+ response=interactions.story_text
169
+ ).where(
170
+ interactions.session_id == game.current_session_id
171
+ ).order_by(
172
+ interactions.turn_number
173
+ ).collect().to_pandas()
174
+
175
+ history_data = [
176
+ [str(row['turn']), row['action'], row['response']]
177
+ for _, row in history_df.iterrows()
178
+ ]
179
+
180
+ return response, gr.Radio(choices=options, interactive=True), "", history_data
181
+ except Exception as e:
182
+ return f"Error: {str(e)}", [], "", []
183
+
184
+ # Connect the start button
185
+ start_button.click(
186
+ start_new_game,
187
+ inputs=[player_name, genre, scenario],
188
+ outputs=[story_display, action_input, custom_action, history_df]
189
+ )
190
+
191
+ # Single action submit button
192
+ submit_action.click(
193
+ process_player_action,
194
+ inputs=[action_input, custom_action],
195
+ outputs=[story_display, action_input, custom_action, history_df]
196
+ )
197
+
198
+ # Example scenarios
199
+ gr.Markdown("### 💫 Example Adventures")
200
+ gr.Examples(
201
+ examples=[
202
+ ["Eldric", "🧙‍♂️ Fantasy", "You find yourself in an ancient forest clearing, standing before a mysterious glowing portal. Your journey begins..."],
203
+ ["Commander Nova", "🚀 Sci-Fi", "Aboard the starship Nebula, alarms blare as unknown entities approach. The fate of the crew rests in your hands..."],
204
+ ["Detective Blake", "🔍 Mystery", "In the fog-shrouded streets of Victorian London, a peculiar letter arrives at your doorstep..."],
205
+ ],
206
+ inputs=[player_name, genre, scenario]
207
+ )
208
+
209
+ # Footer with links
210
+ gr.HTML(
211
+ """
212
+ <div style="margin-top: 2rem; padding-top: 1rem; border-top: 1px solid #e5e7eb;">
213
+ <div style="display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 1rem;">
214
+ <div style="flex: 1;">
215
+ <h4 style="margin: 0; color: #374151;">🚀 Built with Pixeltable</h4>
216
+ <p style="margin: 0.5rem 0; color: #6b7280;">
217
+ AI Data infrastructure providing a declarative, incremental approach for multimodal workloads.
218
+ </p>
219
+ </div>
220
+ <div style="flex: 1;">
221
+ <h4 style="margin: 0; color: #374151;">🔗 Resources</h4>
222
+ <div style="display: flex; gap: 1.5rem; margin-top: 0.5rem;">
223
+ <a href="https://github.com/pixeltable/pixeltable" target="_blank" style="color: #4F46E5; text-decoration: none; display: flex; align-items: center; gap: 0.25rem;">
224
+ 💻 GitHub
225
+ </a>
226
+ <a href="https://docs.pixeltable.com" target="_blank" style="color: #4F46E5; text-decoration: none; display: flex; align-items: center; gap: 0.25rem;">
227
+ 📚 Documentation
228
+ </a>
229
+ <a href="https://huggingface.co/Pixeltable" target="_blank" style="color: #4F46E5; text-decoration: none; display: flex; align-items: center; gap: 0.25rem;">
230
+ 🤗 Hugging Face
231
+ </a>
232
+ </div>
233
+ </div>
234
+ </div>
235
+ <p style="margin: 1rem 0 0; text-align: center; color: #9CA3AF; font-size: 0.875rem;">
236
+ ✨ © 2024 Pixeltable. This demo is open source and available on
237
+ <a href="https://huggingface.co/spaces/Pixeltable/AI-RPG-Adventure" target="_blank" style="color: #4F46E5; text-decoration: none;">
238
+ Hugging Face Spaces 🚀
239
+ </a>
240
+ </p>
241
+ </div>
242
+ """
243
+ )
244
+
245
+ return demo
src/__init__.py ADDED
File without changes
src/database.py ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pixeltable as pxt
2
+ from pixeltable.functions import openai
3
+ import os
4
+ import getpass
5
+
6
+ # Set up OpenAI API key
7
+ if 'OPENAI_API_KEY' not in os.environ:
8
+ os.environ['OPENAI_API_KEY'] = getpass.getpass('Enter your OpenAI API key: ')
9
+
10
+ # Initialize Pixeltable
11
+ pxt.drop_dir('ai_rpg', force=True)
12
+ pxt.create_dir('ai_rpg')
13
+
14
+ # Create a single table for all game data
15
+ interactions = pxt.create_table(
16
+ 'ai_rpg.interactions',
17
+ {
18
+ 'session_id': pxt.StringType(),
19
+ 'player_name': pxt.StringType(),
20
+ 'genre': pxt.StringType(),
21
+ 'initial_scenario': pxt.StringType(),
22
+ 'turn_number': pxt.IntType(),
23
+ 'player_input': pxt.StringType(),
24
+ 'timestamp': pxt.TimestampType(),
25
+ }
26
+ )
27
+
28
+ # Add computed columns for AI responses
29
+ from src.utils import generate_messages, extract_options
30
+
31
+ interactions['messages'] = generate_messages(
32
+ interactions.genre,
33
+ interactions.player_name,
34
+ interactions.initial_scenario,
35
+ interactions.player_input,
36
+ interactions.turn_number
37
+ )
38
+
39
+ interactions['ai_response'] = openai.chat_completions(
40
+ messages=interactions.messages,
41
+ model='gpt-4o-mini-2024-07-18',
42
+ max_tokens=500,
43
+ temperature=0.8
44
+ )
45
+
46
+ interactions['story_text'] = interactions.ai_response.choices[0].message.content
47
+ interactions['options'] = extract_options(interactions.story_text)
src/game_logic.py ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from datetime import datetime
2
+ from src.database import interactions
3
+
4
+ class RPGGame:
5
+ def __init__(self):
6
+ self.current_session_id = None
7
+ self.turn_number = 0
8
+
9
+ def start_game(self, player_name: str, genre: str, scenario: str) -> str:
10
+ session_id = f"session_{datetime.now().strftime('%Y%m%d%H%M%S')}_{player_name}"
11
+ self.current_session_id = session_id
12
+ self.turn_number = 0
13
+
14
+ # Create initial interaction with all session data
15
+ interactions.insert([{
16
+ 'session_id': session_id,
17
+ 'player_name': player_name,
18
+ 'genre': genre,
19
+ 'initial_scenario': scenario,
20
+ 'turn_number': 0,
21
+ 'player_input': "Game starts",
22
+ 'timestamp': datetime.now()
23
+ }])
24
+
25
+ # Get initial story and options
26
+ initial_response = interactions.select(interactions.story_text).where(
27
+ (interactions.session_id == session_id) &
28
+ (interactions.turn_number == 0)
29
+ ).collect()['story_text'][0]
30
+
31
+ return session_id, initial_response
32
+
33
+ def process_action(self, action: str) -> str:
34
+ if not self.current_session_id:
35
+ return "No active game session. Please start a new game."
36
+
37
+ self.turn_number += 1
38
+
39
+ # Get session info from previous turn
40
+ prev_turn = interactions.select(
41
+ interactions.player_name,
42
+ interactions.genre,
43
+ interactions.initial_scenario
44
+ ).where(
45
+ (interactions.session_id == self.current_session_id) &
46
+ (interactions.turn_number == 0)
47
+ ).collect()
48
+
49
+ # Record player action with session data
50
+ interactions.insert([{
51
+ 'session_id': self.current_session_id,
52
+ 'player_name': prev_turn['player_name'][0],
53
+ 'genre': prev_turn['genre'][0],
54
+ 'initial_scenario': prev_turn['initial_scenario'][0],
55
+ 'turn_number': self.turn_number,
56
+ 'player_input': action,
57
+ 'timestamp': datetime.now()
58
+ }])
59
+
60
+ # Get AI response
61
+ response = interactions.select(interactions.story_text).where(
62
+ (interactions.session_id == self.current_session_id) &
63
+ (interactions.turn_number == self.turn_number)
64
+ ).collect()['story_text'][0]
65
+
66
+ return response
src/utils.py ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pixeltable as pxt
2
+ import re
3
+
4
+ @pxt.udf
5
+ def generate_messages(genre: str, player_name: str, initial_scenario: str, player_input: str, turn_number: int) -> list[dict]:
6
+ return [
7
+ {
8
+ 'role': 'system',
9
+ 'content': f"""You are the game master for a {genre} RPG. The player's name is {player_name}.
10
+ Provide an engaging response to the player's action and present exactly 3 numbered options for their next move:
11
+ 1. A dialogue option (saying something)
12
+ 2. A random action they could take
13
+ 3. A unique or unexpected choice
14
+
15
+ Format each option with a number (1., 2., 3.) followed by the action description."""
16
+ },
17
+ {
18
+ 'role': 'user',
19
+ 'content': f"Current scenario: {initial_scenario}\n"
20
+ f"Player's action: {player_input}\n"
21
+ f"Turn number: {turn_number}\n\n"
22
+ "Provide a response and 3 options:"
23
+ }
24
+ ]
25
+
26
+ @pxt.udf
27
+ def extract_options(story_text: str) -> list[str]:
28
+ """Extract the three options from the story text"""
29
+ # Look for numbered options (1., 2., 3.) and grab the text after them
30
+ options = re.findall(r'\d\.\s*(.*?)(?=\d\.|$)', story_text, re.DOTALL)
31
+ # Clean up the options and ensure we have exactly 3
32
+ options = [opt.strip() for opt in options[:3]]
33
+ while len(options) < 3:
34
+ options.append("Take another action...")
35
+ return options