Spaces:
Sleeping
Sleeping
USAMA BHATTI
commited on
Commit
·
4f47bd4
1
Parent(s):
acbbba2
Final SaaS Security & Asset Support
Browse files- backend/src/api/routes/auth.py +21 -9
- backend/src/api/routes/chat.py +33 -25
- backend/src/api/routes/settings.py +1 -0
- backend/src/core/config.py +1 -1
- backend/src/db/session.py +1 -0
- backend/src/main.py +1 -0
- backend/src/models/user.py +9 -2
- backend/src/schemas/chat.py +1 -4
- backend/src/services/chat_service.py +0 -388
- backend/src/utils/auth.py +13 -3
- backend/src/utils/security.py +1 -0
- requirements.txt +1 -0
- static/widget.js +71 -39
backend/src/api/routes/auth.py
CHANGED
|
@@ -6,7 +6,8 @@ from pydantic import BaseModel, EmailStr
|
|
| 6 |
|
| 7 |
from backend.src.db.session import get_db
|
| 8 |
from backend.src.models.user import User
|
| 9 |
-
|
|
|
|
| 10 |
|
| 11 |
router = APIRouter()
|
| 12 |
|
|
@@ -16,12 +17,18 @@ class UserCreate(BaseModel):
|
|
| 16 |
password: str
|
| 17 |
full_name: str | None = None
|
| 18 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
class Token(BaseModel):
|
| 20 |
access_token: str
|
| 21 |
token_type: str
|
| 22 |
|
| 23 |
# --- 1. Registration Endpoint ---
|
| 24 |
-
@router.post("/auth/register", response_model=
|
| 25 |
async def register(user_in: UserCreate, db: AsyncSession = Depends(get_db)):
|
| 26 |
# Check agar email pehle se exist karta hai
|
| 27 |
result = await db.execute(select(User).where(User.email == user_in.email))
|
|
@@ -33,28 +40,34 @@ async def register(user_in: UserCreate, db: AsyncSession = Depends(get_db)):
|
|
| 33 |
detail="Email already registered"
|
| 34 |
)
|
| 35 |
|
| 36 |
-
# Naya User Banao
|
| 37 |
new_user = User(
|
| 38 |
email=user_in.email,
|
| 39 |
hashed_password=get_password_hash(user_in.password),
|
| 40 |
-
full_name=user_in.full_name
|
|
|
|
|
|
|
| 41 |
)
|
|
|
|
| 42 |
db.add(new_user)
|
| 43 |
await db.commit()
|
| 44 |
await db.refresh(new_user)
|
| 45 |
|
| 46 |
# Direct Login Token do
|
| 47 |
access_token = create_access_token(data={"sub": str(new_user.id)})
|
| 48 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 49 |
|
| 50 |
# --- 2. Login Endpoint ---
|
| 51 |
@router.post("/auth/login", response_model=Token)
|
| 52 |
async def login(form_data: OAuth2PasswordRequestForm = Depends(), db: AsyncSession = Depends(get_db)):
|
| 53 |
-
|
| 54 |
-
result = await db.execute(select(User).where(User.email == form_data.username)) # OAuth2 form mein email 'username' field mein hota hai
|
| 55 |
user = result.scalars().first()
|
| 56 |
|
| 57 |
-
# Password check karo
|
| 58 |
if not user or not verify_password(form_data.password, user.hashed_password):
|
| 59 |
raise HTTPException(
|
| 60 |
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
@@ -62,6 +75,5 @@ async def login(form_data: OAuth2PasswordRequestForm = Depends(), db: AsyncSessi
|
|
| 62 |
headers={"WWW-Authenticate": "Bearer"},
|
| 63 |
)
|
| 64 |
|
| 65 |
-
# Token generate karo
|
| 66 |
access_token = create_access_token(data={"sub": str(user.id)})
|
| 67 |
return {"access_token": access_token, "token_type": "bearer"}
|
|
|
|
| 6 |
|
| 7 |
from backend.src.db.session import get_db
|
| 8 |
from backend.src.models.user import User
|
| 9 |
+
# generate_api_key ko import kiya 👇
|
| 10 |
+
from backend.src.utils.auth import get_password_hash, verify_password, create_access_token, generate_api_key
|
| 11 |
|
| 12 |
router = APIRouter()
|
| 13 |
|
|
|
|
| 17 |
password: str
|
| 18 |
full_name: str | None = None
|
| 19 |
|
| 20 |
+
# Response model ko extend kiya taake registration par API Key nazar aaye
|
| 21 |
+
class RegistrationResponse(BaseModel):
|
| 22 |
+
access_token: str
|
| 23 |
+
token_type: str
|
| 24 |
+
api_key: str # User ko registeration par hi uski chabi mil jayegi 🔑
|
| 25 |
+
|
| 26 |
class Token(BaseModel):
|
| 27 |
access_token: str
|
| 28 |
token_type: str
|
| 29 |
|
| 30 |
# --- 1. Registration Endpoint ---
|
| 31 |
+
@router.post("/auth/register", response_model=RegistrationResponse)
|
| 32 |
async def register(user_in: UserCreate, db: AsyncSession = Depends(get_db)):
|
| 33 |
# Check agar email pehle se exist karta hai
|
| 34 |
result = await db.execute(select(User).where(User.email == user_in.email))
|
|
|
|
| 40 |
detail="Email already registered"
|
| 41 |
)
|
| 42 |
|
| 43 |
+
# Naya User Banao + API Key Generate Karo (🔐)
|
| 44 |
new_user = User(
|
| 45 |
email=user_in.email,
|
| 46 |
hashed_password=get_password_hash(user_in.password),
|
| 47 |
+
full_name=user_in.full_name,
|
| 48 |
+
api_key=generate_api_key(), # <--- Yeh line jadoo karegi
|
| 49 |
+
allowed_domains="*" # Default: Har jagah allow karo, user baad mein settings se lock kar lega
|
| 50 |
)
|
| 51 |
+
|
| 52 |
db.add(new_user)
|
| 53 |
await db.commit()
|
| 54 |
await db.refresh(new_user)
|
| 55 |
|
| 56 |
# Direct Login Token do
|
| 57 |
access_token = create_access_token(data={"sub": str(new_user.id)})
|
| 58 |
+
|
| 59 |
+
return {
|
| 60 |
+
"access_token": access_token,
|
| 61 |
+
"token_type": "bearer",
|
| 62 |
+
"api_key": new_user.api_key # Registeration ke waqt hi key show kar di
|
| 63 |
+
}
|
| 64 |
|
| 65 |
# --- 2. Login Endpoint ---
|
| 66 |
@router.post("/auth/login", response_model=Token)
|
| 67 |
async def login(form_data: OAuth2PasswordRequestForm = Depends(), db: AsyncSession = Depends(get_db)):
|
| 68 |
+
result = await db.execute(select(User).where(User.email == form_data.username))
|
|
|
|
| 69 |
user = result.scalars().first()
|
| 70 |
|
|
|
|
| 71 |
if not user or not verify_password(form_data.password, user.hashed_password):
|
| 72 |
raise HTTPException(
|
| 73 |
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
|
|
| 75 |
headers={"WWW-Authenticate": "Bearer"},
|
| 76 |
)
|
| 77 |
|
|
|
|
| 78 |
access_token = create_access_token(data={"sub": str(user.id)})
|
| 79 |
return {"access_token": access_token, "token_type": "bearer"}
|
backend/src/api/routes/chat.py
CHANGED
|
@@ -1,50 +1,58 @@
|
|
| 1 |
-
|
| 2 |
-
from fastapi import APIRouter, Depends, HTTPException
|
| 3 |
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
| 4 |
from backend.src.db.session import get_db
|
| 5 |
from backend.src.schemas.chat import ChatRequest, ChatResponse
|
| 6 |
from backend.src.services.chat_service import process_chat
|
| 7 |
-
from backend.src.core.config import settings
|
| 8 |
-
|
| 9 |
-
# --- Security Imports ---
|
| 10 |
-
from backend.src.api.routes.deps import get_current_user
|
| 11 |
from backend.src.models.user import User
|
| 12 |
|
| 13 |
router = APIRouter()
|
| 14 |
|
| 15 |
@router.post("/chat", response_model=ChatResponse)
|
| 16 |
async def chat_endpoint(
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
):
|
| 21 |
-
"""
|
| 22 |
-
Protected Chat Endpoint.
|
| 23 |
-
Only accessible with a valid JWT Token.
|
| 24 |
-
"""
|
| 25 |
try:
|
| 26 |
-
#
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
|
| 31 |
-
# --- FIX IS HERE: 'user_id' pass kiya ja raha hai ---
|
| 32 |
response_text = await process_chat(
|
| 33 |
-
message=
|
| 34 |
session_id=session_id,
|
| 35 |
-
user_id=
|
| 36 |
db=db
|
| 37 |
)
|
| 38 |
|
| 39 |
return ChatResponse(
|
| 40 |
response=response_text,
|
| 41 |
session_id=session_id,
|
| 42 |
-
# 'provider' ab chat_service se aayega, humein yahan hardcode nahi karna
|
| 43 |
provider="omni_agent"
|
| 44 |
)
|
| 45 |
|
|
|
|
| 46 |
except Exception as e:
|
| 47 |
-
print(f"
|
| 48 |
-
|
| 49 |
-
traceback.print_exc() # Poora error print karega
|
| 50 |
-
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
| 1 |
+
from fastapi import APIRouter, Depends, HTTPException, Request
|
|
|
|
| 2 |
from sqlalchemy.ext.asyncio import AsyncSession
|
| 3 |
+
from sqlalchemy.future import select
|
| 4 |
from backend.src.db.session import get_db
|
| 5 |
from backend.src.schemas.chat import ChatRequest, ChatResponse
|
| 6 |
from backend.src.services.chat_service import process_chat
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
from backend.src.models.user import User
|
| 8 |
|
| 9 |
router = APIRouter()
|
| 10 |
|
| 11 |
@router.post("/chat", response_model=ChatResponse)
|
| 12 |
async def chat_endpoint(
|
| 13 |
+
request_body: ChatRequest,
|
| 14 |
+
request: Request, # Browser headers read karne ke liye
|
| 15 |
+
db: AsyncSession = Depends(get_db)
|
| 16 |
):
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
try:
|
| 18 |
+
# 1. API Key se Bot Owner (User) ko dhoondo
|
| 19 |
+
stmt = select(User).where(User.api_key == request_body.api_key)
|
| 20 |
+
result = await db.execute(stmt)
|
| 21 |
+
bot_owner = result.scalars().first()
|
| 22 |
+
|
| 23 |
+
if not bot_owner:
|
| 24 |
+
raise HTTPException(status_code=401, detail="Invalid API Key. Unauthorized access.")
|
| 25 |
+
|
| 26 |
+
# 2. DOMAIN LOCK LOGIC (Whitelisting)
|
| 27 |
+
# Browser automatically 'origin' ya 'referer' header bhejta hai
|
| 28 |
+
client_origin = request.headers.get("origin") or request.headers.get("referer") or ""
|
| 29 |
+
|
| 30 |
+
if bot_owner.allowed_domains != "*":
|
| 31 |
+
allowed = [d.strip() for d in bot_owner.allowed_domains.split(",")]
|
| 32 |
+
# Check if client_origin contains any of the allowed domains
|
| 33 |
+
is_authorized = any(domain in client_origin for domain in allowed)
|
| 34 |
+
|
| 35 |
+
if not is_authorized:
|
| 36 |
+
print(f"🚫 Blocked unauthorized domain: {client_origin}")
|
| 37 |
+
raise HTTPException(status_code=403, detail="Domain not authorized to use this bot.")
|
| 38 |
+
|
| 39 |
+
# 3. Process Chat (Using the bot_owner's credentials)
|
| 40 |
+
session_id = request_body.session_id or f"guest_{bot_owner.id}"
|
| 41 |
|
|
|
|
| 42 |
response_text = await process_chat(
|
| 43 |
+
message=request_body.message,
|
| 44 |
session_id=session_id,
|
| 45 |
+
user_id=str(bot_owner.id), # Owner ki ID use hogi DB lookup ke liye
|
| 46 |
db=db
|
| 47 |
)
|
| 48 |
|
| 49 |
return ChatResponse(
|
| 50 |
response=response_text,
|
| 51 |
session_id=session_id,
|
|
|
|
| 52 |
provider="omni_agent"
|
| 53 |
)
|
| 54 |
|
| 55 |
+
except HTTPException as he: raise he
|
| 56 |
except Exception as e:
|
| 57 |
+
print(f"❌ Chat Error: {e}")
|
| 58 |
+
raise HTTPException(status_code=500, detail="AI Service Interrupted.")
|
|
|
|
|
|
backend/src/api/routes/settings.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
| 1 |
|
|
|
|
| 2 |
import json
|
| 3 |
from fastapi import APIRouter, Depends, HTTPException, status
|
| 4 |
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
| 1 |
|
| 2 |
+
# --- ---
|
| 3 |
import json
|
| 4 |
from fastapi import APIRouter, Depends, HTTPException, status
|
| 5 |
from sqlalchemy.ext.asyncio import AsyncSession
|
backend/src/core/config.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
|
| 2 |
import os
|
| 3 |
from pydantic_settings import BaseSettings, SettingsConfigDict
|
| 4 |
from functools import lru_cache
|
|
|
|
| 1 |
+
# --- EXTERNAL IMPORTS ---
|
| 2 |
import os
|
| 3 |
from pydantic_settings import BaseSettings, SettingsConfigDict
|
| 4 |
from functools import lru_cache
|
backend/src/db/session.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
|
|
| 1 |
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_sessionmaker
|
| 2 |
from sqlalchemy import create_engine
|
| 3 |
from backend.src.core.config import settings
|
|
|
|
| 1 |
+
# --- EXTERNAL IMPORTS ---
|
| 2 |
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_sessionmaker
|
| 3 |
from sqlalchemy import create_engine
|
| 4 |
from backend.src.core.config import settings
|
backend/src/main.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
|
|
| 1 |
import os
|
| 2 |
from fastapi import FastAPI
|
| 3 |
from fastapi.staticfiles import StaticFiles # <--- New Import
|
|
|
|
| 1 |
+
# --- EXTERNAL IMPORTS ---
|
| 2 |
import os
|
| 3 |
from fastapi import FastAPI
|
| 4 |
from fastapi.staticfiles import StaticFiles # <--- New Import
|
backend/src/models/user.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
from sqlalchemy import Column, Integer, String, DateTime, Boolean, Text
|
| 2 |
from sqlalchemy.sql import func
|
| 3 |
from backend.src.db.base import Base
|
| 4 |
|
|
@@ -11,7 +11,14 @@ class User(Base):
|
|
| 11 |
full_name = Column(String, nullable=True)
|
| 12 |
is_active = Column(Boolean, default=True)
|
| 13 |
|
| 14 |
-
# ---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
bot_name = Column(String, default="Support Agent")
|
| 16 |
bot_instruction = Column(Text, default="You are a helpful customer support agent. Only answer questions related to the provided data.")
|
| 17 |
|
|
|
|
| 1 |
+
from sqlalchemy import Column, Integer, String, DateTime, Boolean, Text
|
| 2 |
from sqlalchemy.sql import func
|
| 3 |
from backend.src.db.base import Base
|
| 4 |
|
|
|
|
| 11 |
full_name = Column(String, nullable=True)
|
| 12 |
is_active = Column(Boolean, default=True)
|
| 13 |
|
| 14 |
+
# --- SaaS SECURITY & IDENTIFICATION (New 🔐) ---
|
| 15 |
+
# Har user ki apni unique API Key hogi
|
| 16 |
+
api_key = Column(String, unique=True, index=True, nullable=True)
|
| 17 |
+
# Konsi website par bot chal sakta hai (e.g., "usamatechodyssey.github.io")
|
| 18 |
+
# Default "*" ka matlab hai abhi har jagah chalega, baad mein lock kar sakte hain
|
| 19 |
+
allowed_domains = Column(String, default="*")
|
| 20 |
+
|
| 21 |
+
# --- Bot Customization ---
|
| 22 |
bot_name = Column(String, default="Support Agent")
|
| 23 |
bot_instruction = Column(Text, default="You are a helpful customer support agent. Only answer questions related to the provided data.")
|
| 24 |
|
backend/src/schemas/chat.py
CHANGED
|
@@ -1,15 +1,12 @@
|
|
| 1 |
from pydantic import BaseModel
|
| 2 |
from typing import Optional
|
| 3 |
|
| 4 |
-
# User jab sawal bhejegaecho $GOOGLE_API_KEY
|
| 5 |
class ChatRequest(BaseModel):
|
| 6 |
message: str
|
| 7 |
-
#
|
| 8 |
session_id: Optional[str] = None
|
| 9 |
|
| 10 |
-
# Server jab jawab dega
|
| 11 |
class ChatResponse(BaseModel):
|
| 12 |
response: str
|
| 13 |
-
# Yahan bhi Optional, kyunki guest ke paas ID nahi hogi
|
| 14 |
session_id: Optional[str] = None
|
| 15 |
provider: str
|
|
|
|
| 1 |
from pydantic import BaseModel
|
| 2 |
from typing import Optional
|
| 3 |
|
|
|
|
| 4 |
class ChatRequest(BaseModel):
|
| 5 |
message: str
|
| 6 |
+
api_key: str # <--- Unique key for security 🔑
|
| 7 |
session_id: Optional[str] = None
|
| 8 |
|
|
|
|
| 9 |
class ChatResponse(BaseModel):
|
| 10 |
response: str
|
|
|
|
| 11 |
session_id: Optional[str] = None
|
| 12 |
provider: str
|
backend/src/services/chat_service.py
CHANGED
|
@@ -1,182 +1,4 @@
|
|
| 1 |
|
| 2 |
-
# import json
|
| 3 |
-
# from sqlalchemy.ext.asyncio import AsyncSession
|
| 4 |
-
# from sqlalchemy.future import select
|
| 5 |
-
|
| 6 |
-
# # --- Model Imports ---
|
| 7 |
-
# from backend.src.models.chat import ChatHistory
|
| 8 |
-
# from backend.src.models.integration import UserIntegration
|
| 9 |
-
|
| 10 |
-
# # --- Dynamic Factory & Tool Imports ---
|
| 11 |
-
# from backend.src.services.llm.factory import get_llm_model
|
| 12 |
-
# from backend.src.services.vector_store.qdrant_adapter import get_vector_store
|
| 13 |
-
# from backend.src.services.security.pii_scrubber import PIIScrubber
|
| 14 |
-
|
| 15 |
-
# # --- Agents ---
|
| 16 |
-
# from backend.src.services.tools.secure_agent import get_secure_agent
|
| 17 |
-
# from backend.src.services.tools.nosql_agent import get_nosql_agent
|
| 18 |
-
# from backend.src.services.tools.cms_agent import get_cms_agent
|
| 19 |
-
|
| 20 |
-
# # --- Router ---
|
| 21 |
-
# from backend.src.services.routing.semantic_router import SemanticRouter
|
| 22 |
-
|
| 23 |
-
# # --- LangChain Core ---
|
| 24 |
-
# from langchain_core.messages import HumanMessage, AIMessage
|
| 25 |
-
# from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
|
| 26 |
-
|
| 27 |
-
# # ==========================================
|
| 28 |
-
# # HELPER FUNCTIONS (UPDATED STRICT LOGIC)
|
| 29 |
-
# # ==========================================
|
| 30 |
-
|
| 31 |
-
# async def get_user_integrations(user_id: str, db: AsyncSession) -> dict:
|
| 32 |
-
# if not user_id: return {}
|
| 33 |
-
|
| 34 |
-
# query = select(UserIntegration).where(UserIntegration.user_id == user_id, UserIntegration.is_active == True)
|
| 35 |
-
# result = await db.execute(query)
|
| 36 |
-
# integrations = result.scalars().all()
|
| 37 |
-
|
| 38 |
-
# settings = {}
|
| 39 |
-
# for i in integrations:
|
| 40 |
-
# try:
|
| 41 |
-
# creds = json.loads(i.credentials)
|
| 42 |
-
# creds['provider'] = i.provider
|
| 43 |
-
# creds['schema_map'] = i.schema_map if i.schema_map else {}
|
| 44 |
-
|
| 45 |
-
# # --- 🔥 FIX: NO DEFAULT DESCRIPTION ---
|
| 46 |
-
# # Agar DB mein description NULL hai, to NULL hi rehne do.
|
| 47 |
-
# # Hum isay Router mein add hi nahi karenge.
|
| 48 |
-
# creds['description'] = i.profile_description
|
| 49 |
-
|
| 50 |
-
# settings[i.provider] = creds
|
| 51 |
-
# except (json.JSONDecodeError, TypeError):
|
| 52 |
-
# continue
|
| 53 |
-
# return settings
|
| 54 |
-
|
| 55 |
-
# async def save_chat_to_db(db: AsyncSession, session_id: str, human_msg: str, ai_msg: str, provider: str):
|
| 56 |
-
# if not session_id: return
|
| 57 |
-
# safe_human = PIIScrubber.scrub(human_msg)
|
| 58 |
-
# safe_ai = PIIScrubber.scrub(ai_msg)
|
| 59 |
-
# new_chat = ChatHistory(
|
| 60 |
-
# session_id=session_id, human_message=safe_human, ai_message=safe_ai, provider=provider
|
| 61 |
-
# )
|
| 62 |
-
# db.add(new_chat)
|
| 63 |
-
# await db.commit()
|
| 64 |
-
|
| 65 |
-
# async def get_chat_history(session_id: str, db: AsyncSession):
|
| 66 |
-
# if not session_id: return []
|
| 67 |
-
# query = select(ChatHistory).where(ChatHistory.session_id == session_id).order_by(ChatHistory.timestamp.asc())
|
| 68 |
-
# result = await db.execute(query)
|
| 69 |
-
# return result.scalars().all()
|
| 70 |
-
|
| 71 |
-
# OMNI_SUPPORT_PROMPT = "You are OmniAgent. Answer based on the provided context or chat history."
|
| 72 |
-
|
| 73 |
-
# # ==========================================
|
| 74 |
-
# # MAIN CHAT LOGIC
|
| 75 |
-
# # ==========================================
|
| 76 |
-
# async def process_chat(message: str, session_id: str, user_id: str, db: AsyncSession):
|
| 77 |
-
|
| 78 |
-
# # 1. User Settings
|
| 79 |
-
# user_settings = await get_user_integrations(user_id, db)
|
| 80 |
-
|
| 81 |
-
# # 2. LLM Check
|
| 82 |
-
# llm_creds = user_settings.get('groq') or user_settings.get('openai')
|
| 83 |
-
# if not llm_creds:
|
| 84 |
-
# return "Please configure your AI Model in Settings."
|
| 85 |
-
|
| 86 |
-
# # 3. Build Tool Map for Router (STRICT FILTERING)
|
| 87 |
-
# tools_map = {}
|
| 88 |
-
# for provider, config in user_settings.items():
|
| 89 |
-
# if provider in ['sanity', 'sql', 'mongodb']:
|
| 90 |
-
# # 🔥 Check: Agar Description hai, tabhi Router mein daalo
|
| 91 |
-
# if config.get('description'):
|
| 92 |
-
# tools_map[provider] = config['description']
|
| 93 |
-
# else:
|
| 94 |
-
# print(f"⚠️ [Router] Skipping {provider} - No Description found.")
|
| 95 |
-
|
| 96 |
-
# # 4. SEMANTIC DECISION
|
| 97 |
-
# selected_provider = None
|
| 98 |
-
# if tools_map:
|
| 99 |
-
# router = SemanticRouter()
|
| 100 |
-
# selected_provider = router.route(message, tools_map)
|
| 101 |
-
# else:
|
| 102 |
-
# print("⚠️ [Router] No active tools with descriptions found.")
|
| 103 |
-
|
| 104 |
-
# response_text = ""
|
| 105 |
-
# provider_name = "general_chat"
|
| 106 |
-
|
| 107 |
-
# # 5. Route to Winner
|
| 108 |
-
# if selected_provider:
|
| 109 |
-
# print(f"👉 [Router] Selected Tool: {selected_provider.upper()}")
|
| 110 |
-
# try:
|
| 111 |
-
# if selected_provider == 'sanity':
|
| 112 |
-
# schema = user_settings['sanity'].get('schema_map', {})
|
| 113 |
-
# agent = get_cms_agent(user_id=user_id, schema_map=schema, llm_credentials=llm_creds)
|
| 114 |
-
# res = await agent.ainvoke({"input": message})
|
| 115 |
-
# response_text = str(res.get('output', ''))
|
| 116 |
-
# provider_name = "cms_agent"
|
| 117 |
-
|
| 118 |
-
# elif selected_provider == 'sql':
|
| 119 |
-
# role = "admin" if user_id == '99' else "customer"
|
| 120 |
-
# agent = get_secure_agent(int(user_id), role, user_settings['sql'], llm_credentials=llm_creds)
|
| 121 |
-
# res = await agent.ainvoke({"input": message})
|
| 122 |
-
# response_text = str(res.get('output', ''))
|
| 123 |
-
# provider_name = "sql_agent"
|
| 124 |
-
|
| 125 |
-
# elif selected_provider == 'mongodb':
|
| 126 |
-
# agent = get_nosql_agent(user_id, user_settings['mongodb'], llm_credentials=llm_creds)
|
| 127 |
-
# res = await agent.ainvoke({"input": message})
|
| 128 |
-
# response_text = str(res.get('output', ''))
|
| 129 |
-
# provider_name = "nosql_agent"
|
| 130 |
-
|
| 131 |
-
# # Anti-Hallucination
|
| 132 |
-
# if not response_text or "error" in response_text.lower():
|
| 133 |
-
# response_text = "" # Trigger Fallback
|
| 134 |
-
|
| 135 |
-
# except Exception as e:
|
| 136 |
-
# print(f"❌ [Router] Execution Failed: {e}")
|
| 137 |
-
# response_text = ""
|
| 138 |
-
|
| 139 |
-
# # 6. Fallback / RAG
|
| 140 |
-
# if not response_text:
|
| 141 |
-
# print("👉 [Router] Fallback to RAG/General Chat...")
|
| 142 |
-
# try:
|
| 143 |
-
# llm = get_llm_model(credentials=llm_creds)
|
| 144 |
-
|
| 145 |
-
# context = ""
|
| 146 |
-
# if 'qdrant' in user_settings:
|
| 147 |
-
# try:
|
| 148 |
-
# vector_store = get_vector_store(credentials=user_settings['qdrant'])
|
| 149 |
-
# docs = await vector_store.asimilarity_search(message, k=3)
|
| 150 |
-
# if docs:
|
| 151 |
-
# context = "\n\n".join([d.page_content for d in docs])
|
| 152 |
-
# except Exception as e:
|
| 153 |
-
# print(f"⚠️ RAG Warning: {e}")
|
| 154 |
-
|
| 155 |
-
# system_instruction = OMNI_SUPPORT_PROMPT
|
| 156 |
-
# if context: system_instruction = f"Context:\n{context}"
|
| 157 |
-
|
| 158 |
-
# history = await get_chat_history(session_id, db)
|
| 159 |
-
# formatted_history = []
|
| 160 |
-
# for chat in history:
|
| 161 |
-
# formatted_history.append(HumanMessage(content=chat.human_message))
|
| 162 |
-
# if chat.ai_message: formatted_history.append(AIMessage(content=chat.ai_message))
|
| 163 |
-
|
| 164 |
-
# prompt = ChatPromptTemplate.from_messages([
|
| 165 |
-
# ("system", system_instruction),
|
| 166 |
-
# MessagesPlaceholder(variable_name="chat_history"),
|
| 167 |
-
# ("human", "{question}")
|
| 168 |
-
# ])
|
| 169 |
-
# chain = prompt | llm
|
| 170 |
-
|
| 171 |
-
# ai_response = await chain.ainvoke({"chat_history": formatted_history, "question": message})
|
| 172 |
-
# response_text = ai_response.content
|
| 173 |
-
# provider_name = "rag_fallback"
|
| 174 |
-
|
| 175 |
-
# except Exception as e:
|
| 176 |
-
# response_text = "I am currently unable to process your request."
|
| 177 |
-
|
| 178 |
-
# await save_chat_to_db(db, session_id, message, response_text, provider_name)
|
| 179 |
-
# return response_text
|
| 180 |
import json
|
| 181 |
from sqlalchemy.ext.asyncio import AsyncSession
|
| 182 |
from sqlalchemy.future import select
|
|
@@ -386,213 +208,3 @@ async def process_chat(message: str, session_id: str, user_id: str, db: AsyncSes
|
|
| 386 |
# 7. Save to DB
|
| 387 |
await save_chat_to_db(db, session_id, message, response_text, provider_name)
|
| 388 |
return response_text
|
| 389 |
-
# import json
|
| 390 |
-
# from sqlalchemy.ext.asyncio import AsyncSession
|
| 391 |
-
# from sqlalchemy.future import select
|
| 392 |
-
|
| 393 |
-
# # --- Model Imports ---
|
| 394 |
-
# from backend.src.models.chat import ChatHistory
|
| 395 |
-
# from backend.src.models.integration import UserIntegration
|
| 396 |
-
# from backend.src.models.user import User # Added User model for Bot Persona
|
| 397 |
-
|
| 398 |
-
# # --- Dynamic Factory & Tool Imports ---
|
| 399 |
-
# from backend.src.services.llm.factory import get_llm_model
|
| 400 |
-
# from backend.src.services.vector_store.qdrant_adapter import get_vector_store
|
| 401 |
-
# from backend.src.services.security.pii_scrubber import PIIScrubber
|
| 402 |
-
|
| 403 |
-
# # --- Agents ---
|
| 404 |
-
# from backend.src.services.tools.secure_agent import get_secure_agent
|
| 405 |
-
# from backend.src.services.tools.nosql_agent import get_nosql_agent
|
| 406 |
-
# from backend.src.services.tools.cms_agent import get_cms_agent
|
| 407 |
-
|
| 408 |
-
# # --- Router ---
|
| 409 |
-
# from backend.src.services.routing.semantic_router import SemanticRouter
|
| 410 |
-
|
| 411 |
-
# # --- LangChain Core ---
|
| 412 |
-
# from langchain_core.messages import HumanMessage, AIMessage
|
| 413 |
-
# from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
|
| 414 |
-
|
| 415 |
-
# # ==========================================
|
| 416 |
-
# # HELPER FUNCTIONS
|
| 417 |
-
# # ==========================================
|
| 418 |
-
|
| 419 |
-
# async def get_user_integrations(user_id: str, db: AsyncSession) -> dict:
|
| 420 |
-
# """Fetches active integrations and filters valid descriptions."""
|
| 421 |
-
# if not user_id: return {}
|
| 422 |
-
|
| 423 |
-
# query = select(UserIntegration).where(UserIntegration.user_id == user_id, UserIntegration.is_active == True)
|
| 424 |
-
# result = await db.execute(query)
|
| 425 |
-
# integrations = result.scalars().all()
|
| 426 |
-
|
| 427 |
-
# settings = {}
|
| 428 |
-
# for i in integrations:
|
| 429 |
-
# try:
|
| 430 |
-
# creds = json.loads(i.credentials)
|
| 431 |
-
# creds['provider'] = i.provider
|
| 432 |
-
# creds['schema_map'] = i.schema_map if i.schema_map else {}
|
| 433 |
-
|
| 434 |
-
# # --- STRICT CHECK ---
|
| 435 |
-
# # Agar Description NULL hai to dictionary mein mat daalo
|
| 436 |
-
# # Taake Router confuse na ho
|
| 437 |
-
# if i.profile_description:
|
| 438 |
-
# creds['description'] = i.profile_description
|
| 439 |
-
|
| 440 |
-
# settings[i.provider] = creds
|
| 441 |
-
# except (json.JSONDecodeError, TypeError):
|
| 442 |
-
# continue
|
| 443 |
-
# return settings
|
| 444 |
-
|
| 445 |
-
# async def save_chat_to_db(db: AsyncSession, session_id: str, human_msg: str, ai_msg: str, provider: str):
|
| 446 |
-
# """Saves chat history with PII redaction."""
|
| 447 |
-
# if not session_id: return
|
| 448 |
-
# safe_human = PIIScrubber.scrub(human_msg)
|
| 449 |
-
# safe_ai = PIIScrubber.scrub(ai_msg)
|
| 450 |
-
# new_chat = ChatHistory(
|
| 451 |
-
# session_id=session_id, human_message=safe_human, ai_message=safe_ai, provider=provider
|
| 452 |
-
# )
|
| 453 |
-
# db.add(new_chat)
|
| 454 |
-
# await db.commit()
|
| 455 |
-
|
| 456 |
-
# async def get_chat_history(session_id: str, db: AsyncSession):
|
| 457 |
-
# """Retrieves past conversation history."""
|
| 458 |
-
# if not session_id: return []
|
| 459 |
-
# query = select(ChatHistory).where(ChatHistory.session_id == session_id).order_by(ChatHistory.timestamp.asc())
|
| 460 |
-
# result = await db.execute(query)
|
| 461 |
-
# return result.scalars().all()
|
| 462 |
-
|
| 463 |
-
# async def get_bot_persona(user_id: str, db: AsyncSession):
|
| 464 |
-
# """Fetches custom Bot Name and Instructions from User table."""
|
| 465 |
-
# try:
|
| 466 |
-
# result = await db.execute(select(User).where(User.id == int(user_id)))
|
| 467 |
-
# user = result.scalars().first()
|
| 468 |
-
# if user:
|
| 469 |
-
# return {
|
| 470 |
-
# "name": getattr(user, "bot_name", "OmniAgent"),
|
| 471 |
-
# "instruction": getattr(user, "bot_instruction", "You are a helpful AI assistant.")
|
| 472 |
-
# }
|
| 473 |
-
# except Exception:
|
| 474 |
-
# pass
|
| 475 |
-
# return {"name": "OmniAgent", "instruction": "You are a helpful AI assistant."}
|
| 476 |
-
|
| 477 |
-
# # ==========================================
|
| 478 |
-
# # MAIN CHAT LOGIC
|
| 479 |
-
# # ==========================================
|
| 480 |
-
# async def process_chat(message: str, session_id: str, user_id: str, db: AsyncSession):
|
| 481 |
-
|
| 482 |
-
# # 1. Fetch User Settings & Persona
|
| 483 |
-
# user_settings = await get_user_integrations(user_id, db)
|
| 484 |
-
# bot_persona = await get_bot_persona(user_id, db)
|
| 485 |
-
|
| 486 |
-
# # 2. LLM Check
|
| 487 |
-
# llm_creds = user_settings.get('groq') or user_settings.get('openai')
|
| 488 |
-
# if not llm_creds:
|
| 489 |
-
# return "Please configure your AI Model in Settings."
|
| 490 |
-
|
| 491 |
-
# # 3. Build Tool Map for Router (STRICT FILTERING)
|
| 492 |
-
# tools_map = {}
|
| 493 |
-
# for provider, config in user_settings.items():
|
| 494 |
-
# if provider in ['sanity', 'sql', 'mongodb']:
|
| 495 |
-
# # Sirf tab add karo agar description exist karti hai
|
| 496 |
-
# if config.get('description'):
|
| 497 |
-
# tools_map[provider] = config['description']
|
| 498 |
-
# else:
|
| 499 |
-
# print(f"⚠️ [Router] Skipping {provider} - No Description found.")
|
| 500 |
-
|
| 501 |
-
# # 4. SEMANTIC DECISION (Router)
|
| 502 |
-
# selected_provider = None
|
| 503 |
-
# if tools_map:
|
| 504 |
-
# router = SemanticRouter() # Singleton Instance
|
| 505 |
-
# selected_provider = router.route(message, tools_map)
|
| 506 |
-
# else:
|
| 507 |
-
# print("⚠️ [Router] No active tools with descriptions found.")
|
| 508 |
-
|
| 509 |
-
# response_text = ""
|
| 510 |
-
# provider_name = "general_chat"
|
| 511 |
-
|
| 512 |
-
# # 5. Route to Winner (Tool Execution)
|
| 513 |
-
# if selected_provider:
|
| 514 |
-
# print(f"👉 [Router] Selected Tool: {selected_provider.upper()}")
|
| 515 |
-
# try:
|
| 516 |
-
# if selected_provider == 'sanity':
|
| 517 |
-
# schema = user_settings['sanity'].get('schema_map', {})
|
| 518 |
-
# agent = get_cms_agent(user_id=user_id, schema_map=schema, llm_credentials=llm_creds)
|
| 519 |
-
# res = await agent.ainvoke({"input": message})
|
| 520 |
-
# response_text = str(res.get('output', ''))
|
| 521 |
-
# provider_name = "cms_agent"
|
| 522 |
-
|
| 523 |
-
# elif selected_provider == 'sql':
|
| 524 |
-
# role = "admin" if user_id == '99' else "customer"
|
| 525 |
-
# agent = get_secure_agent(int(user_id), role, user_settings['sql'], llm_credentials=llm_creds)
|
| 526 |
-
# res = await agent.ainvoke({"input": message})
|
| 527 |
-
# response_text = str(res.get('output', ''))
|
| 528 |
-
# provider_name = "sql_agent"
|
| 529 |
-
|
| 530 |
-
# elif selected_provider == 'mongodb':
|
| 531 |
-
# agent = get_nosql_agent(user_id, user_settings['mongodb'], llm_credentials=llm_creds)
|
| 532 |
-
# res = await agent.ainvoke({"input": message})
|
| 533 |
-
# response_text = str(res.get('output', ''))
|
| 534 |
-
# provider_name = "nosql_agent"
|
| 535 |
-
|
| 536 |
-
# # Anti-Hallucination Check
|
| 537 |
-
# if not response_text or "error" in response_text.lower():
|
| 538 |
-
# print(f"⚠️ [Router] Tool {selected_provider} failed/empty. Triggering Fallback.")
|
| 539 |
-
# response_text = "" # Clears response to trigger fallback below
|
| 540 |
-
|
| 541 |
-
# except Exception as e:
|
| 542 |
-
# print(f"❌ [Router] Execution Failed: {e}")
|
| 543 |
-
# response_text = ""
|
| 544 |
-
|
| 545 |
-
# # 6. Fallback / RAG (General Chat)
|
| 546 |
-
# if not response_text:
|
| 547 |
-
# print("👉 [Router] Fallback to RAG/General Chat...")
|
| 548 |
-
# try:
|
| 549 |
-
# llm = get_llm_model(credentials=llm_creds)
|
| 550 |
-
|
| 551 |
-
# # Context from Vector DB
|
| 552 |
-
# context = ""
|
| 553 |
-
# if 'qdrant' in user_settings:
|
| 554 |
-
# try:
|
| 555 |
-
# vector_store = get_vector_store(credentials=user_settings['qdrant'])
|
| 556 |
-
# docs = await vector_store.asimilarity_search(message, k=3)
|
| 557 |
-
# if docs:
|
| 558 |
-
# context = "\n\n".join([d.page_content for d in docs])
|
| 559 |
-
# except Exception as e:
|
| 560 |
-
# print(f"⚠️ RAG Warning: {e}")
|
| 561 |
-
|
| 562 |
-
# # --- DYNAMIC SYSTEM PROMPT (PERSONA) ---
|
| 563 |
-
# system_instruction = f"""
|
| 564 |
-
# IDENTITY: You are '{bot_persona['name']}'.
|
| 565 |
-
# MISSION: {bot_persona['instruction']}
|
| 566 |
-
|
| 567 |
-
# CONTEXT FROM KNOWLEDGE BASE:
|
| 568 |
-
# {context if context else "No specific documents found."}
|
| 569 |
-
|
| 570 |
-
# Answer the user's question based on the context above or your general knowledge if permitted by your mission.
|
| 571 |
-
# """
|
| 572 |
-
|
| 573 |
-
# # History Load
|
| 574 |
-
# history = await get_chat_history(session_id, db)
|
| 575 |
-
# formatted_history = []
|
| 576 |
-
# for chat in history:
|
| 577 |
-
# formatted_history.append(HumanMessage(content=chat.human_message))
|
| 578 |
-
# if chat.ai_message: formatted_history.append(AIMessage(content=chat.ai_message))
|
| 579 |
-
|
| 580 |
-
# # LLM Call
|
| 581 |
-
# prompt = ChatPromptTemplate.from_messages([
|
| 582 |
-
# ("system", system_instruction),
|
| 583 |
-
# MessagesPlaceholder(variable_name="chat_history"),
|
| 584 |
-
# ("human", "{question}")
|
| 585 |
-
# ])
|
| 586 |
-
# chain = prompt | llm
|
| 587 |
-
|
| 588 |
-
# ai_response = await chain.ainvoke({"chat_history": formatted_history, "question": message})
|
| 589 |
-
# response_text = ai_response.content
|
| 590 |
-
# provider_name = "rag_fallback"
|
| 591 |
-
|
| 592 |
-
# except Exception as e:
|
| 593 |
-
# print(f"❌ Fallback Error: {e}")
|
| 594 |
-
# response_text = "I am currently unable to process your request. Please check your AI configuration."
|
| 595 |
-
|
| 596 |
-
# # 7. Save to DB
|
| 597 |
-
# await save_chat_to_db(db, session_id, message, response_text, provider_name)
|
| 598 |
-
# return response_text
|
|
|
|
| 1 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
import json
|
| 3 |
from sqlalchemy.ext.asyncio import AsyncSession
|
| 4 |
from sqlalchemy.future import select
|
|
|
|
| 208 |
# 7. Save to DB
|
| 209 |
await save_chat_to_db(db, session_id, message, response_text, provider_name)
|
| 210 |
return response_text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
backend/src/utils/auth.py
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
|
|
| 1 |
from passlib.context import CryptContext
|
| 2 |
from datetime import datetime, timedelta
|
| 3 |
from jose import jwt
|
| 4 |
from backend.src.core.config import settings
|
| 5 |
|
| 6 |
-
# Password Hasher (Bcrypt)
|
| 7 |
pwd_context = CryptContext(schemes=["argon2"], deprecated="auto")
|
| 8 |
|
| 9 |
# JWT Configuration
|
|
@@ -24,7 +25,16 @@ def create_access_token(data: dict):
|
|
| 24 |
expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
|
| 25 |
to_encode.update({"exp": expire})
|
| 26 |
|
| 27 |
-
# Secret Key config se lenge (Ensure karein ke config mein ho)
|
| 28 |
secret_key = settings.SECRET_KEY
|
| 29 |
encoded_jwt = jwt.encode(to_encode, secret_key, algorithm=ALGORITHM)
|
| 30 |
-
return encoded_jwt
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import secrets # Cryptographically strong random numbers generate karne ke liye
|
| 2 |
from passlib.context import CryptContext
|
| 3 |
from datetime import datetime, timedelta
|
| 4 |
from jose import jwt
|
| 5 |
from backend.src.core.config import settings
|
| 6 |
|
| 7 |
+
# Password Hasher (Bcrypt/Argon2)
|
| 8 |
pwd_context = CryptContext(schemes=["argon2"], deprecated="auto")
|
| 9 |
|
| 10 |
# JWT Configuration
|
|
|
|
| 25 |
expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
|
| 26 |
to_encode.update({"exp": expire})
|
| 27 |
|
|
|
|
| 28 |
secret_key = settings.SECRET_KEY
|
| 29 |
encoded_jwt = jwt.encode(to_encode, secret_key, algorithm=ALGORITHM)
|
| 30 |
+
return encoded_jwt
|
| 31 |
+
|
| 32 |
+
# --- NEW: SaaS API KEY GENERATOR (🔐) ---
|
| 33 |
+
def generate_api_key():
|
| 34 |
+
"""
|
| 35 |
+
Ek unique aur secure API Key banata hai.
|
| 36 |
+
Format: omni_as87d... (64 characters long)
|
| 37 |
+
"""
|
| 38 |
+
# 32 bytes ka random token jo URL-safe string ban jayega
|
| 39 |
+
random_string = secrets.token_urlsafe(32)
|
| 40 |
+
return f"omni_{random_string}"
|
backend/src/utils/security.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
|
|
| 1 |
from cryptography.fernet import Fernet
|
| 2 |
import base64
|
| 3 |
|
|
|
|
| 1 |
+
# --- EXTERNAL IMPORTS ---
|
| 2 |
from cryptography.fernet import Fernet
|
| 3 |
import base64
|
| 4 |
|
requirements.txt
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
|
|
| 1 |
accelerate==1.12.0
|
| 2 |
aiofiles==25.1.0
|
| 3 |
aiohappyeyeballs==2.6.1
|
|
|
|
| 1 |
+
# Requirements for the project
|
| 2 |
accelerate==1.12.0
|
| 3 |
aiofiles==25.1.0
|
| 4 |
aiohappyeyeballs==2.6.1
|
static/widget.js
CHANGED
|
@@ -1,55 +1,70 @@
|
|
| 1 |
(function() {
|
| 2 |
// ----------------------------------------------------
|
| 3 |
-
// 1. CONFIGURATION
|
| 4 |
// ----------------------------------------------------
|
| 5 |
const scriptTag = document.currentScript;
|
| 6 |
|
| 7 |
-
|
|
|
|
| 8 |
const API_URL = scriptTag.getAttribute("data-api-url");
|
| 9 |
-
const THEME_COLOR = scriptTag.getAttribute("data-theme-color") || "#
|
| 10 |
|
| 11 |
-
if (!
|
| 12 |
-
console.error("OmniAgent
|
| 13 |
return;
|
| 14 |
}
|
| 15 |
|
| 16 |
const CHAT_SESSION_ID = "omni_session_" + Math.random().toString(36).slice(2, 11);
|
| 17 |
|
| 18 |
// ----------------------------------------------------
|
| 19 |
-
// 2. STYLES
|
| 20 |
// ----------------------------------------------------
|
| 21 |
const style = document.createElement('style');
|
| 22 |
style.innerHTML = `
|
| 23 |
#omni-widget-container {
|
| 24 |
-
position: fixed; bottom: 20px; right: 20px; z-index: 999999;
|
|
|
|
| 25 |
}
|
| 26 |
#omni-chat-btn {
|
| 27 |
background: ${THEME_COLOR}; color: white; border: none; padding: 15px; border-radius: 50%;
|
| 28 |
-
cursor: pointer; box-shadow: 0 4px
|
| 29 |
-
display: flex; align-items: center; justify-content: center; transition:
|
| 30 |
}
|
| 31 |
-
#omni-chat-btn:hover { transform: scale(1.1); }
|
|
|
|
| 32 |
#omni-chat-window {
|
| 33 |
-
display: none; width:
|
| 34 |
-
box-shadow: 0
|
| 35 |
-
margin-bottom:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
}
|
|
|
|
| 37 |
#omni-header {
|
| 38 |
-
background: ${THEME_COLOR}; color: white; padding:
|
| 39 |
-
justify-content: space-between; align-items: center;
|
| 40 |
}
|
| 41 |
-
#omni-messages { flex: 1; padding:
|
| 42 |
-
#omni-input-area { display: flex; border-top: 1px solid #
|
| 43 |
-
#omni-input { flex: 1; padding: 12px; border:
|
| 44 |
-
#omni-send { background: transparent; border: none; color: ${THEME_COLOR}; font-weight: bold; cursor: pointer; padding: 0
|
| 45 |
-
|
| 46 |
-
.omni-msg
|
| 47 |
-
.omni-msg.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
`;
|
| 49 |
document.head.appendChild(style);
|
| 50 |
|
| 51 |
// ----------------------------------------------------
|
| 52 |
-
// 3. UI LOGIC
|
| 53 |
// ----------------------------------------------------
|
| 54 |
window.toggleOmniChat = function() {
|
| 55 |
const win = document.getElementById('omni-chat-window');
|
|
@@ -62,7 +77,7 @@
|
|
| 62 |
};
|
| 63 |
|
| 64 |
// ----------------------------------------------------
|
| 65 |
-
// 4. HTML STRUCTURE
|
| 66 |
// ----------------------------------------------------
|
| 67 |
const container = document.createElement('div');
|
| 68 |
container.id = 'omni-widget-container';
|
|
@@ -70,12 +85,15 @@
|
|
| 70 |
container.innerHTML = `
|
| 71 |
<div id="omni-chat-window">
|
| 72 |
<div id="omni-header">
|
| 73 |
-
<
|
| 74 |
-
|
|
|
|
|
|
|
|
|
|
| 75 |
</div>
|
| 76 |
<div id="omni-messages"></div>
|
| 77 |
<div id="omni-input-area">
|
| 78 |
-
<input type="text" id="omni-input" placeholder="
|
| 79 |
<button id="omni-send">➤</button>
|
| 80 |
</div>
|
| 81 |
</div>
|
|
@@ -85,7 +103,7 @@
|
|
| 85 |
document.body.appendChild(container);
|
| 86 |
|
| 87 |
// ----------------------------------------------------
|
| 88 |
-
// 5. CHAT
|
| 89 |
// ----------------------------------------------------
|
| 90 |
const inputField = document.getElementById('omni-input');
|
| 91 |
const sendButton = document.getElementById('omni-send');
|
|
@@ -94,9 +112,10 @@
|
|
| 94 |
function addMessage(text, sender) {
|
| 95 |
const div = document.createElement('div');
|
| 96 |
div.className = `omni-msg ${sender}`;
|
|
|
|
| 97 |
div.innerHTML = text.replace(/(https?:\/\/[^\s]+)/g, '<a href="$1" target="_blank" style="color:inherit; text-decoration:underline;">$1</a>');
|
| 98 |
messagesContainer.appendChild(div);
|
| 99 |
-
messagesContainer.
|
| 100 |
}
|
| 101 |
|
| 102 |
async function sendMessage() {
|
|
@@ -106,40 +125,53 @@
|
|
| 106 |
addMessage(text, 'user');
|
| 107 |
inputField.value = '';
|
| 108 |
|
| 109 |
-
//
|
| 110 |
const loadingDiv = document.createElement('div');
|
| 111 |
loadingDiv.className = 'omni-msg bot';
|
| 112 |
-
loadingDiv.innerHTML = '
|
| 113 |
messagesContainer.appendChild(loadingDiv);
|
|
|
|
| 114 |
|
| 115 |
try {
|
| 116 |
const response = await fetch(`${API_URL}/api/v1/chat`, {
|
| 117 |
method: 'POST',
|
| 118 |
-
headers: {
|
|
|
|
|
|
|
| 119 |
body: JSON.stringify({
|
| 120 |
message: text,
|
| 121 |
session_id: CHAT_SESSION_ID,
|
| 122 |
-
|
| 123 |
})
|
| 124 |
});
|
|
|
|
| 125 |
const data = await response.json();
|
| 126 |
messagesContainer.removeChild(loadingDiv);
|
| 127 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 128 |
} catch (error) {
|
| 129 |
-
messagesContainer.removeChild(loadingDiv);
|
| 130 |
-
addMessage("Connection
|
| 131 |
console.error("OmniAgent API Error:", error);
|
| 132 |
}
|
| 133 |
}
|
| 134 |
|
|
|
|
| 135 |
sendButton.addEventListener('click', sendMessage);
|
| 136 |
inputField.addEventListener('keypress', (e) => {
|
| 137 |
if(e.key === 'Enter') { sendMessage(); }
|
| 138 |
});
|
| 139 |
|
| 140 |
-
// Welcome
|
| 141 |
setTimeout(() => {
|
| 142 |
-
addMessage("Hello! How can I
|
| 143 |
-
},
|
| 144 |
|
| 145 |
})();
|
|
|
|
| 1 |
(function() {
|
| 2 |
// ----------------------------------------------------
|
| 3 |
+
// 1. CONFIGURATION: Security & Metadata
|
| 4 |
// ----------------------------------------------------
|
| 5 |
const scriptTag = document.currentScript;
|
| 6 |
|
| 7 |
+
// Ab hum User ID nahi, balki secure API Key mangenge 🔑
|
| 8 |
+
const API_KEY = scriptTag.getAttribute("data-api-key");
|
| 9 |
const API_URL = scriptTag.getAttribute("data-api-url");
|
| 10 |
+
const THEME_COLOR = scriptTag.getAttribute("data-theme-color") || "#FF0000"; // Default Red for your theme
|
| 11 |
|
| 12 |
+
if (!API_KEY || !API_URL) {
|
| 13 |
+
console.error("OmniAgent Security Error: data-api-key or data-api-url is missing!");
|
| 14 |
return;
|
| 15 |
}
|
| 16 |
|
| 17 |
const CHAT_SESSION_ID = "omni_session_" + Math.random().toString(36).slice(2, 11);
|
| 18 |
|
| 19 |
// ----------------------------------------------------
|
| 20 |
+
// 2. STYLES: UI & Responsive Design
|
| 21 |
// ----------------------------------------------------
|
| 22 |
const style = document.createElement('style');
|
| 23 |
style.innerHTML = `
|
| 24 |
#omni-widget-container {
|
| 25 |
+
position: fixed; bottom: 20px; right: 20px; z-index: 999999;
|
| 26 |
+
font-family: 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
|
| 27 |
}
|
| 28 |
#omni-chat-btn {
|
| 29 |
background: ${THEME_COLOR}; color: white; border: none; padding: 15px; border-radius: 50%;
|
| 30 |
+
cursor: pointer; box-shadow: 0 4px 15px rgba(0,0,0,0.3); width: 60px; height: 60px; font-size: 24px;
|
| 31 |
+
display: flex; align-items: center; justify-content: center; transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
| 32 |
}
|
| 33 |
+
#omni-chat-btn:hover { transform: scale(1.1) rotate(5deg); }
|
| 34 |
+
|
| 35 |
#omni-chat-window {
|
| 36 |
+
display: none; width: 370px; height: 550px; background: white; border-radius: 16px;
|
| 37 |
+
box-shadow: 0 12px 40px rgba(0,0,0,0.2); flex-direction: column; overflow: hidden;
|
| 38 |
+
margin-bottom: 20px; border: 1px solid #f0f0f0; animation: omniSlideUp 0.4s ease;
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
@keyframes omniSlideUp {
|
| 42 |
+
from { opacity: 0; transform: translateY(30px); }
|
| 43 |
+
to { opacity: 1; transform: translateY(0); }
|
| 44 |
}
|
| 45 |
+
|
| 46 |
#omni-header {
|
| 47 |
+
background: ${THEME_COLOR}; color: white; padding: 18px; font-weight: 600; display: flex;
|
| 48 |
+
justify-content: space-between; align-items: center; letter-spacing: 0.5px;
|
| 49 |
}
|
| 50 |
+
#omni-messages { flex: 1; padding: 20px; overflow-y: auto; background: #ffffff; display: flex; flex-direction: column; }
|
| 51 |
+
#omni-input-area { display: flex; border-top: 1px solid #eee; background: #fff; padding: 10px; }
|
| 52 |
+
#omni-input { flex: 1; padding: 12px; border: 1px solid #eee; border-radius: 25px; outline: none; font-size: 14px; background: #f8f9fa; }
|
| 53 |
+
#omni-send { background: transparent; border: none; color: ${THEME_COLOR}; font-weight: bold; cursor: pointer; padding: 0 12px; font-size: 22px; }
|
| 54 |
+
|
| 55 |
+
.omni-msg { margin: 10px 0; padding: 12px 16px; border-radius: 18px; max-width: 85%; font-size: 14px; line-height: 1.5; word-wrap: break-word; position: relative; }
|
| 56 |
+
.omni-msg.user { background: ${THEME_COLOR}; color: white; align-self: flex-end; border-bottom-right-radius: 2px; box-shadow: 0 4px 10px rgba(0,0,0,0.1); }
|
| 57 |
+
.omni-msg.bot { background: #f0f2f5; color: #1c1e21; align-self: flex-start; border-bottom-left-radius: 2px; }
|
| 58 |
+
|
| 59 |
+
/* Custom Scrollbar */
|
| 60 |
+
#omni-messages::-webkit-scrollbar { width: 5px; }
|
| 61 |
+
#omni-messages::-webkit-scrollbar-track { background: #f1f1f1; }
|
| 62 |
+
#omni-messages::-webkit-scrollbar-thumb { background: #ccc; border-radius: 10px; }
|
| 63 |
`;
|
| 64 |
document.head.appendChild(style);
|
| 65 |
|
| 66 |
// ----------------------------------------------------
|
| 67 |
+
// 3. UI LOGIC: Global Toggle Function
|
| 68 |
// ----------------------------------------------------
|
| 69 |
window.toggleOmniChat = function() {
|
| 70 |
const win = document.getElementById('omni-chat-window');
|
|
|
|
| 77 |
};
|
| 78 |
|
| 79 |
// ----------------------------------------------------
|
| 80 |
+
// 4. HTML STRUCTURE: Dynamic Insertion
|
| 81 |
// ----------------------------------------------------
|
| 82 |
const container = document.createElement('div');
|
| 83 |
container.id = 'omni-widget-container';
|
|
|
|
| 85 |
container.innerHTML = `
|
| 86 |
<div id="omni-chat-window">
|
| 87 |
<div id="omni-header">
|
| 88 |
+
<div style="display:flex; align-items:center; gap:10px;">
|
| 89 |
+
<div style="width:10px; height:10px; background:#2ecc71; border-radius:50%;"></div>
|
| 90 |
+
<span>AI Knowledge Assistant</span>
|
| 91 |
+
</div>
|
| 92 |
+
<span style="cursor:pointer; font-size: 24px; font-weight:300;" onclick="window.toggleOmniChat()">×</span>
|
| 93 |
</div>
|
| 94 |
<div id="omni-messages"></div>
|
| 95 |
<div id="omni-input-area">
|
| 96 |
+
<input type="text" id="omni-input" placeholder="Type a message..." autocomplete="off" />
|
| 97 |
<button id="omni-send">➤</button>
|
| 98 |
</div>
|
| 99 |
</div>
|
|
|
|
| 103 |
document.body.appendChild(container);
|
| 104 |
|
| 105 |
// ----------------------------------------------------
|
| 106 |
+
// 5. CHAT ENGINE: Fetch & Security Headers
|
| 107 |
// ----------------------------------------------------
|
| 108 |
const inputField = document.getElementById('omni-input');
|
| 109 |
const sendButton = document.getElementById('omni-send');
|
|
|
|
| 112 |
function addMessage(text, sender) {
|
| 113 |
const div = document.createElement('div');
|
| 114 |
div.className = `omni-msg ${sender}`;
|
| 115 |
+
// URL auto-linking logic
|
| 116 |
div.innerHTML = text.replace(/(https?:\/\/[^\s]+)/g, '<a href="$1" target="_blank" style="color:inherit; text-decoration:underline;">$1</a>');
|
| 117 |
messagesContainer.appendChild(div);
|
| 118 |
+
messagesContainer.scrollTo({ top: messagesContainer.scrollHeight, behavior: 'smooth' });
|
| 119 |
}
|
| 120 |
|
| 121 |
async function sendMessage() {
|
|
|
|
| 125 |
addMessage(text, 'user');
|
| 126 |
inputField.value = '';
|
| 127 |
|
| 128 |
+
// Loading dots logic
|
| 129 |
const loadingDiv = document.createElement('div');
|
| 130 |
loadingDiv.className = 'omni-msg bot';
|
| 131 |
+
loadingDiv.innerHTML = '<span class="omni-dots">...</span>';
|
| 132 |
messagesContainer.appendChild(loadingDiv);
|
| 133 |
+
messagesContainer.scrollTop = messagesContainer.scrollHeight;
|
| 134 |
|
| 135 |
try {
|
| 136 |
const response = await fetch(`${API_URL}/api/v1/chat`, {
|
| 137 |
method: 'POST',
|
| 138 |
+
headers: {
|
| 139 |
+
'Content-Type': 'application/json'
|
| 140 |
+
},
|
| 141 |
body: JSON.stringify({
|
| 142 |
message: text,
|
| 143 |
session_id: CHAT_SESSION_ID,
|
| 144 |
+
api_key: API_KEY // 🔑 Secure Auth
|
| 145 |
})
|
| 146 |
});
|
| 147 |
+
|
| 148 |
const data = await response.json();
|
| 149 |
messagesContainer.removeChild(loadingDiv);
|
| 150 |
+
|
| 151 |
+
if (response.status === 401) {
|
| 152 |
+
addMessage("🚫 Security Error: Invalid API Key.", 'bot');
|
| 153 |
+
} else if (response.status === 403) {
|
| 154 |
+
addMessage("🚫 Security Error: Domain not authorized.", 'bot');
|
| 155 |
+
} else {
|
| 156 |
+
addMessage(data.response || "I couldn't process that. Please try again.", 'bot');
|
| 157 |
+
}
|
| 158 |
+
|
| 159 |
} catch (error) {
|
| 160 |
+
if (loadingDiv.parentNode) messagesContainer.removeChild(loadingDiv);
|
| 161 |
+
addMessage("📡 Connection lost. Is the AI server online?", 'bot');
|
| 162 |
console.error("OmniAgent API Error:", error);
|
| 163 |
}
|
| 164 |
}
|
| 165 |
|
| 166 |
+
// Event Listeners
|
| 167 |
sendButton.addEventListener('click', sendMessage);
|
| 168 |
inputField.addEventListener('keypress', (e) => {
|
| 169 |
if(e.key === 'Enter') { sendMessage(); }
|
| 170 |
});
|
| 171 |
|
| 172 |
+
// Initial Welcome (AI Persona)
|
| 173 |
setTimeout(() => {
|
| 174 |
+
addMessage("Hello! I am your AI assistant. How can I help you today?", "bot");
|
| 175 |
+
}, 1500);
|
| 176 |
|
| 177 |
})();
|