ziadmostafa commited on
Commit
a71db49
·
2 Parent(s): c5f9e29 19f456e

Replace with updated NoteGenie version

Browse files
Files changed (9) hide show
  1. .dockerignore +14 -0
  2. .gitattributes +0 -35
  3. .gitignore +5 -2
  4. Dockerfile +4 -2
  5. README.md +24 -0
  6. app.py +66 -15
  7. static/js/main.js +73 -7
  8. templates/index.html +24 -0
  9. utils/error_handler.py +0 -24
.dockerignore ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ __pycache__/
2
+ *.py[cod]
3
+ *$py.class
4
+ *.so
5
+ .env
6
+ .venv
7
+ env/
8
+ venv/
9
+ ENV/
10
+ .idea/
11
+ .vscode/
12
+ .git/
13
+ flask_session/
14
+ *.log
.gitattributes DELETED
@@ -1,35 +0,0 @@
1
- *.7z filter=lfs diff=lfs merge=lfs -text
2
- *.arrow filter=lfs diff=lfs merge=lfs -text
3
- *.bin filter=lfs diff=lfs merge=lfs -text
4
- *.bz2 filter=lfs diff=lfs merge=lfs -text
5
- *.ckpt filter=lfs diff=lfs merge=lfs -text
6
- *.ftz filter=lfs diff=lfs merge=lfs -text
7
- *.gz filter=lfs diff=lfs merge=lfs -text
8
- *.h5 filter=lfs diff=lfs merge=lfs -text
9
- *.joblib filter=lfs diff=lfs merge=lfs -text
10
- *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
- *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
- *.model filter=lfs diff=lfs merge=lfs -text
13
- *.msgpack filter=lfs diff=lfs merge=lfs -text
14
- *.npy filter=lfs diff=lfs merge=lfs -text
15
- *.npz filter=lfs diff=lfs merge=lfs -text
16
- *.onnx filter=lfs diff=lfs merge=lfs -text
17
- *.ot filter=lfs diff=lfs merge=lfs -text
18
- *.parquet filter=lfs diff=lfs merge=lfs -text
19
- *.pb filter=lfs diff=lfs merge=lfs -text
20
- *.pickle filter=lfs diff=lfs merge=lfs -text
21
- *.pkl filter=lfs diff=lfs merge=lfs -text
22
- *.pt filter=lfs diff=lfs merge=lfs -text
23
- *.pth filter=lfs diff=lfs merge=lfs -text
24
- *.rar filter=lfs diff=lfs merge=lfs -text
25
- *.safetensors filter=lfs diff=lfs merge=lfs -text
26
- saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
- *.tar.* filter=lfs diff=lfs merge=lfs -text
28
- *.tar filter=lfs diff=lfs merge=lfs -text
29
- *.tflite filter=lfs diff=lfs merge=lfs -text
30
- *.tgz filter=lfs diff=lfs merge=lfs -text
31
- *.wasm filter=lfs diff=lfs merge=lfs -text
32
- *.xz filter=lfs diff=lfs merge=lfs -text
33
- *.zip filter=lfs diff=lfs merge=lfs -text
34
- *.zst filter=lfs diff=lfs merge=lfs -text
35
- *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
.gitignore CHANGED
@@ -1,2 +1,5 @@
1
- flask_session/
2
- utils/_pycache_/
 
 
 
 
1
+ # Ignore Flask session data
2
+ /flask_session/
3
+
4
+ # Ignore Python cache files
5
+ /utils/__pycache__/
Dockerfile CHANGED
@@ -12,8 +12,10 @@ COPY . .
12
  # Create necessary directories
13
  RUN mkdir -p flask_session
14
 
15
- # Set permissions for flask_session directory
16
- RUN chmod -R 777 flask_session
 
 
17
 
18
  # Set environment variables
19
  ENV FLASK_APP=app.py
 
12
  # Create necessary directories
13
  RUN mkdir -p flask_session
14
 
15
+ # Set permissions for flask_session directory and touch API key file
16
+ RUN chmod -R 777 flask_session && \
17
+ touch api_key.txt && \
18
+ chmod 666 api_key.txt
19
 
20
  # Set environment variables
21
  ENV FLASK_APP=app.py
README.md CHANGED
@@ -21,6 +21,15 @@ NoteGenie is an AI-powered Jupyter notebook generator that uses Google's Gemini
21
  - Download notebooks as .ipynb files
22
  - Streaming responses for a better user experience
23
 
 
 
 
 
 
 
 
 
 
24
  ## Local Development
25
 
26
  1. Install dependencies:
@@ -35,6 +44,21 @@ NoteGenie is an AI-powered Jupyter notebook generator that uses Google's Gemini
35
 
36
  3. Open http://localhost:5000 in your browser
37
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
  ## API Key Setup
39
 
40
  NoteGenie requires a Google Gemini API key. Users can set their own API key in the web interface.
 
21
  - Download notebooks as .ipynb files
22
  - Streaming responses for a better user experience
23
 
24
+ ## Deployment on Hugging Face Spaces
25
+
26
+ 1. Create a new Space on Hugging Face
27
+ 2. Choose Docker template
28
+ 3. Upload this repository to the Space
29
+ 4. Set the following environment variables in your Space settings:
30
+ - SECRET_KEY: A secure random string for Flask sessions
31
+ - PORT: 7860 (default for Hugging Face Spaces)
32
+
33
  ## Local Development
34
 
35
  1. Install dependencies:
 
44
 
45
  3. Open http://localhost:5000 in your browser
46
 
47
+ ## Using Docker
48
+
49
+ Build and run the Docker container:
50
+
51
+ ```bash
52
+ docker build -t notegenie .
53
+ docker run -p 7860:7860 -e SECRET_KEY=your_secret_key notegenie
54
+ ```
55
+
56
+ ## Environment Variables
57
+
58
+ - SECRET_KEY: Secret key for Flask session encryption
59
+ - PORT: Port to run the application on (defaults to 5000 locally, 7860 for Hugging Face)
60
+ - FLASK_ENV: Set to "development" for debug mode, "production" for production
61
+
62
  ## API Key Setup
63
 
64
  NoteGenie requires a Google Gemini API key. Users can set their own API key in the web interface.
app.py CHANGED
@@ -4,9 +4,17 @@ import google.generativeai as genai
4
  import json
5
  import uuid
6
  import os
 
7
  from utils.ai_helpers import generate_notebook, stream_notebook_generation, stream_notebook_edit, edit_notebook
8
  from utils.notebook_helpers import format_notebook, extract_notebook_info
9
 
 
 
 
 
 
 
 
10
  app = Flask(__name__)
11
  app.config["SECRET_KEY"] = os.environ.get("SECRET_KEY", "notegenie-secret-key-change-in-production")
12
  app.config["SESSION_TYPE"] = "filesystem"
@@ -16,6 +24,9 @@ app.config["PERMANENT_SESSION_LIFETIME"] = 60 * 60 * 24 * 30 # 30 days
16
  app.config["SESSION_FILE_DIR"] = os.path.join(os.path.dirname(os.path.abspath(__file__)), "flask_session")
17
  os.makedirs(app.config["SESSION_FILE_DIR"], exist_ok=True) # Ensure directory exists
18
 
 
 
 
19
  Session(app)
20
 
21
  # Map front-end model names to API model names
@@ -28,8 +39,22 @@ MODEL_MAPPING = {
28
  def get_api_model_name(frontend_model_name):
29
  return MODEL_MAPPING.get(frontend_model_name, frontend_model_name)
30
 
 
 
 
 
 
 
 
 
 
 
 
31
  @app.route("/", methods=["GET"])
32
  def index():
 
 
 
33
  return render_template("index.html")
34
 
35
  @app.route("/set_api_key", methods=["POST"])
@@ -44,19 +69,26 @@ def set_api_key():
44
  model = genai.GenerativeModel("gemini-2.0-pro-exp-02-05")
45
  response = model.generate_content("Say 'API key is valid'")
46
 
47
- # Store API key in session with permanent flag
48
  session.permanent = True
49
  session["api_key"] = api_key
50
 
 
51
  return jsonify({"success": True})
52
  except Exception as e:
 
53
  return jsonify({"success": False, "message": str(e)}), 400
54
 
55
  @app.route("/generate_notebook", methods=["GET", "POST"])
56
  def generate_notebook_route():
57
- if "api_key" not in session:
 
 
58
  return jsonify({"success": False, "message": "API key not set"}), 401
59
 
 
 
 
60
  # Handle both GET (for streaming) and POST requests
61
  if request.method == "GET":
62
  prompt = request.args.get("prompt")
@@ -75,8 +107,6 @@ def generate_notebook_route():
75
  # Map the frontend model name to the API model name
76
  api_model_name = get_api_model_name(model_name)
77
 
78
- genai.configure(api_key=session["api_key"])
79
-
80
  try:
81
  # OPTIMIZATION: If format_only is True, skip the AI call and just format the provided content
82
  if request.method == "POST" and format_only:
@@ -105,17 +135,14 @@ def generate_notebook_route():
105
  "description": notebook_info["description"]
106
  })
107
  except Exception as e:
108
- print(f"Error: Failed to generate notebook: {str(e)}")
109
- print("Detailed exception information:")
110
- import traceback
111
- traceback.print_exc()
112
- # Still raise the user-friendly message to the UI if needed
113
- raise Exception("Failed to generate notebook. Please check terminal for details.")
114
 
115
  @app.route("/prepare_edit_notebook", methods=["POST"])
116
  def prepare_edit_notebook():
117
  """Store the notebook in the session for editing."""
118
- if "api_key" not in session:
 
119
  return jsonify({"success": False, "message": "API key not set"}), 401
120
 
121
  data = request.json
@@ -125,15 +152,19 @@ def prepare_edit_notebook():
125
  return jsonify({"success": False, "message": "Notebook content is required"}), 400
126
 
127
  # Store the notebook in the session for later access
128
- session["current_notebook"] = notebook_json
129
 
130
  return jsonify({"success": True})
131
 
132
  @app.route("/edit_notebook", methods=["GET", "POST"])
133
  def edit_notebook_route():
134
- if "api_key" not in session:
 
135
  return jsonify({"success": False, "message": "API key not set"}), 401
136
 
 
 
 
137
  # Get edit prompt and current notebook
138
  if request.method == "GET":
139
  edit_prompt = request.args.get("edit_prompt")
@@ -141,6 +172,8 @@ def edit_notebook_route():
141
  stream = request.args.get("stream", "true").lower() == "true"
142
  # For GET streaming requests, get notebook from session
143
  notebook_json = session.get("current_notebook")
 
 
144
  else:
145
  data = request.json
146
  edit_prompt = data.get("edit_prompt")
@@ -157,8 +190,6 @@ def edit_notebook_route():
157
  # Map the frontend model name to the API model name
158
  api_model_name = get_api_model_name(model_name)
159
 
160
- genai.configure(api_key=session["api_key"])
161
-
162
  try:
163
  if stream:
164
  return stream_notebook_edit(edit_prompt, notebook_json, api_model_name)
@@ -200,6 +231,26 @@ def download_notebook():
200
 
201
  return response
202
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
203
  if __name__ == "__main__":
204
  port = int(os.environ.get("PORT", 5000))
205
  app.run(host="0.0.0.0", port=port, debug=(os.environ.get("FLASK_ENV") == "development"))
 
4
  import json
5
  import uuid
6
  import os
7
+ import logging
8
  from utils.ai_helpers import generate_notebook, stream_notebook_generation, stream_notebook_edit, edit_notebook
9
  from utils.notebook_helpers import format_notebook, extract_notebook_info
10
 
11
+ # Configure logging
12
+ logging.basicConfig(
13
+ level=logging.INFO,
14
+ format='%(asctime)s - notegenie - %(levelname)s - %(message)s'
15
+ )
16
+ logger = logging.getLogger()
17
+
18
  app = Flask(__name__)
19
  app.config["SECRET_KEY"] = os.environ.get("SECRET_KEY", "notegenie-secret-key-change-in-production")
20
  app.config["SESSION_TYPE"] = "filesystem"
 
24
  app.config["SESSION_FILE_DIR"] = os.path.join(os.path.dirname(os.path.abspath(__file__)), "flask_session")
25
  os.makedirs(app.config["SESSION_FILE_DIR"], exist_ok=True) # Ensure directory exists
26
 
27
+ # Set a more permissive file mode for session files to avoid permission issues
28
+ app.config["SESSION_FILE_MODE"] = 0o666
29
+
30
  Session(app)
31
 
32
  # Map front-end model names to API model names
 
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")
50
+
51
+ return api_key
52
+
53
  @app.route("/", methods=["GET"])
54
  def index():
55
+ # Test session functionality
56
+ session["session_test"] = True
57
+ logger.info(f"Session check - variables: {list(session.keys())}")
58
  return render_template("index.html")
59
 
60
  @app.route("/set_api_key", methods=["POST"])
 
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:
79
+ logger.error(f"API key validation error: {str(e)}")
80
  return jsonify({"success": False, "message": str(e)}), 400
81
 
82
  @app.route("/generate_notebook", methods=["GET", "POST"])
83
  def generate_notebook_route():
84
+ api_key = get_api_key()
85
+ if not api_key:
86
+ logger.warning("Generate notebook request without API key")
87
  return jsonify({"success": False, "message": "API key not set"}), 401
88
 
89
+ # Always configure genai with the API key for each request
90
+ genai.configure(api_key=api_key)
91
+
92
  # Handle both GET (for streaming) and POST requests
93
  if request.method == "GET":
94
  prompt = request.args.get("prompt")
 
107
  # Map the frontend model name to the API model name
108
  api_model_name = get_api_model_name(model_name)
109
 
 
 
110
  try:
111
  # OPTIMIZATION: If format_only is True, skip the AI call and just format the provided content
112
  if request.method == "POST" and format_only:
 
135
  "description": notebook_info["description"]
136
  })
137
  except Exception as e:
138
+ logger.error(f"Error generating notebook: {str(e)}")
139
+ return jsonify({"success": False, "message": str(e)}), 500
 
 
 
 
140
 
141
  @app.route("/prepare_edit_notebook", methods=["POST"])
142
  def prepare_edit_notebook():
143
  """Store the notebook in the session for editing."""
144
+ api_key = get_api_key()
145
+ if not api_key:
146
  return jsonify({"success": False, "message": "API key not set"}), 401
147
 
148
  data = request.json
 
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
 
159
  @app.route("/edit_notebook", methods=["GET", "POST"])
160
  def edit_notebook_route():
161
+ api_key = get_api_key()
162
+ if not api_key:
163
  return jsonify({"success": False, "message": "API key not set"}), 401
164
 
165
+ # Always configure genai with the API key for each request
166
+ genai.configure(api_key=api_key)
167
+
168
  # Get edit prompt and current notebook
169
  if request.method == "GET":
170
  edit_prompt = request.args.get("edit_prompt")
 
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")
 
190
  # Map the frontend model name to the API model name
191
  api_model_name = get_api_model_name(model_name)
192
 
 
 
193
  try:
194
  if stream:
195
  return stream_notebook_edit(edit_prompt, notebook_json, api_model_name)
 
231
 
232
  return response
233
 
234
+ # Add a session diagnostic endpoint
235
+ @app.route("/check_session", methods=["GET"])
236
+ def check_session():
237
+ # For debugging only - would be disabled in production
238
+ session_data = {
239
+ "has_api_key": "api_key" in session,
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
246
+ is_hf_space = "SPACE_ID" in os.environ
247
+ session_data["is_huggingface_space"] = is_hf_space
248
+
249
+ if is_hf_space:
250
+ logger.info("Running on Hugging Face Spaces environment")
251
+
252
+ return jsonify(session_data)
253
+
254
  if __name__ == "__main__":
255
  port = int(os.environ.get("PORT", 5000))
256
  app.run(host="0.0.0.0", port=port, debug=(os.environ.get("FLASK_ENV") == "development"))
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
 
@@ -295,12 +317,29 @@ document.addEventListener('DOMContentLoaded', function() {
295
  }
296
  }, 5000); // Check every 5 seconds
297
 
298
- // Create a new event source
299
- eventSource = new EventSource(`/generate_notebook?${new URLSearchParams({
 
 
 
300
  prompt: prompt,
301
  model: modelName,
302
  stream: true
303
- }).toString()}`);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
304
 
305
  eventSource.onmessage = function(event) {
306
  // Update our last-activity timestamp
@@ -411,14 +450,24 @@ document.addEventListener('DOMContentLoaded', function() {
411
  let lastPreviewUpdate = 0;
412
  const PREVIEW_UPDATE_INTERVAL = 1000; // Update preview every 1 second during streaming, same as generate
413
 
 
 
 
 
 
 
 
 
414
  // First, send the notebook data to the server so it's available in the session
415
  fetch('/prepare_edit_notebook', {
416
  method: 'POST',
417
  headers: {
418
  'Content-Type': 'application/json',
 
 
419
  },
420
  body: JSON.stringify({
421
- notebook: notebook
422
  })
423
  })
424
  .then(response => {
@@ -429,12 +478,28 @@ document.addEventListener('DOMContentLoaded', function() {
429
  })
430
  .then(data => {
431
  if (data.success) {
432
- // Now create a new event source for editing - the notebook is now in the session
433
- eventSource = new EventSource(`/edit_notebook?${new URLSearchParams({
 
 
434
  edit_prompt: editPrompt,
435
  model: modelName,
436
  stream: true
437
- }).toString()}`);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
438
 
439
  // Track the last time we got a chunk - for timeout detection
440
  let lastChunkTime = Date.now();
@@ -569,6 +634,7 @@ document.addEventListener('DOMContentLoaded', function() {
569
  .catch(error => {
570
  console.error('Error preparing notebook for edit:', error);
571
  updateAiMessage(aiMessageId, '**Error:** ' + error.message);
 
572
  setGeneratingState(false);
573
  });
574
  }
 
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
 
 
317
  }
318
  }, 5000); // Check every 5 seconds
319
 
320
+ // Get API key from localStorage as a backup
321
+ const backupApiKey = localStorage.getItem('notegenie_api_key');
322
+
323
+ // Create URL parameters including API key as fallback
324
+ const urlParams = new URLSearchParams({
325
  prompt: prompt,
326
  model: modelName,
327
  stream: true
328
+ });
329
+
330
+ // Add API key to URL params if available from localStorage (as a fallback)
331
+ if (backupApiKey) {
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',
464
  headers: {
465
  'Content-Type': 'application/json',
466
+ // Add API key as header if available
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,
486
  model: modelName,
487
  stream: true
488
+ });
489
+
490
+ // Add API key to URL params if available from localStorage (as a fallback)
491
+ if (backupApiKey) {
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
  }
templates/index.html CHANGED
@@ -16,6 +16,30 @@
16
  <link href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism.min.css" rel="stylesheet">
17
  <!-- Custom styles -->
18
  <link href="{{ url_for('static', filename='css/style.css') }}" rel="stylesheet">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  </head>
20
  <body>
21
  <div class="app-container">
 
16
  <link href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism.min.css" rel="stylesheet">
17
  <!-- Custom styles -->
18
  <link href="{{ url_for('static', filename='css/style.css') }}" rel="stylesheet">
19
+ <link rel="icon" type="image/png" href="{{ url_for('static', filename='images/favicon.png') }}">
20
+ <script>
21
+ document.fonts.ready.then(() => {
22
+ var canvas = document.createElement('canvas');
23
+ canvas.width = 64;
24
+ canvas.height = 64;
25
+ var ctx = canvas.getContext('2d');
26
+ ctx.fillStyle = '#4285f4'; // use desired color matching the logo-icon
27
+ ctx.font = '48px "Material Icons"';
28
+ ctx.textAlign = 'center';
29
+ ctx.textBaseline = 'middle';
30
+ ctx.fillText('auto_awesome', 32, 32);
31
+ var dataURL = canvas.toDataURL();
32
+ var link = document.querySelector('link[rel="icon"]');
33
+ if(link) {
34
+ link.href = dataURL;
35
+ } else {
36
+ var newLink = document.createElement('link');
37
+ newLink.rel = 'icon';
38
+ newLink.href = dataURL;
39
+ document.head.appendChild(newLink);
40
+ }
41
+ });
42
+ </script>
43
  </head>
44
  <body>
45
  <div class="app-container">
utils/error_handler.py DELETED
@@ -1,24 +0,0 @@
1
- import logging
2
- import traceback
3
- import sys
4
-
5
- # Configure logging to output to terminal
6
- logging.basicConfig(
7
- level=logging.DEBUG,
8
- format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
9
- stream=sys.stderr
10
- )
11
-
12
- logger = logging.getLogger('NoteGenie')
13
-
14
- def handle_error(func):
15
- """Decorator to handle exceptions and log them to terminal"""
16
- def wrapper(*args, **kwargs):
17
- try:
18
- return func(*args, **kwargs)
19
- except Exception as e:
20
- logger.error(f"Error in {func.__name__}: {str(e)}")
21
- logger.error(traceback.format_exc())
22
- # You can still raise a user-friendly error for the UI
23
- raise Exception("Failed to generate notebook. Please check terminal for details.")
24
- return wrapper