File size: 9,164 Bytes
29c9c84
ae36ed6
29c9c84
5476612
ae36ed6
29c9c84
29fc612
29c9c84
 
5476612
29c9c84
 
ae36ed6
29c9c84
 
 
ae36ed6
 
 
 
 
 
29c9c84
 
 
 
 
 
 
ae36ed6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29c9c84
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ae36ed6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29c9c84
ae36ed6
c8c48ae
ae36ed6
c8c48ae
ae36ed6
c8c48ae
 
29c9c84
 
e032c7b
c8c48ae
29c9c84
 
2fefd0a
29c9c84
c8c48ae
29c9c84
 
ae36ed6
29c9c84
 
ae36ed6
 
 
 
 
 
2fefd0a
29c9c84
2fefd0a
29c9c84
29fc612
29c9c84
 
29fc612
 
 
e032c7b
2fefd0a
5476612
29fc612
ae36ed6
e032c7b
29fc612
 
e032c7b
 
29c9c84
 
ae36ed6
e032c7b
ae36ed6
aaf0edc
ae36ed6
 
29c9c84
 
 
 
 
 
e032c7b
29c9c84
 
 
29fc612
 
29c9c84
c8c48ae
29c9c84
e032c7b
29c9c84
c8c48ae
e032c7b
 
 
 
 
 
 
 
c8c48ae
29c9c84
29fc612
29c9c84
e032c7b
c8c48ae
e032c7b
29c9c84
 
 
29fc612
29c9c84
 
c8c48ae
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
import os
import requests
import gradio as gr
from google import genai
from typing import List, Dict, Any, Optional
from supabase import create_client, Client
import jwt
from datetime import datetime, timedelta

# --- Configuration ---
GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY")
SUPABASE_URL = os.getenv("SUPABASE_URL")
SUPABASE_KEY = os.getenv("SUPABASE_KEY")
SUPABASE_SERVICE_KEY = os.getenv("SUPABASE_SERVICE_KEY")
JWT_SECRET = os.getenv("JWT_SECRET", "a-strong-secret-key-for-local-testing")

# --- External API Config ---
QUANTUM_API_BASE_URL = os.getenv("QUANTUM_API_BASE_URL", "https://virtserver.swaggerhub.com/JOCALL3_1/jamesburvel/1.0")
# CHANGED: The API key is now optional. It will be None if the secret is not set.
QUANTUM_API_KEY = os.getenv("QUANTUM_API_KEY")

# --- Initialize Supabase Admin Client ---
try:
    supabase_admin: Client = create_client(SUPABASE_URL, SUPABASE_SERVICE_KEY)
    print("Successfully created Supabase admin client.")
except Exception as e:
    supabase_admin = None
    print(f"Warning: Supabase service key invalid. User authentication will fail. Error: {e}")

# --- Real API Client for the "Quantum Core 3.0" Service ---
class QuantumAPIClient:
    # CHANGED: The __init__ method is updated to handle an optional API key.
    def __init__(self, base_url: str, api_key: Optional[str] = None):
        self.base_url = base_url
        self.headers = {
            "Content-Type": "application/json",
            "Accept": "application/json"
        }
        if api_key:
            print("Quantum API Key found, adding Authorization header.")
            self.headers["Authorization"] = f"Bearer {api_key}"
        else:
            print("No Quantum API Key found, making public API calls.")

    def _get(self, endpoint: str, params: Dict[str, Any] = None):
        try:
            response = requests.get(f"{self.base_url}{endpoint}", headers=self.headers, params=params, timeout=20)
            response.raise_for_status()
            return response.json()
        except Exception as e:
            return {"error": f"API call failed: {e}"}

    def _post(self, endpoint: str, data: Dict[str, Any]):
        try:
            response = requests.post(f"{self.base_url}{endpoint}", headers=self.headers, json=data, timeout=20)
            response.raise_for_status()
            return response.json()
        except Exception as e:
            return {"error": f"API call failed: {e}"}

quantum_client = QuantumAPIClient(base_url=QUANTUM_API_BASE_URL, api_key=QUANTUM_API_KEY)

# --- Tool Definitions (Internal Memory + External API) ---

def get_ai_advisor_chat_history(supabase_user_client: Client, user_id: str):
    print(f"TOOL CALL: get_ai_advisor_chat_history for user '{user_id}' from Supabase")
    try:
        response = supabase_user_client.table('chat_history').select("*").eq('user_id', user_id).order('timestamp').limit(50).execute()
        return response.data
    except Exception as e:
        return {"error": f"Database query failed: {str(e)}"}

def save_chat_turn(supabase_user_client: Client, user_id: str, session_id: str, user_message: str, assistant_message: str):
    try:
        supabase_user_client.table('chat_history').insert([
            {"user_id": user_id, "session_id": session_id, "role": "user", "content": user_message},
            {"user_id": user_id, "session_id": session_id, "role": "assistant", "content": assistant_message}
        ]).execute()
    except Exception as e:
        print(f"Error saving chat turn: {e}")

def run_standard_financial_simulation(prompt: str, duration_years: int = 5):
    """Submits a 'What-If' scenario to the Quantum Oracle AI for standard financial impact analysis."""
    print(f"TOOL CALL: run_standard_financial_simulation with prompt: '{prompt}'")
    payload = {"prompt": prompt, "parameters": {"durationYears": duration_years}}
    return quantum_client._post("/ai/oracle/simulate", data=payload)

def generate_video_ad(prompt: str, style: str = "Cinematic", length_seconds: int = 15):
    """Submits a request to generate a high-quality video ad using the Veo 2.0 generative AI model."""
    print(f"TOOL CALL: generate_video_ad with prompt: '{prompt}'")
    payload = {"prompt": prompt, "style": style, "lengthSeconds": length_seconds}
    return quantum_client._post("/ai/ads/generate", data=payload)

def list_available_ai_tools():
    """Retrieves a list of all integrated AI tools that Quantum can invoke."""
    print("TOOL CALL: list_available_ai_tools -> GET /ai/advisor/tools")
    return quantum_client._get("/ai/advisor/tools")

# --- Core Agent & Auth Logic ---
def perform_login(user_id: str):
    if not supabase_admin: raise Exception("Auth service not configured.")
    payload = { "sub": user_id, "aud": "authenticated", "exp": datetime.utcnow() + timedelta(hours=1), "role": "authenticated" }
    return jwt.encode(payload, JWT_SECRET, algorithm="HS256")

def run_agent_logic(prompt: str, user_id: str, session_id: str, token: str):
    try:
        decoded_token = jwt.decode(token, JWT_SECRET, algorithms=["HS256"], audience="authenticated")
        if not decoded_token.get("sub") or decoded_token.get("sub") != user_id:
            raise Exception("Invalid token or user mismatch.")
        
        supabase_user_client = create_client(SUPABASE_URL, SUPABASE_KEY)
        supabase_user_client.auth.set_session(access_token=token, refresh_token=token)
    except jwt.PyJWTError as e:
        raise Exception(f"Invalid token: {e}")

    def get_my_chat_history():
        """Fetches my most recent conversation history to provide context."""
        return get_ai_advisor_chat_history(supabase_user_client, user_id)
    
    all_tools = [
        get_my_chat_history,
        run_standard_financial_simulation,
        generate_video_ad,
        list_available_ai_tools,
    ]
    
    try:
        genai.configure(api_key=GOOGLE_API_KEY)
        history_data = get_my_chat_history()
        conversation_history = []
        if isinstance(history_data, list):
            for turn in history_data:
                role = "user" if turn['role'] == 'user' else "model"
                conversation_history.append({'role': role, 'parts': [{'text': turn['content']}]})
        
        conversation_history.append({'role': 'user', 'parts': [{'text': prompt}]})
        
        model = genai.GenerativeModel('gemini-pro')
        response = model.generate_content(
            conversation_history, tools=all_tools,
            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'}
        )
        final_response_text = response.text
        save_chat_turn(supabase_user_client, user_id, session_id, prompt, final_response_text)
        return final_response_text
    except Exception as e:
        print(f"Error in agent invocation: {e}")
        return f"An error occurred: {e}"

# --- Gradio UI (Pure Gradio, No FastAPI) ---
with gr.Blocks() as demo:
    gr.Markdown("# 💎 Secure, Multi-User AI Agent (Full API)")
    gr.Markdown("Log in with a User ID. The agent can fetch chat history (from Supabase) and call external APIs for simulations or ads.")

    user_id_state = gr.State("")
    token_state = gr.State("")
    session_id_state = gr.State("session-" + os.urandom(8).hex())

    with gr.Row():
        user_id_input = gr.Textbox(label="Enter User ID to Log In", placeholder="e.g., user-a")
        login_button = gr.Button("Login")
        login_status = gr.Markdown("")

    chatbot = gr.Chatbot(label="Agent Chat", visible=False, height=500)
    msg_input = gr.Textbox(label="Your Message", visible=False, show_label=False, placeholder="Type your message here...")
    
    def login_fn(user_id):
        if not user_id:
            return {login_status: gr.update(value="<p style='color:red;'>Please enter a User ID.</p>")}
        try:
            token = perform_login(user_id)
            status_html = f"<p style='color:green;'>Logged in as: {user_id}. You can start chatting.</p>"
            return {
                login_status: gr.update(value=status_html), user_id_state: user_id, token_state: token,
                chatbot: gr.update(visible=True), msg_input: gr.update(visible=True),
            }
        except Exception as e:
            return {login_status: gr.update(value=f"<p style='color:red;'>Login failed: {e}</p>")}

    def chat_fn(message, chat_history, user_id, token, session_id):
        if not token:
            chat_history.append((message, "Error: You are not logged in."))
            return "", chat_history
        
        bot_message = run_agent_logic(message, user_id, session_id, token)
        
        chat_history.append((message, bot_message))
        return "", chat_history

    login_button.click(login_fn, inputs=[user_id_input], outputs=[login_status, user_id_state, token_state, chatbot, msg_input])
    msg_input.submit(chat_fn, [msg_input, chatbot, user_id_state, token_state, session_id_state], [msg_input, chatbot])

# The standard way to launch a Gradio app on Hugging Face
demo.launch()