npc0 commited on
Commit
c447b37
·
verified ·
1 Parent(s): 00261f9

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +171 -368
app.py CHANGED
@@ -9,124 +9,35 @@ import openai
9
  import gradio as gr
10
  from epub2txt import epub2txt
11
 
12
- class GUI:
13
- def __init__(self, *args, **kwargs):
14
- # Configuration - restored your original settings
 
 
 
 
15
  self.model_name = os.getenv("POE_MODEL", "GPT-5-mini")
16
  self.prompt = os.getenv("prompt", "Summarize the following text:")
17
  self.client = None
18
- self.api_key = os.getenv("POE_API_KEY")
19
- self.current_user = None
20
  self.keys_file = "user_keys.json"
21
 
22
- with gr.Blocks(title="ePub Summarizer", css=self._get_css()) as demo:
23
- with gr.Row():
24
- welcome_md = gr.Markdown()
25
- # Updated for Gradio 5 - removed LogoutButton (LoginButton transforms automatically)
26
- login_btn = gr.LoginButton()
27
- refresh_btn = gr.Button("🔄 Refresh", size="sm", variant="secondary")
28
-
29
- # API Key input section (shown after login)
30
- api_key_section = gr.Column(visible=False)
31
- with api_key_section:
32
- gr.Markdown("""
33
- ### Poe API Key Setup
34
-
35
- To use this tool, you need a Poe API key:
36
-
37
- 1. Visit [https://poe.com/api_key](https://poe.com/api_key)
38
- 2. If you don't have an account, create one first
39
- 3. Generate a new API key or copy your existing one
40
- 4. Paste it in the field below
41
-
42
- **Security**: Your API key will be encrypted and tied to your login account.
43
- """)
44
- api_key_input = gr.Textbox(
45
- label="Poe API Key",
46
- placeholder="Enter your Poe API key here...",
47
- type="password"
48
- )
49
- with gr.Row():
50
- remember_key = gr.Checkbox(
51
- label="Remember my API key (encrypted storage)",
52
- value=True,
53
- info="Your key will be encrypted and saved for future sessions"
54
- )
55
- api_key_btn = gr.Button("Set API Key", variant="primary")
56
- clear_key_btn = gr.Button("Clear Saved Key", variant="secondary", visible=False)
57
-
58
- out = gr.Markdown()
59
- inp = gr.File(file_types=['.epub'], visible=False, label="Upload ePub File")
60
-
61
- # Event handlers - Fixed for Gradio 5
62
- demo.load(
63
- fn=self.on_load,
64
- outputs=[welcome_md, api_key_section, inp, clear_key_btn]
65
- )
66
-
67
- # Direct login button event handling
68
- login_btn.click(
69
- fn=self.handle_login_state_change,
70
- outputs=[welcome_md, api_key_section, inp, clear_key_btn]
71
- )
72
-
73
- # Manual refresh button for debugging
74
- refresh_btn.click(
75
- fn=self.handle_login_state_change,
76
- outputs=[welcome_md, api_key_section, inp, clear_key_btn]
77
- )
78
-
79
- api_key_btn.click(
80
- self.set_api_key,
81
- inputs=[api_key_input, remember_key],
82
- outputs=[out, inp, api_key_section, clear_key_btn]
83
- )
84
- clear_key_btn.click(
85
- self.clear_saved_key,
86
- outputs=[out, api_key_section, clear_key_btn, api_key_input]
87
- )
88
- # Your original ePub processing
89
- inp.change(self.process, inputs=[inp], outputs=[out])
90
-
91
- # Updated launch for Gradio 5
92
- demo.queue().launch(
93
- share=True,
94
- ssr_mode=False, # Disable SSR to avoid i18n issues
95
- show_error=True
96
- )
97
 
98
- def _get_css(self):
99
- """Custom CSS for better styling"""
100
- return """
101
- .gradio-container {
102
- max-width: 1200px !important;
103
- }
104
- .epub-processor {
105
- border: 2px dashed #ccc;
106
- border-radius: 10px;
107
- padding: 20px;
108
- text-align: center;
109
- }
110
- .progress-display {
111
- background: #f8f9fa;
112
- padding: 15px;
113
- border-radius: 8px;
114
- margin: 10px 0;
115
- }
116
- """
117
 
118
- def _get_user_id(self, request):
119
- """Generate a unique user ID from request - Updated for Gradio 5"""
120
- if not hasattr(request, 'username') or not request.username:
121
  return None
122
- # Use username as identifier
123
- identifier = request.username
124
- return hashlib.sha256(identifier.encode()).hexdigest()
125
 
126
  def _generate_key_from_user(self, user_id):
127
- """Generate encryption key from user ID"""
128
- # Use user ID as salt for key derivation
129
- salt = user_id.encode()[:32].ljust(32, b'0') # Ensure 32 bytes
130
  kdf = PBKDF2HMAC(
131
  algorithm=hashes.SHA256(),
132
  length=32,
@@ -137,254 +48,127 @@ class GUI:
137
  return Fernet(key)
138
 
139
  def _load_user_keys(self):
140
- """Load encrypted user keys from file"""
141
- try:
142
- if os.path.exists(self.keys_file):
143
- with open(self.keys_file, 'r') as f:
144
- return json.load(f)
145
- except Exception as e:
146
- print(f"Error loading user keys: {e}")
147
  return {}
148
 
149
  def _save_user_keys(self, keys_data):
150
- """Save encrypted user keys to file"""
151
- try:
152
- with open(self.keys_file, 'w') as f:
153
- json.dump(keys_data, f)
154
- except Exception as e:
155
- print(f"Error saving user keys: {e}")
156
 
157
- def _get_saved_key(self, user_id):
158
- """Retrieve and decrypt user's API key"""
159
- try:
160
- keys_data = self._load_user_keys()
161
- if user_id in keys_data:
162
- cipher_suite = self._generate_key_from_user(user_id)
163
- encrypted_key = base64.urlsafe_b64decode(keys_data[user_id].encode())
164
  return cipher_suite.decrypt(encrypted_key).decode()
165
- except Exception as e:
166
- print(f"Error retrieving saved key: {e}")
167
  return None
168
 
169
- def _save_encrypted_key(self, user_id, api_key):
170
- """Encrypt and save user's API key"""
171
- try:
172
- keys_data = self._load_user_keys()
173
- cipher_suite = self._generate_key_from_user(user_id)
174
- encrypted_key = cipher_suite.encrypt(api_key.encode())
175
- keys_data[user_id] = base64.urlsafe_b64encode(encrypted_key).decode()
176
- self._save_user_keys(keys_data)
177
- return True
178
- except Exception as e:
179
- print(f"Error saving encrypted key: {e}")
180
- return False
181
-
182
- def _delete_saved_key(self, user_id):
183
- """Delete user's saved API key"""
184
- try:
185
- keys_data = self._load_user_keys()
186
- if user_id in keys_data:
187
- del keys_data[user_id]
188
- self._save_user_keys(keys_data)
189
- return True
190
- except Exception as e:
191
- print(f"Error deleting saved key: {e}")
192
- return False
193
-
194
- def on_load(self, request: gr.Request = None):
195
- """Handle initial page load - Updated for Gradio 5"""
196
- print(f"Page load - Username: {getattr(request, 'username', None)}") # Debug
197
- return self.handle_login_state_change(request)
198
-
199
- def handle_login_state_change(self, request: gr.Request = None):
200
- """Handle login state changes - works for both load and login button click"""
201
- try:
202
- print(f"Login state check - Username: {getattr(request, 'username', None)}") # Debug
203
- print(f"Request attrs: {[attr for attr in dir(request) if not attr.startswith('_')]}") # Debug
204
-
205
- if hasattr(request, 'username') and request.username:
206
- return self.handle_user_logged_in(request)
207
- else:
208
- return self.handle_user_not_logged_in()
209
- except Exception as e:
210
- print(f"Error in login state change: {e}")
211
- return self.handle_user_not_logged_in()
212
-
213
- def check_login_state(self, request: gr.Request = None):
214
- """Check login state - with debug info"""
215
- return self.handle_login_state_change(request)
216
-
217
- def handle_user_not_logged_in(self):
218
- """Handle when user is not logged in"""
219
- return (
220
- gr.update(value='# ePub Summarization Tool\n\nPlease login with Hugging Face to access the tool.\n\n*If you just logged in, click the 🔄 Refresh button above.*'),
221
- gr.update(visible=False), # api_key_section
222
- gr.update(visible=False), # inp
223
- gr.update(visible=False) # clear_key_btn
224
- )
225
-
226
- def handle_user_logged_in(self, request):
227
- """Handle when user is logged in"""
228
- if not self.current_user or self.current_user != self._get_user_id(request):
229
- # New login or different user
230
- self.current_user = self._get_user_id(request)
231
- user_name = request.username
232
-
233
- # Check if user has a saved API key
234
- saved_key = self._get_saved_key(self.current_user)
235
- if saved_key:
236
- self.api_key = saved_key
237
- if self._initialize_client():
238
- return (
239
- gr.update(value=f'# ePub Summarization Tool\n\nWelcome back {user_name}! ✅ Your saved API key is loaded and ready.'),
240
- gr.update(visible=False), # api_key_section
241
- gr.update(visible=True), # inp
242
- gr.update(visible=True) # clear_key_btn
243
- )
244
-
245
- # No saved key or failed to initialize
246
- return (
247
- gr.update(value=f'# ePub Summarization Tool\n\nWelcome {user_name}! Please set up your Poe API key below.'),
248
- gr.update(visible=True), # api_key_section
249
- gr.update(visible=False), # inp
250
- gr.update(visible=False) # clear_key_btn
251
- )
252
-
253
- # User already processed, return current state
254
- if self.client:
255
- return (
256
- gr.update(value=f'# ePub Summarization Tool\n\nWelcome {request.username}! ✅ Ready to process ePub files.'),
257
- gr.update(visible=False), # api_key_section
258
- gr.update(visible=True), # inp
259
- gr.update(visible=True) # clear_key_btn
260
- )
261
- else:
262
- return (
263
- gr.update(value=f'# ePub Summarization Tool\n\nWelcome {request.username}! Please set up your Poe API key below.'),
264
- gr.update(visible=True), # api_key_section
265
- gr.update(visible=False), # inp
266
- gr.update(visible=False) # clear_key_btn
267
- )
268
 
 
 
 
 
 
 
 
 
269
  def _initialize_client(self):
270
- """Initialize the Poe API client"""
 
 
271
  try:
272
  self.client = openai.OpenAI(
273
  api_key=self.api_key,
274
  base_url="https://api.poe.com/v1",
275
  )
276
- return True
 
277
  except Exception as e:
278
- print(f"Error initializing Poe client: {e}")
279
  return False
280
 
 
 
 
 
 
 
 
281
  def set_api_key(self, api_key, remember_key):
282
- """Set and validate the API key"""
283
- if not self.current_user:
284
- return (
285
- gr.update(value="❌ Please login first."),
286
- gr.update(visible=False),
287
- gr.update(visible=True),
288
- gr.update(visible=False)
289
- )
290
-
291
  if not api_key or not api_key.strip():
292
  return (
293
- gr.update(value="⚠️ Please enter a valid API key."),
294
  gr.update(visible=False),
295
  gr.update(visible=True),
296
- gr.update(visible=False)
297
  )
298
 
299
  self.api_key = api_key.strip()
300
 
301
  if self._initialize_client():
302
- # Test the API key with a simple request
303
  try:
304
- test_chat = self.client.chat.completions.create(
305
- model=self.model_name,
306
- messages=[{"role": "user", "content": "Hello"}],
307
- max_tokens=10
308
  )
309
 
310
- # Save key if user wants to remember it
311
  if remember_key:
312
- if self._save_encrypted_key(self.current_user, self.api_key):
313
- success_msg = "✅ API key validated and saved successfully! You can now upload an ePub file."
314
- else:
315
- success_msg = "✅ API key validated successfully! (Note: Failed to save for future use)"
316
  else:
317
- success_msg = "✅ API key validated successfully! You can now upload an ePub file."
318
-
 
319
  return (
320
- gr.update(value=success_msg),
321
- gr.update(visible=True), # inp
322
- gr.update(visible=False), # api_key_section
323
- gr.update(visible=remember_key) # clear_key_btn
324
  )
325
  except Exception as e:
 
326
  error_msg = str(e)
327
- if "401" in error_msg or "unauthorized" in error_msg.lower():
328
- return (
329
- gr.update(value="❌ Invalid API key. Please check your key and try again."),
330
- gr.update(visible=False),
331
- gr.update(visible=True),
332
- gr.update(visible=False)
333
- )
334
- elif "quota" in error_msg.lower() or "limit" in error_msg.lower():
335
- return (
336
- gr.update(value="⚠️ API key valid but quota exceeded. Please check your Poe account."),
337
- gr.update(visible=False),
338
- gr.update(visible=True),
339
- gr.update(visible=False)
340
- )
341
- else:
342
- return (
343
- gr.update(value=f"❌ API connection error: {error_msg}"),
344
- gr.update(visible=False),
345
- gr.update(visible=True),
346
- gr.update(visible=False)
347
- )
348
  else:
349
- return (
350
- gr.update(value="❌ Failed to initialize API client."),
351
- gr.update(visible=False),
352
- gr.update(visible=True),
353
- gr.update(visible=False)
354
- )
355
 
356
  def clear_saved_key(self):
357
- """Clear the user's saved API key"""
358
- if not self.current_user:
359
- return (
360
- gr.update(value="❌ No user logged in."),
361
- gr.update(visible=False),
362
- gr.update(visible=False),
363
- gr.update(value="")
364
- )
365
-
366
- if self._delete_saved_key(self.current_user):
367
- self.api_key = None
368
- self.client = None
369
- return (
370
- gr.update(value="✅ Saved API key cleared. Please enter your API key below."),
371
- gr.update(visible=True), # api_key_section
372
- gr.update(visible=False), # clear_key_btn
373
- gr.update(value="") # clear input
374
- )
375
- else:
376
- return (
377
- gr.update(value="⚠️ Failed to clear saved key."),
378
- gr.update(visible=True),
379
- gr.update(visible=True),
380
- gr.update(value="")
381
- )
382
-
383
  def get_model_response(self, text: str) -> str:
384
- """Get response from Poe API for the given text - Your original function"""
385
  if not self.client:
386
- return "Error: API client not initialized"
387
-
388
  try:
389
  chat = self.client.chat.completions.create(
390
  model=self.model_name,
@@ -392,85 +176,50 @@ class GUI:
392
  )
393
  return chat.choices[0].message.content
394
  except Exception as e:
395
- print(f"Error calling Poe API: {e}")
396
- return f"Error processing text: {str(e)}"
397
-
398
- def process(self, file, request: gr.Request = None):
399
- """Your original ePub processing workflow - restored completely"""
400
- if not hasattr(request, 'username') or not request.username:
401
- return gr.update(value='⚠️ Please login to access the tool.')
402
 
 
 
403
  if not self.client:
404
- return gr.update(value='⚠️ Please set your Poe API key first.')
405
-
406
  if file is None:
407
- return gr.update(value='Please upload an ePub file.')
 
408
 
409
  try:
410
- # Extract content from ePub - Your original workflow
411
  ch_list = epub2txt(file.name, outputlist=True)
412
  chapter_titles = epub2txt.content_titles
413
  title = epub2txt.title
414
 
415
  yield gr.update(value=f"# {title}\n\nProcessing ePub file...")
416
-
417
  sm_list = []
418
 
419
- # Process each chapter (skip first 2 as they're usually metadata) - Your original logic
420
  for idx, text in enumerate(ch_list[2:], 1):
421
- if not text.strip():
422
- continue
423
-
424
  yield gr.update(value=f"# {title}\n\nProcessing chapter {idx}...")
425
 
426
  docs = []
427
- # Split chapter into chunks for processing - Your original chunking strategy
428
  chunk_size = 2000
429
  for i in range(0, len(text), chunk_size):
430
- chunk = text[i:i+2048] # Slight overlap for context
431
  if len(chunk.strip()) > 0:
432
  response = self.get_model_response(self.prompt + "\n\n" + chunk)
433
  docs.append(response)
434
-
435
- # Update UI with current progress
436
- current_summaries = "\n\n".join([
437
- f"## {ct}\n\n{sm}"
438
- for ct, sm in zip(chapter_titles[2:idx+1], sm_list + [f"Processing chunk {len(docs)}..."])
439
- ])
440
- yield gr.update(value=f"# {title}\n\n{current_summaries}")
441
 
442
- # Combine chunk summaries into chapter summary - Your original combining logic
443
  if docs:
444
- if len(docs) == 1:
445
- hist = docs[0]
446
- else:
447
- hist = docs[0]
448
- for doc in docs[1:]:
449
- combined_text = f"{self.prompt}\n\nCombine these summaries:\n\n{hist}\n\n{doc}"
450
- hist = self.get_model_response(combined_text)
451
-
452
- # Update UI with draft summary
453
- current_summaries = "\n\n".join([
454
- f"## {ct}\n\n{sm}"
455
- for ct, sm in zip(chapter_titles[2:idx+1], sm_list + [f"Draft: {hist}"])
456
- ])
457
- yield gr.update(value=f"# {title}\n\n{current_summaries}")
458
-
459
  sm_list.append(hist)
460
 
461
- # Update final output for this chapter
462
- final_summaries = "\n\n".join([
463
- f"## {ct}\n\n{sm}"
464
- for ct, sm in zip(chapter_titles[2:idx+1], sm_list)
465
- ])
466
  yield gr.update(value=f"# {title}\n\n{final_summaries}")
467
 
468
- # Final complete summary - Your original final output
469
  if sm_list:
470
- complete_summary = f"# {title}\n\n" + "\n\n".join([
471
- f"## {ct}\n\n{sm}"
472
- for ct, sm in zip(chapter_titles[2:len(sm_list)+2], sm_list)
473
- ])
474
  yield gr.update(value=complete_summary)
475
  else:
476
  yield gr.update(value=f"# {title}\n\nNo content found to summarize.")
@@ -478,6 +227,60 @@ class GUI:
478
  except Exception as e:
479
  yield gr.update(value=f"Error processing file: {str(e)}")
480
 
481
- # Run the application
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
482
  if __name__ == "__main__":
483
- GUI()
 
 
 
 
 
 
 
 
 
 
9
  import gradio as gr
10
  from epub2txt import epub2txt
11
 
12
+ class AppLogic:
13
+ """
14
+ This class now ONLY contains the application logic (handling keys, processing files).
15
+ The UI definition has been moved into the `build_ui` function.
16
+ """
17
+ def __init__(self, request: gr.Request):
18
+ # Configuration
19
  self.model_name = os.getenv("POE_MODEL", "GPT-5-mini")
20
  self.prompt = os.getenv("prompt", "Summarize the following text:")
21
  self.client = None
22
+ self.api_key = None
 
23
  self.keys_file = "user_keys.json"
24
 
25
+ # User-specific properties derived from the request
26
+ self.username = request.username
27
+ self.user_id = self._get_user_id(self.username)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
 
29
+ # Initialize by loading the saved key, if it exists
30
+ self.load_and_initialize_saved_key()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
 
32
+ def _get_user_id(self, username):
33
+ """Generate a unique user ID from the username."""
34
+ if not username:
35
  return None
36
+ return hashlib.sha256(username.encode()).hexdigest()
 
 
37
 
38
  def _generate_key_from_user(self, user_id):
39
+ """Generate encryption key from user ID."""
40
+ salt = user_id.encode()[:32].ljust(32, b'0')
 
41
  kdf = PBKDF2HMAC(
42
  algorithm=hashes.SHA256(),
43
  length=32,
 
48
  return Fernet(key)
49
 
50
  def _load_user_keys(self):
51
+ """Load encrypted user keys from file."""
52
+ if os.path.exists(self.keys_file):
53
+ with open(self.keys_file, 'r') as f:
54
+ return json.load(f)
 
 
 
55
  return {}
56
 
57
  def _save_user_keys(self, keys_data):
58
+ """Save encrypted user keys to file."""
59
+ with open(self.keys_file, 'w') as f:
60
+ json.dump(keys_data, f)
 
 
 
61
 
62
+ def _get_saved_key(self):
63
+ """Retrieve and decrypt user's API key."""
64
+ keys_data = self._load_user_keys()
65
+ if self.user_id in keys_data:
66
+ try:
67
+ cipher_suite = self._generate_key_from_user(self.user_id)
68
+ encrypted_key = base64.urlsafe_b64decode(keys_data[self.user_id].encode())
69
  return cipher_suite.decrypt(encrypted_key).decode()
70
+ except Exception as e:
71
+ print(f"Error retrieving saved key for {self.username}: {e}")
72
  return None
73
 
74
+ def _save_encrypted_key(self, api_key):
75
+ """Encrypt and save user's API key."""
76
+ keys_data = self._load_user_keys()
77
+ cipher_suite = self._generate_key_from_user(self.user_id)
78
+ encrypted_key = cipher_suite.encrypt(api_key.encode())
79
+ keys_data[self.user_id] = base64.urlsafe_b64encode(encrypted_key).decode()
80
+ self._save_user_keys(keys_data)
81
+ return True
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82
 
83
+ def _delete_saved_key(self):
84
+ """Delete user's saved API key."""
85
+ keys_data = self._load_user_keys()
86
+ if self.user_id in keys_data:
87
+ del keys_data[self.user_id]
88
+ self._save_user_keys(keys_data)
89
+ return True
90
+
91
  def _initialize_client(self):
92
+ """Initialize the Poe API client if an API key is present."""
93
+ if not self.api_key:
94
+ return False
95
  try:
96
  self.client = openai.OpenAI(
97
  api_key=self.api_key,
98
  base_url="https://api.poe.com/v1",
99
  )
100
+ # A quick check to see if the client is functional without making a call
101
+ return hasattr(self.client, 'chat')
102
  except Exception as e:
103
+ print(f"Error initializing Poe client for {self.username}: {e}")
104
  return False
105
 
106
+ def load_and_initialize_saved_key(self):
107
+ """Checks for a saved key and initializes the client if found."""
108
+ saved_key = self._get_saved_key()
109
+ if saved_key:
110
+ self.api_key = saved_key
111
+ self._initialize_client()
112
+
113
  def set_api_key(self, api_key, remember_key):
114
+ """Set and validate the API key."""
 
 
 
 
 
 
 
 
115
  if not api_key or not api_key.strip():
116
  return (
117
+ gr.update(value="⚠️ Please enter a valid API key."),
118
  gr.update(visible=False),
119
  gr.update(visible=True),
120
+ gr.update(visible=False),
121
  )
122
 
123
  self.api_key = api_key.strip()
124
 
125
  if self._initialize_client():
 
126
  try:
127
+ # Test the API key with a simple request
128
+ self.client.chat.completions.create(
129
+ model=self.model_name, messages=[{"role": "user", "content": "Hello"}], max_tokens=10
 
130
  )
131
 
 
132
  if remember_key:
133
+ self._save_encrypted_key(self.api_key)
134
+ msg = "✅ API key validated and saved! You can now upload an ePub file."
 
 
135
  else:
136
+ self._delete_saved_key() # Ensure no old key is lingering if user unchecks
137
+ msg = "✅ API key validated for this session! You can now upload an ePub file."
138
+
139
  return (
140
+ gr.update(value=msg),
141
+ gr.update(visible=True), # Show file input
142
+ gr.update(visible=False), # Hide API key section
143
+ gr.update(visible=remember_key), # Show clear button only if saved
144
  )
145
  except Exception as e:
146
+ self.client = None # Reset client on failure
147
  error_msg = str(e)
148
+ if "401" in error_msg:
149
+ return gr.update(value="❌ Invalid API key. Please check it and try again."), gr.update(), gr.update(), gr.update()
150
+ return gr.update(value=f"❌ API connection error: {error_msg}"), gr.update(), gr.update(), gr.update()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
151
  else:
152
+ return gr.update(value="❌ Failed to initialize API client."), gr.update(), gr.update(), gr.update()
 
 
 
 
 
153
 
154
  def clear_saved_key(self):
155
+ """Clear the user's saved API key."""
156
+ self._delete_saved_key()
157
+ self.api_key = None
158
+ self.client = None
159
+ return (
160
+ gr.update(value="✅ Saved API key cleared. Please enter your API key to continue."),
161
+ gr.update(visible=True), # Show API key section
162
+ gr.update(visible=False), # Hide clear button
163
+ gr.update(value=""), # Clear API key input field
164
+ gr.update(visible=False), # Hide file input
165
+ )
166
+
167
+ # ... (get_model_response and process methods are unchanged) ...
 
 
 
 
 
 
 
 
 
 
 
 
 
168
  def get_model_response(self, text: str) -> str:
169
+ """Get response from Poe API for the given text."""
170
  if not self.client:
171
+ return "Error: API client not initialized. Please set your API key."
 
172
  try:
173
  chat = self.client.chat.completions.create(
174
  model=self.model_name,
 
176
  )
177
  return chat.choices[0].message.content
178
  except Exception as e:
179
+ return f"Error calling Poe API: {str(e)}"
 
 
 
 
 
 
180
 
181
+ def process(self, file):
182
+ """Processes the uploaded ePub file."""
183
  if not self.client:
184
+ yield gr.update(value='⚠️ Please set your Poe API key first.')
185
+ return
186
  if file is None:
187
+ yield gr.update(value='Please upload an ePub file.')
188
+ return
189
 
190
  try:
 
191
  ch_list = epub2txt(file.name, outputlist=True)
192
  chapter_titles = epub2txt.content_titles
193
  title = epub2txt.title
194
 
195
  yield gr.update(value=f"# {title}\n\nProcessing ePub file...")
 
196
  sm_list = []
197
 
198
+ # Skip first 2 chapters (usually metadata)
199
  for idx, text in enumerate(ch_list[2:], 1):
200
+ if not text.strip(): continue
 
 
201
  yield gr.update(value=f"# {title}\n\nProcessing chapter {idx}...")
202
 
203
  docs = []
 
204
  chunk_size = 2000
205
  for i in range(0, len(text), chunk_size):
206
+ chunk = text[i:i + 2048]
207
  if len(chunk.strip()) > 0:
208
  response = self.get_model_response(self.prompt + "\n\n" + chunk)
209
  docs.append(response)
 
 
 
 
 
 
 
210
 
 
211
  if docs:
212
+ hist = docs[0]
213
+ for doc in docs[1:]:
214
+ combined_text = f"{self.prompt}\n\nCombine these summaries:\n\n{hist}\n\n{doc}"
215
+ hist = self.get_model_response(combined_text)
 
 
 
 
 
 
 
 
 
 
 
216
  sm_list.append(hist)
217
 
218
+ final_summaries = "\n\n".join([f"## {ct}\n\n{sm}" for ct, sm in zip(chapter_titles[2:idx+1], sm_list)])
 
 
 
 
219
  yield gr.update(value=f"# {title}\n\n{final_summaries}")
220
 
 
221
  if sm_list:
222
+ 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)])
 
 
 
223
  yield gr.update(value=complete_summary)
224
  else:
225
  yield gr.update(value=f"# {title}\n\nNo content found to summarize.")
 
227
  except Exception as e:
228
  yield gr.update(value=f"Error processing file: {str(e)}")
229
 
230
+ # --- UI Building Function ---
231
+ def build_ui(request: gr.Request):
232
+ """
233
+ This function is the main entry point for the UI.
234
+ It's only called AFTER the user has logged in.
235
+ """
236
+ # Instantiate the logic class for this specific user session
237
+ logic = AppLogic(request)
238
+
239
+ # Determine the initial state based on whether a key was loaded
240
+ has_saved_key = logic.client is not None
241
+
242
+ welcome_message = (f"# ePub Summarization Tool\n\nWelcome back, **{logic.username}**! "
243
+ "✅ Your saved API key is loaded and ready.") if has_saved_key else \
244
+ (f"# ePub Summarization Tool\n\nWelcome, **{logic.username}**! "
245
+ "Please set up your Poe API key to begin.")
246
+
247
+ with gr.Blocks(title="ePub Summarizer") as demo:
248
+ gr.Markdown(welcome_message)
249
+
250
+ # API Key input section
251
+ with gr.Column(visible=not has_saved_key) as api_key_section:
252
+ api_key_input = gr.Textbox(label="Poe API Key", type="password")
253
+ remember_key = gr.Checkbox(label="Remember my API key", value=True)
254
+ api_key_btn = gr.Button("Set API Key", variant="primary")
255
+
256
+ # Main content area
257
+ out = gr.Markdown()
258
+ inp = gr.File(file_types=['.epub'], visible=has_saved_key, label="Upload ePub File")
259
+ clear_key_btn = gr.Button("Clear Saved Key", variant="secondary", visible=has_saved_key)
260
+
261
+ # --- Event Handlers ---
262
+ api_key_btn.click(
263
+ logic.set_api_key,
264
+ inputs=[api_key_input, remember_key],
265
+ outputs=[out, inp, api_key_section, clear_key_btn]
266
+ )
267
+ clear_key_btn.click(
268
+ logic.clear_saved_key,
269
+ outputs=[out, api_key_section, clear_key_btn, api_key_input, inp]
270
+ )
271
+ inp.change(logic.process, inputs=[inp], outputs=[out])
272
+
273
+ return demo
274
+
275
+ # --- Main Application Execution ---
276
  if __name__ == "__main__":
277
+ # This dummy function is for local testing. On Hugging Face, it's ignored.
278
+ def auth(username, password):
279
+ return True
280
+
281
+ # Use gr.mount_gradio_app for FastAPI integration if needed, or just launch directly
282
+ with gr.Blocks() as demo:
283
+ # The auth() method gates the UI. build_ui is only called after successful login.
284
+ demo.auth(build_ui, auth)
285
+
286
+ demo.queue().launch(show_error=True)