import os import openai import gradio as gr from epub2txt import epub2txt class GUI: def __init__(self, *args, **kwargs): # Configuration self.model_name = os.getenv("POE_MODEL", "GPT-5-mini") self.prompt = os.getenv("prompt", "Summarize the following text:") self.client = None self.api_key = os.getenv("POE_API_KEY") with gr.Blocks(title="ePub Summarizer") as demo: with gr.Row(): gr.Markdown(scale=2).attach_load_event(self.hello, None) gr.LoginButton() gr.LogoutButton() # API Key input section with gr.Row(visible=not self.api_key): with gr.Column(): gr.Markdown(""" ### Poe API Key Required To use this tool, you need a Poe API key: 1. Visit [https://poe.com/api_key](https://poe.com/api_key) 2. If you don't have an account, create one first 3. Generate a new API key or copy your existing one 4. Paste it in the field below **Note**: Your API key is only stored temporarily during this session for security. """) api_key_input = gr.Textbox( label="Poe API Key", placeholder="Enter your Poe API key here...", type="password" ) api_key_btn = gr.Button("Set API Key", variant="primary") out = gr.Markdown() inp = gr.File(file_types=['.epub'], visible=bool(self.api_key)) # Set up event handlers if not self.api_key: api_key_btn.click( self.set_api_key, inputs=[api_key_input], outputs=[out, inp] ) else: self._initialize_client() inp.change(self.process, inp, out) demo.queue(concurrency_count=2).launch() def _initialize_client(self): """Initialize the Poe API client""" try: self.client = openai.OpenAI( api_key=self.api_key, base_url="https://api.poe.com/v1", ) return True except Exception as e: print(f"Error initializing Poe client: {e}") return False def set_api_key(self, api_key): """Set and validate the API key""" if not api_key or not api_key.strip(): return gr.update(value="⚠️ Please enter a valid API key."), gr.update(visible=False) self.api_key = api_key.strip() if self._initialize_client(): # Test the API key with a simple request try: test_chat = self.client.chat.completions.create( model=self.model_name, messages=[{"role": "user", "content": "Hello"}], max_tokens=10 ) return ( gr.update(value="✅ API key validated successfully! You can now upload an ePub file."), gr.update(visible=True) ) except Exception as e: error_msg = str(e) if "401" in error_msg or "unauthorized" in error_msg.lower(): return ( gr.update(value="❌ Invalid API key. Please check your key and try again."), gr.update(visible=False) ) elif "quota" in error_msg.lower() or "limit" in error_msg.lower(): return ( gr.update(value="⚠️ API key valid but quota exceeded. Please check your Poe account."), gr.update(visible=False) ) else: return ( gr.update(value=f"❌ API connection error: {error_msg}"), gr.update(visible=False) ) else: return ( gr.update(value="❌ Failed to initialize API client."), gr.update(visible=False) ) def get_model_response(self, text: str) -> str: """ Get response from Poe API for the given text """ if not self.client: return "Error: API client not initialized" try: chat = self.client.chat.completions.create( model=self.model_name, messages=[{"role": "user", "content": text}], ) return chat.choices[0].message.content except Exception as e: print(f"Error calling Poe API: {e}") return f"Error processing text: {str(e)}" def process(self, file, profile: gr.OAuthProfile | None = None): if profile is None: return gr.update(value='Login to access the tool.') if not self.client: return gr.update(value='⚠️ Please set your Poe API key first.') if file is None: return gr.update(value='Please upload an ePub file.') try: # Extract content from ePub ch_list = epub2txt(file.name, outputlist=True) chapter_titles = epub2txt.content_titles title = epub2txt.title yield gr.update(value=f"# {title}\n\nProcessing ePub file...") sm_list = [] # Process each chapter (skip first 2 as they're usually metadata) for idx, text in enumerate(ch_list[2:], 1): if not text.strip(): continue yield gr.update(value=f"# {title}\n\nProcessing chapter {idx}...") docs = [] # Split chapter into chunks for processing chunk_size = 2000 for i in range(0, len(text), chunk_size): chunk = text[i:i+2048] # Slight overlap for context if len(chunk.strip()) > 0: response = self.get_model_response(self.prompt + "\n\n" + chunk) docs.append(response) # Update UI with current progress current_summaries = "\n\n".join([ f"## {ct}\n\n{sm}" for ct, sm in zip(chapter_titles[2:idx+1], sm_list + [f"Processing chunk {len(docs)}..."]) ]) yield gr.update(value=f"# {title}\n\n{current_summaries}") # Combine chunk summaries into chapter summary if docs: if len(docs) == 1: hist = docs[0] else: hist = docs[0] for doc in docs[1:]: combined_text = f"{self.prompt}\n\nCombine these summaries:\n\n{hist}\n\n{doc}" hist = self.get_model_response(combined_text) # Update UI with draft summary current_summaries = "\n\n".join([ f"## {ct}\n\n{sm}" for ct, sm in zip(chapter_titles[2:idx+1], sm_list + [f"Draft: {hist}"]) ]) yield gr.update(value=f"# {title}\n\n{current_summaries}") sm_list.append(hist) # Update final output for this chapter final_summaries = "\n\n".join([ f"## {ct}\n\n{sm}" for ct, sm in zip(chapter_titles[2:idx+1], sm_list) ]) yield gr.update(value=f"# {title}\n\n{final_summaries}") # Final complete summary if sm_list: complete_summary = f"# {title}\n\n" + "\n\n".join([ f"## {ct}\n\n{sm}" for ct, sm in zip(chapter_titles[2:len(sm_list)+2], sm_list) ]) yield gr.update(value=complete_summary) else: yield gr.update(value=f"# {title}\n\nNo content found to summarize.") except Exception as e: yield gr.update(value=f"Error processing file: {str(e)}") def hello(self, profile: gr.OAuthProfile | None = None): if profile is None: return '# ePub Summarization Tool\n\nLogin to access the tool.' if not self.api_key: return f"# ePub Summarization Tool\n\nWelcome {profile.name}!\n\nPlease set your Poe API key below to get started." return f"# ePub Summarization Tool\n\nWelcome {profile.name}! Ready to summarize ePub files." # Run the application if __name__ == "__main__": GUI()