- 1#!/usr/bin/env python3
- 2
- 3import os
- 4import sys
- 5import subprocess
- 6import json
- 7import logging
- 8import time
- 9import hashlib
- 10import random
- 11import string
- 12import uuid
- 13import re
- 14import traceback
- 15from datetime import datetime, timedelta, timezone
- 16from flask import Flask, request, jsonify
- 17from openai import OpenAI, APIError, APIConnectionError, Timeout
- 18import openai
- 19import flask
- 20
- 21try:
- 22 from zoneinfo import ZoneInfo # Python 3.9+
- 23except Exception: # pragma: no cover
- 24 ZoneInfo = None
- 25
- 26# ------------------------------------------------------------------------------
- 27# Logging
- 28# ------------------------------------------------------------------------------
- 29logging.basicConfig(
- 30 level=logging.DEBUG,
- 31 format='%(asctime)s - %(levelname)s - %(message)s',
- 32 handlers=[
- 33 logging.StreamHandler(sys.stdout),
- 34 logging.FileHandler('/tmp/xaiChatApi.log') # Will be overridden by config.json
- 35 ]
- 36)
- 37logger = logging.getLogger(__name__)
- 38logger.info("Starting XaiChatApi.py initialization")
- 39
- 40# ------------------------------------------------------------------------------
- 41# Config
- 42# ------------------------------------------------------------------------------
- 43def load_config():
- 44 config_path = os.path.join(os.path.dirname(__file__), 'config.json')
- 45 logger.debug(f"Attempting to load config from {config_path}")
- 46 try:
- 47 if not os.access(config_path, os.R_OK):
- 48 logger.error(f"No read permission for {config_path}")
- 49 sys.exit(1)
- 50 with open(config_path, 'r') as f:
- 51 config = json.load(f)
- 52 config['xai_api_key'] = os.getenv('XAI_API_KEY', config.get('xai_api_key', ''))
- 53 required_fields = ['xai_api_key','api_base_url','api_timeout','max_tokens','temperature',
- 54 'max_search_results','ignore_inputs','log_file','flask_host','flask_port',
- 55 'run_startup_test','system_prompt']
- 56 missing = [f for f in required_fields if f not in config]
- 57 if missing:
- 58 logger.error(f"Missing config fields: {missing}")
- 59 sys.exit(1)
- 60 if not config.get('system_prompt') or '{message}' not in config['system_prompt']:
- 61 logger.error("Invalid system_prompt in config.json: must include {message}")
- 62 sys.exit(1)
- 63 # switch log file to config's path
- 64 for h in logger.handlers[:]:
- 65 if isinstance(h, logging.FileHandler):
- 66 logger.removeHandler(h)
- 67 logger.addHandler(logging.FileHandler(config['log_file']))
- 68 logger.info(f"Config loaded: {json.dumps({k: '****' if k == 'xai_api_key' else v for k, v in config.items()}, indent=2)}")
- 69 return config
- 70 except FileNotFoundError:
- 71 logger.error(f"Config file {config_path} not found"); sys.exit(1)
- 72 except json.JSONDecodeError as e:
- 73 logger.error(f"Invalid JSON in {config_path}: {str(e)}"); sys.exit(1)
- 74 except Exception as e:
- 75 logger.error(f"Config loading failed: {type(e).__name__}: {str(e)}")
- 76 logger.debug(f"Stack trace: {traceback.format_exc()}"); sys.exit(1)
- 77
- 78logger.info("Loading configuration")
- 79config = load_config()
- 80last_api_success = None
- 81
- 82# ------------------------------------------------------------------------------
- 83# Dependencies
- 84# ------------------------------------------------------------------------------
- 85def install_dependencies():
- 86 packages = ['flask>=3.0.0', 'openai>=1.0.0', 'gunicorn>=22.0']
- 87 logger.info("Checking dependencies")
- 88 try:
- 89 installed = subprocess.check_output([sys.executable, '-m', 'pip', 'list']).decode('utf-8')
- 90 logger.debug(f"Installed packages:\n{installed}")
- 91 for spec in packages:
- 92 name = spec.split('>=')[0].split('==')[0]
- 93 try:
- 94 __import__(name)
- 95 logger.info(f"{spec} already installed")
- 96 except ImportError:
- 97 logger.info(f"Installing {spec}...")
- 98 subprocess.check_call([sys.executable, '-m', 'pip', 'install', spec])
- 99 logger.info(f"Successfully installed {spec}")
- 100 except subprocess.CalledProcessError as e:
- 101 logger.error(f"Dependency installation failed: {str(e)}"); sys.exit(1)
- 102
- 103logger.info("Installing dependencies")
- 104try:
- 105 install_dependencies()
- 106except Exception as e:
- 107 logger.error(f"Startup failed during dependency installation: {type(e).__name__}: {str(e)}")
- 108 logger.debug(f"Stack trace: {traceback.format_exc()}"); sys.exit(1)
- 109
- 110# ------------------------------------------------------------------------------
- 111# Flask
- 112# ------------------------------------------------------------------------------
- 113logger.info("Initializing Flask app")
- 114try:
- 115 app = Flask(__name__)
- 116 app.secret_key = os.urandom(24)
- 117 app.start_time = time.time()
- 118except Exception as e:
- 119 logger.error(f"Startup failed during Flask initialization: {type(e).__name__}: {str(e)}")
- 120 logger.debug(f"Stack trace: {traceback.format_exc()}"); sys.exit(1)
- 121
- 122logger.info(f"Python version: {sys.version}")
- 123logger.info(f"Flask version: {flask.__version__}")
- 124logger.info(f"OpenAI version: {openai.__version__}")
- 125logger.info(f"Gunicorn command: {' '.join(sys.argv)}")
- 126logger.info(f"Environment: {json.dumps(dict(os.environ), indent=2)}")
- 127
- 128if not config['xai_api_key']:
- 129 logger.error("XAI_API_KEY not provided in config or environment"); sys.exit(1)
- 130
- 131# ------------------------------------------------------------------------------
- 132# Time intent detection (STRICT; avoids 'know' and 'story about time')
- 133# ------------------------------------------------------------------------------
- 134TIME_PATTERNS = [
- 135 r"\bwhat(?:'s|\s+is)?\s+(?:the\s+)?time\b", # what's the time
- 136 r"\bwhat(?:'s|\s+is)?\s+(?:the\s+)?time\s+(?:in|for)\s+.+", # what's the time in/for X
- 137 r"\b(?:current|local)\s+time\b", # current time / local time
- 138 r"\btime\s+(?:right\s+)?now\b", # time now / time right now
- 139 r"^\s*now\??\s*$", # "now?"
- 140 r"\bwhat(?:'s|\s+is)?\s+(?:the\s+)?date\b", # what's the date
- 141 r"\btoday'?s?\s+date\b", # today's date
- 142 r"\bdate\s+today\b", # date today
- 143 r"\bwhat\s+day\s+is\s+it\b", # what day is it
- 144 r"\bday\s+of\s+week\b", # day of week
- 145 r"\byesterday\b", # yesterday
- 146 r"\btime\s+(?:in|for)\s+.+", # time in/for X
- 147]
- 148
- 149def has_time_intent(msg: str) -> bool:
- 150 m = (msg or "").strip().lower()
- 151 return any(re.search(p, m, re.IGNORECASE) for p in TIME_PATTERNS)
- 152
- 153# ------------------------------------------------------------------------------
- 154# Response text normalizer (fix odd contractions/phrasing)
- 155# ------------------------------------------------------------------------------
- 156_CONTRACTION_FIXES = [
- 157 (re.compile(r"\bThe[’']ve\b"), "They’ve"), # fix 'The’ve' => 'They’ve'
- 158]
- 159
- 160_START_THEYVE_CHECKED = re.compile(
- 161 r"^\s*They[’']ve\s+checked\s+the\s+current\s+time\s+for\s+(.*?),(?:\s*and\s*)?",
- 162 re.IGNORECASE
- 163)
- 164
- 165def normalize_reply_text(text: str) -> str:
- 166 if not text:
- 167 return text
- 168 out = text
- 169 for pat, repl in _CONTRACTION_FIXES:
- 170 out = pat.sub(repl, out)
- 171 m = _START_THEYVE_CHECKED.match(out)
- 172 if m:
- 173 place = m.group(1).strip()
- 174 out = _START_THEYVE_CHECKED.sub(f"In {place}, ", out, count=1)
- 175 return out
- 176
- 177# ------------------------------------------------------------------------------
- 178# Optional: local time for common cities (offline)
- 179# ------------------------------------------------------------------------------
- 180CITY_TZ = {
- 181 "new york": "America/New_York", "nyc": "America/New_York",
- 182 "london": "Europe/London", "uk": "Europe/London", "britain": "Europe/London",
- 183 "paris": "Europe/Paris", "berlin": "Europe/Berlin", "madrid": "Europe/Madrid",
- 184 "rome": "Europe/Rome", "amsterdam": "Europe/Amsterdam", "zurich": "Europe/Zurich",
- 185 "stockholm": "Europe/Stockholm", "oslo": "Europe/Oslo", "copenhagen": "Europe/Copenhagen",
- 186 "helsinki": "Europe/Helsinki", "lisbon": "Europe/Lisbon", "dublin": "Europe/Dublin",
- 187 "chicago": "America/Chicago", "toronto": "America/Toronto", "vancouver": "America/Vancouver",
- 188 "los angeles": "America/Los_Angeles", "la": "America/Los_Angeles",
- 189 "san francisco": "America/Los_Angeles", "sf": "America/Los_Angeles",
- 190 "seattle": "America/Los_Angeles", "denver": "America/Denver", "phoenix": "America/Phoenix",
- 191 "mexico city": "America/Mexico_City", "boston": "America/New_York",
- 192 "sydney": "Australia/Sydney", "melbourne": "Australia/Melbourne",
- 193 "tokyo": "Asia/Tokyo", "seoul": "Asia/Seoul", "singapore": "Asia/Singapore",
- 194 "hong kong": "Asia/Hong_Kong", "shanghai": "Asia/Shanghai", "beijing": "Asia/Shanghai",
- 195 "delhi": "Asia/Kolkata", "mumbai": "Asia/Kolkata", "kolkata": "Asia/Kolkata",
- 196 "istanbul": "Europe/Istanbul", "moscow": "Europe/Moscow",
- 197 "cape town": "Africa/Johannesburg", "johannesburg": "Africa/Johannesburg",
- 198 "rio": "America/Sao_Paulo", "são paulo": "America/Sao_Paulo", "sao paulo": "America/Sao_Paulo",
- 199 "buenos aires": "America/Argentina/Buenos_Aires",
- 200}
- 201
- 202_LOC_RE = re.compile(r"\btime\s+(?:in|for)\s+(.+)", re.IGNORECASE)
- 203
- 204def _extract_location(q: str) -> str | None:
- 205 """Pull 'X' out of phrases like 'time in X' or 'time for X'."""
- 206 m = _LOC_RE.search(q or "")
- 207 if not m:
- 208 return None
- 209 loc = m.group(1).strip().lower()
- 210 # strip trailing punctuation
- 211 loc = re.sub(r"[?.!,;:\s]+$", "", loc)
- 212 return loc if loc else None
- 213
- 214def _local_time_string(now_utc: datetime, tz_name: str, city_label: str) -> str:
- 215 try:
- 216 if ZoneInfo is None:
- 217 return now_utc.strftime(f"It’s %I:%M %p UTC on %A, %B %d, %Y (no local timezone available).")
- 218 tz = ZoneInfo(tz_name)
- 219 local = now_utc.astimezone(tz)
- 220 abbr = local.tzname() or tz_name
- 221 return local.strftime(f"It’s %I:%M %p {abbr} on %A, %B %d, %Y in {city_label}.")
- 222 except Exception:
- 223 return now_utc.strftime(f"It’s %I:%M %p UTC on %A, %B %d, %Y (timezone error).")
- 224
- 225# ------------------------------------------------------------------------------
- 226# Startup ping
- 227# ------------------------------------------------------------------------------
- 228def test_api_connectivity():
- 229 global last_api_success
- 230 logger.info("Initializing OpenAI client for connectivity test")
- 231 try:
- 232 client = OpenAI(api_key=config['xai_api_key'], base_url=config['api_base_url'])
- 233 response = client.chat.completions.create(
- 234 model="grok-3",
- 235 messages=[{"role": "user", "content": "ping"}],
- 236 max_tokens=10,
- 237 timeout=10.0
- 238 )
- 239 last_api_success = time.time()
- 240 logger.info(f"API connectivity test successful: {response.choices[0].message.content}")
- 241 except Exception as e:
- 242 logger.error(f"API connectivity test failed: {type(e).__name__}: {str(e)}")
- 243 logger.debug(f"Stack trace: {traceback.format_exc()}"); sys.exit(1)
- 244
- 245if config['run_startup_test']:
- 246 logger.info("Running startup API connectivity test")
- 247 test_api_connectivity()
- 248else:
- 249 logger.info("Startup API connectivity test disabled in config")
- 250
- 251# ------------------------------------------------------------------------------
- 252# Prompt, parsing, fallback
- 253# ------------------------------------------------------------------------------
- 254def generate_system_prompt(session_id: str, timestamp: str) -> list:
- 255 try:
- 256 current_time = datetime.fromtimestamp(float(timestamp), tz=timezone.utc).strftime('%Y-%m-%d %H:%M:%S UTC')
- 257 prompt = config['system_prompt'].format(
- 258 session_id=session_id,
- 259 timestamp=timestamp,
- 260 current_time=current_time,
- 261 max_tokens=config['max_tokens'],
- 262 ignore_inputs=', '.join(config['ignore_inputs']),
- 263 message='{message}'
- 264 )
- 265 logger.debug(f"Generated system prompt: {prompt[:100]}... (length: {len(prompt)})")
- 266 return [
- 267 {"role": "system", "content": prompt},
- 268 {"role": "user", "content": "{message}"}
- 269 ]
- 270 except Exception as e:
- 271 logger.error(f"Prompt formatting failed: {type(e).__name__}: {str(e)}")
- 272 logger.debug(f"Stack trace: {traceback.format_exc()}"); raise
- 273
- 274def parse_response_date(response: str) -> datetime | None:
- 275 """Regex parse; no year-only matches to avoid false positives."""
- 276 try:
- 277 date_patterns = [
- 278 r'\b(\w+\s+\d{1,2},\s+\d{4})\b', # September 03, 2025
- 279 r'\b(\d{4}-\d{2}-\d{2})\b', # 2025-09-03
- 280 r'\b(\d{1,2}\s+\w+\s+\d{4})\b', # 03 September 2025
- 281 r'\b(\d{1,2}:\d{2}\s*(?:AM|PM))\b', # 04:14 PM
- 282 r'\b(\d{1,2}:\d{2})\b', # 04:14
- 283 ]
- 284 formats = ['%B %d, %Y','%Y-%m-%d','%d %B %Y','%I:%M %p','%H:%M']
- 285 for pattern in date_patterns:
- 286 m = re.search(pattern, response or "", re.IGNORECASE)
- 287 if not m: continue
- 288 date_str = m.group(1)
- 289 for fmt in formats:
- 290 try:
- 291 parsed = datetime.strptime(date_str, fmt)
- 292 if fmt in ('%I:%M %p','%H:%M'):
- 293 current = datetime.now(timezone.utc)
- 294 parsed = current.replace(hour=parsed.hour, minute=parsed.minute, second=0, microsecond=0)
- 295 return parsed.replace(tzinfo=timezone.utc)
- 296 except ValueError:
- 297 continue
- 298 logger.debug(f"No date parsed from response: {response}")
- 299 return None
- 300 except Exception as e:
- 301 logger.debug(f"Date parsing failed: {type(e).__name__}: {str(e)}")
- 302 logger.debug(f"Stack trace: {traceback.format_exc()}"); return None
- 303
- 304def calculate_time_fallback(query: str, current_time: str) -> str | None:
- 305 """Only answer explicit time/date questions. Supports 'time in/for CITY' via zoneinfo."""
- 306 try:
- 307 if not has_time_intent(query):
- 308 return None
- 309 lower = (query or "").lower()
- 310 now_utc = datetime.fromtimestamp(float(current_time), tz=timezone.utc)
- 311
- 312 # 'yesterday' explicit
- 313 if re.search(r"\byesterday\b", lower):
- 314 return (now_utc - timedelta(days=1)).strftime('Yesterday was %A, %B %d, %Y (UTC).')
- 315
- 316 # time in/for CITY
- 317 loc = _extract_location(query)
- 318 if loc:
- 319 # best-effort mapping
- 320 tz_name = CITY_TZ.get(loc)
- 321 # allow partial keys (e.g., "new york city" -> "new york")
- 322 if tz_name is None:
- 323 for key, val in CITY_TZ.items():
- 324 if key in loc:
- 325 tz_name = val; break
- 326 if tz_name:
- 327 return _local_time_string(now_utc, tz_name, loc.title())
- 328
- 329 # generic "what's the time"/"now?": answer in UTC
- 330 return now_utc.strftime("It’s %I:%M %p UTC on %A, %B %d, %Y.")
- 331 except Exception as e:
- 332 logger.error(f"Time fallback failed: {type(e).__name__}: {str(e)}")
- 333 logger.debug(f"Stack trace: {traceback.format_exc()}"); return None
- 334
- 335def process_grok_response(response, message: str, timestamp: str) -> str:
- 336 """Post-process model response and apply safe fallback only for explicit time/date questions."""
- 337 reply = response.choices[0].message.content.strip().replace(r'\\n', '\n')
- 338 logger.debug(f"Processing response: {reply}, token_usage={response.usage}")
- 339
- 340 if has_time_intent(message):
- 341 current = datetime.fromtimestamp(float(timestamp), tz=timezone.utc)
- 342 parsed_date = parse_response_date(reply)
- 343 is_valid = False
- 344 if parsed_date:
- 345 time_diff = abs((current - parsed_date).total_seconds())
- 346 is_valid = time_diff < 86400 # within 24h
- 347 logger.debug(f"Time validation: parsed_date={parsed_date}, diff={time_diff}s, valid={is_valid}")
- 348 else:
- 349 logger.debug("Time validation: no date parsed from model reply")
- 350
- 351 if not reply or 'unavailable' in (reply or '').lower() or not is_valid:
- 352 fallback = calculate_time_fallback(message, timestamp)
- 353 if fallback:
- 354 logger.info(f"Used fallback for explicit time query: {fallback}")
- 355 return fallback
- 356
- 357 # Final cleanup
- 358 reply = normalize_reply_text(reply)
- 359 return reply
- 360
- 361# ------------------------------------------------------------------------------
- 362# Endpoints
- 363# ------------------------------------------------------------------------------
- 364@app.route('/health', methods=['GET'])
- 365def health():
- 366 logger.info("Health check called")
- 367 return jsonify({'status': 'healthy'}), 200, {'Cache-Control': 'no-store, no-cache, must-revalidate, max-age=0'}
- 368
- 369@app.route('/debug', methods=['GET'])
- 370def debug():
- 371 logger.info("Debug endpoint called")
- 372 try:
- 373 with open(config['log_file'], 'r') as f:
- 374 recent_logs = f.readlines()[-5:]
- 375 except Exception as e:
- 376 recent_logs = [f"Error reading log file: {str(e)}"]
- 377
- 378 status = {
- 379 'config': {k: '****' if k == 'xai_api_key' else v for k, v in config.items()},
- 380 'uptime': time.time() - app.start_time,
- 381 'python_version': sys.version,
- 382 'flask_version': flask.__version__,
- 383 'openai_version': openai.__version__,
- 384 'last_api_success': last_api_success if last_api_success else 'Never',
- 385 'recent_logs': recent_logs,
- 386 'flask_host': config['flask_host'],
- 387 'flask_port': config['flask_port']
- 388 }
- 389 return jsonify(status), 200, {'Cache-Control': 'no-store, no-cache, must-revalidate, max-age=0'}
- 390
- 391@app.route('/chat', methods=['GET', 'POST'])
- 392def chat():
- 393 start_time = time.time()
- 394 session_id = str(uuid.uuid4())
- 395 timestamp = str(time.time())
- 396
- 397 if request.method == 'GET':
- 398 message = request.args.get('message', '')
- 399 nick = request.args.get('nick', 'unknown')
- 400 request_details = {'method': 'GET', 'args': dict(request.args), 'headers': dict(request.headers)}
- 401 else:
- 402 data = request.get_json(silent=True) or {}
- 403 message = data.get('message', '')
- 404 nick = data.get('nick', 'unknown')
- 405 request_details = {'method': 'POST', 'json': data, 'headers': dict(request.headers)}
- 406
- 407 logger.debug(f"Session ID: {session_id}, Timestamp: {timestamp}, Request details: {json.dumps(request_details, indent=2)}")
- 408
- 409 if not message:
- 410 logger.error(f"Session ID: {session_id}, Timestamp: {timestamp}, No message provided")
- 411 return jsonify({'error': 'No message provided', 'fallback': 'Please provide a message!'}), 400, {'Cache-Control': 'no-store, no-cache, must-revalidate, max-age=0'}
- 412
- 413 if message.lower().strip() in config['ignore_inputs']:
- 414 logger.info(f"Ignored non-substantive input from nick: {nick}, message: {message}")
- 415 return jsonify({'reply': ''}), 200, {'Cache-Control': 'no-store, no-cache, must-revalidate, max-age=0', 'X-Session-ID': session_id, 'X-Timestamp': timestamp}
- 416
- 417 logger.info(f"Session ID: {session_id}, Timestamp: {timestamp}, Request from nick: {nick}, message: {message}")
- 418
- 419 try:
- 420 conversation = generate_system_prompt(session_id, timestamp)
- 421 conversation[-1]['content'] = conversation[-1]['content'].format(message=message)
- 422 except Exception as e:
- 423 logger.error(f"Prompt generation failed: {type(e).__name__}: {str(e)}")
- 424 logger.debug(f"Stack trace: {traceback.format_exc()}")
- 425 return jsonify({'error': f"Prompt generation failed: {str(e)}", 'fallback': 'Sorry, I couldn\'t process that!'}), 500, {'Cache-Control': 'no-store, no-cache, must-revalidate, max-age=0'}
- 426
- 427 search_params = {}
- 428 search_keywords = ['weather', 'death', 'died', 'recent', 'news', 'what happened'] # no time keywords
- 429 if any(keyword in (message or '').lower() for keyword in search_keywords):
- 430 search_params = {'mode': 'on', 'max_search_results': config['max_search_results']}
- 431 logger.info(f"Live Search enabled for query: {message}")
- 432
- 433 logger.debug(f"API request payload: {json.dumps(conversation, indent=2)}")
- 434
- 435 try:
- 436 logger.info("Initializing OpenAI client")
- 437 client = OpenAI(api_key=config['xai_api_key'], base_url=config['api_base_url'])
- 438 api_start = time.time()
- 439 nonce = ''.join(random.choices(string.ascii_letters + string.digits, k=16))
- 440 headers = {
- 441 'X-Cache-Bypass': f"{time.time()}-{nonce}",
- 442 'X-Request-ID': str(random.randint(100000, 999999)),
- 443 'X-Session-ID': session_id,
- 444 'X-Timestamp': timestamp
- 445 }
- 446 logger.debug(f"Request headers: {headers}")
- 447
- 448 response = client.chat.completions.create(
- 449 model="grok-3",
- 450 messages=conversation,
- 451 temperature=config['temperature'],
- 452 max_tokens=config['max_tokens'],
- 453 extra_headers=headers,
- 454 extra_body={'search_parameters': search_params} if search_params else {},
- 455 timeout=config['api_timeout']
- 456 )
- 457 api_duration = time.time() - api_start
- 458 global last_api_success
- 459 last_api_success = time.time()
- 460 logger.debug(f"API call took {api_duration:.2f}s")
- 461 logger.debug(f"Raw Grok response: {response.choices[0].message.content}")
- 462 logger.debug(f"Full response: {response.model_dump()}")
- 463
- 464 reply = process_grok_response(response, message, timestamp)
- 465 reply_hash = hashlib.sha256(reply.encode()).hexdigest()
- 466 logger.info(f"Reply (len={len(reply)}, hash={reply_hash}): {reply}")
- 467 logger.info(f"Total time: {time.time() - start_time:.2f}s")
- 468 return jsonify({'reply': reply}), 200, {
- 469 'Cache-Control': 'no-store, no-cache, must-revalidate, max-age=0',
- 470 'X-Session-ID': session_id,
- 471 'X-Timestamp': timestamp
- 472 }
- 473
- 474 except (APIError, APIConnectionError, Timeout) as e:
- 475 logger.error(f"API call failed: {type(e).__name__}: {str(e)}")
- 476 logger.debug(f"Stack trace: {traceback.format_exc()}")
- 477 # Only use time fallback for explicit time/date intent
- 478 if has_time_intent(message):
- 479 fallback = calculate_time_fallback(message, timestamp)
- 480 if fallback:
- 481 logger.info(f"Used fallback for time query (API failure): {fallback}")
- 482 return jsonify({'reply': fallback}), 200, {'Cache-Control': 'no-store, no-cache, must-revalidate, max-age=0', 'X-Session-ID': session_id, 'X-Timestamp': timestamp}
- 483 return jsonify({'error': f"API call failed: {str(e)}", 'fallback': 'Sorry, I couldn\'t connect to Grok!'}), 500, {'Cache-Control': 'no-store, no-cache, must-revalidate, max-age=0', 'X-Session-ID': session_id, 'X-Timestamp': timestamp}
- 484
- 485# ------------------------------------------------------------------------------
- 486Main
- 487# ------------------------------------------------------------------------------
- 488if __name__ == '__main__':
- 489 logger.info(f"Starting Flask server on {config['flask_host']}:{config['flask_port']}")
- 490 app.run(host=config['flask_host'], port=config['flask_port'], debug=False)
Raw Paste