""" GenAI for Easy Read: Open-Source Multimodal Solution A Gradio-based prototype for converting documents into accessible Easy Read formats. """ import gradio as gr import os from typing import List, Tuple, Optional import re # ============================================================================ # DUMMY/MOCK FUNCTIONS FOR BACKEND PROCESSING # TODO: Replace these with actual API calls to OpenAI, GlobalSymbols, etc. # ============================================================================ def split_into_sentences(text: str) -> List[str]: """ Splits text into individual sentences. TODO: Enhance with better sentence boundary detection (e.g., using spaCy) """ if not text or not text.strip(): return [] # Simple sentence splitting (can be improved with NLP libraries like spaCy) sentences = re.split(r'(?<=[.!?])\s+', text.strip()) return [s.strip() for s in sentences if s.strip()] def convert_to_easy_read( pdf_file: Optional[str], text_input: Optional[str], context: str, unalterable_terms: str ) -> Tuple[str, List[str], List[str]]: """ Simulates the conversion of complex text to simplified Easy Read format. TODO: Replace with actual LLM API call (e.g., OpenAI GPT-4, Anthropic Claude) - Process PDF if provided, or use text_input - Apply simplification rules - Respect context/custom instructions - Preserve unalterable terms """ # Use text_input if provided, otherwise dummy text input_text = text_input if text_input else ( "This is sentence one. " "This is sentence two. " "This is sentence three. " "Each sentence can be edited separately." ) # Split into sentences sentences = split_into_sentences(input_text) # Dummy simplified text (sentence-by-sentence) dummy_simplified = "\n".join(sentences) # Dummy keyword list for symbol matching dummy_keywords = ["person", "help", "document", "read"] return dummy_simplified, dummy_keywords, sentences def update_sentence(sentence_index: int, new_text: str, all_sentences: List[str]) -> str: """ Updates a specific sentence and returns the combined text. """ if 0 <= sentence_index < len(all_sentences): all_sentences[sentence_index] = new_text return "\n".join(all_sentences) def fetch_symbols_from_libraries( keywords: List[str], selected_libraries: List[str] ) -> List[str]: """ Fetches symbols from selected symbol libraries based on keywords. TODO: Replace with actual API calls to: - GlobalSymbols API - ARASAAC API - AAC Image Library API - PiCom API """ # Dummy: Return placeholder image paths # In production, these would be URLs or file paths to actual symbols dummy_symbols = [ "https://via.placeholder.com/200x200/4A90E2/FFFFFF?text=Symbol1", "https://via.placeholder.com/200x200/50C878/FFFFFF?text=Symbol2", "https://via.placeholder.com/200x200/E94B3C/FFFFFF?text=Symbol3", "https://via.placeholder.com/200x200/F5A623/FFFFFF?text=Symbol4", ] return dummy_symbols def generate_ai_image(prompt: str) -> Optional[str]: """ Generates an image using AI based on the provided prompt. TODO: Replace with actual AI image generation API call: - OpenAI DALL-E - Stability AI Stable Diffusion - Midjourney API - Other image generation services """ # Dummy: Return placeholder image return "https://via.placeholder.com/400x400/9B59B6/FFFFFF?text=AI+Generated" def export_to_pdf(text: str, images: List[str]) -> str: """ Exports the Easy Read document to PDF format. TODO: Implement PDF generation using libraries like: - reportlab - fpdf - weasyprint """ return "PDF export functionality - TODO: Implement" def export_to_word(text: str, images: List[str]) -> str: """ Exports the Easy Read document to Word format. TODO: Implement Word document generation using: - python-docx """ return "Word export functionality - TODO: Implement" def generate_audio(text: str) -> str: """ Generates audio narration of the Easy Read text. TODO: Implement text-to-speech using: - OpenAI TTS API - Google Cloud Text-to-Speech - Amazon Polly - Azure Cognitive Services """ return "Audio generation functionality - TODO: Implement" # ============================================================================ # GRADIO INTERFACE COMPONENTS # ============================================================================ def create_convert_handler( pdf_file, text_input, context, unalterable_terms, selected_libraries ): """ Main handler for the "Convert to Easy Read" button. Processes input and returns simplified text and symbol suggestions. """ # Step 1: Convert text to Easy Read format simplified_text, keywords, sentences = convert_to_easy_read( pdf_file, text_input, context, unalterable_terms ) # Step 2: Fetch symbols from selected libraries symbols = fetch_symbols_from_libraries(keywords, selected_libraries) # Step 3: Create sentence components for display sentence_components = [] for i, sentence in enumerate(sentences): sentence_components.append((sentence, i)) return simplified_text, symbols, sentences def create_ai_image_handler(prompt: str): """Handler for AI image generation tab.""" if not prompt.strip(): return None return generate_ai_image(prompt) # ============================================================================ # MAIN GRADIO INTERFACE # ============================================================================ def create_interface(): """Creates and configures the main Gradio interface.""" with gr.Blocks(theme=gr.themes.Soft()) as app: # Store sentences in state sentences_state = gr.State([]) # ==================================================================== # HEADER SECTION # ==================================================================== gr.Markdown( """ # GenAI for Easy Read: Open-Source Multimodal Solution Convert documents into accessible Easy Read formats with simplified text and supported symbols. """, elem_classes=["header"] ) # ==================================================================== # STEP 1: INPUT SECTION # ==================================================================== gr.Markdown("## Step 1: Input Document or Text") with gr.Row(): # Left Column: File Upload with gr.Column(): pdf_upload = gr.File( label="Upload Document", file_types=[".pdf"], type="filepath" ) # Right Column: Text Input with gr.Column(): text_input = gr.Textbox( label="Or Paste Text Here", lines=10, placeholder="Enter your text here..." ) # Advanced Settings Accordion with gr.Accordion("Advanced Settings", open=False): context_input = gr.Textbox( label="Context/Custom Instructions", placeholder="e.g., 'never use term X', 'target audience: children'", lines=3 ) unalterable_terms_input = gr.Textbox( label="Unalterable Terms", placeholder="Enter terms that must not be changed (comma-separated)", lines=2 ) # ==================================================================== # STEP 2: SYMBOL CONFIGURATION # ==================================================================== gr.Markdown("## Step 2: Select Symbol Libraries") symbol_libraries = gr.CheckboxGroup( choices=[ "AAC Image Library", "GlobalSymbols", "ARASAAC", "PiCom", "AI Realistic Symbols" ], label="Available Symbol Libraries", value=["ARASAAC", "GlobalSymbols"] # Default selections ) # ==================================================================== # STEP 3: EASY READ EDITOR # ==================================================================== gr.Markdown("## Step 3: Easy Read Editor") # Convert Button convert_btn = gr.Button( "Convert to Easy Read", variant="primary", size="lg" ) # Main Editor Layout: Text Editing + Image Selection with gr.Row(): # Left Side: Full Text Editor with gr.Column(scale=1): simplified_text = gr.Textbox( label="Full Simplified Text (Editable)", lines=15, placeholder="Simplified text will appear here after conversion..." ) # Right Side: Multi-Tab Image Interface with gr.Column(scale=1): with gr.Tabs() as image_tabs: # Tab 1: Symbol Library / Alternatives with gr.Tab("Symbol Library / Alternatives"): symbol_gallery = gr.Gallery( label="Available Symbols", show_label=True, elem_id="symbol_gallery", columns=3, rows=2, height="auto" ) # Tab 2: Generate with AI with gr.Tab("Generate with AI"): ai_prompt = gr.Textbox( label="Image Prompt", placeholder="Describe the image you want to generate...", lines=3 ) generate_ai_btn = gr.Button("Generate", variant="secondary") ai_generated_image = gr.Image( label="Generated Image", type="filepath" ) # Tab 3: Upload / Personal with gr.Tab("Upload / Personal"): personal_image_upload = gr.Image( label="Upload Your Image", sources=["upload"], type="filepath" ) # Tab 4: Favorites with gr.Tab("Favorites"): favorites_gallery = gr.Gallery( label="Saved Favorites", show_label=True, elem_id="favorites_gallery", columns=3, rows=2, height="auto" ) # ==================================================================== # INDIVIDUAL SENTENCE EDITOR # ==================================================================== gr.Markdown("## Individual Sentence Editor") gr.Markdown("*Edit each sentence separately below*") # Container for individual sentences sentence_editor_container = gr.Column() with sentence_editor_container: # Dynamic sentence textboxes will be created here sentence_textboxes = [] for i in range(10): # Pre-create 10 textboxes (will show/hide as needed) with gr.Row(visible=False) as sentence_row: sentence_num = gr.Markdown(f"**Sentence {i+1}:**") sentence_box = gr.Textbox( label="", lines=2, show_label=False, scale=4 ) sentence_textboxes.append((sentence_row, sentence_box, sentence_num)) # ==================================================================== # FOOTER / EXPORT SECTION # ==================================================================== gr.Markdown("## Export Options") with gr.Row(): export_pdf_btn = gr.Button("Export to PDF", variant="secondary") export_word_btn = gr.Button("Export to Word", variant="secondary") generate_audio_btn = gr.Button("Generate Audio", variant="secondary") # ==================================================================== # EVENT HANDLERS # ==================================================================== def update_sentence_display(sentences): """Updates the visibility and content of sentence textboxes""" updates = [] for i, (row, box, num) in enumerate(sentence_textboxes): if i < len(sentences): # Show this sentence updates.extend([ gr.update(visible=True), # row gr.update(value=sentences[i]), # textbox gr.update(value=f"**Sentence {i+1}:**") # label ]) else: # Hide this sentence updates.extend([ gr.update(visible=False), # row gr.update(value=""), # textbox gr.update(value="") # label ]) return updates def merge_sentences_to_full_text(*sentence_values): """Merges individual sentence values back into full text""" # Filter out empty sentences sentences = [s for s in sentence_values if s and s.strip()] return "\n".join(sentences) # Convert button handler def convert_handler(*args): simplified_text, symbols, sentences = create_convert_handler(*args) # Update sentence display sentence_updates = update_sentence_display(sentences) return [simplified_text, symbols, sentences] + sentence_updates # Collect all outputs for convert button convert_outputs = [simplified_text, symbol_gallery, sentences_state] for row, box, num in sentence_textboxes: convert_outputs.extend([row, box, num]) convert_btn.click( fn=convert_handler, inputs=[ pdf_upload, text_input, context_input, unalterable_terms_input, symbol_libraries ], outputs=convert_outputs ) # Update full text when any sentence is edited for row, box, num in sentence_textboxes: box.change( fn=merge_sentences_to_full_text, inputs=[b for r, b, n in sentence_textboxes], outputs=[simplified_text] ) # AI image generation handler generate_ai_btn.click( fn=create_ai_image_handler, inputs=[ai_prompt], outputs=[ai_generated_image] ) # Export handlers (placeholder) export_pdf_btn.click( fn=lambda t, imgs: gr.Info("PDF export - TODO: Implement backend"), inputs=[simplified_text, symbol_gallery], outputs=[] ) export_word_btn.click( fn=lambda t, imgs: gr.Info("Word export - TODO: Implement backend"), inputs=[simplified_text, symbol_gallery], outputs=[] ) generate_audio_btn.click( fn=lambda t: gr.Info("Audio generation - TODO: Implement backend"), inputs=[simplified_text], outputs=[] ) return app def main(): """Main entry point for the application.""" app = create_interface() app.launch( server_name="0.0.0.0", server_port=7860, share=False ) if __name__ == "__main__": main()