npc0 commited on
Commit
0612cdd
Β·
verified Β·
1 Parent(s): aaef75c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +244 -47
app.py CHANGED
@@ -1,4 +1,10 @@
1
  import os
 
 
 
 
 
 
2
  import openai
3
  import gradio as gr
4
  from epub2txt import epub2txt
@@ -10,52 +16,192 @@ class GUI:
10
  self.prompt = os.getenv("prompt", "Summarize the following text:")
11
  self.client = None
12
  self.api_key = os.getenv("POE_API_KEY")
 
 
13
 
14
  with gr.Blocks(title="ePub Summarizer") as demo:
15
  with gr.Row():
16
- gr.Markdown(scale=2).attach_load_event(self.hello, None)
17
- gr.LoginButton()
18
- gr.LogoutButton()
19
 
20
- # API Key input section
21
- with gr.Row(visible=not self.api_key):
22
- with gr.Column():
23
- gr.Markdown("""
24
- ### Poe API Key Required
25
-
26
- To use this tool, you need a Poe API key:
27
-
28
- 1. Visit [https://poe.com/api_key](https://poe.com/api_key)
29
- 2. If you don't have an account, create one first
30
- 3. Generate a new API key or copy your existing one
31
- 4. Paste it in the field below
32
-
33
- **Note**: Your API key is only stored temporarily during this session for security.
34
- """)
35
- api_key_input = gr.Textbox(
36
- label="Poe API Key",
37
- placeholder="Enter your Poe API key here...",
38
- type="password"
 
 
 
 
 
 
39
  )
40
  api_key_btn = gr.Button("Set API Key", variant="primary")
 
41
 
42
  out = gr.Markdown()
43
- inp = gr.File(file_types=['.epub'], visible=bool(self.api_key))
44
-
45
- # Set up event handlers
46
- if not self.api_key:
47
- api_key_btn.click(
48
- self.set_api_key,
49
- inputs=[api_key_input],
50
- outputs=[out, inp]
51
- )
52
- else:
53
- self._initialize_client()
54
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
  inp.change(self.process, inp, out)
56
 
57
  demo.queue(concurrency_count=2).launch()
58
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
  def _initialize_client(self):
60
  """Initialize the Poe API client"""
61
  try:
@@ -68,10 +214,23 @@ class GUI:
68
  print(f"Error initializing Poe client: {e}")
69
  return False
70
 
71
- def set_api_key(self, api_key):
72
  """Set and validate the API key"""
 
 
 
 
 
 
 
 
73
  if not api_key or not api_key.strip():
74
- return gr.update(value="⚠️ Please enter a valid API key."), gr.update(visible=False)
 
 
 
 
 
75
 
76
  self.api_key = api_key.strip()
77
 
@@ -83,33 +242,80 @@ class GUI:
83
  messages=[{"role": "user", "content": "Hello"}],
84
  max_tokens=10
85
  )
 
 
 
 
 
 
 
 
 
 
86
  return (
87
- gr.update(value="βœ… API key validated successfully! You can now upload an ePub file."),
88
- gr.update(visible=True)
 
 
89
  )
90
  except Exception as e:
91
  error_msg = str(e)
92
  if "401" in error_msg or "unauthorized" in error_msg.lower():
93
  return (
94
  gr.update(value="❌ Invalid API key. Please check your key and try again."),
 
 
95
  gr.update(visible=False)
96
  )
97
  elif "quota" in error_msg.lower() or "limit" in error_msg.lower():
98
  return (
99
  gr.update(value="⚠️ API key valid but quota exceeded. Please check your Poe account."),
 
 
100
  gr.update(visible=False)
101
  )
102
  else:
103
  return (
104
  gr.update(value=f"❌ API connection error: {error_msg}"),
 
 
105
  gr.update(visible=False)
106
  )
107
  else:
108
  return (
109
  gr.update(value="❌ Failed to initialize API client."),
 
 
110
  gr.update(visible=False)
111
  )
112
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
113
  def get_model_response(self, text: str) -> str:
114
  """
115
  Get response from Poe API for the given text
@@ -129,7 +335,7 @@ class GUI:
129
 
130
  def process(self, file, profile: gr.OAuthProfile | None = None):
131
  if profile is None:
132
- return gr.update(value='Login to access the tool.')
133
 
134
  if not self.client:
135
  return gr.update(value='⚠️ Please set your Poe API key first.')
@@ -209,15 +415,6 @@ class GUI:
209
  except Exception as e:
210
  yield gr.update(value=f"Error processing file: {str(e)}")
211
 
212
- def hello(self, profile: gr.OAuthProfile | None = None):
213
- if profile is None:
214
- return '# ePub Summarization Tool\n\nLogin to access the tool.'
215
-
216
- if not self.api_key:
217
- return f"# ePub Summarization Tool\n\nWelcome {profile.name}!\n\nPlease set your Poe API key below to get started."
218
-
219
- return f"# ePub Summarization Tool\n\nWelcome {profile.name}! Ready to summarize ePub files."
220
-
221
  # Run the application
222
  if __name__ == "__main__":
223
  GUI()
 
1
  import os
2
+ import json
3
+ import hashlib
4
+ from cryptography.fernet import Fernet
5
+ from cryptography.hazmat.primitives import hashes
6
+ from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
7
+ import base64
8
  import openai
9
  import gradio as gr
10
  from epub2txt import epub2txt
 
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") as demo:
23
  with gr.Row():
24
+ welcome_md = gr.Markdown(scale=2)
25
+ login_btn = gr.LoginButton()
26
+ logout_btn = gr.LogoutButton()
27
 
28
+ # API Key input section (shown after login)
29
+ api_key_section = gr.Column(visible=False)
30
+ with api_key_section:
31
+ gr.Markdown("""
32
+ ### Poe API Key Setup
33
+
34
+ To use this tool, you need a Poe API key:
35
+
36
+ 1. Visit [https://poe.com/api_key](https://poe.com/api_key)
37
+ 2. If you don't have an account, create one first
38
+ 3. Generate a new API key or copy your existing one
39
+ 4. Paste it in the field below
40
+
41
+ **Security**: Your API key will be encrypted and tied to your login account.
42
+ """)
43
+ api_key_input = gr.Textbox(
44
+ label="Poe API Key",
45
+ placeholder="Enter your Poe API key here...",
46
+ type="password"
47
+ )
48
+ with gr.Row():
49
+ remember_key = gr.Checkbox(
50
+ label="Remember my API key (encrypted storage)",
51
+ value=True,
52
+ info="Your key will be encrypted and saved for future sessions"
53
  )
54
  api_key_btn = gr.Button("Set API Key", variant="primary")
55
+ clear_key_btn = gr.Button("Clear Saved Key", variant="secondary", visible=False)
56
 
57
  out = gr.Markdown()
58
+ inp = gr.File(file_types=['.epub'], visible=False)
 
 
 
 
 
 
 
 
 
 
59
 
60
+ # Event handlers
61
+ welcome_md.attach_load_event(self.on_load, None, [welcome_md, api_key_section, inp, clear_key_btn])
62
+ login_btn.login(self.on_login, None, [welcome_md, api_key_section, inp, clear_key_btn])
63
+ logout_btn.logout(self.on_logout, None, [welcome_md, api_key_section, inp, clear_key_btn])
64
+ api_key_btn.click(
65
+ self.set_api_key,
66
+ inputs=[api_key_input, remember_key],
67
+ outputs=[out, inp, api_key_section, clear_key_btn]
68
+ )
69
+ clear_key_btn.click(
70
+ self.clear_saved_key,
71
+ outputs=[out, api_key_section, clear_key_btn, api_key_input]
72
+ )
73
  inp.change(self.process, inp, out)
74
 
75
  demo.queue(concurrency_count=2).launch()
76
 
77
+ def _get_user_id(self, profile):
78
+ """Generate a unique user ID from profile"""
79
+ if not profile:
80
+ return None
81
+ # Use email as primary identifier, fallback to username
82
+ identifier = profile.email if hasattr(profile, 'email') and profile.email else profile.username
83
+ return hashlib.sha256(identifier.encode()).hexdigest()
84
+
85
+ def _generate_key_from_user(self, user_id):
86
+ """Generate encryption key from user ID"""
87
+ # Use user ID as salt for key derivation
88
+ salt = user_id.encode()[:32].ljust(32, b'0') # Ensure 32 bytes
89
+ kdf = PBKDF2HMAC(
90
+ algorithm=hashes.SHA256(),
91
+ length=32,
92
+ salt=salt,
93
+ iterations=100000,
94
+ )
95
+ key = base64.urlsafe_b64encode(kdf.derive(user_id.encode()))
96
+ return Fernet(key)
97
+
98
+ def _load_user_keys(self):
99
+ """Load encrypted user keys from file"""
100
+ try:
101
+ if os.path.exists(self.keys_file):
102
+ with open(self.keys_file, 'r') as f:
103
+ return json.load(f)
104
+ except Exception as e:
105
+ print(f"Error loading user keys: {e}")
106
+ return {}
107
+
108
+ def _save_user_keys(self, keys_data):
109
+ """Save encrypted user keys to file"""
110
+ try:
111
+ with open(self.keys_file, 'w') as f:
112
+ json.dump(keys_data, f)
113
+ except Exception as e:
114
+ print(f"Error saving user keys: {e}")
115
+
116
+ def _get_saved_key(self, user_id):
117
+ """Retrieve and decrypt user's API key"""
118
+ try:
119
+ keys_data = self._load_user_keys()
120
+ if user_id in keys_data:
121
+ cipher_suite = self._generate_key_from_user(user_id)
122
+ encrypted_key = base64.urlsafe_b64decode(keys_data[user_id].encode())
123
+ return cipher_suite.decrypt(encrypted_key).decode()
124
+ except Exception as e:
125
+ print(f"Error retrieving saved key: {e}")
126
+ return None
127
+
128
+ def _save_encrypted_key(self, user_id, api_key):
129
+ """Encrypt and save user's API key"""
130
+ try:
131
+ keys_data = self._load_user_keys()
132
+ cipher_suite = self._generate_key_from_user(user_id)
133
+ encrypted_key = cipher_suite.encrypt(api_key.encode())
134
+ keys_data[user_id] = base64.urlsafe_b64encode(encrypted_key).decode()
135
+ self._save_user_keys(keys_data)
136
+ return True
137
+ except Exception as e:
138
+ print(f"Error saving encrypted key: {e}")
139
+ return False
140
+
141
+ def _delete_saved_key(self, user_id):
142
+ """Delete user's saved API key"""
143
+ try:
144
+ keys_data = self._load_user_keys()
145
+ if user_id in keys_data:
146
+ del keys_data[user_id]
147
+ self._save_user_keys(keys_data)
148
+ return True
149
+ except Exception as e:
150
+ print(f"Error deleting saved key: {e}")
151
+ return False
152
+
153
+ def on_load(self, profile: gr.OAuthProfile | None = None):
154
+ """Handle initial page load"""
155
+ if profile is None:
156
+ return (
157
+ gr.update(value='# ePub Summarization Tool\n\nPlease login to access the tool.'),
158
+ gr.update(visible=False), # api_key_section
159
+ gr.update(visible=False), # inp
160
+ gr.update(visible=False) # clear_key_btn
161
+ )
162
+ return self.on_login(profile)
163
+
164
+ def on_login(self, profile: gr.OAuthProfile | None = None):
165
+ """Handle user login"""
166
+ if not profile:
167
+ return self.on_logout()
168
+
169
+ self.current_user = self._get_user_id(profile)
170
+ user_name = profile.name if hasattr(profile, 'name') else profile.username
171
+
172
+ # Check if user has a saved API key
173
+ saved_key = self._get_saved_key(self.current_user)
174
+ if saved_key:
175
+ self.api_key = saved_key
176
+ if self._initialize_client():
177
+ return (
178
+ gr.update(value=f'# ePub Summarization Tool\n\nWelcome back {user_name}! βœ… Your saved API key is loaded and ready.'),
179
+ gr.update(visible=False), # api_key_section
180
+ gr.update(visible=True), # inp
181
+ gr.update(visible=True) # clear_key_btn
182
+ )
183
+
184
+ # No saved key or failed to initialize
185
+ return (
186
+ gr.update(value=f'# ePub Summarization Tool\n\nWelcome {user_name}! Please set up your Poe API key below.'),
187
+ gr.update(visible=True), # api_key_section
188
+ gr.update(visible=False), # inp
189
+ gr.update(visible=False) # clear_key_btn
190
+ )
191
+
192
+ def on_logout(self, profile: gr.OAuthProfile | None = None):
193
+ """Handle user logout"""
194
+ self.current_user = None
195
+ self.api_key = None
196
+ self.client = None
197
+
198
+ return (
199
+ gr.update(value='# ePub Summarization Tool\n\nPlease login to access the tool.'),
200
+ gr.update(visible=False), # api_key_section
201
+ gr.update(visible=False), # inp
202
+ gr.update(visible=False) # clear_key_btn
203
+ )
204
+
205
  def _initialize_client(self):
206
  """Initialize the Poe API client"""
207
  try:
 
214
  print(f"Error initializing Poe client: {e}")
215
  return False
216
 
217
+ def set_api_key(self, api_key, remember_key):
218
  """Set and validate the API key"""
219
+ if not self.current_user:
220
+ return (
221
+ gr.update(value="❌ Please login first."),
222
+ gr.update(visible=False),
223
+ gr.update(visible=True),
224
+ gr.update(visible=False)
225
+ )
226
+
227
  if not api_key or not api_key.strip():
228
+ return (
229
+ gr.update(value="⚠️ Please enter a valid API key."),
230
+ gr.update(visible=False),
231
+ gr.update(visible=True),
232
+ gr.update(visible=False)
233
+ )
234
 
235
  self.api_key = api_key.strip()
236
 
 
242
  messages=[{"role": "user", "content": "Hello"}],
243
  max_tokens=10
244
  )
245
+
246
+ # Save key if user wants to remember it
247
+ if remember_key:
248
+ if self._save_encrypted_key(self.current_user, self.api_key):
249
+ success_msg = "βœ… API key validated and saved successfully! You can now upload an ePub file."
250
+ else:
251
+ success_msg = "βœ… API key validated successfully! (Note: Failed to save for future use)"
252
+ else:
253
+ success_msg = "βœ… API key validated successfully! You can now upload an ePub file."
254
+
255
  return (
256
+ gr.update(value=success_msg),
257
+ gr.update(visible=True), # inp
258
+ gr.update(visible=False), # api_key_section
259
+ gr.update(visible=remember_key) # clear_key_btn
260
  )
261
  except Exception as e:
262
  error_msg = str(e)
263
  if "401" in error_msg or "unauthorized" in error_msg.lower():
264
  return (
265
  gr.update(value="❌ Invalid API key. Please check your key and try again."),
266
+ gr.update(visible=False),
267
+ gr.update(visible=True),
268
  gr.update(visible=False)
269
  )
270
  elif "quota" in error_msg.lower() or "limit" in error_msg.lower():
271
  return (
272
  gr.update(value="⚠️ API key valid but quota exceeded. Please check your Poe account."),
273
+ gr.update(visible=False),
274
+ gr.update(visible=True),
275
  gr.update(visible=False)
276
  )
277
  else:
278
  return (
279
  gr.update(value=f"❌ API connection error: {error_msg}"),
280
+ gr.update(visible=False),
281
+ gr.update(visible=True),
282
  gr.update(visible=False)
283
  )
284
  else:
285
  return (
286
  gr.update(value="❌ Failed to initialize API client."),
287
+ gr.update(visible=False),
288
+ gr.update(visible=True),
289
  gr.update(visible=False)
290
  )
291
 
292
+ def clear_saved_key(self):
293
+ """Clear the user's saved API key"""
294
+ if not self.current_user:
295
+ return (
296
+ gr.update(value="❌ No user logged in."),
297
+ gr.update(visible=False),
298
+ gr.update(visible=False),
299
+ gr.update(value="")
300
+ )
301
+
302
+ if self._delete_saved_key(self.current_user):
303
+ self.api_key = None
304
+ self.client = None
305
+ return (
306
+ gr.update(value="βœ… Saved API key cleared. Please enter your API key below."),
307
+ gr.update(visible=True), # api_key_section
308
+ gr.update(visible=False), # clear_key_btn
309
+ gr.update(value="") # clear input
310
+ )
311
+ else:
312
+ return (
313
+ gr.update(value="⚠️ Failed to clear saved key."),
314
+ gr.update(visible=True),
315
+ gr.update(visible=True),
316
+ gr.update(value="")
317
+ )
318
+
319
  def get_model_response(self, text: str) -> str:
320
  """
321
  Get response from Poe API for the given text
 
335
 
336
  def process(self, file, profile: gr.OAuthProfile | None = None):
337
  if profile is None:
338
+ return gr.update(value='⚠️ Please login to access the tool.')
339
 
340
  if not self.client:
341
  return gr.update(value='⚠️ Please set your Poe API key first.')
 
415
  except Exception as e:
416
  yield gr.update(value=f"Error processing file: {str(e)}")
417
 
 
 
 
 
 
 
 
 
 
418
  # Run the application
419
  if __name__ == "__main__":
420
  GUI()