USAMA BHATTI commited on
Commit
4f47bd4
·
1 Parent(s): acbbba2

Final SaaS Security & Asset Support

Browse files
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
- from backend.src.utils.auth import get_password_hash, verify_password, create_access_token
 
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=Token)
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
- return {"access_token": access_token, "token_type": "bearer"}
 
 
 
 
 
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
- # User dhoondo
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
- request: ChatRequest,
18
- db: AsyncSession = Depends(get_db),
19
- current_user: User = Depends(get_current_user) # <-- User Logged in hai
20
  ):
21
- """
22
- Protected Chat Endpoint.
23
- Only accessible with a valid JWT Token.
24
- """
25
  try:
26
- # User ki ID token se aayegi (Secure)
27
- # Session ID user maintain kar sakta hai taake alag-alag chats yaad rahein
28
- user_id = str(current_user.id)
29
- session_id = request.session_id or user_id # Fallback
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
 
31
- # --- FIX IS HERE: 'user_id' pass kiya ja raha hai ---
32
  response_text = await process_chat(
33
- message=request.message,
34
  session_id=session_id,
35
- user_id=user_id, # <--- Ye hum bhool gaye thay
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"Error in chat endpoint: {e}")
48
- import traceback
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 # Text add kiya
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
- # --- NEW: Bot Customization ---
 
 
 
 
 
 
 
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
- # Isay Optional bana diya. Default value None hai.
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
- const USER_ID = scriptTag.getAttribute("data-user-id");
 
8
  const API_URL = scriptTag.getAttribute("data-api-url");
9
- const THEME_COLOR = scriptTag.getAttribute("data-theme-color") || "#007bff";
10
 
11
- if (!USER_ID || !API_URL) {
12
- console.error("OmniAgent Widget Error: data-user-id or data-api-url is missing!");
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; font-family: 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
 
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 12px rgba(0,0,0,0.4); width: 60px; height: 60px; font-size: 24px;
29
- display: flex; align-items: center; justify-content: center; transition: transform 0.2s;
30
  }
31
- #omni-chat-btn:hover { transform: scale(1.1); }
 
32
  #omni-chat-window {
33
- display: none; width: 350px; height: 500px; background: white; border-radius: 12px;
34
- box-shadow: 0 10px 30px rgba(0,0,0,0.5); flex-direction: column; overflow: hidden;
35
- margin-bottom: 15px; border: 1px solid #ddd;
 
 
 
 
 
36
  }
 
37
  #omni-header {
38
- background: ${THEME_COLOR}; color: white; padding: 15px; font-weight: 600; display: flex;
39
- justify-content: space-between; align-items: center;
40
  }
41
- #omni-messages { flex: 1; padding: 15px; overflow-y: auto; background: #f9f9f9; display: flex; flex-direction: column; }
42
- #omni-input-area { display: flex; border-top: 1px solid #ddd; background: white; }
43
- #omni-input { flex: 1; padding: 12px; border: none; outline: none; font-size: 14px; }
44
- #omni-send { background: transparent; border: none; color: ${THEME_COLOR}; font-weight: bold; cursor: pointer; padding: 0 15px; font-size: 20px; }
45
- .omni-msg { margin: 8px 0; padding: 10px 14px; border-radius: 15px; max-width: 85%; font-size: 14px; word-wrap: break-word; }
46
- .omni-msg.user { background: ${THEME_COLOR}; color: white; align-self: flex-end; border-bottom-right-radius: 2px; }
47
- .omni-msg.bot { background: #eef2f7; color: #333; align-self: flex-start; border-bottom-left-radius: 2px; border: 1px solid #d1d9e6; }
 
 
 
 
 
 
48
  `;
49
  document.head.appendChild(style);
50
 
51
  // ----------------------------------------------------
52
- // 3. UI LOGIC (Pehle Define Karen)
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
- <span>AI Support Agent</span>
74
- <span style="cursor:pointer; font-size: 22px; line-height: 20px;" onclick="window.toggleOmniChat()">×</span>
 
 
 
75
  </div>
76
  <div id="omni-messages"></div>
77
  <div id="omni-input-area">
78
- <input type="text" id="omni-input" placeholder="Ask me anything..." autocomplete="off" />
79
  <button id="omni-send">➤</button>
80
  </div>
81
  </div>
@@ -85,7 +103,7 @@
85
  document.body.appendChild(container);
86
 
87
  // ----------------------------------------------------
88
- // 5. CHAT FUNCTIONALITY
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.scrollTop = messagesContainer.scrollHeight;
100
  }
101
 
102
  async function sendMessage() {
@@ -106,40 +125,53 @@
106
  addMessage(text, 'user');
107
  inputField.value = '';
108
 
109
- // Show Loading State
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: { 'Content-Type': 'application/json' },
 
 
119
  body: JSON.stringify({
120
  message: text,
121
  session_id: CHAT_SESSION_ID,
122
- user_id: USER_ID
123
  })
124
  });
 
125
  const data = await response.json();
126
  messagesContainer.removeChild(loadingDiv);
127
- addMessage(data.response || data.message || "No response received", 'bot');
 
 
 
 
 
 
 
 
128
  } catch (error) {
129
- messagesContainer.removeChild(loadingDiv);
130
- addMessage("Connection error. Please try again.", 'bot');
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 Message
141
  setTimeout(() => {
142
- addMessage("Hello! How can I assist you with the SDD-RI Book today?", "bot");
143
- }, 1000);
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
  })();