Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,7 +1,8 @@
|
|
| 1 |
import os
|
|
|
|
| 2 |
import gradio as gr
|
| 3 |
from google import genai
|
| 4 |
-
from typing import List, Dict, Any
|
| 5 |
from supabase import create_client, Client
|
| 6 |
import jwt
|
| 7 |
from datetime import datetime, timedelta
|
|
@@ -9,11 +10,16 @@ from datetime import datetime, timedelta
|
|
| 9 |
# --- Configuration ---
|
| 10 |
GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY")
|
| 11 |
SUPABASE_URL = os.getenv("SUPABASE_URL")
|
| 12 |
-
SUPABASE_KEY = os.getenv("SUPABASE_KEY")
|
| 13 |
SUPABASE_SERVICE_KEY = os.getenv("SUPABASE_SERVICE_KEY")
|
| 14 |
JWT_SECRET = os.getenv("JWT_SECRET", "a-strong-secret-key-for-local-testing")
|
| 15 |
|
| 16 |
-
# ---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
try:
|
| 18 |
supabase_admin: Client = create_client(SUPABASE_URL, SUPABASE_SERVICE_KEY)
|
| 19 |
print("Successfully created Supabase admin client.")
|
|
@@ -21,7 +27,41 @@ except Exception as e:
|
|
| 21 |
supabase_admin = None
|
| 22 |
print(f"Warning: Supabase service key invalid. User authentication will fail. Error: {e}")
|
| 23 |
|
| 24 |
-
# ---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
def get_ai_advisor_chat_history(supabase_user_client: Client, user_id: str):
|
| 26 |
print(f"TOOL CALL: get_ai_advisor_chat_history for user '{user_id}' from Supabase")
|
| 27 |
try:
|
|
@@ -31,27 +71,38 @@ def get_ai_advisor_chat_history(supabase_user_client: Client, user_id: str):
|
|
| 31 |
return {"error": f"Database query failed: {str(e)}"}
|
| 32 |
|
| 33 |
def save_chat_turn(supabase_user_client: Client, user_id: str, session_id: str, user_message: str, assistant_message: str):
|
| 34 |
-
print(f"TOOL CALL: Saving chat turn for user '{user_id}' to Supabase")
|
| 35 |
try:
|
| 36 |
supabase_user_client.table('chat_history').insert([
|
| 37 |
{"user_id": user_id, "session_id": session_id, "role": "user", "content": user_message},
|
| 38 |
{"user_id": user_id, "session_id": session_id, "role": "assistant", "content": assistant_message}
|
| 39 |
]).execute()
|
| 40 |
-
return {"status": "success"}
|
| 41 |
except Exception as e:
|
| 42 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
|
| 44 |
-
# --- Core Agent & Auth Logic
|
| 45 |
def perform_login(user_id: str):
|
| 46 |
-
|
| 47 |
-
if not supabase_admin:
|
| 48 |
-
raise Exception("Authentication service is not configured.")
|
| 49 |
payload = { "sub": user_id, "aud": "authenticated", "exp": datetime.utcnow() + timedelta(hours=1), "role": "authenticated" }
|
| 50 |
-
|
| 51 |
-
return access_token
|
| 52 |
|
| 53 |
def run_agent_logic(prompt: str, user_id: str, session_id: str, token: str):
|
| 54 |
-
"""The main logic for the agent, now a simple callable function."""
|
| 55 |
try:
|
| 56 |
decoded_token = jwt.decode(token, JWT_SECRET, algorithms=["HS256"], audience="authenticated")
|
| 57 |
if not decoded_token.get("sub") or decoded_token.get("sub") != user_id:
|
|
@@ -63,9 +114,15 @@ def run_agent_logic(prompt: str, user_id: str, session_id: str, token: str):
|
|
| 63 |
raise Exception(f"Invalid token: {e}")
|
| 64 |
|
| 65 |
def get_my_chat_history():
|
|
|
|
| 66 |
return get_ai_advisor_chat_history(supabase_user_client, user_id)
|
| 67 |
|
| 68 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 69 |
|
| 70 |
try:
|
| 71 |
genai.configure(api_key=GOOGLE_API_KEY)
|
|
@@ -80,7 +137,7 @@ def run_agent_logic(prompt: str, user_id: str, session_id: str, token: str):
|
|
| 80 |
|
| 81 |
model = genai.GenerativeModel('gemini-pro')
|
| 82 |
response = model.generate_content(
|
| 83 |
-
conversation_history, tools=
|
| 84 |
safety_settings={'HARM_CATEGORY_DANGEROUS_CONTENT': 'BLOCK_NONE', 'HARM_CATEGORY_HARASSMENT': 'BLOCK_NONE', 'HARM_CATEGORY_HATE_SPEECH': 'BLOCK_NONE', 'HARM_CATEGORY_SEXUALLY_EXPLICIT': 'BLOCK_NONE'}
|
| 85 |
)
|
| 86 |
final_response_text = response.text
|
|
@@ -88,12 +145,12 @@ def run_agent_logic(prompt: str, user_id: str, session_id: str, token: str):
|
|
| 88 |
return final_response_text
|
| 89 |
except Exception as e:
|
| 90 |
print(f"Error in agent invocation: {e}")
|
| 91 |
-
return f"An error occurred
|
| 92 |
|
| 93 |
-
# --- Gradio UI ---
|
| 94 |
with gr.Blocks() as demo:
|
| 95 |
-
gr.Markdown("# 💎 Secure, Multi-User AI Agent (
|
| 96 |
-
gr.Markdown("Log in with a User ID
|
| 97 |
|
| 98 |
user_id_state = gr.State("")
|
| 99 |
token_state = gr.State("")
|
|
|
|
| 1 |
import os
|
| 2 |
+
import requests
|
| 3 |
import gradio as gr
|
| 4 |
from google import genai
|
| 5 |
+
from typing import List, Dict, Any, Optional
|
| 6 |
from supabase import create_client, Client
|
| 7 |
import jwt
|
| 8 |
from datetime import datetime, timedelta
|
|
|
|
| 10 |
# --- Configuration ---
|
| 11 |
GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY")
|
| 12 |
SUPABASE_URL = os.getenv("SUPABASE_URL")
|
| 13 |
+
SUPABASE_KEY = os.getenv("SUPABASE_KEY")
|
| 14 |
SUPABASE_SERVICE_KEY = os.getenv("SUPABASE_SERVICE_KEY")
|
| 15 |
JWT_SECRET = os.getenv("JWT_SECRET", "a-strong-secret-key-for-local-testing")
|
| 16 |
|
| 17 |
+
# --- External API Config ---
|
| 18 |
+
QUANTUM_API_BASE_URL = os.getenv("QUANTUM_API_BASE_URL", "https://virtserver.swaggerhub.com/JOCALL3_1/jamesburvel/1.0")
|
| 19 |
+
# CHANGED: The API key is now optional. It will be None if the secret is not set.
|
| 20 |
+
QUANTUM_API_KEY = os.getenv("QUANTUM_API_KEY")
|
| 21 |
+
|
| 22 |
+
# --- Initialize Supabase Admin Client ---
|
| 23 |
try:
|
| 24 |
supabase_admin: Client = create_client(SUPABASE_URL, SUPABASE_SERVICE_KEY)
|
| 25 |
print("Successfully created Supabase admin client.")
|
|
|
|
| 27 |
supabase_admin = None
|
| 28 |
print(f"Warning: Supabase service key invalid. User authentication will fail. Error: {e}")
|
| 29 |
|
| 30 |
+
# --- Real API Client for the "Quantum Core 3.0" Service ---
|
| 31 |
+
class QuantumAPIClient:
|
| 32 |
+
# CHANGED: The __init__ method is updated to handle an optional API key.
|
| 33 |
+
def __init__(self, base_url: str, api_key: Optional[str] = None):
|
| 34 |
+
self.base_url = base_url
|
| 35 |
+
self.headers = {
|
| 36 |
+
"Content-Type": "application/json",
|
| 37 |
+
"Accept": "application/json"
|
| 38 |
+
}
|
| 39 |
+
if api_key:
|
| 40 |
+
print("Quantum API Key found, adding Authorization header.")
|
| 41 |
+
self.headers["Authorization"] = f"Bearer {api_key}"
|
| 42 |
+
else:
|
| 43 |
+
print("No Quantum API Key found, making public API calls.")
|
| 44 |
+
|
| 45 |
+
def _get(self, endpoint: str, params: Dict[str, Any] = None):
|
| 46 |
+
try:
|
| 47 |
+
response = requests.get(f"{self.base_url}{endpoint}", headers=self.headers, params=params, timeout=20)
|
| 48 |
+
response.raise_for_status()
|
| 49 |
+
return response.json()
|
| 50 |
+
except Exception as e:
|
| 51 |
+
return {"error": f"API call failed: {e}"}
|
| 52 |
+
|
| 53 |
+
def _post(self, endpoint: str, data: Dict[str, Any]):
|
| 54 |
+
try:
|
| 55 |
+
response = requests.post(f"{self.base_url}{endpoint}", headers=self.headers, json=data, timeout=20)
|
| 56 |
+
response.raise_for_status()
|
| 57 |
+
return response.json()
|
| 58 |
+
except Exception as e:
|
| 59 |
+
return {"error": f"API call failed: {e}"}
|
| 60 |
+
|
| 61 |
+
quantum_client = QuantumAPIClient(base_url=QUANTUM_API_BASE_URL, api_key=QUANTUM_API_KEY)
|
| 62 |
+
|
| 63 |
+
# --- Tool Definitions (Internal Memory + External API) ---
|
| 64 |
+
|
| 65 |
def get_ai_advisor_chat_history(supabase_user_client: Client, user_id: str):
|
| 66 |
print(f"TOOL CALL: get_ai_advisor_chat_history for user '{user_id}' from Supabase")
|
| 67 |
try:
|
|
|
|
| 71 |
return {"error": f"Database query failed: {str(e)}"}
|
| 72 |
|
| 73 |
def save_chat_turn(supabase_user_client: Client, user_id: str, session_id: str, user_message: str, assistant_message: str):
|
|
|
|
| 74 |
try:
|
| 75 |
supabase_user_client.table('chat_history').insert([
|
| 76 |
{"user_id": user_id, "session_id": session_id, "role": "user", "content": user_message},
|
| 77 |
{"user_id": user_id, "session_id": session_id, "role": "assistant", "content": assistant_message}
|
| 78 |
]).execute()
|
|
|
|
| 79 |
except Exception as e:
|
| 80 |
+
print(f"Error saving chat turn: {e}")
|
| 81 |
+
|
| 82 |
+
def run_standard_financial_simulation(prompt: str, duration_years: int = 5):
|
| 83 |
+
"""Submits a 'What-If' scenario to the Quantum Oracle AI for standard financial impact analysis."""
|
| 84 |
+
print(f"TOOL CALL: run_standard_financial_simulation with prompt: '{prompt}'")
|
| 85 |
+
payload = {"prompt": prompt, "parameters": {"durationYears": duration_years}}
|
| 86 |
+
return quantum_client._post("/ai/oracle/simulate", data=payload)
|
| 87 |
+
|
| 88 |
+
def generate_video_ad(prompt: str, style: str = "Cinematic", length_seconds: int = 15):
|
| 89 |
+
"""Submits a request to generate a high-quality video ad using the Veo 2.0 generative AI model."""
|
| 90 |
+
print(f"TOOL CALL: generate_video_ad with prompt: '{prompt}'")
|
| 91 |
+
payload = {"prompt": prompt, "style": style, "lengthSeconds": length_seconds}
|
| 92 |
+
return quantum_client._post("/ai/ads/generate", data=payload)
|
| 93 |
+
|
| 94 |
+
def list_available_ai_tools():
|
| 95 |
+
"""Retrieves a list of all integrated AI tools that Quantum can invoke."""
|
| 96 |
+
print("TOOL CALL: list_available_ai_tools -> GET /ai/advisor/tools")
|
| 97 |
+
return quantum_client._get("/ai/advisor/tools")
|
| 98 |
|
| 99 |
+
# --- Core Agent & Auth Logic ---
|
| 100 |
def perform_login(user_id: str):
|
| 101 |
+
if not supabase_admin: raise Exception("Auth service not configured.")
|
|
|
|
|
|
|
| 102 |
payload = { "sub": user_id, "aud": "authenticated", "exp": datetime.utcnow() + timedelta(hours=1), "role": "authenticated" }
|
| 103 |
+
return jwt.encode(payload, JWT_SECRET, algorithm="HS256")
|
|
|
|
| 104 |
|
| 105 |
def run_agent_logic(prompt: str, user_id: str, session_id: str, token: str):
|
|
|
|
| 106 |
try:
|
| 107 |
decoded_token = jwt.decode(token, JWT_SECRET, algorithms=["HS256"], audience="authenticated")
|
| 108 |
if not decoded_token.get("sub") or decoded_token.get("sub") != user_id:
|
|
|
|
| 114 |
raise Exception(f"Invalid token: {e}")
|
| 115 |
|
| 116 |
def get_my_chat_history():
|
| 117 |
+
"""Fetches my most recent conversation history to provide context."""
|
| 118 |
return get_ai_advisor_chat_history(supabase_user_client, user_id)
|
| 119 |
|
| 120 |
+
all_tools = [
|
| 121 |
+
get_my_chat_history,
|
| 122 |
+
run_standard_financial_simulation,
|
| 123 |
+
generate_video_ad,
|
| 124 |
+
list_available_ai_tools,
|
| 125 |
+
]
|
| 126 |
|
| 127 |
try:
|
| 128 |
genai.configure(api_key=GOOGLE_API_KEY)
|
|
|
|
| 137 |
|
| 138 |
model = genai.GenerativeModel('gemini-pro')
|
| 139 |
response = model.generate_content(
|
| 140 |
+
conversation_history, tools=all_tools,
|
| 141 |
safety_settings={'HARM_CATEGORY_DANGEROUS_CONTENT': 'BLOCK_NONE', 'HARM_CATEGORY_HARASSMENT': 'BLOCK_NONE', 'HARM_CATEGORY_HATE_SPEECH': 'BLOCK_NONE', 'HARM_CATEGORY_SEXUALLY_EXPLICIT': 'BLOCK_NONE'}
|
| 142 |
)
|
| 143 |
final_response_text = response.text
|
|
|
|
| 145 |
return final_response_text
|
| 146 |
except Exception as e:
|
| 147 |
print(f"Error in agent invocation: {e}")
|
| 148 |
+
return f"An error occurred: {e}"
|
| 149 |
|
| 150 |
+
# --- Gradio UI (Pure Gradio, No FastAPI) ---
|
| 151 |
with gr.Blocks() as demo:
|
| 152 |
+
gr.Markdown("# 💎 Secure, Multi-User AI Agent (Full API)")
|
| 153 |
+
gr.Markdown("Log in with a User ID. The agent can fetch chat history (from Supabase) and call external APIs for simulations or ads.")
|
| 154 |
|
| 155 |
user_id_state = gr.State("")
|
| 156 |
token_state = gr.State("")
|