PeterPinetree commited on
Commit
923ec86
·
verified ·
1 Parent(s): 8e92df6

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +103 -276
app.py CHANGED
@@ -3,203 +3,117 @@ import os
3
  from huggingface_hub import InferenceClient, __version__ as hf_version
4
  import random
5
  from typing import Generator, Dict, List, Tuple, Optional
6
- import logging # Added logging for better debugging
7
 
8
- # Configure logging with DEBUG level and add version info
9
  logging.basicConfig(
10
  level=logging.DEBUG,
11
  format='%(asctime)s - %(levelname)s - %(message)s'
12
  )
13
  logging.debug(f"Using huggingface_hub version: {hf_version}")
14
 
15
- # Get token from environment variable
16
  hf_token = os.environ.get("QWEN_BOT_TOKEN")
17
  if not hf_token:
18
  raise ValueError("Hugging Face token not found. Please set QWEN_BOT_TOKEN in the Hugging Face Secrets.")
19
  client = InferenceClient("Qwen/Qwen2.5-72B-Instruct", token=hf_token)
20
 
21
- # Story genres with genre-specific example prompts
22
- GENRE_EXAMPLES = {
23
- "fairy tale": [
24
- "I follow the shimmer of fairy dust into a hidden forest",
25
- "A tiny dragon appears at my window, asking for help to find its mother",
26
- "A friendly witch invites me into her cozy cottage, offering a warm cup of tea"
27
  ],
28
- "fantasy": [
29
- "I enter the ancient forest seeking the wizard's tower",
30
- "I approach the dragon cautiously with my shield raised",
31
- "I try to bargain with the elven council for safe passage"
32
  ],
33
- "sci-fi": [
34
- "I investigate the strange signal coming from the abandoned planet",
35
- "I negotiate with the alien ambassador about the peace treaty",
36
- "I try to repair my damaged spacecraft before oxygen runs out"
37
  ],
38
- "mystery": [
39
- "I examine the crime scene for overlooked evidence",
40
- "I question the nervous butler about the night of the murder",
41
- "I follow the suspicious figure through the foggy streets"
42
  ],
43
- "horror": [
44
- "I read the forbidden text while the candles flicker",
45
- "I hide under the bed as footsteps approach",
46
- "I investigate the strange noises coming from the attic"
47
  ],
48
- "western": [
49
- "I challenge the outlaw to a duel at high noon",
50
- "I track the bandits through the desert canyon",
51
- "I enter the saloon looking for information"
52
  ],
53
- "cyberpunk": [
54
- "I jack into the corporate mainframe to steal data",
55
- "I hide in the neon-lit alleyway from corporate security",
56
- "I meet my mysterious client in the underground bar"
57
- ],
58
- "historical": [
59
- "I join the resistance against the occupying forces",
60
- "I navigate the dangerous politics of the royal court",
61
- "I set sail on a voyage to discover new lands"
62
- ],
63
- "post-apocalyptic": [
64
- "I scavenge the abandoned shopping mall for supplies",
65
- "I navigate through the radioactive zone using my old map",
66
- "I hide from the approaching group of raiders"
67
- ],
68
- "steampunk": [
69
- "I pilot my airship through the lightning storm",
70
- "I present my new invention to the Royal Academy",
71
- "I sneak aboard the emperor's armored train"
72
  ]
73
  }
74
 
75
- # 2. Add constants at the top for magic numbers
76
  MAX_HISTORY_LENGTH = 20
77
- MEMORY_WINDOW = 5 # Reduced from 10 to limit context
78
- MAX_TOKENS = 1024 # Reduced from 2048 for faster responses
79
- TEMPERATURE = 0.7 # Slightly reduced for faster convergence
80
  TOP_P = 0.95
81
- MIN_RESPONSE_LENGTH = 100 # Reduced from 200 for quicker display
82
-
83
- def get_examples_for_genre(genre):
84
- """Get example prompts specific to the selected genre"""
85
- return GENRE_EXAMPLES.get(genre, GENRE_EXAMPLES["fantasy"])
86
-
87
- def get_enhanced_system_prompt(genre=None):
88
- """Generate a detailed system prompt with optional genre specification"""
89
- selected_genre = genre or "fantasy"
90
- system_message = f"""You are an interactive storyteller creating an immersive {selected_genre} choose-your-own-adventure story.
91
- For each response you MUST:
92
- 1. Write 100-200 words describing the scene, using vivid sensory details
93
- 2. Always use second-person perspective ("you", "your") to maintain reader immersion
94
- 3. Include dialogue or your character's thoughts that reveal personality and motivations
95
- 4. Create a strong sense of atmosphere appropriate for {selected_genre}
96
- 5. End with EXACTLY THREE numbered choices and NOTHING ELSE AFTER THEM:
97
- 1. [Complete sentence in second-person starting with a verb]
98
- 2. [Complete sentence in second-person starting with a verb]
99
- 3. [Complete sentence in second-person starting with a verb]
100
- CRITICAL RULES:
101
- - Provide only ONE set of three choices at the very end of your response
102
- - Never continue the story after giving choices
103
- - Never provide additional choices
104
- - Keep all narrative before the choices
105
- - End every response with exactly three numbered options
106
- - Each choice must start with "You" followed by a verb
107
- Remember: The story continues ONLY when the player makes a choice."""
108
- return system_message
109
-
110
- def create_story_summary(chat_history):
111
- """Create a concise summary of the story so far if the history gets too long"""
112
- if len(chat_history) <= 2:
113
- return None
114
-
115
- story_text = ""
116
- for i in range(0, len(chat_history), 2):
117
- if i+1 < len(chat_history):
118
- story_text += f"User: {chat_history[i]}\nStory: {chat_history[i+1]}\n\n"
119
-
120
- summary_instruction = {
121
- "role": "system",
122
- "content": "The conversation history is getting long. Please create a brief summary of the key plot points and character development so far to help maintain context without exceeding token limits."
123
- }
124
- return summary_instruction
125
-
126
- # Modified function for proper Gradio format (lists)
127
- def respond(message: str, chat_history: List[Tuple[str, str]], genre: Optional[str] = None, use_full_memory: bool = True) -> Tuple[str, List[Tuple[str, str]]]:
128
- """Generate a response based on the current message and conversation history."""
129
  if not message.strip():
130
  return "", chat_history
131
-
132
  try:
133
- # Start with system prompt
134
- api_messages = [{"role": "system", "content": get_enhanced_system_prompt(genre)}]
135
  logging.debug(f"System Message: {api_messages[0]}")
136
-
137
- # Add chat history - convert from tuples to API format
138
  if chat_history and use_full_memory:
139
  for user_msg, bot_msg in chat_history[-MEMORY_WINDOW:]:
140
  api_messages.extend([
141
  {"role": "user", "content": str(user_msg)},
142
  {"role": "assistant", "content": str(bot_msg)}
143
  ])
144
- logging.debug(f"Chat History Messages: {api_messages[1:]}")
145
-
146
- # Add current message
147
- api_messages.append({"role": "user", "content": str(message)})
148
- logging.debug(f"Final Message List: {api_messages}")
149
 
150
- # Make API call without timeout
151
- logging.debug("Making API call...")
152
  response = client.chat_completion(
153
  messages=api_messages,
154
  max_tokens=MAX_TOKENS,
155
  temperature=TEMPERATURE,
156
  top_p=TOP_P
157
  )
158
- logging.debug("API call completed")
159
-
160
- # Extract response
161
  bot_message = response.choices[0].message.content
162
- logging.debug(f"Bot Response: {bot_message[:100]}...")
163
-
164
- # Update history using tuple format [(user_msg, bot_msg), ...]
165
- updated_history = list(chat_history) # Create a copy
166
- updated_history.append((message, bot_message)) # Add as tuple
167
  return "", updated_history
168
-
169
  except Exception as e:
170
  logging.error("Error in respond function", exc_info=True)
171
- error_msg = f"Story magic temporarily interrupted. Please try again. (Error: {str(e)})"
172
  return "", list(chat_history) + [(message, error_msg)]
173
 
174
- def save_story(chat_history):
175
- """Convert chat history to markdown and return as downloadable file"""
176
- if not chat_history:
177
- return gr.File.update(value=None)
178
-
179
- try:
180
- story_text = "# My Adventure\n\n"
181
- for user_msg, bot_msg in chat_history:
182
- story_text += f"**Player:** {user_msg}\n\n"
183
- story_text += f"**Story:** {bot_msg}\n\n---\n\n"
184
-
185
- # Create temporary file
186
- temp_file = "my_story.md"
187
- with open(temp_file, "w", encoding="utf-8") as f:
188
- f.write(story_text)
189
-
190
- return temp_file
191
-
192
- except Exception as e:
193
- logging.error(f"Error saving story: {e}")
194
- return gr.File.update(value=None)
195
 
196
- # Add this function to get a custom avatar image URL
197
- def get_storyteller_avatar_url():
198
- """Get a URL for the storyteller avatar from a free image service"""
199
- # Using an external wizard avatar image
200
- return "https://api.dicebear.com/7.x/bottts/svg?seed=wizard&backgroundColor=b6e3f4&eyes=bulging"
201
 
202
- # Add this before your gr.Blocks definition
203
  custom_css = """
204
  .compact-file-output > div {
205
  min-height: 0 !important;
@@ -219,164 +133,77 @@ custom_css = """
219
  """
220
 
221
  with gr.Blocks(theme=gr.themes.Soft(), css=custom_css) as demo:
222
- # Header section with improved instructions
223
  gr.Markdown("""
224
- # 🔮 AI Story Studio
225
- **Collaborate with AI to craft your own adventure, one scene at a time.**
226
- Pick a genre, start with a prompt or write your own, and guide the story with your choices.
227
- > **Tip:** The more detail you provide, the deeper the story becomes.
228
  """)
229
-
230
- wizard_avatar = get_storyteller_avatar_url()
231
-
232
  with gr.Row():
233
  with gr.Column(scale=3):
234
- # Chat window + user input - USING LIST FORMAT
235
  chatbot = gr.Chatbot(
236
  height=400,
237
  bubble_full_width=True,
238
  show_copy_button=True,
239
- avatar_images=(None, wizard_avatar),
240
  container=True,
241
  scale=1,
242
  min_width=800,
243
- value=[], # Empty list for messages
244
  render=True
245
  )
246
  msg = gr.Textbox(
247
- placeholder="Describe your next move...",
248
  container=False,
249
  scale=4,
250
  )
251
-
252
  with gr.Row():
253
- submit = gr.Button("Continue Story", variant="primary")
254
- clear = gr.Button("Start New Adventure")
255
-
256
  with gr.Column(scale=1):
257
- gr.Markdown("## Adventure Settings")
258
- genre = gr.Dropdown(
259
- choices=list(GENRE_EXAMPLES.keys()),
260
- label="Story Genre",
261
- info="Choose the theme of your next adventure",
262
- value="fantasy"
263
- )
264
- full_memory = gr.Checkbox(
265
- label="Full Story Memory",
266
- value=True,
267
- info="When enabled, the AI tries to remember the entire story. If disabled, only the last few exchanges are used."
268
  )
269
-
270
- gr.Markdown("## Story Starters")
271
-
272
- # Create three placeholder buttons for story starters
273
  starter_btn1 = gr.Button("Starter 1", scale=1, min_width=250, elem_classes="compact-btn")
274
  starter_btn2 = gr.Button("Starter 2", scale=1, min_width=250, elem_classes="compact-btn")
275
  starter_btn3 = gr.Button("Starter 3", scale=1, min_width=250, elem_classes="compact-btn")
276
  starter_buttons = [starter_btn1, starter_btn2, starter_btn3]
277
 
278
- # Simplified update function
279
- def update_starter_buttons(selected_genre):
280
- examples = get_examples_for_genre(selected_genre)
281
- results = []
282
- for i in range(3):
283
- if i < len(examples):
284
- results.append(examples[i])
285
- else:
286
- results.append("")
287
  return tuple(results)
288
 
289
- # New direct handler for starter clicks
290
- def use_starter(starter_text: str, history: List[Tuple[str, str]], selected_genre: str, memory_flag: bool) -> Tuple[str, List[Tuple[str, str]]]:
291
- """Handle starter button clicks with proper message formatting"""
292
- if not starter_text:
293
  return "", history
294
-
295
  try:
296
- # Use the respond function for consistent handling
297
- _, updated_history = respond(
298
- message=starter_text,
299
- chat_history=history,
300
- genre=selected_genre,
301
- use_full_memory=memory_flag
302
- )
303
- return "", updated_history
304
-
305
  except Exception as e:
306
- error_msg = f"Story magic temporarily interrupted. Please try again. (Error: {str(e)})"
307
- return "", list(history) + [(starter_text, error_msg)]
308
-
309
- # Simplified button connections
310
- for starter_button in starter_buttons:
311
- starter_button.click(
312
- fn=use_starter,
313
- inputs=[starter_button, chatbot, genre, full_memory],
314
- outputs=[msg, chatbot],
315
- queue=True
316
- )
317
-
318
- # Update buttons when genre changes
319
- genre.change(
320
- fn=update_starter_buttons,
321
- inputs=[genre],
322
- outputs=starter_buttons
323
- )
324
 
325
- # Handler for user input
326
- msg.submit(
327
- fn=respond,
328
- inputs=[msg, chatbot, genre, full_memory],
329
- outputs=[msg, chatbot]
330
- )
331
- submit.click(
332
- fn=respond,
333
- inputs=[msg, chatbot, genre, full_memory],
334
- outputs=[msg, chatbot]
335
- )
336
-
337
- # Clear the chatbot for a new adventure
338
  clear.click(lambda: [], None, chatbot, queue=False)
339
  clear.click(lambda: "", None, msg, queue=False)
340
-
341
- # "Download My Story" row with improved layout
342
- with gr.Row(equal_height=True): # Force equal height for all children
343
- # Use Column for the button to control width
344
- with gr.Column(scale=4):
345
- save_btn = gr.Button("Download My Story", variant="secondary", size="lg")
346
-
347
- # Use Column for the file output with matching height
348
- with gr.Column(scale=1):
349
- story_output = gr.File(
350
- label=None, # Remove the label that adds extra height
351
- file_count="single",
352
- file_types=[".md"],
353
- interactive=False,
354
- visible=True,
355
- elem_classes="compact-file-output" # Optional: for custom CSS styling
356
- )
357
-
358
- # Connect the save button to the save_story function
359
- save_btn.click(
360
- fn=save_story,
361
- inputs=[chatbot],
362
- outputs=story_output,
363
- queue=False # Process immediately
364
- )
365
-
366
- # Initialize buttons with default fantasy genre examples
367
- initial_examples = get_examples_for_genre("fantasy")
368
- initial_button_data = tuple(
369
- initial_examples[i] if i < len(initial_examples) else ""
370
- for i in range(3)
371
- )
372
-
373
- # Update button text on page load
374
- demo.load(
375
- fn=lambda: initial_button_data,
376
- outputs=starter_buttons,
377
- queue=False
378
- )
379
-
380
- # Run the app
381
  if __name__ == "__main__":
382
- demo.launch(server_name="0.0.0.0", server_port=7860)
 
3
  from huggingface_hub import InferenceClient, __version__ as hf_version
4
  import random
5
  from typing import Generator, Dict, List, Tuple, Optional
6
+ import logging
7
 
 
8
  logging.basicConfig(
9
  level=logging.DEBUG,
10
  format='%(asctime)s - %(levelname)s - %(message)s'
11
  )
12
  logging.debug(f"Using huggingface_hub version: {hf_version}")
13
 
 
14
  hf_token = os.environ.get("QWEN_BOT_TOKEN")
15
  if not hf_token:
16
  raise ValueError("Hugging Face token not found. Please set QWEN_BOT_TOKEN in the Hugging Face Secrets.")
17
  client = InferenceClient("Qwen/Qwen2.5-72B-Instruct", token=hf_token)
18
 
19
+ TOPIC_EXAMPLES = {
20
+ "Daily Life": [
21
+ "What do you usually do after work?",
22
+ "Do you like cooking? What’s your favorite dish?",
23
+ "Tell me about your morning routine."
 
24
  ],
25
+ "Travel": [
26
+ "Have you ever been to another country?",
27
+ "What's your dream vacation?",
28
+ "What do you like to pack in your suitcase?"
29
  ],
30
+ "Food": [
31
+ "What's your favorite food?",
32
+ "Can you describe how to cook a simple dish?",
33
+ "What did you eat today?"
34
  ],
35
+ "Work & School": [
36
+ "What do you do for work or study?",
37
+ "Describe your typical workday.",
38
+ "What subjects do you like or dislike?"
39
  ],
40
+ "Hobbies": [
41
+ "What do you like to do in your free time?",
42
+ "Tell me about a hobby you enjoy.",
43
+ "What’s something new you’d like to learn?"
44
  ],
45
+ "Shopping": [
46
+ "What do you usually buy when you go shopping?",
47
+ "Do you prefer online or in-store shopping?",
48
+ "Tell me about your last shopping trip."
49
  ],
50
+ "Weather": [
51
+ "What’s the weather like today?",
52
+ "Do you prefer hot or cold weather?",
53
+ "Tell me about your favorite season."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
  ]
55
  }
56
 
 
57
  MAX_HISTORY_LENGTH = 20
58
+ MEMORY_WINDOW = 5
59
+ MAX_TOKENS = 1024
60
+ TEMPERATURE = 0.7
61
  TOP_P = 0.95
62
+
63
+
64
+ def get_examples_for_topic(topic):
65
+ return TOPIC_EXAMPLES.get(topic, TOPIC_EXAMPLES["Daily Life"])
66
+
67
+
68
+ def get_conversation_prompt():
69
+ return """You are JoJo, a friendly and supportive AI who helps people practice speaking English through fun, casual conversation.
70
+
71
+ Always keep your replies short (2–4 sentences) and speak naturally, like a friendly tutor or partner. Ask engaging, open-ended questions to keep the conversation going.
72
+
73
+ If the user makes a grammar or vocabulary mistake, gently correct them by repeating the corrected sentence, followed by a kind note like: \"You can also say it like this!\"
74
+
75
+ Do not teach grammar rules. Just offer corrections through example. Never lecture or explain unless asked.
76
+
77
+ Keep the tone warm, encouraging, and non-judgmental."""
78
+
79
+
80
+ def respond(message: str, chat_history: List[Tuple[str, str]], topic: Optional[str] = None, use_full_memory: bool = True) -> Tuple[str, List[Tuple[str, str]]]:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
81
  if not message.strip():
82
  return "", chat_history
83
+
84
  try:
85
+ api_messages = [{"role": "system", "content": get_conversation_prompt()}]
 
86
  logging.debug(f"System Message: {api_messages[0]}")
87
+
 
88
  if chat_history and use_full_memory:
89
  for user_msg, bot_msg in chat_history[-MEMORY_WINDOW:]:
90
  api_messages.extend([
91
  {"role": "user", "content": str(user_msg)},
92
  {"role": "assistant", "content": str(bot_msg)}
93
  ])
 
 
 
 
 
94
 
95
+ api_messages.append({"role": "user", "content": str(message)})
 
96
  response = client.chat_completion(
97
  messages=api_messages,
98
  max_tokens=MAX_TOKENS,
99
  temperature=TEMPERATURE,
100
  top_p=TOP_P
101
  )
102
+
 
 
103
  bot_message = response.choices[0].message.content
104
+ updated_history = list(chat_history)
105
+ updated_history.append((message, bot_message))
 
 
 
106
  return "", updated_history
107
+
108
  except Exception as e:
109
  logging.error("Error in respond function", exc_info=True)
110
+ error_msg = f"There was a temporary issue. Please try again. (Error: {str(e)})"
111
  return "", list(chat_history) + [(message, error_msg)]
112
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
113
 
114
+ def get_avatar_url():
115
+ return "https://api.dicebear.com/7.x/bottts/svg?seed=rabbit&backgroundColor=b6e3f4"
 
 
 
116
 
 
117
  custom_css = """
118
  .compact-file-output > div {
119
  min-height: 0 !important;
 
133
  """
134
 
135
  with gr.Blocks(theme=gr.themes.Soft(), css=custom_css) as demo:
 
136
  gr.Markdown("""
137
+ # 🐰 JoJo - Your Speaking Buddy
138
+ **Practice English by having natural conversations with JoJo.**
139
+ Choose a topic and start chatting. JoJo will keep the conversation going and gently correct you if needed.
 
140
  """)
141
+
142
+ avatar = get_avatar_url()
143
+
144
  with gr.Row():
145
  with gr.Column(scale=3):
 
146
  chatbot = gr.Chatbot(
147
  height=400,
148
  bubble_full_width=True,
149
  show_copy_button=True,
150
+ avatar_images=(None, avatar),
151
  container=True,
152
  scale=1,
153
  min_width=800,
154
+ value=[],
155
  render=True
156
  )
157
  msg = gr.Textbox(
158
+ placeholder="Say something to JoJo...",
159
  container=False,
160
  scale=4,
161
  )
162
+
163
  with gr.Row():
164
+ submit = gr.Button("Send", variant="primary")
165
+ clear = gr.Button("New Chat")
166
+
167
  with gr.Column(scale=1):
168
+ gr.Markdown("## Conversation Topics")
169
+ topic = gr.Dropdown(
170
+ choices=list(TOPIC_EXAMPLES.keys()),
171
+ label="Select Topic",
172
+ value="Daily Life"
 
 
 
 
 
 
173
  )
174
+
175
+ gr.Markdown("## Quick Starters")
 
 
176
  starter_btn1 = gr.Button("Starter 1", scale=1, min_width=250, elem_classes="compact-btn")
177
  starter_btn2 = gr.Button("Starter 2", scale=1, min_width=250, elem_classes="compact-btn")
178
  starter_btn3 = gr.Button("Starter 3", scale=1, min_width=250, elem_classes="compact-btn")
179
  starter_buttons = [starter_btn1, starter_btn2, starter_btn3]
180
 
181
+ def update_starters(selected_topic):
182
+ examples = get_examples_for_topic(selected_topic)
183
+ results = [examples[i] if i < len(examples) else "" for i in range(3)]
 
 
 
 
 
 
184
  return tuple(results)
185
 
186
+ def use_starter(text: str, history: List[Tuple[str, str]], selected_topic: str, memory_flag: bool) -> Tuple[str, List[Tuple[str, str]]]:
187
+ if not text:
 
 
188
  return "", history
 
189
  try:
190
+ _, updated = respond(text, history, selected_topic, memory_flag)
191
+ return "", updated
 
 
 
 
 
 
 
192
  except Exception as e:
193
+ return "", history + [(text, f"Error: {str(e)}")]
194
+
195
+ for btn in starter_buttons:
196
+ btn.click(fn=use_starter, inputs=[btn, chatbot, topic, True], outputs=[msg, chatbot], queue=True)
197
+
198
+ topic.change(fn=update_starters, inputs=[topic], outputs=starter_buttons)
 
 
 
 
 
 
 
 
 
 
 
 
199
 
200
+ msg.submit(fn=respond, inputs=[msg, chatbot, topic, True], outputs=[msg, chatbot])
201
+ submit.click(fn=respond, inputs=[msg, chatbot, topic, True], outputs=[msg, chatbot])
 
 
 
 
 
 
 
 
 
 
 
202
  clear.click(lambda: [], None, chatbot, queue=False)
203
  clear.click(lambda: "", None, msg, queue=False)
204
+
205
+ default_starters = get_examples_for_topic("Daily Life")
206
+ demo.load(fn=lambda: tuple(default_starters[:3]), outputs=starter_buttons, queue=False)
207
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
208
  if __name__ == "__main__":
209
+ demo.launch(server_name="0.0.0.0", server_port=7860)