Spaces:
Sleeping
Sleeping
| import os | |
| import sys | |
| import gradio as gr | |
| sys.path.append(os.path.dirname(os.path.dirname(__file__))) | |
| from common import ( | |
| PoetryModel, load_prompt_template, fill_prompt_template, | |
| filter_output, create_fill_in_blank, select_words_to_blank, | |
| load_themes, load_interests, check_input_safety | |
| ) | |
| def create_app(model: PoetryModel): | |
| """Create Poem Generator Gradio app with intelligent word selection and position-aware checking.""" | |
| prompt_path = os.path.join(os.path.dirname(__file__), 'prompts.md') | |
| prompt_template = load_prompt_template(prompt_path) | |
| themes_path = os.path.join(os.path.dirname(__file__), 'themes.md') | |
| interests_path = os.path.join(os.path.dirname(__file__), 'interests.md') | |
| THEMES = load_themes(themes_path) | |
| INTERESTS = load_interests(interests_path) | |
| MAX_INTERESTS = 3 | |
| def generate_poem(age, length, difficulty, selected_theme, selected_interests): | |
| # Convert age to int if it's a string | |
| try: | |
| age = int(age) | |
| except (ValueError, TypeError): | |
| age = 9 # Default | |
| # Build theme and interests strings | |
| theme_text = selected_theme if selected_theme else "general" | |
| interests_text = ", ".join(selected_interests) if selected_interests else "various things" | |
| prompt = fill_prompt_template( | |
| prompt_template, | |
| age=age, | |
| length=length, | |
| theme=theme_text, | |
| interests=interests_text | |
| ) | |
| # Generate poem with debugging output | |
| print(f"\n{'='*70}\n🎨 GENERATING POEM\n{'='*70}") | |
| print(f"Age: {age} | Length: {length} | Difficulty: {difficulty}") | |
| print(f"Themes: {theme_text}") | |
| print(f"Interests: {interests_text}") | |
| print(f"\nPrompt sent to model:\n{'-'*70}\n{prompt}\n{'-'*70}") | |
| poem = model.generate(prompt, max_tokens=350, temperature=0.7) | |
| poem = filter_output(poem) | |
| print(f"\n✓ Generated Poem:\n{'-'*70}\n{poem}\n{'-'*70}") | |
| # Use AI to intelligently select words to blank | |
| selected_words = select_words_to_blank(poem, difficulty, age, model) | |
| # Create blanks with exact positions | |
| blanked, correct_answers, positions, all_words = create_fill_in_blank(poem, difficulty, selected_words) | |
| print(f"\n📊 Blank Information:") | |
| print(f"Number of blanks: {len(positions)}") | |
| print(f"Positions: {positions}") | |
| print(f"Correct answers: {correct_answers}") | |
| print(f"\nBlanked poem:\n{'-'*70}\n{blanked}\n{'='*70}\n") | |
| # Format poem with line breaks for better display | |
| poem_lines = blanked.split('. ') | |
| # Use double line break for clear separation | |
| formatted_blanked = "<br><br>".join([line.strip() + ('.' if i < len(poem_lines)-1 else '') for i, line in enumerate(poem_lines) if line.strip()]) | |
| # Create shuffled word bank | |
| import random | |
| word_bank = correct_answers.copy() | |
| random.shuffle(word_bank) | |
| word_bank_html = "**Word Bank:** " + " | ".join([f"<span style='background: #e0e7ff; padding: 4px 12px; border-radius: 12px; color: #667eea; font-weight: 600;'>{word}</span>" for word in word_bank]) | |
| # Create input fields for each blank | |
| blank_components = [] | |
| for i in range(len(correct_answers)): | |
| blank_components.append(gr.update( | |
| value="", | |
| visible=True, | |
| placeholder=f"Blank {i+1}", | |
| interactive=True, | |
| type = 'text' | |
| )) | |
| # Hide unused textboxes | |
| for i in range(len(correct_answers), 9): | |
| blank_components.append(gr.update(visible=False)) | |
| # Store state with positions and correct answers | |
| state = { | |
| "correct_answers": correct_answers, | |
| "positions": positions, | |
| "all_words": all_words, | |
| "original_poem": poem, | |
| "word_bank": word_bank | |
| } | |
| return { | |
| output_markdown: gr.update(value=f"**Your Poem:**\n\n{formatted_blanked}\n\n{word_bank_html}", visible=True), | |
| blank_inputs: gr.update(visible=True), | |
| **{blank_textboxes[i]: blank_components[i] for i in range(9)}, | |
| check_button: gr.update(visible=True), | |
| solution_button: gr.update(visible=False), | |
| result_markdown: gr.update(visible=False), | |
| solution_display: gr.update(visible=False), | |
| poem_state: state | |
| } | |
| def check_answers(*args): | |
| """Check user answers with position-aware validation and color coding.""" | |
| state = args[-1] | |
| user_inputs = list(args[:-1]) | |
| correct_answers = state.get("correct_answers", []) | |
| positions = state.get("positions", []) | |
| all_words = state.get("all_words", []) | |
| word_bank = state.get("word_bank", []) | |
| # Recreate word bank HTML | |
| word_bank_html = "**Word Bank:** " + " | ".join([f"<span style='background: #e0e7ff; padding: 4px 12px; border-radius: 12px; color: #667eea; font-weight: 600;'>{word}</span>" for word in word_bank]) | |
| # Check each answer against correct answer at same position | |
| results = [] | |
| for i, (user_word, correct_word) in enumerate(zip(user_inputs[:len(correct_answers)], correct_answers)): | |
| is_correct = (user_word or "").lower().strip() == correct_word.lower().strip() | |
| results.append(is_correct) | |
| # Build poem with user's answers color-coded (in place) | |
| display_words = all_words.copy() | |
| for i, pos in enumerate(positions): | |
| if i < len(user_inputs) and user_inputs[i]: | |
| # Color code based on correctness | |
| if results[i]: | |
| display_words[pos] = f"<span style='color: #38ef7d; font-weight: bold;'>{user_inputs[i]}</span>" | |
| else: | |
| display_words[pos] = f"<span style='color: #f5576c; font-weight: bold;'>{user_inputs[i]}</span>" | |
| else: | |
| # Empty input - keep underscores in poem and mark as incorrect (red) | |
| display_words[pos] = f"<span style='color: #f5576c; font-weight: bold;'>_______________</span>" | |
| # Format poem with line breaks (split at periods or newlines) | |
| poem_text = " ".join(display_words) | |
| # Try to detect natural line breaks in original poem | |
| poem_lines = poem_text.replace('\n', '. ').split('. ') | |
| formatted_poem = "<br><br>".join([line.strip() + ('.' if i < len(poem_lines)-1 and not line.endswith('.') else '') for i, line in enumerate(poem_lines) if line.strip()]) | |
| # Generate result message | |
| if all(results): | |
| message = "## 🎉 Perfect! All answers are correct!" | |
| show_solution = False | |
| else: | |
| incorrect_count = sum(1 for r in results if not r) | |
| empty_count = sum(1 for i, inp in enumerate(user_inputs[:len(correct_answers)]) if not inp) | |
| if empty_count > 0: | |
| message = f"## ⚠️ {empty_count} blank(s) still empty, {incorrect_count} incorrect. Keep trying!" | |
| else: | |
| message = f"## ❌ {incorrect_count} incorrect. Try again or check the solution!" | |
| show_solution = True | |
| # Update the main output_markdown instead of result_markdown to avoid duplication | |
| return { | |
| output_markdown: gr.update(value=f"{message}\n\n**Your Poem:**\n\n{formatted_poem}\n\n{word_bank_html}", visible=True), | |
| result_markdown: gr.update(visible=False), # Hide result markdown as we use main output | |
| solution_button: gr.update(visible=show_solution), | |
| **{blank_textboxes[i]: gr.update() for i in range(9)} | |
| } | |
| def show_solution(state): | |
| """Show the complete poem with all correct answers in green.""" | |
| correct_answers = state.get("correct_answers", []) | |
| positions = state.get("positions", []) | |
| all_words = state.get("all_words", []) | |
| # Build poem with all correct answers in green | |
| solution_words = all_words.copy() | |
| for i, pos in enumerate(positions): | |
| solution_words[pos] = f"<span style='color: #38ef7d; font-weight: bold;'>{correct_answers[i]}</span>" | |
| # Format poem with line breaks (split at periods or newlines) | |
| poem_text = " ".join(solution_words) | |
| poem_lines = poem_text.replace('\n', '. ').split('. ') | |
| formatted_poem = "<br><br>".join([line.strip() + ('.' if i < len(poem_lines)-1 and not line.endswith('.') else '') for i, line in enumerate(poem_lines) if line.strip()]) | |
| # Update main output_markdown to show solution in place | |
| return { | |
| output_markdown: gr.update(value=f"## ✅ Complete Poem (Solution)\n\n{formatted_poem}", visible=True), | |
| solution_display: gr.update(visible=False), # Hide separate solution display | |
| solution_button: gr.update(visible=False), | |
| result_markdown: gr.update(visible=False) | |
| } | |
| with gr.Blocks(title="Poem Generator") as demo: | |
| gr.Markdown("# 📝 Poem Generator with Fill-in-the-Blank Quiz") | |
| gr.Markdown("Create a personalized poem and test your vocabulary!") | |
| with gr.Row(): | |
| age_input = gr.Textbox(value="9", label="Age", placeholder="Enter age (6-99)") | |
| length_dropdown = gr.Dropdown(["short", "medium", "long"], value="short", label="Length") | |
| difficulty_dropdown = gr.Dropdown(["Easy", "Medium", "Hard"], value="Easy", label="Difficulty") | |
| with gr.Row(): | |
| theme_dropdown = gr.Dropdown( | |
| choices=THEMES, | |
| label="Theme (select one)", | |
| value=THEMES[0] if THEMES else None | |
| ) | |
| with gr.Row(): | |
| interests_checkboxes = gr.CheckboxGroup( | |
| choices=INTERESTS, | |
| label=f"Interests (select up to {MAX_INTERESTS})", | |
| value=[] | |
| ) | |
| generate_button = gr.Button("✨ Generate Poem", variant="primary") | |
| output_markdown = gr.Markdown("", visible=False) | |
| # Container for blank input fields | |
| with gr.Column(visible=False) as blank_inputs: | |
| gr.Markdown("### Fill in the blanks") | |
| with gr.Row(): | |
| blank_textboxes = [ | |
| gr.Textbox(label="", visible=False, placeholder=f"Blank {i+1}", value="_______________", scale=1, show_label=False, type="text") | |
| for i in range(9) | |
| ] | |
| with gr.Row(): | |
| check_button = gr.Button("Check Answers", visible=False, variant="primary") | |
| solution_button = gr.Button("Show Solution", visible=False, variant="secondary") | |
| result_markdown = gr.Markdown("", visible=False) | |
| solution_display = gr.Markdown("", visible=False) | |
| poem_state = gr.State({}) | |
| # Limit interests selection | |
| def limit_interests(selected): | |
| if len(selected) > MAX_INTERESTS: | |
| return selected[:MAX_INTERESTS] | |
| return selected | |
| interests_checkboxes.change( | |
| limit_interests, | |
| inputs=[interests_checkboxes], | |
| outputs=[interests_checkboxes] | |
| ) | |
| generate_button.click( | |
| generate_poem, | |
| inputs=[age_input, length_dropdown, difficulty_dropdown, theme_dropdown, interests_checkboxes], | |
| outputs=[output_markdown, blank_inputs] + blank_textboxes + [check_button, solution_button, result_markdown, solution_display, poem_state], | |
| ) | |
| check_button.click( | |
| check_answers, | |
| inputs=blank_textboxes + [poem_state], | |
| outputs=[output_markdown, result_markdown, solution_button] + blank_textboxes | |
| ) | |
| solution_button.click( | |
| show_solution, | |
| inputs=[poem_state], | |
| outputs=[output_markdown, solution_display, solution_button, result_markdown] | |
| ) | |
| # Enable the Check Answers button only when all required blanks are filled | |
| def validate_blanks(*args): | |
| # args: first 9 are textbox values, last is state | |
| *vals, state = args | |
| n_required = len(state.get("correct_answers", [])) if isinstance(state, dict) else 0 | |
| # consider only first n_required textboxes | |
| all_filled = n_required > 0 and all((vals[i] or "").strip() for i in range(n_required)) | |
| return {check_button: gr.update(interactive=all_filled)} | |
| # Attach change handlers to all blank textboxes | |
| for tb in blank_textboxes: | |
| tb.change( | |
| validate_blanks, | |
| inputs=blank_textboxes + [poem_state], | |
| outputs=[check_button] | |
| ) | |
| return demo | |
| if __name__ == "__main__": | |
| from dotenv import load_dotenv | |
| load_dotenv() | |
| model = PoetryModel() | |
| app = create_app(model) | |
| app.launch() |