Spaces:
Sleeping
Sleeping
Commit
·
d8bdc8d
1
Parent(s):
e8b4723
changes
Browse files- app.py +44 -16
- flask_session/2029240f6d1128be89ddc32729463129 +0 -0
- flask_session/45cc51dcfe7cf83166facbd1c993e325 +0 -0
- static/js/main.js +27 -2
- templates/index.html +23 -0
- utils/__pycache__/ai_helpers.cpython-39.pyc +0 -0
- utils/__pycache__/notebook_helpers.cpython-39.pyc +0 -0
- utils/ai_helpers.py +10 -2
app.py
CHANGED
|
@@ -34,6 +34,10 @@ if IS_HUGGINGFACE:
|
|
| 34 |
app.config["SESSION_COOKIE_SECURE"] = True
|
| 35 |
app.config["SESSION_COOKIE_HTTPONLY"] = True
|
| 36 |
app.config["SESSION_COOKIE_SAMESITE"] = "Lax"
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
|
| 38 |
Session(app)
|
| 39 |
|
|
@@ -44,6 +48,8 @@ MODEL_MAPPING = {
|
|
| 44 |
"gemini-2.0-flash-thinking": "gemini-2.0-flash-thinking-exp-01-21"
|
| 45 |
}
|
| 46 |
|
|
|
|
|
|
|
| 47 |
def get_api_model_name(frontend_model_name):
|
| 48 |
return MODEL_MAPPING.get(frontend_model_name, frontend_model_name)
|
| 49 |
|
|
@@ -56,18 +62,15 @@ def set_api_key():
|
|
| 56 |
api_key = request.form.get("api_key")
|
| 57 |
if not api_key:
|
| 58 |
return jsonify({"success": False, "message": "API key is required"}), 400
|
| 59 |
-
|
| 60 |
try:
|
| 61 |
# Test the API key
|
| 62 |
genai.configure(api_key=api_key)
|
| 63 |
model = genai.GenerativeModel("gemini-2.0-pro-exp-02-05")
|
| 64 |
response = model.generate_content("Say 'API key is valid'")
|
| 65 |
-
|
| 66 |
# Store API key in session with permanent flag
|
| 67 |
session.permanent = True
|
| 68 |
session["api_key"] = api_key
|
| 69 |
logger.info("API key successfully set and validated")
|
| 70 |
-
|
| 71 |
return jsonify({"success": True})
|
| 72 |
except Exception as e:
|
| 73 |
logger.error(f"API key validation error: {str(e)}")
|
|
@@ -78,7 +81,6 @@ def generate_notebook_route():
|
|
| 78 |
if "api_key" not in session:
|
| 79 |
logger.warning("Generate notebook request without API key")
|
| 80 |
return jsonify({"success": False, "message": "API key not set"}), 401
|
| 81 |
-
|
| 82 |
# Handle both GET (for streaming) and POST requests
|
| 83 |
if request.method == "GET":
|
| 84 |
prompt = request.args.get("prompt")
|
|
@@ -100,14 +102,12 @@ def generate_notebook_route():
|
|
| 100 |
|
| 101 |
try:
|
| 102 |
genai.configure(api_key=session["api_key"])
|
| 103 |
-
|
| 104 |
# OPTIMIZATION: If format_only is True, skip the AI call and just format the provided content
|
| 105 |
if request.method == "POST" and format_only:
|
| 106 |
# Use client-provided content as is (it's already the AI response)
|
| 107 |
notebook_content = prompt
|
| 108 |
notebook_json = format_notebook(notebook_content)
|
| 109 |
notebook_info = extract_notebook_info(notebook_content)
|
| 110 |
-
|
| 111 |
return jsonify({
|
| 112 |
"success": True,
|
| 113 |
"notebook": notebook_json,
|
|
@@ -121,7 +121,6 @@ def generate_notebook_route():
|
|
| 121 |
notebook_content = generate_notebook(prompt, api_model_name)
|
| 122 |
notebook_json = format_notebook(notebook_content)
|
| 123 |
notebook_info = extract_notebook_info(notebook_content)
|
| 124 |
-
|
| 125 |
return jsonify({
|
| 126 |
"success": True,
|
| 127 |
"notebook": notebook_json,
|
|
@@ -177,9 +176,8 @@ def edit_notebook_route():
|
|
| 177 |
# Map the frontend model name to the API model name
|
| 178 |
api_model_name = get_api_model_name(model_name)
|
| 179 |
|
| 180 |
-
genai.configure(api_key=session["api_key"])
|
| 181 |
-
|
| 182 |
try:
|
|
|
|
| 183 |
if stream:
|
| 184 |
return stream_notebook_edit(edit_prompt, notebook_json, api_model_name)
|
| 185 |
else:
|
|
@@ -187,7 +185,6 @@ def edit_notebook_route():
|
|
| 187 |
edited_content = edit_notebook(edit_prompt, notebook_json, api_model_name)
|
| 188 |
notebook_json = format_notebook(edited_content)
|
| 189 |
notebook_info = extract_notebook_info(edited_content)
|
| 190 |
-
|
| 191 |
return jsonify({
|
| 192 |
"success": True,
|
| 193 |
"notebook": notebook_json,
|
|
@@ -195,13 +192,11 @@ def edit_notebook_route():
|
|
| 195 |
"description": notebook_info["description"]
|
| 196 |
})
|
| 197 |
except Exception as e:
|
| 198 |
-
|
| 199 |
return jsonify({"success": False, "message": str(e)}), 500
|
| 200 |
|
| 201 |
@app.route("/download_notebook", methods=["POST"])
|
| 202 |
def download_notebook():
|
| 203 |
-
from flask import Response
|
| 204 |
-
|
| 205 |
data = request.json
|
| 206 |
notebook_json = data.get("notebook")
|
| 207 |
filename = data.get("filename", f"notebook_{uuid.uuid4()}.ipynb")
|
|
@@ -217,10 +212,43 @@ def download_notebook():
|
|
| 217 |
mimetype="application/json",
|
| 218 |
headers={"Content-Disposition": f"attachment;filename={filename}"}
|
| 219 |
)
|
| 220 |
-
|
| 221 |
return response
|
| 222 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 223 |
if __name__ == "__main__":
|
| 224 |
-
port = int(os.environ.get("PORT", 5000))
|
| 225 |
debug_mode = os.environ.get("FLASK_ENV") == "development"
|
| 226 |
-
|
|
|
|
|
|
| 34 |
app.config["SESSION_COOKIE_SECURE"] = True
|
| 35 |
app.config["SESSION_COOKIE_HTTPONLY"] = True
|
| 36 |
app.config["SESSION_COOKIE_SAMESITE"] = "Lax"
|
| 37 |
+
# Add more durable storage for Hugging Face
|
| 38 |
+
app.config["SESSION_FILE_THRESHOLD"] = 10 # Low threshold to ensure writes
|
| 39 |
+
# Important: Don't use large session lifetime on Hugging Face
|
| 40 |
+
app.config["PERMANENT_SESSION_LIFETIME"] = 60 * 60 * 24 # 1 day only
|
| 41 |
|
| 42 |
Session(app)
|
| 43 |
|
|
|
|
| 48 |
"gemini-2.0-flash-thinking": "gemini-2.0-flash-thinking-exp-01-21"
|
| 49 |
}
|
| 50 |
|
| 51 |
+
global_api_key = None
|
| 52 |
+
|
| 53 |
def get_api_model_name(frontend_model_name):
|
| 54 |
return MODEL_MAPPING.get(frontend_model_name, frontend_model_name)
|
| 55 |
|
|
|
|
| 62 |
api_key = request.form.get("api_key")
|
| 63 |
if not api_key:
|
| 64 |
return jsonify({"success": False, "message": "API key is required"}), 400
|
|
|
|
| 65 |
try:
|
| 66 |
# Test the API key
|
| 67 |
genai.configure(api_key=api_key)
|
| 68 |
model = genai.GenerativeModel("gemini-2.0-pro-exp-02-05")
|
| 69 |
response = model.generate_content("Say 'API key is valid'")
|
|
|
|
| 70 |
# Store API key in session with permanent flag
|
| 71 |
session.permanent = True
|
| 72 |
session["api_key"] = api_key
|
| 73 |
logger.info("API key successfully set and validated")
|
|
|
|
| 74 |
return jsonify({"success": True})
|
| 75 |
except Exception as e:
|
| 76 |
logger.error(f"API key validation error: {str(e)}")
|
|
|
|
| 81 |
if "api_key" not in session:
|
| 82 |
logger.warning("Generate notebook request without API key")
|
| 83 |
return jsonify({"success": False, "message": "API key not set"}), 401
|
|
|
|
| 84 |
# Handle both GET (for streaming) and POST requests
|
| 85 |
if request.method == "GET":
|
| 86 |
prompt = request.args.get("prompt")
|
|
|
|
| 102 |
|
| 103 |
try:
|
| 104 |
genai.configure(api_key=session["api_key"])
|
|
|
|
| 105 |
# OPTIMIZATION: If format_only is True, skip the AI call and just format the provided content
|
| 106 |
if request.method == "POST" and format_only:
|
| 107 |
# Use client-provided content as is (it's already the AI response)
|
| 108 |
notebook_content = prompt
|
| 109 |
notebook_json = format_notebook(notebook_content)
|
| 110 |
notebook_info = extract_notebook_info(notebook_content)
|
|
|
|
| 111 |
return jsonify({
|
| 112 |
"success": True,
|
| 113 |
"notebook": notebook_json,
|
|
|
|
| 121 |
notebook_content = generate_notebook(prompt, api_model_name)
|
| 122 |
notebook_json = format_notebook(notebook_content)
|
| 123 |
notebook_info = extract_notebook_info(notebook_content)
|
|
|
|
| 124 |
return jsonify({
|
| 125 |
"success": True,
|
| 126 |
"notebook": notebook_json,
|
|
|
|
| 176 |
# Map the frontend model name to the API model name
|
| 177 |
api_model_name = get_api_model_name(model_name)
|
| 178 |
|
|
|
|
|
|
|
| 179 |
try:
|
| 180 |
+
genai.configure(api_key=session["api_key"])
|
| 181 |
if stream:
|
| 182 |
return stream_notebook_edit(edit_prompt, notebook_json, api_model_name)
|
| 183 |
else:
|
|
|
|
| 185 |
edited_content = edit_notebook(edit_prompt, notebook_json, api_model_name)
|
| 186 |
notebook_json = format_notebook(edited_content)
|
| 187 |
notebook_info = extract_notebook_info(edited_content)
|
|
|
|
| 188 |
return jsonify({
|
| 189 |
"success": True,
|
| 190 |
"notebook": notebook_json,
|
|
|
|
| 192 |
"description": notebook_info["description"]
|
| 193 |
})
|
| 194 |
except Exception as e:
|
| 195 |
+
logger.error(f"Error editing notebook: {str(e)}", exc_info=True)
|
| 196 |
return jsonify({"success": False, "message": str(e)}), 500
|
| 197 |
|
| 198 |
@app.route("/download_notebook", methods=["POST"])
|
| 199 |
def download_notebook():
|
|
|
|
|
|
|
| 200 |
data = request.json
|
| 201 |
notebook_json = data.get("notebook")
|
| 202 |
filename = data.get("filename", f"notebook_{uuid.uuid4()}.ipynb")
|
|
|
|
| 212 |
mimetype="application/json",
|
| 213 |
headers={"Content-Disposition": f"attachment;filename={filename}"}
|
| 214 |
)
|
|
|
|
| 215 |
return response
|
| 216 |
|
| 217 |
+
@app.route("/session_test", methods=["GET"])
|
| 218 |
+
def session_test():
|
| 219 |
+
if "session_test" not in session:
|
| 220 |
+
session["session_test"] = True
|
| 221 |
+
is_new = True
|
| 222 |
+
else:
|
| 223 |
+
is_new = False
|
| 224 |
+
|
| 225 |
+
# Log the current session state
|
| 226 |
+
session_vars = list(session.keys()) if session else []
|
| 227 |
+
logger.info(f"Session check - variables: {session_vars}")
|
| 228 |
+
|
| 229 |
+
# Check if API key is available from URL parameter (fallback for HF)
|
| 230 |
+
api_key_param = request.args.get('api_key_param')
|
| 231 |
+
if api_key_param and "api_key" not in session:
|
| 232 |
+
global global_api_key
|
| 233 |
+
try:
|
| 234 |
+
# Validate it quickly before accepting
|
| 235 |
+
genai.configure(api_key=api_key_param)
|
| 236 |
+
model = genai.GenerativeModel("gemini-2.0-pro-exp-02-05")
|
| 237 |
+
# If no exception, store it
|
| 238 |
+
session["api_key"] = api_key_param
|
| 239 |
+
global_api_key = api_key_param
|
| 240 |
+
logger.info("API key set from URL parameter")
|
| 241 |
+
except Exception as e:
|
| 242 |
+
logger.error(f"Invalid API key from URL parameter: {str(e)}")
|
| 243 |
+
|
| 244 |
+
return jsonify({
|
| 245 |
+
"session_works": True,
|
| 246 |
+
"is_new_session": is_new,
|
| 247 |
+
"has_api_key": "api_key" in session,
|
| 248 |
+
"session_vars": session_vars
|
| 249 |
+
})
|
| 250 |
+
|
| 251 |
if __name__ == "__main__":
|
|
|
|
| 252 |
debug_mode = os.environ.get("FLASK_ENV") == "development"
|
| 253 |
+
port = int(os.environ.get("PORT", 5000))
|
| 254 |
+
app.run(host="0.0.0.0", port=port, debug=debug_mode)
|
flask_session/2029240f6d1128be89ddc32729463129
ADDED
|
Binary file (9 Bytes). View file
|
|
|
flask_session/45cc51dcfe7cf83166facbd1c993e325
ADDED
|
Binary file (86 Bytes). View file
|
|
|
static/js/main.js
CHANGED
|
@@ -166,6 +166,9 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
| 166 |
// Store API key in localStorage as backup
|
| 167 |
localStorage.setItem('notegenie_api_key', apiKey);
|
| 168 |
|
|
|
|
|
|
|
|
|
|
| 169 |
if (document.querySelector('#apiKeyModal.show')) {
|
| 170 |
showApiKeyFeedback('API key saved successfully!', 'success');
|
| 171 |
setTimeout(() => {
|
|
@@ -302,12 +305,24 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
| 302 |
function createEventSource() {
|
| 303 |
// Create a new event source with a unique timestamp to prevent caching
|
| 304 |
const timestamp = Date.now();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 305 |
eventSource = new EventSource(`/generate_notebook?${new URLSearchParams({
|
| 306 |
prompt: prompt,
|
| 307 |
model: modelName,
|
| 308 |
stream: true,
|
| 309 |
t: timestamp // Add timestamp to prevent caching
|
| 310 |
-
}).toString()}`);
|
| 311 |
|
| 312 |
eventSource.onmessage = function(event) {
|
| 313 |
// Update our last-activity timestamp
|
|
@@ -408,7 +423,17 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
| 408 |
|
| 409 |
// Check if it's an auth error (most likely API key not set)
|
| 410 |
if (err.status === 401) {
|
| 411 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 412 |
showApiKeyModal();
|
| 413 |
} else {
|
| 414 |
// Try to salvage what we have so far
|
|
|
|
| 166 |
// Store API key in localStorage as backup
|
| 167 |
localStorage.setItem('notegenie_api_key', apiKey);
|
| 168 |
|
| 169 |
+
// For Hugging Face, add a flag that API key was set in this session
|
| 170 |
+
sessionStorage.setItem('api_key_set', 'true');
|
| 171 |
+
|
| 172 |
if (document.querySelector('#apiKeyModal.show')) {
|
| 173 |
showApiKeyFeedback('API key saved successfully!', 'success');
|
| 174 |
setTimeout(() => {
|
|
|
|
| 305 |
function createEventSource() {
|
| 306 |
// Create a new event source with a unique timestamp to prevent caching
|
| 307 |
const timestamp = Date.now();
|
| 308 |
+
|
| 309 |
+
// On Hugging Face, add API key as URL parameter if stored (fallback mechanism)
|
| 310 |
+
let apiKeyParam = '';
|
| 311 |
+
if (window.location.hostname.includes('huggingface.co') ||
|
| 312 |
+
window.location.hostname.includes('hf.space')) {
|
| 313 |
+
const storedApiKey = localStorage.getItem('notegenie_api_key');
|
| 314 |
+
if (storedApiKey && !sessionStorage.getItem('api_key_tried_in_url')) {
|
| 315 |
+
apiKeyParam = `&api_key_param=${encodeURIComponent(storedApiKey)}`;
|
| 316 |
+
sessionStorage.setItem('api_key_tried_in_url', 'true');
|
| 317 |
+
}
|
| 318 |
+
}
|
| 319 |
+
|
| 320 |
eventSource = new EventSource(`/generate_notebook?${new URLSearchParams({
|
| 321 |
prompt: prompt,
|
| 322 |
model: modelName,
|
| 323 |
stream: true,
|
| 324 |
t: timestamp // Add timestamp to prevent caching
|
| 325 |
+
}).toString()}${apiKeyParam}`);
|
| 326 |
|
| 327 |
eventSource.onmessage = function(event) {
|
| 328 |
// Update our last-activity timestamp
|
|
|
|
| 423 |
|
| 424 |
// Check if it's an auth error (most likely API key not set)
|
| 425 |
if (err.status === 401) {
|
| 426 |
+
// For Hugging Face: If we have an API key in storage but it's not being recognized
|
| 427 |
+
// in the session, show a special message
|
| 428 |
+
if ((window.location.hostname.includes('huggingface.co') ||
|
| 429 |
+
window.location.hostname.includes('hf.space')) &&
|
| 430 |
+
localStorage.getItem('notegenie_api_key') &&
|
| 431 |
+
sessionStorage.getItem('api_key_set')) {
|
| 432 |
+
|
| 433 |
+
updateAiMessage(aiMessageId, '**Error: Session issue with API key.** \n\nPlease try refreshing the page completely, then set your API key again.');
|
| 434 |
+
} else {
|
| 435 |
+
updateAiMessage(aiMessageId, '**Error: API key not set or invalid.** \n\nPlease click the API Key button in the top right corner to set your Google Gemini API key.');
|
| 436 |
+
}
|
| 437 |
showApiKeyModal();
|
| 438 |
} else {
|
| 439 |
// Try to salvage what we have so far
|
templates/index.html
CHANGED
|
@@ -229,5 +229,28 @@
|
|
| 229 |
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/autoloader/prism-autoloader.min.js"></script>
|
| 230 |
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
| 231 |
<script src="{{ url_for('static', filename='js/main.js') }}"></script>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 232 |
</body>
|
| 233 |
</html>
|
|
|
|
| 229 |
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/autoloader/prism-autoloader.min.js"></script>
|
| 230 |
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
| 231 |
<script src="{{ url_for('static', filename='js/main.js') }}"></script>
|
| 232 |
+
|
| 233 |
+
<!-- Hugging Face Spaces Session Check -->
|
| 234 |
+
<script>
|
| 235 |
+
if (window.location.hostname.includes('huggingface.co') || window.location.hostname.includes('hf.space')) {
|
| 236 |
+
console.log("Running on Hugging Face Spaces");
|
| 237 |
+
// Check if cookies are enabled
|
| 238 |
+
if (navigator.cookieEnabled) {
|
| 239 |
+
console.log("Cookies are enabled");
|
| 240 |
+
} else {
|
| 241 |
+
console.warn("Cookies are disabled - sessions won't work!");
|
| 242 |
+
}
|
| 243 |
+
|
| 244 |
+
// Add a hidden diagonstic endpoint
|
| 245 |
+
fetch('/session_check')
|
| 246 |
+
.then(response => response.json())
|
| 247 |
+
.then(data => {
|
| 248 |
+
console.log("Session check:", data);
|
| 249 |
+
})
|
| 250 |
+
.catch(err => {
|
| 251 |
+
console.error("Session check failed:", err);
|
| 252 |
+
});
|
| 253 |
+
}
|
| 254 |
+
</script>
|
| 255 |
</body>
|
| 256 |
</html>
|
utils/__pycache__/ai_helpers.cpython-39.pyc
ADDED
|
Binary file (7.73 kB). View file
|
|
|
utils/__pycache__/notebook_helpers.cpython-39.pyc
ADDED
|
Binary file (5.24 kB). View file
|
|
|
utils/ai_helpers.py
CHANGED
|
@@ -115,8 +115,12 @@ def edit_notebook(edit_request, notebook_json, model_name="gemini-2.0-pro-exp-02
|
|
| 115 |
|
| 116 |
return response.text
|
| 117 |
|
| 118 |
-
def stream_notebook_generation(user_prompt, model_name="gemini-2.0-pro-exp-02-05"):
|
| 119 |
"""Stream notebook generation responses from Gemini API."""
|
|
|
|
|
|
|
|
|
|
|
|
|
| 120 |
model = genai.GenerativeModel(model_name)
|
| 121 |
enhanced_prompt = craft_notebook_prompt(user_prompt)
|
| 122 |
|
|
@@ -208,8 +212,12 @@ def stream_notebook_generation(user_prompt, model_name="gemini-2.0-pro-exp-02-05
|
|
| 208 |
headers=headers
|
| 209 |
)
|
| 210 |
|
| 211 |
-
def stream_notebook_edit(edit_request, notebook_json, model_name="gemini-2.0-pro-exp-02-05"):
|
| 212 |
"""Stream notebook editing responses from Gemini API."""
|
|
|
|
|
|
|
|
|
|
|
|
|
| 213 |
model = genai.GenerativeModel(model_name)
|
| 214 |
enhanced_prompt = craft_edit_prompt(edit_request, notebook_json)
|
| 215 |
|
|
|
|
| 115 |
|
| 116 |
return response.text
|
| 117 |
|
| 118 |
+
def stream_notebook_generation(user_prompt, model_name="gemini-2.0-pro-exp-02-05", api_key=None):
|
| 119 |
"""Stream notebook generation responses from Gemini API."""
|
| 120 |
+
# Configure the API with the key if provided
|
| 121 |
+
if api_key:
|
| 122 |
+
genai.configure(api_key=api_key)
|
| 123 |
+
|
| 124 |
model = genai.GenerativeModel(model_name)
|
| 125 |
enhanced_prompt = craft_notebook_prompt(user_prompt)
|
| 126 |
|
|
|
|
| 212 |
headers=headers
|
| 213 |
)
|
| 214 |
|
| 215 |
+
def stream_notebook_edit(edit_request, notebook_json, model_name="gemini-2.0-pro-exp-02-05", api_key=None):
|
| 216 |
"""Stream notebook editing responses from Gemini API."""
|
| 217 |
+
# Configure the API with the key if provided
|
| 218 |
+
if api_key:
|
| 219 |
+
genai.configure(api_key=api_key)
|
| 220 |
+
|
| 221 |
model = genai.GenerativeModel(model_name)
|
| 222 |
enhanced_prompt = craft_edit_prompt(edit_request, notebook_json)
|
| 223 |
|