File size: 7,147 Bytes
f5b0cd7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
195
196
197
198
199
200
201
202
203
204
import logging
from typing import Optional
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from agents import Runner
from simple_agents.aagents import Triage_Agent
from models.user_context import UserContext
from pydantic import BaseModel
from services.rag import RAGService
from data.vector_store import VectorStore

# Initialize services globally but handle initialization errors gracefully
try:
    vector_store = VectorStore()
    rag_service = RAGService()
    rag_service.set_vector_store(vector_store)
except Exception as e:
    logging.error(f"Failed to initialize services: {e}")
    vector_store = None
    rag_service = None

app = FastAPI()

# CORS middleware for Vercel deployment
app.add_middleware(
    CORSMiddleware,
    allow_origins=[
        "https://muhammedsuhaib.github.io",
        "http://localhost:3000",
        "http://localhost:8080",
    ],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# ---------------------------
# Pydantic Models for Frontend Requests
# ---------------------------

# Matches the payload for the general chat endpoint (/api/query)
class QueryRequest(BaseModel):
    query: str
    user_context: Optional[dict] = None

# Matches the payload for the selection endpoint (/api/selection)
class SelectionRequest(BaseModel):
    selected_text: str
    question: str
    user_context: Optional[dict] = None

# Matches the payload for the translation endpoint (/api/translate-text)
class TranslationRequest(BaseModel):
    text: str
    target_language: str

# ---------------------------
# FastAPI Endpoints (Matching React expectations)
# ---------------------------

@app.get("/")
def read_root():
    return {"message": "Python Assistant Backend is running."}

@app.post("/api/query")
async def handle_query(req: QueryRequest):
    """Handles general chat queries from the React component."""
    logging.info(f"Received general query: {req.query}")

    # Check if services are properly initialized
    if not rag_service or not vector_store:
        logging.error("RAG service not initialized")
        return {
            "answer": "Service temporarily unavailable",
            "sources": []
        }

    # Create user context from request data
    user_context_data = req.user_context or {}
    user_context = UserContext(
        name=user_context_data.get('name', 'User'),
        uid=user_context_data.get('uid'),
        email=user_context_data.get('email'),
        personalization_data=user_context_data.get('personalization_data'),
        session_id=user_context_data.get('session_id')
    )

    # Use global RAG service to get context from Qdrant
    # Get relevant context from Qdrant
    try:
        rag_result = await rag_service.query(req.query)
        print(rag_result)
        print(rag_result.sources)
        context = rag_result.answer if rag_result.answer != "I don't know" else ""
    except Exception as e:
        logging.error(f"RAG query failed: {e}")
        # Fallback to no context if RAG fails
        rag_result = None
        context = ""

    # Include context in the agent's query if available
    if context and context != "I don't know":
        enhanced_query = f"Based on the following context: {context}\n\nQuestion: {req.query}"
    else:
        enhanced_query = req.query

    # Run the main agent with the enhanced query and user context
    result = await Runner.run(
        Triage_Agent,
        enhanced_query,
        context=user_context
    )

    # CRITICAL: Response structure must match React component: {"answer": "...", "sources": []}
    return {
        "answer": result.final_output,
        "sources": rag_result.sources if rag_result and hasattr(rag_result, 'sources') else [] # Must be included, even if empty
    }

@app.post("/api/selection")
async def handle_selection(req: SelectionRequest):
    """Handles queries based on selected text (RAG context)."""
    logging.info(f"Received selection query. Question: {req.question}")

    # Create user context from request data
    user_context_data = req.user_context or {}
    user_context = UserContext(
        name=user_context_data.get('name', 'User'),
        uid=user_context_data.get('uid'),
        email=user_context_data.get('email'),
        personalization_data=user_context_data.get('personalization_data'),
        session_id=user_context_data.get('session_id')
    )

    # Check if services are properly initialized
    if not rag_service or not vector_store:
        logging.error("RAG service not initialized")
        return {
            "answer": "Service temporarily unavailable",
            "sources": []
        }

    # Use global RAG service to get additional context from Qdrant
    # Get relevant context from Qdrant based on the question
    try:
        rag_result = await rag_service.query(req.question)
        additional_context = rag_result.answer if rag_result.answer != "I don't know" else ""
    except Exception as e:
        logging.error(f"RAG query failed: {e}")
        # Fallback to no context if RAG fails
        rag_result = None
        additional_context = ""

    # Construct a RAG-style prompt for the agent
    if additional_context and additional_context != "I don't know":
        prompt = (
            f"Based *only* on the following context, answer the user's question. "
            f"If the context does not contain the answer, state that. "
            f"Context: \"{req.selected_text}\"\n\nAdditional context from knowledge base: {additional_context} "
            f"Question: {req.question}"
        )
    else:
        prompt = (
            f"Based *only* on the following context, answer the user's question. "
            f"If the context does not contain the answer, state that. "
            f"Context: \"{req.selected_text}\" "
            f"Question: {req.question}"
        )
    # Run the agent with the context-aware prompt and user context
    result = await Runner.run(
        Triage_Agent,
        prompt,
        context=user_context
    )

    # CRITICAL: Response structure must match React component: {"answer": "...", "sources": []}
    return {
        "answer": result.final_output,
        "sources": rag_result.sources if rag_result and hasattr(rag_result, 'sources') else [] # Must be included, even if empty
    }

@app.get("/health")
def health_check():
    """Health check endpoint for Vercel deployment."""
    return {"status": "healthy", "message": "Backend is running"}

@app.post("/api/translate-text")
async def translate_text(req: TranslationRequest):
    """Translates text to the specified target language."""
    from deep_translator import GoogleTranslator

    try:
        # Validate target language
        if req.target_language != 'ur':
            return {"error": "Currently only Urdu (ur) translation is supported"}

        # Perform translation
        translated = GoogleTranslator(source='en', target=req.target_language).translate(req.text)

        return {"translated_text": translated}
    except Exception as e:
        logging.error(f"Translation error: {e}")
        return {"error": str(e)}