Spaces:
Sleeping
Sleeping
Commit
·
19f456e
1
Parent(s):
0d48ff6
Replace with updated NoteGenie version
Browse files- app.py +5 -28
- 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
|
| 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 |
-
//
|
| 314 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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:
|
| 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 |
-
//
|
| 458 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
}
|