File size: 7,669 Bytes
b07a1e9
 
f051da6
 
 
 
b07a1e9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f051da6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import os
import sys
import time
from uuid import uuid4
from fastapi import APIRouter, Depends, HTTPException, Header, Request
from fastapi.responses import JSONResponse
import logging
from qdrant_client import QdrantClient

sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

from services.rag_service import RAGService
from auth.jwt_utils import get_current_user_id_from_token

router = APIRouter()

# Configure OpenRouter and RAG service
openrouter_api_key = os.getenv("OPENAI_API_KEY")
qdrant_url = os.getenv("QDRANT_URL")
qdrant_api_key = os.getenv("QDRANT_API_KEY")
collection_name = os.getenv("QDRANT_COLLECTION", "project_documents")

if openrouter_api_key and openrouter_api_key != "your_openrouter_api_key_here":
    # Initialize Qdrant client
    try:
        # Check if cloud Qdrant is configured
        qdrant_host = os.getenv("QDRANT_HOST")
        if qdrant_api_key and qdrant_host and "qdrant.io" in qdrant_host:
            # Use Qdrant Cloud
            qdrant_client = QdrantClient(
                url=qdrant_host,  # Use QDRANT_HOST which has the full URL
                api_key=qdrant_api_key,
                prefer_grpc=False
            )
        else:
            # Use local Qdrant - extract host from URL if needed
            if qdrant_url and qdrant_url.startswith("http"):
                # Extract host and port from URL
                from urllib.parse import urlparse
                parsed = urlparse(qdrant_url)
                host = parsed.hostname or "localhost"
                port = parsed.port or 6333
            else:
                host = qdrant_url or "localhost"
                port = int(os.getenv("QDRANT_PORT", 6333))

            qdrant_client = QdrantClient(host=host, port=port)
    except Exception as e:
        logger.error(f"Failed to initialize Qdrant client: {str(e)}")
        # Create a dummy client to allow service to work without Qdrant
        qdrant_client = None

    # Initialize RAG service with OpenRouter
    rag_service = RAGService(openrouter_api_key, qdrant_client, collection_name)
else:
    rag_service = None

logger = logging.getLogger(__name__)


# JWT verification dependency
def verify_jwt_token(authorization: str = Header(None)) -> dict:
    """Verify JWT token from Authorization header"""
    if not authorization:
        raise HTTPException(status_code=401, detail="Authorization header missing")

    if not authorization.startswith("Bearer "):
        raise HTTPException(status_code=401, detail="Invalid authorization format")

    token = authorization[7:]  # Remove "Bearer " prefix
    user_id = get_current_user_id_from_token(token)

    if not user_id:
        raise HTTPException(status_code=401, detail="Invalid or expired token")

    return {"user_id": user_id}


@router.post("/chat")
async def chat(payload: dict, user_data: dict = Depends(verify_jwt_token)):
    user_msg = payload["message"]
    selected_text = payload.get("selected_text", "")

    # If selected text is provided, try to use RAG service to answer based only on that text
    if selected_text and rag_service:
        try:
            # Use the RAG service to answer based on selected text only (with OpenRouter)
            answer = rag_service.query_rag(selected_text, user_msg)
            return {"answer": answer}
        except Exception as e:
            logger.error(f"RAG service failed: {str(e)}")
            # Fall through to fallback response below
    elif selected_text and not rag_service:
        logger.warning("RAG service not available, using fallback")

    # Fallback response when API is unavailable or not configured
    fallback_responses = {
        "hello": "Hello! I'm your AI textbook assistant. Feel free to ask questions about the content you're studying!",
        "hi": "Hi there! I'm here to help you understand the AI and robotics concepts in your textbook. What would you like to know?",
        "help": "I can help explain concepts from your AI and robotics textbook! Please select some text and ask questions about it.",
        "default": f"I'm currently unable to process your request about '{user_msg}'. This might be because the AI service is temporarily unavailable or needs to be configured with a valid API key. The system is working properly but requires a valid OPENROUTER_API_KEY to provide AI-generated responses."
    }

    response_text = fallback_responses.get(user_msg.lower().strip(), fallback_responses["default"])

    result = {"answer": response_text}
    if not rag_service:
        result["setup_needed"] = "Please configure a valid OPENROUTER_API_KEY in the .env file to enable AI responses"

    return result


# ============================================================
# OpenAI ChatKit SDK Compatible Endpoint
# Provides OpenAI Chat Completions API format for ChatKit React
# ============================================================

@router.post("/chatkit/session")
async def chatkit_session(request: Request, user_data: dict = Depends(verify_jwt_token)):
    """

    Create a ChatKit session. Returns a client_secret for the ChatKit React component.

    This is a simplified session endpoint for self-hosted ChatKit.

    """
    session_id = str(uuid4())
    return {
        "client_secret": f"chatkit_{session_id}_{user_data['user_id']}",
        "session_id": session_id,
        "user_id": user_data["user_id"],
    }


@router.post("/chatkit/chat")
async def chatkit_chat(payload: dict, user_data: dict = Depends(verify_jwt_token)):
    """

    ChatKit-compatible chat endpoint.

    Accepts OpenAI Chat Completions format and routes through RAG service.

    Used by the @openai/chatkit-react frontend component.

    """
    messages = payload.get("messages", [])
    selected_text = payload.get("selected_text", "")

    # Extract the last user message
    user_msg = ""
    for msg in reversed(messages):
        if msg.get("role") == "user":
            user_msg = msg.get("content", "")
            break

    if not user_msg:
        user_msg = payload.get("message", "")

    # Process through RAG service
    answer = ""
    if selected_text and rag_service:
        try:
            answer = rag_service.query_rag(selected_text, user_msg)
        except Exception as e:
            logger.error(f"ChatKit RAG service failed: {str(e)}")
            answer = "Is sawal ka jawab provided data me mojood nahi hai."
    elif rag_service:
        try:
            answer = rag_service.query_rag(user_msg, user_msg)
        except Exception as e:
            logger.error(f"ChatKit RAG service failed: {str(e)}")
            answer = "Please select text from the textbook to ask questions."
    else:
        answer = "AI service is not configured. Please set up OPENROUTER_API_KEY."

    # Return in OpenAI Chat Completions format (ChatKit compatible)
    return {
        "id": f"chatcmpl-{uuid4().hex[:12]}",
        "object": "chat.completion",
        "created": int(time.time()),
        "model": "openai/gpt-3.5-turbo",
        "choices": [
            {
                "index": 0,
                "message": {
                    "role": "assistant",
                    "content": answer,
                },
                "finish_reason": "stop",
            }
        ],
        "usage": {
            "prompt_tokens": len(user_msg.split()),
            "completion_tokens": len(answer.split()),
            "total_tokens": len(user_msg.split()) + len(answer.split()),
        },
    }