ziadmostafa commited on
Commit
19f456e
·
1 Parent(s): 0d48ff6

Replace with updated NoteGenie version

Browse files
Files changed (2) hide show
  1. app.py +5 -28
  2. static/js/main.js +50 -5
app.py CHANGED
@@ -29,9 +29,6 @@ app.config["SESSION_FILE_MODE"] = 0o666
29
 
30
  Session(app)
31
 
32
- # API key file storage path (as backup for session)
33
- API_KEY_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "api_key.txt")
34
-
35
  # Map front-end model names to API model names
36
  MODEL_MAPPING = {
37
  "gemini-2.0-pro": "gemini-2.0-pro-exp-02-05",
@@ -42,24 +39,11 @@ MODEL_MAPPING = {
42
  def get_api_model_name(frontend_model_name):
43
  return MODEL_MAPPING.get(frontend_model_name, frontend_model_name)
44
 
45
- # Function to get API key with multiple fallbacks
46
  def get_api_key():
47
  # Try to get from session first
48
  api_key = session.get("api_key")
49
 
50
- # If not in session, try to get from file
51
- if not api_key:
52
- try:
53
- if os.path.exists(API_KEY_FILE):
54
- with open(API_KEY_FILE, "r") as f:
55
- api_key = f.read().strip()
56
- # Restore session if we found a key
57
- if api_key:
58
- session["api_key"] = api_key
59
- logger.info("API key restored from backup file")
60
- except Exception as e:
61
- logger.error(f"Error reading API key file: {str(e)}")
62
-
63
  # Try to get from request header or param (for direct API calls)
64
  if not api_key:
65
  api_key = request.headers.get("X-API-Key") or request.args.get("api_key")
@@ -85,18 +69,10 @@ def set_api_key():
85
  model = genai.GenerativeModel("gemini-2.0-pro-exp-02-05")
86
  response = model.generate_content("Say 'API key is valid'")
87
 
88
- # Store API key in session
89
  session.permanent = True
90
  session["api_key"] = api_key
91
 
92
- # Also store in backup file as failsafe
93
- try:
94
- with open(API_KEY_FILE, "w") as f:
95
- f.write(api_key)
96
- os.chmod(API_KEY_FILE, 0o666) # Make readable/writable
97
- except Exception as e:
98
- logger.error(f"Failed to write API key to backup file: {str(e)}")
99
-
100
  logger.info("API key successfully set and validated")
101
  return jsonify({"success": True})
102
  except Exception as e:
@@ -176,7 +152,7 @@ def prepare_edit_notebook():
176
  return jsonify({"success": False, "message": "Notebook content is required"}), 400
177
 
178
  # Store the notebook in the session for later access
179
- session["current_notebook"] = notebook_json
180
 
181
  return jsonify({"success": True})
182
 
@@ -196,6 +172,8 @@ def edit_notebook_route():
196
  stream = request.args.get("stream", "true").lower() == "true"
197
  # For GET streaming requests, get notebook from session
198
  notebook_json = session.get("current_notebook")
 
 
199
  else:
200
  data = request.json
201
  edit_prompt = data.get("edit_prompt")
@@ -262,7 +240,6 @@ def check_session():
262
  "session_vars": list(session.keys()),
263
  "session_file_dir_exists": os.path.exists(app.config["SESSION_FILE_DIR"]),
264
  "session_file_dir_writable": os.access(app.config["SESSION_FILE_DIR"], os.W_OK),
265
- "api_key_file_exists": os.path.exists(API_KEY_FILE),
266
  }
267
 
268
  # Check if running on Hugging Face Spaces
 
29
 
30
  Session(app)
31
 
 
 
 
32
  # Map front-end model names to API model names
33
  MODEL_MAPPING = {
34
  "gemini-2.0-pro": "gemini-2.0-pro-exp-02-05",
 
39
  def get_api_model_name(frontend_model_name):
40
  return MODEL_MAPPING.get(frontend_model_name, frontend_model_name)
41
 
42
+ # Function to get API key from different sources
43
  def get_api_key():
44
  # Try to get from session first
45
  api_key = session.get("api_key")
46
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
  # Try to get from request header or param (for direct API calls)
48
  if not api_key:
49
  api_key = request.headers.get("X-API-Key") or request.args.get("api_key")
 
69
  model = genai.GenerativeModel("gemini-2.0-pro-exp-02-05")
70
  response = model.generate_content("Say 'API key is valid'")
71
 
72
+ # Store API key in session only
73
  session.permanent = True
74
  session["api_key"] = api_key
75
 
 
 
 
 
 
 
 
 
76
  logger.info("API key successfully set and validated")
77
  return jsonify({"success": True})
78
  except Exception as e:
 
152
  return jsonify({"success": False, "message": "Notebook content is required"}), 400
153
 
154
  # Store the notebook in the session for later access
155
+ session["current_notebook"] = json.dumps(notebook_json) # Store as JSON string
156
 
157
  return jsonify({"success": True})
158
 
 
172
  stream = request.args.get("stream", "true").lower() == "true"
173
  # For GET streaming requests, get notebook from session
174
  notebook_json = session.get("current_notebook")
175
+ if notebook_json:
176
+ notebook_json = json.loads(notebook_json) # Parse JSON string back to dict
177
  else:
178
  data = request.json
179
  edit_prompt = data.get("edit_prompt")
 
240
  "session_vars": list(session.keys()),
241
  "session_file_dir_exists": os.path.exists(app.config["SESSION_FILE_DIR"]),
242
  "session_file_dir_writable": os.access(app.config["SESSION_FILE_DIR"], os.W_OK),
 
243
  }
244
 
245
  # Check if running on Hugging Face Spaces
static/js/main.js CHANGED
@@ -111,6 +111,14 @@ document.addEventListener('DOMContentLoaded', function() {
111
  // Prevent multiple rapid clicks
112
  if (isActionInProgress) return;
113
 
 
 
 
 
 
 
 
 
114
  // Get the prompt text and check if it's empty
115
  const prompt = promptInputEl.value.trim();
116
 
@@ -253,12 +261,26 @@ document.addEventListener('DOMContentLoaded', function() {
253
  const aiMessageId = 'ai-typing-' + Date.now();
254
  addTypingIndicator(aiMessageId);
255
 
 
 
 
256
  handleEditStreamingResponse(editPrompt, currentNotebook, modelName, aiMessageId);
257
 
258
  // Clear the input field after sending
259
  promptInputEl.value = '';
260
  }
261
 
 
 
 
 
 
 
 
 
 
 
 
262
  function handleStreamingResponse(prompt, modelName, aiMessageId) {
263
  aiResponseText = '';
264
 
@@ -310,8 +332,14 @@ document.addEventListener('DOMContentLoaded', function() {
310
  urlParams.append('api_key', backupApiKey);
311
  }
312
 
313
- // Create a new event source with API key included
314
- eventSource = new EventSource(`/generate_notebook?${urlParams.toString()}`);
 
 
 
 
 
 
315
 
316
  eventSource.onmessage = function(event) {
317
  // Update our last-activity timestamp
@@ -422,6 +450,14 @@ document.addEventListener('DOMContentLoaded', function() {
422
  let lastPreviewUpdate = 0;
423
  const PREVIEW_UPDATE_INTERVAL = 1000; // Update preview every 1 second during streaming, same as generate
424
 
 
 
 
 
 
 
 
 
425
  // First, send the notebook data to the server so it's available in the session
426
  fetch('/prepare_edit_notebook', {
427
  method: 'POST',
@@ -431,7 +467,7 @@ document.addEventListener('DOMContentLoaded', function() {
431
  ...(backupApiKey && {'X-API-Key': backupApiKey})
432
  },
433
  body: JSON.stringify({
434
- notebook: notebook
435
  })
436
  })
437
  .then(response => {
@@ -442,6 +478,8 @@ document.addEventListener('DOMContentLoaded', function() {
442
  })
443
  .then(data => {
444
  if (data.success) {
 
 
445
  // Create URL parameters including API key as fallback
446
  const urlParams = new URLSearchParams({
447
  edit_prompt: editPrompt,
@@ -454,8 +492,14 @@ document.addEventListener('DOMContentLoaded', function() {
454
  urlParams.append('api_key', backupApiKey);
455
  }
456
 
457
- // Now create a new event source for editing with the API key included
458
- eventSource = new EventSource(`/edit_notebook?${urlParams.toString()}`);
 
 
 
 
 
 
459
 
460
  // Track the last time we got a chunk - for timeout detection
461
  let lastChunkTime = Date.now();
@@ -590,6 +634,7 @@ document.addEventListener('DOMContentLoaded', function() {
590
  .catch(error => {
591
  console.error('Error preparing notebook for edit:', error);
592
  updateAiMessage(aiMessageId, '**Error:** ' + error.message);
 
593
  setGeneratingState(false);
594
  });
595
  }
 
111
  // Prevent multiple rapid clicks
112
  if (isActionInProgress) return;
113
 
114
+ // Check if API key is set
115
+ const apiKey = localStorage.getItem('notegenie_api_key');
116
+ if (!apiKey) {
117
+ addSystemMessage('<i class="bi bi-exclamation-triangle"></i> API key is not set. Please set your API key first.');
118
+ showApiKeyModal();
119
+ return;
120
+ }
121
+
122
  // Get the prompt text and check if it's empty
123
  const prompt = promptInputEl.value.trim();
124
 
 
261
  const aiMessageId = 'ai-typing-' + Date.now();
262
  addTypingIndicator(aiMessageId);
263
 
264
+ // Update AI message to show we're starting the edit
265
+ updateAiMessage(aiMessageId, "**NoteGenie:** Starting to edit your notebook...");
266
+
267
  handleEditStreamingResponse(editPrompt, currentNotebook, modelName, aiMessageId);
268
 
269
  // Clear the input field after sending
270
  promptInputEl.value = '';
271
  }
272
 
273
+ // Create a custom EventSource that supports headers
274
+ function createEventSourceWithHeaders(url, headers) {
275
+ if (typeof EventSourcePolyfill !== 'undefined') {
276
+ return new EventSourcePolyfill(url, { headers: headers });
277
+ }
278
+
279
+ // If native EventSource is all we have but we need headers,
280
+ // we fall back to using URL parameters for authentication
281
+ return new EventSource(url);
282
+ }
283
+
284
  function handleStreamingResponse(prompt, modelName, aiMessageId) {
285
  aiResponseText = '';
286
 
 
332
  urlParams.append('api_key', backupApiKey);
333
  }
334
 
335
+ // Set headers with API key if available
336
+ const headers = {};
337
+ if (backupApiKey) {
338
+ headers['X-API-Key'] = backupApiKey;
339
+ }
340
+
341
+ // Create a new event source with API key included in both URL and headers
342
+ eventSource = createEventSourceWithHeaders(`/generate_notebook?${urlParams.toString()}`, headers);
343
 
344
  eventSource.onmessage = function(event) {
345
  // Update our last-activity timestamp
 
450
  let lastPreviewUpdate = 0;
451
  const PREVIEW_UPDATE_INTERVAL = 1000; // Update preview every 1 second during streaming, same as generate
452
 
453
+ // Get API key from localStorage as a backup
454
+ const backupApiKey = localStorage.getItem('notegenie_api_key');
455
+
456
+ // Make sure notebook is in proper format before sending
457
+ const notebookToSend = typeof notebook === 'string' ? JSON.parse(notebook) : notebook;
458
+
459
+ console.log('Sending notebook for editing:', notebookToSend); // Debug log
460
+
461
  // First, send the notebook data to the server so it's available in the session
462
  fetch('/prepare_edit_notebook', {
463
  method: 'POST',
 
467
  ...(backupApiKey && {'X-API-Key': backupApiKey})
468
  },
469
  body: JSON.stringify({
470
+ notebook: notebookToSend
471
  })
472
  })
473
  .then(response => {
 
478
  })
479
  .then(data => {
480
  if (data.success) {
481
+ updateAiMessage(aiMessageId, "**NoteGenie:** Processing your edit request...");
482
+
483
  // Create URL parameters including API key as fallback
484
  const urlParams = new URLSearchParams({
485
  edit_prompt: editPrompt,
 
492
  urlParams.append('api_key', backupApiKey);
493
  }
494
 
495
+ // Set headers with API key if available
496
+ const headers = {};
497
+ if (backupApiKey) {
498
+ headers['X-API-Key'] = backupApiKey;
499
+ }
500
+
501
+ // Now create a new event source for editing with the API key included in both URL and headers
502
+ eventSource = createEventSourceWithHeaders(`/edit_notebook?${urlParams.toString()}`, headers);
503
 
504
  // Track the last time we got a chunk - for timeout detection
505
  let lastChunkTime = Date.now();
 
634
  .catch(error => {
635
  console.error('Error preparing notebook for edit:', error);
636
  updateAiMessage(aiMessageId, '**Error:** ' + error.message);
637
+ notebookPreviewEl.innerHTML = '<div class="alert alert-danger">Error: Failed to prepare notebook for editing. Please try regenerating the notebook.</div>';
638
  setGeneratingState(false);
639
  });
640
  }