Your Name commited on
Commit
e2bbbec
·
1 Parent(s): 0daace9

Add availability checking and error page for missing Gemini API key

Browse files

- Add /api/availability endpoint to check app state
- Add /api/is_initialized endpoint for polling initialization
- Add /error route with informative error page
- Update frontend to check availability and redirect on missing key
- Implement continuous polling during initialization
- Fix API URL building for static page compatibility

Files changed (3) hide show
  1. app.py +75 -12
  2. static/js/script.js +118 -36
  3. templates/error.html +100 -0
app.py CHANGED
@@ -1,4 +1,4 @@
1
- from flask import Flask, render_template, request, jsonify
2
  import os
3
  import time
4
  from dotenv import load_dotenv
@@ -17,15 +17,17 @@ logging.info("=" * 60)
17
  logging.info("ENVIRONMENT VARIABLES CHECK")
18
  logging.info("=" * 60)
19
  gemini_key = os.environ.get('GEMINI_API_KEY')
 
20
  if gemini_key:
21
  logging.info(f"✓ GEMINI_API_KEY found (length: {len(gemini_key)} chars)")
22
  logging.info(f" First 10 chars: {gemini_key[:10]}...")
23
  else:
 
24
  logging.error("=" * 60)
25
- logging.error("✗ FATAL ERROR: GEMINI_API_KEY not found in environment!")
26
  logging.error("=" * 60)
27
  logging.error("")
28
- logging.error("The GEMINI_API_KEY environment variable is required to run this application.")
29
  logging.error("")
30
  logging.error("For Hugging Face Spaces:")
31
  logging.error(" 1. Go to Settings → Repository secrets")
@@ -44,10 +46,6 @@ else:
44
  str([k for k in os.environ.keys() if 'GOOGLE' in k.upper()]))
45
  logging.error("=" * 60)
46
 
47
- # Exit the application
48
- import sys
49
- sys.exit(1)
50
-
51
  logging.info("=" * 60)
52
 
53
  # Download required NLTK data on startup
@@ -90,12 +88,16 @@ if missing_vars:
90
 
91
  def initialize_gemini():
92
  """Initialize Gemini API specifically"""
93
- global galatea_ai, gemini_initialized
94
 
95
  if not galatea_ai:
96
  logging.warning("Cannot initialize Gemini: GalateaAI instance not created yet")
97
  return False
98
 
 
 
 
 
99
  try:
100
  # Check for GEMINI_API_KEY
101
  if not os.environ.get('GEMINI_API_KEY'):
@@ -124,6 +126,10 @@ def initialize_components():
124
 
125
  if initializing or is_initialized:
126
  return
 
 
 
 
127
 
128
  initializing = True
129
  logging.info("Starting to initialize Galatea components...")
@@ -163,7 +169,7 @@ def home():
163
  # Add error handling for template rendering
164
  try:
165
  # Start initialization in background if not already started
166
- if not is_initialized and not initializing:
167
  Thread(target=initialize_components).start()
168
 
169
  return render_template('index.html')
@@ -174,9 +180,15 @@ def home():
174
  @app.route('/api/chat', methods=['POST'])
175
  def chat():
176
  # Check if components are initialized
 
 
 
 
 
 
177
  if not is_initialized:
178
  # Start initialization if not already started
179
- if not initializing:
180
  Thread(target=initialize_components).start()
181
 
182
  return jsonify({
@@ -390,9 +402,54 @@ def health():
390
  return jsonify({
391
  'status': 'ok',
392
  'gemini_available': hasattr(galatea_ai, 'gemini_available') and galatea_ai.gemini_available if galatea_ai else False,
393
- 'is_initialized': is_initialized
 
394
  })
395
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
396
  @app.route('/status')
397
  def status():
398
  """Status endpoint to check initialization progress"""
@@ -400,9 +457,15 @@ def status():
400
  'is_initialized': is_initialized,
401
  'initializing': initializing,
402
  'emotions': galatea_ai.emotional_state if galatea_ai else {'joy': 0.2, 'sadness': 0.2, 'anger': 0.2, 'fear': 0.2, 'curiosity': 0.2},
403
- 'avatar_shape': avatar_engine.avatar_model if avatar_engine and is_initialized else 'Circle'
 
404
  })
405
 
 
 
 
 
 
406
  if __name__ == '__main__':
407
  print("Starting Galatea Web Interface...")
408
  print("The chatbot will initialize in the background when first accessed.")
 
1
+ from flask import Flask, render_template, request, jsonify, url_for
2
  import os
3
  import time
4
  from dotenv import load_dotenv
 
17
  logging.info("ENVIRONMENT VARIABLES CHECK")
18
  logging.info("=" * 60)
19
  gemini_key = os.environ.get('GEMINI_API_KEY')
20
+ missing_gemini_key = False
21
  if gemini_key:
22
  logging.info(f"✓ GEMINI_API_KEY found (length: {len(gemini_key)} chars)")
23
  logging.info(f" First 10 chars: {gemini_key[:10]}...")
24
  else:
25
+ missing_gemini_key = True
26
  logging.error("=" * 60)
27
+ logging.error("✗ GEMINI_API_KEY not found in environment!")
28
  logging.error("=" * 60)
29
  logging.error("")
30
+ logging.error("The GEMINI_API_KEY environment variable is required for full functionality.")
31
  logging.error("")
32
  logging.error("For Hugging Face Spaces:")
33
  logging.error(" 1. Go to Settings → Repository secrets")
 
46
  str([k for k in os.environ.keys() if 'GOOGLE' in k.upper()]))
47
  logging.error("=" * 60)
48
 
 
 
 
 
49
  logging.info("=" * 60)
50
 
51
  # Download required NLTK data on startup
 
88
 
89
  def initialize_gemini():
90
  """Initialize Gemini API specifically"""
91
+ global gemini_initialized
92
 
93
  if not galatea_ai:
94
  logging.warning("Cannot initialize Gemini: GalateaAI instance not created yet")
95
  return False
96
 
97
+ if missing_gemini_key:
98
+ logging.error("Cannot initialize Gemini: GEMINI_API_KEY is missing")
99
+ return False
100
+
101
  try:
102
  # Check for GEMINI_API_KEY
103
  if not os.environ.get('GEMINI_API_KEY'):
 
126
 
127
  if initializing or is_initialized:
128
  return
129
+
130
+ if missing_gemini_key:
131
+ logging.error("Initialization aborted: GEMINI_API_KEY missing")
132
+ return
133
 
134
  initializing = True
135
  logging.info("Starting to initialize Galatea components...")
 
169
  # Add error handling for template rendering
170
  try:
171
  # Start initialization in background if not already started
172
+ if not is_initialized and not initializing and not missing_gemini_key:
173
  Thread(target=initialize_components).start()
174
 
175
  return render_template('index.html')
 
180
  @app.route('/api/chat', methods=['POST'])
181
  def chat():
182
  # Check if components are initialized
183
+ if missing_gemini_key:
184
+ return jsonify({
185
+ 'error': 'GEMINI_API_KEY is missing. Chat is unavailable.',
186
+ 'status': 'missing_gemini_key'
187
+ }), 503
188
+
189
  if not is_initialized:
190
  # Start initialization if not already started
191
+ if not initializing and not missing_gemini_key:
192
  Thread(target=initialize_components).start()
193
 
194
  return jsonify({
 
402
  return jsonify({
403
  'status': 'ok',
404
  'gemini_available': hasattr(galatea_ai, 'gemini_available') and galatea_ai.gemini_available if galatea_ai else False,
405
+ 'is_initialized': is_initialized,
406
+ 'missing_gemini_key': missing_gemini_key
407
  })
408
 
409
+ @app.route('/api/availability')
410
+ def availability():
411
+ """Report overall availability state to the frontend"""
412
+ if missing_gemini_key:
413
+ return jsonify({
414
+ 'available': False,
415
+ 'status': 'missing_gemini_key',
416
+ 'is_initialized': False,
417
+ 'initializing': False,
418
+ 'missing_gemini_key': True,
419
+ 'error_page': url_for('error_page')
420
+ })
421
+
422
+ if initializing or not is_initialized:
423
+ return jsonify({
424
+ 'available': False,
425
+ 'status': 'initializing',
426
+ 'is_initialized': is_initialized,
427
+ 'initializing': initializing,
428
+ 'missing_gemini_key': False
429
+ })
430
+
431
+ return jsonify({
432
+ 'available': True,
433
+ 'status': 'ready',
434
+ 'is_initialized': True,
435
+ 'initializing': False,
436
+ 'missing_gemini_key': False
437
+ })
438
+
439
+ @app.route('/api/is_initialized')
440
+ def is_initialized_endpoint():
441
+ """Lightweight endpoint for polling initialization progress"""
442
+ payload = {
443
+ 'is_initialized': is_initialized,
444
+ 'initializing': initializing,
445
+ 'missing_gemini_key': missing_gemini_key
446
+ }
447
+
448
+ if missing_gemini_key:
449
+ payload['error_page'] = url_for('error_page')
450
+
451
+ return jsonify(payload)
452
+
453
  @app.route('/status')
454
  def status():
455
  """Status endpoint to check initialization progress"""
 
457
  'is_initialized': is_initialized,
458
  'initializing': initializing,
459
  'emotions': galatea_ai.emotional_state if galatea_ai else {'joy': 0.2, 'sadness': 0.2, 'anger': 0.2, 'fear': 0.2, 'curiosity': 0.2},
460
+ 'avatar_shape': avatar_engine.avatar_model if avatar_engine and is_initialized else 'Circle',
461
+ 'missing_gemini_key': missing_gemini_key
462
  })
463
 
464
+ @app.route('/error')
465
+ def error_page():
466
+ """Render an informative error page when the app is unavailable"""
467
+ return render_template('error.html', missing_gemini_key=missing_gemini_key)
468
+
469
  if __name__ == '__main__':
470
  print("Starting Galatea Web Interface...")
471
  print("The chatbot will initialize in the background when first accessed.")
static/js/script.js CHANGED
@@ -7,6 +7,18 @@ document.addEventListener('DOMContentLoaded', function() {
7
  const avatar = document.getElementById('avatar');
8
  const statusContainer = document.getElementById('status-container');
9
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  // Debug: Log if elements are found
11
  console.log('Elements found:', {
12
  chatMessages: !!chatMessages,
@@ -26,8 +38,8 @@ document.addEventListener('DOMContentLoaded', function() {
26
  // Check server health on load
27
  checkServerHealth();
28
 
29
- // Start checking initialization status
30
- checkInitializationStatus();
31
 
32
  // Auto-resize textarea as user types
33
  if (userInput) {
@@ -71,10 +83,14 @@ document.addEventListener('DOMContentLoaded', function() {
71
  }
72
 
73
  function checkServerHealth() {
74
- fetch('/health')
75
  .then(response => response.json())
76
  .then(data => {
77
  if (data.status === 'ok') {
 
 
 
 
78
  if (data.gemini_available) {
79
  showStatusMessage('Connected to Gemini API', false);
80
  } else {
@@ -100,6 +116,82 @@ document.addEventListener('DOMContentLoaded', function() {
100
  }, 5000);
101
  }
102
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
103
  function sendMessage() {
104
  console.log('sendMessage called');
105
 
@@ -128,7 +220,7 @@ document.addEventListener('DOMContentLoaded', function() {
128
 
129
  // Send to backend
130
  console.log('Sending to backend...');
131
- fetch('/api/chat', {
132
  method: 'POST',
133
  headers: {
134
  'Content-Type': 'application/json'
@@ -217,6 +309,10 @@ document.addEventListener('DOMContentLoaded', function() {
217
  // Add initialization status check with loading screen
218
  let initCheckCount = 0;
219
  function checkInitializationStatus() {
 
 
 
 
220
  const loadingScreen = document.getElementById('loading-screen');
221
  const chatContainer = document.getElementById('chat-container');
222
  const loadingStatus = document.getElementById('loading-status');
@@ -228,46 +324,32 @@ document.addEventListener('DOMContentLoaded', function() {
228
  loadingProgress.style.width = `${progress}%`;
229
  }
230
 
231
- fetch('/status')
232
  .then(response => response.json())
233
  .then(data => {
 
 
 
 
 
 
 
 
 
 
 
234
  if (data.initializing) {
235
- // Still initializing
236
  if (loadingStatus) {
237
  loadingStatus.textContent = 'Initializing AI components...';
238
  }
239
  setTimeout(checkInitializationStatus, 2000); // Check again in 2 seconds
240
- } else if (data.is_initialized) {
241
- // Initialization complete!
242
- if (loadingProgress) {
243
- loadingProgress.style.width = '100%';
244
- }
245
- if (loadingStatus) {
246
- loadingStatus.textContent = 'Ready! Welcome to Galatea AI';
247
- }
248
-
249
- // Hide loading screen and show chat after a brief delay
250
- setTimeout(() => {
251
- if (loadingScreen) {
252
- loadingScreen.classList.add('fade-out');
253
- setTimeout(() => {
254
- loadingScreen.style.display = 'none';
255
- if (chatContainer) {
256
- chatContainer.style.display = 'flex';
257
- }
258
- }, 500);
259
- }
260
 
261
- // Start polling for avatar updates when initialized
262
- startAvatarPolling();
263
- }, 1000);
264
- } else {
265
- // Something wrong with initialization
266
- if (loadingStatus) {
267
- loadingStatus.textContent = 'Initialization taking longer than expected...';
268
- }
269
- setTimeout(checkInitializationStatus, 3000);
270
  }
 
271
  })
272
  .catch(error => {
273
  console.error('Error checking status:', error);
@@ -294,7 +376,7 @@ document.addEventListener('DOMContentLoaded', function() {
294
  }
295
 
296
  function pollAvatarState() {
297
- fetch('/api/avatar')
298
  .then(response => response.json())
299
  .then(data => {
300
  if (data.avatar_shape && data.is_initialized) {
 
7
  const avatar = document.getElementById('avatar');
8
  const statusContainer = document.getElementById('status-container');
9
 
10
+ // Base API URL detection
11
+ const DEFAULT_API_BASE = 'http://127.0.0.1:7860';
12
+ const origin = window.location.origin;
13
+ const API_BASE_URL = origin && origin !== 'null' ? origin : DEFAULT_API_BASE;
14
+ const buildApiUrl = (path) => {
15
+ const normalizedPath = path.startsWith('/') ? path : `/${path}`;
16
+ return `${API_BASE_URL}${normalizedPath}`;
17
+ };
18
+
19
+ let initializationComplete = false;
20
+ let initializationPollingActive = false;
21
+
22
  // Debug: Log if elements are found
23
  console.log('Elements found:', {
24
  chatMessages: !!chatMessages,
 
38
  // Check server health on load
39
  checkServerHealth();
40
 
41
+ // Determine availability state
42
+ checkAvailability();
43
 
44
  // Auto-resize textarea as user types
45
  if (userInput) {
 
83
  }
84
 
85
  function checkServerHealth() {
86
+ fetch(buildApiUrl('/health'))
87
  .then(response => response.json())
88
  .then(data => {
89
  if (data.status === 'ok') {
90
+ if (data.missing_gemini_key) {
91
+ showStatusMessage('GEMINI_API_KEY missing. Chat will remain unavailable until it is configured.', true);
92
+ return;
93
+ }
94
  if (data.gemini_available) {
95
  showStatusMessage('Connected to Gemini API', false);
96
  } else {
 
116
  }, 5000);
117
  }
118
 
119
+ function ensureLoadingScreenVisible() {
120
+ const loadingScreen = document.getElementById('loading-screen');
121
+ const chatContainer = document.getElementById('chat-container');
122
+ if (loadingScreen) {
123
+ loadingScreen.style.display = 'flex';
124
+ loadingScreen.classList.remove('fade-out');
125
+ }
126
+ if (chatContainer) {
127
+ chatContainer.style.display = 'none';
128
+ }
129
+ }
130
+
131
+ function onAppReady() {
132
+ if (initializationComplete) {
133
+ return;
134
+ }
135
+
136
+ initializationComplete = true;
137
+ initializationPollingActive = false;
138
+
139
+ const loadingScreen = document.getElementById('loading-screen');
140
+ const chatContainer = document.getElementById('chat-container');
141
+ const loadingStatus = document.getElementById('loading-status');
142
+ const loadingProgress = document.getElementById('loading-progress');
143
+
144
+ if (loadingProgress) {
145
+ loadingProgress.style.width = '100%';
146
+ }
147
+ if (loadingStatus) {
148
+ loadingStatus.textContent = 'Ready! Welcome to Galatea AI';
149
+ }
150
+
151
+ if (loadingScreen) {
152
+ loadingScreen.classList.add('fade-out');
153
+ setTimeout(() => {
154
+ loadingScreen.style.display = 'none';
155
+ if (chatContainer) {
156
+ chatContainer.style.display = 'flex';
157
+ }
158
+ }, 500);
159
+ } else if (chatContainer) {
160
+ chatContainer.style.display = 'flex';
161
+ }
162
+
163
+ startAvatarPolling();
164
+ }
165
+
166
+ function checkAvailability() {
167
+ fetch(buildApiUrl('/api/availability'))
168
+ .then(response => response.json())
169
+ .then(data => {
170
+ if (!data.available) {
171
+ if (data.status === 'missing_gemini_key') {
172
+ const errorPath = data.error_page || '/error';
173
+ window.location.href = `${API_BASE_URL}${errorPath}`;
174
+ return;
175
+ }
176
+
177
+ if (data.status === 'initializing') {
178
+ ensureLoadingScreenVisible();
179
+ if (!initializationPollingActive) {
180
+ initializationPollingActive = true;
181
+ checkInitializationStatus();
182
+ }
183
+ return;
184
+ }
185
+ }
186
+
187
+ onAppReady();
188
+ })
189
+ .catch(error => {
190
+ console.error('Error checking availability:', error);
191
+ setTimeout(checkAvailability, 3000);
192
+ });
193
+ }
194
+
195
  function sendMessage() {
196
  console.log('sendMessage called');
197
 
 
220
 
221
  // Send to backend
222
  console.log('Sending to backend...');
223
+ fetch(buildApiUrl('/api/chat'), {
224
  method: 'POST',
225
  headers: {
226
  'Content-Type': 'application/json'
 
309
  // Add initialization status check with loading screen
310
  let initCheckCount = 0;
311
  function checkInitializationStatus() {
312
+ if (initializationComplete) {
313
+ return;
314
+ }
315
+
316
  const loadingScreen = document.getElementById('loading-screen');
317
  const chatContainer = document.getElementById('chat-container');
318
  const loadingStatus = document.getElementById('loading-status');
 
324
  loadingProgress.style.width = `${progress}%`;
325
  }
326
 
327
+ fetch(buildApiUrl('/api/is_initialized'))
328
  .then(response => response.json())
329
  .then(data => {
330
+ if (data.missing_gemini_key) {
331
+ const errorPath = data.error_page || '/error';
332
+ window.location.href = `${API_BASE_URL}${errorPath}`;
333
+ return;
334
+ }
335
+
336
+ if (data.is_initialized) {
337
+ onAppReady();
338
+ return;
339
+ }
340
+
341
  if (data.initializing) {
 
342
  if (loadingStatus) {
343
  loadingStatus.textContent = 'Initializing AI components...';
344
  }
345
  setTimeout(checkInitializationStatus, 2000); // Check again in 2 seconds
346
+ return;
347
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
348
 
349
+ if (loadingStatus) {
350
+ loadingStatus.textContent = 'Initialization taking longer than expected...';
 
 
 
 
 
 
 
351
  }
352
+ setTimeout(checkInitializationStatus, 3000);
353
  })
354
  .catch(error => {
355
  console.error('Error checking status:', error);
 
376
  }
377
 
378
  function pollAvatarState() {
379
+ fetch(buildApiUrl('/api/avatar'))
380
  .then(response => response.json())
381
  .then(data => {
382
  if (data.avatar_shape && data.is_initialized) {
templates/error.html ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Galatea AI | Configuration Required</title>
7
+ <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600&display=swap" rel="stylesheet">
8
+ <link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
9
+ <style>
10
+ body {
11
+ background-color: var(--background);
12
+ display: flex;
13
+ flex-direction: column;
14
+ align-items: center;
15
+ justify-content: center;
16
+ min-height: 100vh;
17
+ text-align: center;
18
+ padding: 20px;
19
+ }
20
+
21
+ .error-card {
22
+ background-color: var(--card-bg);
23
+ border-radius: 16px;
24
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
25
+ max-width: 520px;
26
+ width: 100%;
27
+ padding: 40px 32px;
28
+ }
29
+
30
+ .error-card h1 {
31
+ color: var(--primary-color);
32
+ font-weight: 600;
33
+ margin-bottom: 16px;
34
+ }
35
+
36
+ .error-card p {
37
+ margin-bottom: 12px;
38
+ color: var(--text-color);
39
+ }
40
+
41
+ .steps {
42
+ margin-top: 24px;
43
+ text-align: left;
44
+ }
45
+
46
+ .steps h2 {
47
+ font-size: 18px;
48
+ margin-bottom: 8px;
49
+ }
50
+
51
+ .steps ol, .steps ul {
52
+ padding-left: 20px;
53
+ margin-bottom: 16px;
54
+ }
55
+
56
+ .steps li {
57
+ margin-bottom: 6px;
58
+ }
59
+
60
+ .back-link {
61
+ margin-top: 24px;
62
+ display: inline-block;
63
+ color: var(--primary-color);
64
+ text-decoration: none;
65
+ font-weight: 500;
66
+ }
67
+ </style>
68
+ </head>
69
+ <body>
70
+ <div class="error-card">
71
+ <h1>Galatea AI is Unavailable</h1>
72
+ {% if missing_gemini_key %}
73
+ <p>We couldn't find a <code>GEMINI_API_KEY</code> in the current environment.</p>
74
+ <p>Please add the key and reload the page to continue.</p>
75
+
76
+ <div class="steps">
77
+ <h2>How to fix this</h2>
78
+ <p><strong>Local setup:</strong></p>
79
+ <ol>
80
+ <li>Create a <code>.env</code> file if it doesn't exist.</li>
81
+ <li>Add <code>GEMINI_API_KEY=&lt;your_key_here&gt;</code> to the file.</li>
82
+ <li>Restart the Flask app.</li>
83
+ </ol>
84
+
85
+ <p><strong>Hugging Face Spaces:</strong></p>
86
+ <ol>
87
+ <li>Open the Space settings and go to <em>Repository secrets</em>.</li>
88
+ <li>Add a secret named <code>GEMINI_API_KEY</code> with your Gemini API key.</li>
89
+ <li>Restart or reload the Space.</li>
90
+ </ol>
91
+ </div>
92
+ {% else %}
93
+ <p>The application is temporarily unavailable. Please try again shortly.</p>
94
+ {% endif %}
95
+
96
+ <a class="back-link" href="/">Return to Home</a>
97
+ </div>
98
+ </body>
99
+ </html>
100
+