Spaces:
Sleeping
Sleeping
| 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} | |
| 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 | |
| # ============================================================ | |
| 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"], | |
| } | |
| 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()), | |
| }, | |
| } |