Abhisingh-18 commited on
Commit
f35583f
·
verified ·
1 Parent(s): d3f55d7

Mirror of github.com/Abhisingh18/Trust-first-AI-Copilot

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. README.md +225 -0
  2. __pycache__/main.cpython-311.pyc +0 -0
  3. app/__pycache__/main.cpython-311.pyc +0 -0
  4. app/api/__pycache__/endpoints.cpython-311.pyc +0 -0
  5. app/api/endpoints.py +182 -0
  6. app/core/__pycache__/config.cpython-311.pyc +0 -0
  7. app/core/__pycache__/prompts.cpython-311.pyc +0 -0
  8. app/core/config.py +28 -0
  9. app/core/prompts.py +164 -0
  10. app/services/__pycache__/files.cpython-311.pyc +0 -0
  11. app/services/__pycache__/intent.cpython-311.pyc +0 -0
  12. app/services/__pycache__/llm.cpython-311.pyc +0 -0
  13. app/services/__pycache__/search.cpython-311.pyc +0 -0
  14. app/services/__pycache__/vector.cpython-311.pyc +0 -0
  15. app/services/files.py +63 -0
  16. app/services/intent.py +63 -0
  17. app/services/llm.py +240 -0
  18. app/services/search.py +55 -0
  19. app/services/vector.py +76 -0
  20. backend/.env +17 -0
  21. backend/__init__.py +0 -0
  22. backend/__pycache__/__init__.cpython-311.pyc +0 -0
  23. backend/debug.log +823 -0
  24. backend/debug_env.py +47 -0
  25. backend/list_available_models.py +21 -0
  26. backend/list_models.py +12 -0
  27. backend/my_models.txt +0 -0
  28. backend/rag_summarizer.py +81 -0
  29. debug.log +18 -0
  30. frontend/.gitignore +41 -0
  31. frontend/README.md +36 -0
  32. frontend/app/favicon.ico +0 -0
  33. frontend/app/globals.css +100 -0
  34. frontend/app/layout.tsx +35 -0
  35. frontend/app/page.tsx +5 -0
  36. frontend/components/Chat/ChatInput.tsx +87 -0
  37. frontend/components/Chat/ChatLayout.tsx +237 -0
  38. frontend/components/Chat/MessageBubble.tsx +121 -0
  39. frontend/components/Chat/Sidebar.tsx +90 -0
  40. frontend/components/ui/SearchBar.tsx +58 -0
  41. frontend/components/ui/SourceList.tsx +42 -0
  42. frontend/env_example.txt +1 -0
  43. frontend/eslint.config.mjs +18 -0
  44. frontend/lib/utils.ts +37 -0
  45. frontend/next.config.ts +7 -0
  46. frontend/package-lock.json +0 -0
  47. frontend/package.json +32 -0
  48. frontend/postcss.config.mjs +7 -0
  49. frontend/public/file.svg +1 -0
  50. frontend/public/globe.svg +1 -0
README.md ADDED
@@ -0,0 +1,225 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Trust-First AI Copilot
2
+ **Perplexity-Style • System-Driven • No Custom LLM**
3
+ Deployed Link: https://trust-first-ai.vercel.app/
4
+
5
+ A trust-first AI Copilot that delivers **verified, source-grounded, and confidence-scored answers** using strict system rules — inspired by Perplexity and designed to fix the core limitations of modern AI copilots.
6
+
7
+ ---
8
+
9
+ ## Problem
10
+ Most AI copilots today:
11
+ - Produce confident but incorrect (hallucinated) answers
12
+ - Lose context in long or multi-file documents
13
+ - Hide sources and assumptions
14
+ - Provide limited admin visibility and control
15
+ - Encourage blind dependency on AI outputs
16
+
17
+ These issues lead to **wrong decisions, rework, and low trust**.
18
+
19
+ ---
20
+
21
+ ## Solution
22
+ This project implements a **system-first AI Copilot** where:
23
+ - Retrieval is mandatory (no context → no answer)
24
+ - Every answer is backed by sources
25
+ - Confidence is explicitly shown
26
+ - Low-confidence answers are refused
27
+ - Automation is human-approved
28
+ - Security, transparency, and auditability are built-in
29
+
30
+ The focus is **system design over model size**.
31
+
32
+ ---
33
+
34
+ ## Core Principles
35
+ - No guessing
36
+ - No hidden sources
37
+ - No blind automation
38
+ - Refusal is a feature, not a failure
39
+
40
+ ---
41
+
42
+ ## Key Features
43
+ - Mandatory Retrieval-Augmented Generation (RAG)
44
+ - Source-linked answers with citations
45
+ - Confidence scoring (High / Medium / Low)
46
+ - Automatic refusal on insufficient data
47
+ - Workspace / project-level context memory
48
+ - Intent detection and auto-clarification
49
+ - Human-in-the-loop automation (n8n)
50
+ - Zero-trust data access
51
+ - Full audit logs (OpenTelemetry)
52
+ - Model-agnostic LLM layer (Groq)
53
+
54
+ ---
55
+
56
+ ## System Architecture
57
+ User
58
+ → Intent Detection
59
+ → Search & Retrieval (Tavily + Vector DB)
60
+ → Context Ranking & Filtering
61
+ → LLM (Groq – language & reasoning only)
62
+ → Verification & Confidence Engine
63
+ → Answer + Sources + Assumptions
64
+ → (Optional) Human-Approved Automation
65
+ → Audit Logs & Admin Dashboard
66
+
67
+
68
+ ## What This Project Is Not
69
+ - Not a chatbot
70
+ - Not prompt-dependent
71
+ - Not blind AI
72
+ - Not a Copilot replacement
73
+
74
+ This is a **controlled, transparent, enterprise-ready AI system**.
75
+
76
+ ---
77
+
78
+ ## Tech Stack
79
+ **Frontend**
80
+ - Next.js
81
+ - React
82
+ - Tailwind CSS
83
+
84
+ **Backend**
85
+ - FastAPI (Python)
86
+
87
+ **AI & Data**
88
+ - LLM: Groq (LLaMA / Mixtral)
89
+ - Search: Tavily API
90
+ - Embeddings: Hugging Face / Local models
91
+ - Vector DB: FAISS / Qdrant
92
+ - Automation: n8n
93
+ - Logging & Audit: OpenTelemetry
94
+
95
+ **Deployment**
96
+ - Frontend: Vercel
97
+ - Backend: Render
98
+
99
+ ---
100
+
101
+ ## Project Structure
102
+ project-root/
103
+ ├── frontend/
104
+ │ ├── pages/
105
+ │ ├── components/
106
+ │ └── services/
107
+ ├── backend/
108
+ │ ├── main.py
109
+ │ ├── rag/
110
+ │ ├── verification/
111
+ │ ├── automation/
112
+ │ └── requirements.txt
113
+ ├── docs/
114
+ └── README.md
115
+
116
+ yaml
117
+ Copy code
118
+
119
+ ---
120
+
121
+ ## Required API Keys
122
+ | Service | Purpose |
123
+ |-------|---------|
124
+ | Groq | LLM inference |
125
+ | Tavily | Web search |
126
+ | Hugging Face | Embeddings |
127
+ | n8n | Automation |
128
+
129
+ > All API keys are stored **only in backend environment variables**.
130
+
131
+ ---
132
+
133
+ ## Local Setup (Backend)
134
+ ```bash
135
+ git clone https://github.com/your-username/your-repo.git
136
+ cd backend
137
+ python -m venv venv
138
+ source venv/bin/activate
139
+ pip install -r requirements.txt
140
+ Create .env:
141
+
142
+ env
143
+ Copy code
144
+ GROQ_API_KEY=xxxx
145
+ TAVILY_API_KEY=tvly_xxxx
146
+ HF_API_KEY=hf_xxxx
147
+ N8N_API_KEY=xxxx
148
+ N8N_BASE_URL=http://localhost:5678
149
+ Run backend:
150
+
151
+ bash
152
+ Copy code
153
+ uvicorn main:app --reload
154
+ Local Setup (Frontend)
155
+ bash
156
+ Copy code
157
+ cd frontend
158
+ npm install
159
+ npm run dev
160
+ Deployment
161
+ Backend
162
+
163
+ Push code to GitHub
164
+
165
+ Connect repository to Render
166
+
167
+ Build command:
168
+
169
+ bash
170
+ Copy code
171
+ pip install -r requirements.txt
172
+ Start command:
173
+
174
+ bash
175
+ Copy code
176
+ uvicorn main:app --host 0.0.0.0 --port 10000
177
+ Frontend
178
+
179
+ Deploy via Vercel
180
+
181
+ Set backend API URL in environment variables
182
+
183
+ Security & Trust Model
184
+ API keys never exposed to frontend
185
+
186
+ Per-user data isolation
187
+
188
+ Role-based access control
189
+
190
+ Full audit trail for AI actions
191
+
192
+ How Hallucinations Are Prevented
193
+ Retrieval is mandatory
194
+
195
+ Claims must map to sources
196
+
197
+ Confidence is evaluated
198
+
199
+ Low confidence triggers refusal
200
+
201
+ No source → No answer
202
+
203
+ Use Cases
204
+ Research and academic assistance
205
+
206
+ Enterprise internal knowledge copilots
207
+
208
+ Policy and compliance analysis
209
+
210
+ Long-document summarization
211
+
212
+ Decision-support systems
213
+
214
+ Future Improvements
215
+ Offline read-only mode
216
+
217
+ Multimodal reasoning (charts + text)
218
+
219
+ Advanced admin dashboards
220
+
221
+ Domain-specific copilots
222
+
223
+ Final Note
224
+ LLMs don’t fail — systems fail.
225
+ This project demonstrates how strong system design beats larger models.
__pycache__/main.cpython-311.pyc ADDED
Binary file (1.21 kB). View file
 
app/__pycache__/main.cpython-311.pyc ADDED
Binary file (1.21 kB). View file
 
app/api/__pycache__/endpoints.cpython-311.pyc ADDED
Binary file (9.31 kB). View file
 
app/api/endpoints.py ADDED
@@ -0,0 +1,182 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter, HTTPException
2
+ from pydantic import BaseModel
3
+ from typing import List, Optional
4
+ from app.services.llm import llm_service
5
+ from app.services.vector import vector_service
6
+ from app.services.search import search_service
7
+ from app.services.intent import IntentService
8
+ from app.services.files import file_service
9
+ from fastapi import UploadFile, File
10
+
11
+ # Initialize Intent Service
12
+ intent_service = IntentService(llm_service)
13
+
14
+ router = APIRouter()
15
+
16
+ # --- Pydantic Models ---
17
+ class QueryRequest(BaseModel):
18
+ query: str
19
+
20
+ class Source(BaseModel):
21
+ title: str
22
+ url: str
23
+ snippet: str
24
+
25
+ class ChallengeRequest(BaseModel):
26
+ original_query: str
27
+ original_answer: str
28
+ sources_text: str
29
+
30
+ class QueryResponse(BaseModel):
31
+ answer: str
32
+ sources: List[Source]
33
+ confidence: str
34
+ search_queries: List[str]
35
+ intent: Optional[str] = None
36
+ thought_process: Optional[str] = None
37
+
38
+ # --- Endpoints ---
39
+
40
+ @router.post("/query", response_model=QueryResponse)
41
+ async def process_query(request: QueryRequest):
42
+ """
43
+ Main orchestration endpoint for the Trust-First Copilot.
44
+ """
45
+ user_query = request.query
46
+ print(f"Refining query: {user_query}")
47
+
48
+ try:
49
+ # --- PHASE 1: INTENT & RISK ANALYSIS ---
50
+ print("🧠 Analyzing Intent...")
51
+ try:
52
+ intent = await intent_service.analyze(user_query)
53
+ print(f" Category: {intent.category}")
54
+ print(f" Reasoning: {intent.reasoning}")
55
+ print(f" Risk: {intent.risk_level}")
56
+ except Exception as e:
57
+ print(f"Intent Error: {e}")
58
+ from app.services.intent import IntentResponse
59
+ intent = IntentResponse(category="SEARCH_REQUIRED", reasoning="Error", risk_level="LOW")
60
+
61
+ # Risk Guard
62
+ if intent.risk_level == "HIGH":
63
+ return QueryResponse(
64
+ answer="I cannot fulfill this request as it has been flagged as high risk/safety violation.",
65
+ sources=[],
66
+ confidence="Blocked",
67
+ search_queries=[],
68
+ intent="High Risk",
69
+ thought_process=f"Blocked by Risk Analyzer. Reasoning: {intent.reasoning}"
70
+ )
71
+
72
+ # --- PHASE 2: EXECUTION ---
73
+ search_results = []
74
+
75
+ # Branch 1: Needs Search
76
+ if intent.category == "SEARCH_REQUIRED" or intent.category == "DATA_ANALYSIS":
77
+ print("🔍 Initiating Web Search...")
78
+ search_results = await search_service.search(user_query)
79
+ if not search_results:
80
+ # Fallback if search finds nothing but intent was search
81
+ pass
82
+
83
+ # Branch 2: Coding (Skip Search usually, unless specific docs needed)
84
+ elif intent.category == "CODING_TASK":
85
+ print("💻 Coding Task - Focused Generation")
86
+ # Potential future improvement: Search for docs if needed
87
+
88
+ # Branch 3: Chat / General
89
+ else:
90
+ print("💬 Chat Mode - Direct Generation")
91
+
92
+ # --- PHASE 3: CONTEXT & RAG ---
93
+ context_text = ""
94
+ final_sources = []
95
+
96
+ if search_results:
97
+ # RAG Logic
98
+ print("Indexing search results in Vector DB...")
99
+ vector_service.create_index_from_results(search_results)
100
+
101
+ print("Searching Vector DB for relevant context...")
102
+ relevant_chunks = vector_service.search_similar(user_query, k=5)
103
+ final_sources = relevant_chunks if relevant_chunks else search_results
104
+
105
+ context_text = "\n\n".join([
106
+ f"Source {i+1}:\nTitle: {r.get('title')}\nURL: {r.get('url')}\nContent: {r.get('content')}"
107
+ for i, r in enumerate(final_sources)
108
+ ])
109
+ else:
110
+ context_text = "No external sources used. Answering from internal knowledge."
111
+
112
+ # --- PHASE 4: SYNTHESIS ---
113
+ # Modify prompt based on intent? For now, standard synthesis but context aware.
114
+ answer = await llm_service.synthesize_answer(user_query, context_text)
115
+
116
+ # --- PHASE 5: VERIFICATION ---
117
+ confidence_level = "Medium"
118
+ if intent.category == "SEARCH_REQUIRED":
119
+ confidence_assessment = await llm_service.verify_confidence(answer, context_text)
120
+ if "High confidence" in confidence_assessment: confidence_level = "High"
121
+ elif "Low confidence" in confidence_assessment: confidence_level = "Low"
122
+ else:
123
+ confidence_level = "N/A (Chat)"
124
+
125
+ # Construct Response
126
+ formatted_sources = [
127
+ Source(title=r.get('title', 'Unknown'), url=r.get('url', '#'), snippet=r.get('content', '')[:200])
128
+ for r in (search_results if search_results else [])
129
+ ]
130
+
131
+ return QueryResponse(
132
+ answer=answer,
133
+ sources=formatted_sources,
134
+ confidence=confidence_level,
135
+ search_queries=[user_query],
136
+ intent=intent.category,
137
+ thought_process=f"Intent: {intent.category}. Reasoning: {intent.reasoning}"
138
+ )
139
+
140
+ except Exception as e:
141
+ print(f"Error processing query: {e}")
142
+ raise HTTPException(status_code=500, detail=str(e))
143
+
144
+ @router.post("/challenge", response_model=QueryResponse)
145
+ async def challenge_answer(request: ChallengeRequest):
146
+ """
147
+ 'Disagree-with-Me' Mode: Critiques the previous answer.
148
+ """
149
+ try:
150
+ from app.core import prompts
151
+ # Construct the critique prompt
152
+ messages = [
153
+ {"role": "system", "content": prompts.MASTER_PROMPT_CHALLENGE},
154
+ {"role": "user", "content": f"Query: {request.original_query}\n\nAnswer to critique: {request.original_answer}\n\nSources used:\n{request.sources_text}"}
155
+ ]
156
+
157
+ critique = await llm_service._generate(messages, temperature=0.7)
158
+
159
+ # Return as a new message, but marked as a critique
160
+ return QueryResponse(
161
+ answer=critique,
162
+ sources=[],
163
+ confidence="High (Critique)",
164
+ search_queries=[],
165
+ intent="CRITIQUE",
166
+ thought_process="Devil's Advocate Mode Activated."
167
+ )
168
+ except Exception as e:
169
+ raise HTTPException(status_code=500, detail=str(e))
170
+
171
+ @router.post("/upload")
172
+ async def upload_file(file: UploadFile = File(...)):
173
+ """
174
+ Parses an uploaded file and returns its text content for RAG.
175
+ """
176
+ try:
177
+ filename = file.filename
178
+ print(f"📂 Processing file: {filename}")
179
+ content = await file_service.process_file(file)
180
+ return {"filename": filename, "content": content}
181
+ except Exception as e:
182
+ raise HTTPException(status_code=500, detail=f"Upload failed: {str(e)}")
app/core/__pycache__/config.cpython-311.pyc ADDED
Binary file (1.26 kB). View file
 
app/core/__pycache__/prompts.cpython-311.pyc ADDED
Binary file (4.07 kB). View file
 
app/core/config.py ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pydantic_settings import BaseSettings
2
+
3
+ class Settings(BaseSettings):
4
+ PROJECT_NAME: str = "Trust-First Copilot"
5
+ API_V1_STR: str = "/api/v1"
6
+
7
+ # LLM Provider (OpenAI/Anthropic/Azure)
8
+ OPENAI_API_KEY: str = ""
9
+
10
+ # Search Provider (Tavily/Serper)
11
+ TAVILY_API_KEY: str = ""
12
+
13
+ # Hugging Face Provider
14
+ HUGGINGFACE_API_KEY: str = ""
15
+
16
+ # Groq Provider
17
+ GROQ_API_KEY: str = ""
18
+
19
+ # Gemini Provider
20
+ GEMINI_API_KEY: str = ""
21
+
22
+ # CORS
23
+ BACKEND_CORS_ORIGINS: list[str] = ["http://localhost:3000"]
24
+
25
+ class Config:
26
+ env_file = ".env"
27
+
28
+ settings = Settings()
app/core/prompts.py ADDED
@@ -0,0 +1,164 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Master Prompts for Trust-First Copilot
2
+ # These prompts MUST be used verbatim to ensure the "Trust Engine" behavior.
3
+
4
+ SYSTEM_PROMPT = """
5
+ You are NOT a chatbot.
6
+
7
+ You are a verification-first, search-grounded AI system similar to Perplexity.
8
+
9
+ Your job is NOT to guess.
10
+ Your job is to:
11
+ - search
12
+ - retrieve
13
+ - verify
14
+ - summarize
15
+ - cite
16
+
17
+ STRICT RULES:
18
+ 1. If you do not find reliable sources, you MUST say "I don't have enough data to answer this."
19
+ 2. Every factual claim MUST be backed by a source.
20
+ 3. No source = no answer.
21
+ 4. Estimates must clearly state assumptions.
22
+ 5. Confidence > verbosity.
23
+ 6. Refusal is better than hallucination.
24
+
25
+ You are building user trust, not impressing users.
26
+ """
27
+
28
+ MASTER_PROMPT_SEARCH = """
29
+ You are a search and retrieval engine.
30
+
31
+ TASK:
32
+ Given a user question, do the following:
33
+
34
+ 1. Break the question into searchable sub-queries
35
+ 2. Identify what kind of sources are needed (reports, papers, govt data, official websites)
36
+ 3. Retrieve ONLY high-quality, reliable sources
37
+ 4. Reject blogs, opinions, or unclear sources
38
+ 5. List the sources with:
39
+ - Source name
40
+ - Year
41
+ - Authority (who published it)
42
+
43
+ DO NOT answer the question yet.
44
+ ONLY return the list of sources and why they are relevant.
45
+ """
46
+
47
+ MASTER_PROMPT_ANSWER = """
48
+ You are an answer synthesis engine in 'Truth-First' mode.
49
+
50
+ INPUT:
51
+ - User question
52
+ - Retrieved sources (from previous step)
53
+
54
+ RULES:
55
+ 1. Answer ONLY using the provided sources.
56
+ 2. Separate FACTS from ASSUMPTIONS clearly.
57
+ 3. If information is missing, list it under 'Unknowns'.
58
+
59
+ FORMAT:
60
+
61
+ ### ✅ Verified Facts
62
+ (List only what is explicitly supported by sources)
63
+
64
+ ### ⚠️ Assumptions & Risks
65
+ (List any logical leaps, estimates, or potential inaccuracies)
66
+
67
+ ### ❌ Unknowns / Limitations
68
+ (List what the sources did NOT cover)
69
+
70
+ ### 💡 Synthesis
71
+ (Your cohesive answer. Use the following structure to make it engaging and clear:)
72
+
73
+ [Direct Answer: 1-2 sentences summarizing the core response]
74
+
75
+ ### **📊 Key Details**
76
+ (Use bullet points. **Bold** the key value or term in each bullet. e.g., "- **Population:** 1.4B")
77
+
78
+ ### **🧑‍🤝‍🧑 Demographic / Structural Breakdown**
79
+ (If applicable, break down by category. **Bold** category names.)
80
+
81
+ ### **🌍 Why This Matters**
82
+ (Explain the significance. **Bold** the main implication.)
83
+
84
+ ### **⚠️ Considerations**
85
+ (Briefly mention major challenges. **Bold** the core challenge.)
86
+
87
+ Tone:
88
+ Neutral, factual, professional. **Use bolding liberally for emphasis and scannability.**
89
+ """
90
+
91
+ MASTER_PROMPT_CHALLENGE = """
92
+ You are a 'Devil's Advocate' Engine.
93
+
94
+ Your task is to CRITIQUE the following answer.
95
+ Don't be polite. Be rigorous.
96
+
97
+ Identify:
98
+ 1. Logical fallacies.
99
+ 2. Unverified assumptions.
100
+ 3. Alternative explanations that were ignored.
101
+
102
+ Output format:
103
+ "Here is why this answer might be wrong..."
104
+ """
105
+
106
+ MASTER_PROMPT_CITATION = """
107
+ You are a citation and transparency engine.
108
+
109
+ TASK:
110
+ For the generated answer:
111
+ 1. Map each factual statement to its exact source
112
+ 2. Display citations inline or as numbered references
113
+ 3. Clearly separate:
114
+ - Facts
115
+ - Estimates
116
+ - Assumptions
117
+
118
+ OUTPUT MUST INCLUDE:
119
+ - Sources section
120
+ - What is known with high confidence
121
+ - What is uncertain or estimated
122
+ """
123
+
124
+ MASTER_PROMPT_CONFIDENCE = """
125
+ You are a confidence evaluation system.
126
+
127
+ Evaluate the answer and assign:
128
+ - High confidence
129
+ - Medium confidence
130
+ - Low confidence
131
+
132
+ RULES:
133
+ - High: Fully supported by multiple reliable sources
134
+ - Medium: Partially supported or indirect evidence
135
+ - Low: Weak or missing evidence → REFUSE answer
136
+
137
+ If refusing:
138
+ - Clearly explain why
139
+ - Suggest what data would be needed to answer
140
+
141
+ Never bluff.
142
+ Never overstate.
143
+ """
144
+
145
+ MASTER_PROMPT_FORMAT = """
146
+ Present the final answer in a website-friendly format.
147
+
148
+ SECTIONS:
149
+ 1. Direct Answer (1–2 lines)
150
+ 2. Explanation
151
+ 3. Sources (clickable)
152
+ 4. Confidence Level
153
+ 5. Assumptions / Limitations
154
+ 6. Shareable Summary (optional)
155
+
156
+ DESIGN PRINCIPLES:
157
+ - Clean
158
+ - Minimal
159
+ - Search-first
160
+ - Trust-focused
161
+
162
+ Avoid chatty language.
163
+ Avoid emojis.
164
+ """
app/services/__pycache__/files.cpython-311.pyc ADDED
Binary file (4.04 kB). View file
 
app/services/__pycache__/intent.cpython-311.pyc ADDED
Binary file (3.54 kB). View file
 
app/services/__pycache__/llm.cpython-311.pyc ADDED
Binary file (13 kB). View file
 
app/services/__pycache__/search.cpython-311.pyc ADDED
Binary file (3.04 kB). View file
 
app/services/__pycache__/vector.cpython-311.pyc ADDED
Binary file (4.43 kB). View file
 
app/services/files.py ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import fitz # PyMuPDF
2
+ import pandas as pd
3
+ from fastapi import UploadFile
4
+ import io
5
+
6
+ class FileService:
7
+ async def process_file(self, file: UploadFile) -> str:
8
+ content_type = file.content_type
9
+ filename = file.filename.lower()
10
+
11
+ content = await file.read()
12
+ extracted_text = ""
13
+
14
+ try:
15
+ # PDF Processing
16
+ if "pdf" in content_type or filename.endswith(".pdf"):
17
+ extracted_text = self._process_pdf(content)
18
+
19
+ # Excel/CSV Processing
20
+ elif "excel" in content_type or "spreadsheet" in content_type or filename.endswith((".xlsx", ".xls", ".csv")):
21
+ extracted_text = self._process_spreadsheet(content, filename)
22
+
23
+ # Text Processing
24
+ elif "text" in content_type or filename.endswith(".txt") or filename.endswith(".md"):
25
+ extracted_text = content.decode("utf-8")
26
+
27
+ else:
28
+ return f"Unsupported file type: {filename}"
29
+
30
+ return extracted_text
31
+
32
+ except Exception as e:
33
+ return f"Error processing file {filename}: {str(e)}"
34
+
35
+ def _process_pdf(self, content: bytes) -> str:
36
+ doc = fitz.open(stream=content, filetype="pdf")
37
+ text_blocks = []
38
+
39
+ for page_num, page in enumerate(doc):
40
+ text = page.get_text()
41
+ if text.strip():
42
+ # Add page marker for citation
43
+ text_blocks.append(f"--- Page {page_num + 1} ---\n{text}")
44
+
45
+ return "\n\n".join(text_blocks)
46
+
47
+ def _process_spreadsheet(self, content: bytes, filename: str) -> str:
48
+ text_blocks = []
49
+ try:
50
+ if filename.endswith(".csv"):
51
+ df = pd.read_csv(io.BytesIO(content))
52
+ text_blocks.append(f"--- CSV Data ---\n{df.to_markdown(index=False)}")
53
+ else:
54
+ excel_file = pd.ExcelFile(io.BytesIO(content))
55
+ for sheet_name in excel_file.sheet_names:
56
+ df = pd.read_excel(excel_file, sheet_name=sheet_name)
57
+ text_blocks.append(f"--- Sheet: {sheet_name} ---\n{df.to_markdown(index=False)}")
58
+ except Exception as e:
59
+ return f"Error parsing spreadsheet: {e}"
60
+
61
+ return "\n\n".join(text_blocks)
62
+
63
+ file_service = FileService()
app/services/intent.py ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Literal
2
+ from pydantic import BaseModel
3
+ from app.services.llm import LLMService
4
+
5
+ class IntentResponse(BaseModel):
6
+ category: Literal["SEARCH_REQUIRED", "CHAT_ONLY", "CODING_TASK", "DATA_ANALYSIS"]
7
+ reasoning: str
8
+ risk_level: Literal["LOW", "MEDIUM", "HIGH"]
9
+
10
+ class IntentService:
11
+ def __init__(self, llm_service: LLMService):
12
+ self.llm = llm_service
13
+ self.system_prompt = """
14
+ You are the 'Intent Analyzer' for an AI Operating System.
15
+ Your job is to route the user's request to the correct module.
16
+
17
+ Analyze the USER QUERY and return a JSON object.
18
+
19
+ CATEGORIES:
20
+ - SEARCH_REQUIRED: Query asks for current events, news, specific facts not in general knowledge, or research. (e.g., "Stock price of Apple", "Latest AI papers")
21
+ - CHAT_ONLY: General greetings, philosophical questions, summaries of previous context, or logic puzzles. (e.g., "Hi", "Explain Stoicism")
22
+ - CODING_TASK: Requests to write, debug, or explain code.
23
+ - DATA_ANALYSIS: Requests involving CSVs, charts, or math aggregations.
24
+
25
+ RISK LEVELS:
26
+ - HIGH: Asking for dangerous/illegal content, PII, or financial advice.
27
+ - MEDIUM: Ambiguous queries or potential controversies.
28
+ - LOW: Safe, standard queries.
29
+
30
+ Output format: {"category": "...", "reasoning": "...", "risk_level": "..."}
31
+ """
32
+
33
+ async def analyze(self, query: str) -> IntentResponse:
34
+ # For efficiency, we can use a faster/smaller model or just the main one with strictly low temp
35
+ prompt = f"{self.system_prompt}\n\nUSER QUERY: {query}"
36
+
37
+ # We'll rely on the main LLM to parse this for now.
38
+ # ideally this uses a 'router' model (cheap/fast)
39
+ response_text = await self.llm._generate(
40
+ messages=[{"role": "user", "content": prompt}],
41
+ temperature=0.0
42
+ )
43
+
44
+ # Simple parsing logic (robustness would require structured output mode or regex)
45
+ # Assuming LLM behaves well with JSON instructions.
46
+ import json
47
+ import re
48
+
49
+ try:
50
+ # Clean markdown code blocks if present
51
+ clean_text = re.sub(r"```json|```", "", response_text).strip()
52
+ data = json.loads(clean_text)
53
+ return IntentResponse(**data)
54
+ except Exception as e:
55
+ # Fallback to Safe Default
56
+ print(f"Intent Parsing Failed: {e}. Defaulting to SEARCH.")
57
+ return IntentResponse(
58
+ category="SEARCH_REQUIRED",
59
+ reasoning="Parsing error, defaulting to search.",
60
+ risk_level="low"
61
+ )
62
+
63
+ intent_service = None # initialized in main/deps
app/services/llm.py ADDED
@@ -0,0 +1,240 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from openai import AsyncOpenAI
2
+ from app.core.config import settings
3
+ from app.core import prompts
4
+ import asyncio
5
+
6
+ # Optional Hugging Face Client
7
+ try:
8
+ from huggingface_hub import AsyncInferenceClient
9
+ except ImportError:
10
+ AsyncInferenceClient = None
11
+
12
+ # Optional Groq Client
13
+ try:
14
+ from groq import AsyncGroq
15
+ except ImportError:
16
+ AsyncGroq = None
17
+
18
+ # Optional Gemini Client
19
+ try:
20
+ import google.generativeai as genai
21
+ except ImportError:
22
+ genai = None
23
+
24
+ class LLMService:
25
+ def __init__(self):
26
+ self.openai_key = settings.OPENAI_API_KEY
27
+ self.hf_key = settings.HUGGINGFACE_API_KEY
28
+ self.groq_key = settings.GROQ_API_KEY
29
+ self.gemini_key = settings.GEMINI_API_KEY
30
+
31
+ with open("debug.log", "a") as f:
32
+ f.write(f"\n--- INIT ---\n")
33
+ f.write(f"OpenAI Key: {bool(self.openai_key)}\n")
34
+ f.write(f"HF Key: {self.hf_key[:5] if self.hf_key else 'None'}\n")
35
+ f.write(f"Groq Key: {self.groq_key[:5] if self.groq_key else 'None'}\n")
36
+ f.write(f"Gemini Key: {self.gemini_key[:5] if self.gemini_key else 'None'}\n")
37
+
38
+ self.provider = "mock"
39
+ self.client = None
40
+ self.model = "gpt-4-turbo-preview"
41
+
42
+ # 1. Prefer Groq (User Requested - Fastest)
43
+ if self.groq_key and "gsk_" in self.groq_key and AsyncGroq:
44
+ self.provider = "groq"
45
+ self.client = AsyncGroq(api_key=self.groq_key)
46
+ self.model = "llama-3.3-70b-versatile"
47
+ print(f"LLM Provider: Groq ({self.model})")
48
+ with open("debug.log", "a") as f: f.write(f"Provider Selected: Groq ({self.model})\n")
49
+
50
+ # 2. Fallback to Gemini
51
+ elif self.gemini_key and genai:
52
+ self.provider = "gemini"
53
+ genai.configure(api_key=self.gemini_key)
54
+
55
+ # Dynamically find a valid model
56
+ valid_model = "gemini-1.5-flash"
57
+ try:
58
+ available_models = [m.name for m in genai.list_models() if 'generateContent' in m.supported_generation_methods]
59
+ print(f"Available Gemini Models: {available_models}")
60
+
61
+ # Rigid preference list
62
+ preferences = [
63
+ "models/gemini-1.5-flash-latest",
64
+ "models/gemini-1.5-flash-001",
65
+ "models/gemini-2.0-flash-exp",
66
+ "models/gemini-1.5-pro-latest",
67
+ ]
68
+
69
+ for pref in preferences:
70
+ if pref in available_models:
71
+ valid_model = pref
72
+ break
73
+ # specific check for partial match if exact not found?
74
+ # sticking to exact or known good partials to avoid experimental/paid models
75
+
76
+ except Exception as e:
77
+ print(f"Error listing Gemini models: {e}")
78
+
79
+ # self.client = genai.GenerativeModel(valid_model) # Do not bind static client
80
+ self.model = "auto-retry-gemini"
81
+ print(f"LLM Provider: Gemini (Auto-Retry Mode)")
82
+ with open("debug.log", "a") as f: f.write(f"Provider Selected: Gemini (Auto-Retry)\n")
83
+
84
+ # 3. Fallback logic removed/moved up
85
+ elif False:
86
+ pass
87
+ # print(f"LLM Provider: Groq ({self.model})")
88
+
89
+ # 2. Fallback to Hugging Face
90
+ elif self.hf_key and "hf_" in self.hf_key and AsyncInferenceClient:
91
+ self.provider = "huggingface"
92
+ self.client = AsyncInferenceClient(token=self.hf_key)
93
+ self.model = "HuggingFaceH4/zephyr-7b-beta"
94
+ print(f"LLM Provider: Hugging Face ({self.model})")
95
+
96
+ # 3. Fallback to OpenAI
97
+ elif self.openai_key and "sk-" in self.openai_key:
98
+ self.provider = "openai"
99
+ self.client = AsyncOpenAI(api_key=self.openai_key)
100
+ print("LLM Provider: OpenAI")
101
+
102
+ async def _generate(self, messages, temperature=0.7):
103
+ """Helper to unify generation calls across providers"""
104
+ if self.provider == "openai":
105
+ response = await self.client.chat.completions.create(
106
+ model=self.model,
107
+ messages=messages,
108
+ temperature=temperature
109
+ )
110
+ return response.choices[0].message.content
111
+
112
+ elif self.provider == "gemini":
113
+ # Dynamic Retry for Gemini
114
+ fallback_models = [
115
+ "models/gemini-1.5-flash",
116
+ "models/gemini-1.5-flash-latest",
117
+ "models/gemini-1.5-flash-001",
118
+ "models/gemini-2.0-flash-exp",
119
+ "models/gemini-2.0-flash",
120
+ "models/gemini-1.5-pro",
121
+ "models/gemini-1.5-pro-latest",
122
+ ]
123
+
124
+ full_prompt = ""
125
+ for m in messages:
126
+ role_prefix = "System" if m['role'] == "system" else "User"
127
+ full_prompt += f"{role_prefix}: {m['content']}\n\n"
128
+
129
+ last_error = None
130
+ for model_name in fallback_models:
131
+ try:
132
+ # Create client on the fly for each model
133
+ client = genai.GenerativeModel(model_name)
134
+ response = await client.generate_content_async(
135
+ full_prompt,
136
+ generation_config=genai.types.GenerationConfig(
137
+ temperature=temperature
138
+ )
139
+ )
140
+ return response.text
141
+ except Exception as e:
142
+ print(f"Gemini Model {model_name} failed: {e}")
143
+ last_error = e
144
+
145
+ # If all fail, raise the last error to be caught by the caller
146
+ raise last_error
147
+
148
+ elif self.provider == "groq":
149
+ try:
150
+ response = await self.client.chat.completions.create(
151
+ model=self.model,
152
+ messages=messages,
153
+ temperature=temperature
154
+ )
155
+ return response.choices[0].message.content
156
+ except Exception as e:
157
+ print(f"Groq Error: {e}")
158
+ with open("debug.log", "a") as f: f.write(f"Groq Error: {e}\n")
159
+ raise e
160
+
161
+ elif self.provider == "huggingface":
162
+ # HF AsyncInferenceClient also supports chat_completion style
163
+ response = await self.client.chat_completion(
164
+ model=self.model,
165
+ messages=messages,
166
+ temperature=temperature,
167
+ max_tokens=1000
168
+ )
169
+ return response.choices[0].message.content
170
+
171
+ return None
172
+
173
+ async def get_search_queries(self, user_query: str) -> str:
174
+ if self.provider == "mock":
175
+ return f"Search Query: {user_query}"
176
+
177
+ try:
178
+ messages = [
179
+ {"role": "system", "content": prompts.SYSTEM_PROMPT},
180
+ {"role": "system", "content": prompts.MASTER_PROMPT_SEARCH},
181
+ {"role": "user", "content": user_query}
182
+ ]
183
+ content = await self._generate(messages, temperature=0.2)
184
+ return content if content else user_query
185
+ except Exception as e:
186
+ with open("debug.log", "a") as f: f.write(f"LLM Error (Search): {e}\n")
187
+ return user_query
188
+
189
+ async def synthesize_answer(self, user_query: str, sources_text: str) -> str:
190
+ if self.provider == "mock":
191
+ await asyncio.sleep(1)
192
+ return self._mock_synthesize(user_query, sources_text)
193
+
194
+ try:
195
+ messages = [
196
+ {"role": "system", "content": prompts.SYSTEM_PROMPT},
197
+ {"role": "system", "content": prompts.MASTER_PROMPT_ANSWER},
198
+ {"role": "user", "content": f"User Question: {user_query}\n\nExisting Sources:\n{sources_text}"}
199
+ ]
200
+ content = await self._generate(messages, temperature=0.1)
201
+ return content if content else self._mock_synthesize(user_query, sources_text)
202
+ except Exception as e:
203
+ with open("debug.log", "a") as f: f.write(f"LLM Error (Synthesis): {e}\n")
204
+ return self._mock_synthesize(user_query, sources_text)
205
+
206
+ async def verify_confidence(self, answer: str, sources_text: str) -> str:
207
+ if self.provider == "mock":
208
+ return "High confidence. (Mock Verification)"
209
+
210
+ try:
211
+ messages = [
212
+ {"role": "system", "content": prompts.MASTER_PROMPT_CONFIDENCE},
213
+ {"role": "user", "content": f"Answer to evaluate:\n{answer}\n\nBased on sources:\n{sources_text}"}
214
+ ]
215
+ content = await self._generate(messages, temperature=0.1)
216
+ return content if content else "Medium confidence"
217
+ except Exception as e:
218
+ print(f"LLM Error (Confidence): {e}")
219
+ return "Medium confidence"
220
+
221
+ def _mock_synthesize(self, query: str, sources_text: str = "") -> str:
222
+ source_preview = "No sources found."
223
+ if sources_text:
224
+ lines = sources_text.split('\n')
225
+ titles = [line.replace("Title: ", "") for line in lines if line.startswith("Title: ")]
226
+ source_preview = f"Found {len(titles)} Sources including: " + ", ".join(titles[:3])
227
+
228
+ return f"""
229
+ **Note: Running in Partial Mode (Tavily Connected, No LLM Key).**
230
+
231
+ The system successfully searched using your **Tavily Key** and found real data:
232
+ _{source_preview}_
233
+
234
+ However, I cannot generate the answer.
235
+ Current Provider: **{self.provider}**
236
+ Check debug.log for specific error (e.g., 404 Model Not Found or Auth Error).
237
+ """
238
+
239
+ llm_service = LLMService()
240
+
app/services/search.py ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from typing import List, Dict, Any
3
+ from app.core.config import settings
4
+
5
+ # Placeholder for Tavily or similar search client
6
+ try:
7
+ from tavily import TavilyClient
8
+ except ImportError:
9
+ TavilyClient = None
10
+
11
+ class SearchService:
12
+ def __init__(self):
13
+ self.api_key = settings.TAVILY_API_KEY
14
+ self.client = None
15
+ if self.api_key and TavilyClient:
16
+ self.client = TavilyClient(api_key=self.api_key)
17
+
18
+ async def search(self, query: str, search_depth: str = "advanced") -> List[Dict[str, Any]]:
19
+ """
20
+ Executes a search using the Master Prompt strategies (conceptually).
21
+ In reality, we send the query to a search API.
22
+ """
23
+ print(f"Executing search for: {query}")
24
+
25
+ if not self.client:
26
+ # Mock response for testing without API Key
27
+ return self._mock_search_results(query)
28
+
29
+ try:
30
+ # Tavily context search is optimized for RAG
31
+ response = self.client.search(query=query, search_depth=search_depth, include_domains=[])
32
+ return response.get("results", [])
33
+ except Exception as e:
34
+ print(f"Search failed: {e}")
35
+ return []
36
+
37
+ def _mock_search_results(self, query: str) -> List[Dict[str, Any]]:
38
+ return [
39
+ {
40
+ "title": f"Mock Source about {query}",
41
+ "url": "https://example.com/source1",
42
+ "content": f"This is a verified source containing specific data about {query}. It confirms the primary query intent.",
43
+ "score": 0.95,
44
+ "published_date": "2024-01-01"
45
+ },
46
+ {
47
+ "title": f"Another Relevant Source for {query}",
48
+ "url": "https://gov.data/report",
49
+ "content": f"Official government data suggests that {query} has seen a 20% increase in importance.",
50
+ "score": 0.88,
51
+ "published_date": "2023-12-15"
52
+ }
53
+ ]
54
+
55
+ search_service = SearchService()
app/services/vector.py ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ try:
3
+ import faiss
4
+ from sentence_transformers import SentenceTransformer
5
+ except Exception as e:
6
+ print(f"VectorService: Failed to import dependencies: {e}")
7
+ faiss = None
8
+ SentenceTransformer = None
9
+
10
+ class VectorService:
11
+ def __init__(self):
12
+ self.model = None
13
+ self.index = None
14
+ self.chunks = []
15
+
16
+ if SentenceTransformer:
17
+ # Load model once. This might be slow on startup.
18
+ print("Loading generic embedding model (all-MiniLM-L6-v2)...")
19
+ try:
20
+ self.model = SentenceTransformer("all-MiniLM-L6-v2")
21
+ print("Embedding model loaded successfully.")
22
+ except Exception as e:
23
+ print(f"Failed to load embedding model: {e}")
24
+
25
+ def create_index_from_results(self, results: list):
26
+ """
27
+ Takes a list of search result dicts, creates embeddings, and builds a FAISS index.
28
+ """
29
+ if not self.model or not faiss:
30
+ print("VectorService: Dependencies missing or model not loaded.")
31
+ return
32
+
33
+ self.chunks = []
34
+ texts_to_embed = []
35
+
36
+ for res in results:
37
+ # Combine Title and Content for a rich embedding context
38
+ text = f"Title: {res.get('title', '')}\nContent: {res.get('content', '')}"
39
+ self.chunks.append(res) # Keep reference to original object
40
+ texts_to_embed.append(text)
41
+
42
+ if not texts_to_embed:
43
+ return
44
+
45
+ try:
46
+ embeddings = self.model.encode(texts_to_embed)
47
+ dimension = embeddings.shape[1]
48
+
49
+ self.index = faiss.IndexFlatL2(dimension)
50
+ self.index.add(np.array(embeddings))
51
+ print(f"VectorService: Created FAISS index with {self.index.ntotal} vectors")
52
+ except Exception as e:
53
+ print(f"VectorService Error during indexing: {e}")
54
+
55
+ def search_similar(self, query: str, k: int = 3):
56
+ """
57
+ Searches the FAISS index for the most relevant chunks to the query.
58
+ """
59
+ if not self.index or not self.model:
60
+ return []
61
+
62
+ try:
63
+ query_emb = self.model.encode([query])
64
+ distances, indices = self.index.search(query_emb, k)
65
+
66
+ top_results = []
67
+ for idx in indices[0]:
68
+ if idx < len(self.chunks) and idx >= 0:
69
+ top_results.append(self.chunks[idx])
70
+
71
+ return top_results
72
+ except Exception as e:
73
+ print(f"VectorService Error during search: {e}")
74
+ return []
75
+
76
+ vector_service = VectorService()
backend/.env ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Trust-First Copilot Configuration
2
+
3
+ # OpenAI API Key (Optional if using Hugging Face)
4
+ OPENAI_API_KEY=
5
+
6
+ # Tavily API Key (Required for Search)
7
+ TAVILY_API_KEY=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
8
+
9
+ # Hugging Face API Key (Free Brain Power!)
10
+ HUGGINGFACE_API_KEY=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
11
+
12
+ # Groq API Key (Fastest Brain)
13
+ GROQ_API_KEY=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
14
+ PROJECT_NAME="Advance Copilot"
15
+
16
+ # Gemini API Key (Google)
17
+ GEMINI_API_KEY=AIzaSyBb27_DT8Py1m2KrctHNZuIcuyu75xWOfo
backend/__init__.py ADDED
File without changes
backend/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (147 Bytes). View file
 
backend/debug.log ADDED
@@ -0,0 +1,823 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ --- INIT ---
3
+ OpenAI Key: True
4
+ HF Key: hf_Js
5
+ AsyncInferenceClient: <class 'huggingface_hub.inference._generated._async_client.AsyncInferenceClient'>
6
+
7
+ --- INIT ---
8
+ OpenAI Key: True
9
+ HF Key: hf_Js
10
+ AsyncInferenceClient: <class 'huggingface_hub.inference._generated._async_client.AsyncInferenceClient'>
11
+
12
+ --- INIT ---
13
+ OpenAI Key: True
14
+ HF Key: hf_Js
15
+ AsyncInferenceClient: <class 'huggingface_hub.inference._generated._async_client.AsyncInferenceClient'>
16
+
17
+ --- INIT ---
18
+ OpenAI Key: True
19
+ HF Key: hf_Js
20
+ AsyncInferenceClient: <class 'huggingface_hub.inference._generated._async_client.AsyncInferenceClient'>
21
+
22
+ --- INIT ---
23
+ OpenAI Key: True
24
+ HF Key: hf_Js
25
+ AsyncInferenceClient: <class 'huggingface_hub.inference._generated._async_client.AsyncInferenceClient'>
26
+
27
+ --- INIT ---
28
+ OpenAI Key: True
29
+ HF Key: hf_Js
30
+ AsyncInferenceClient: <class 'huggingface_hub.inference._generated._async_client.AsyncInferenceClient'>
31
+ LLM Error (Synthesis): Error code: 404 - {'error': {'message': 'The model `gpt-4-turbo-preview` does not exist or you do not have access to it.', 'type': 'invalid_request_error', 'param': None, 'code': 'model_not_found'}}
32
+ LLM Error (Synthesis): Error code: 404 - {'error': {'message': 'The model `gpt-4-turbo-preview` does not exist or you do not have access to it.', 'type': 'invalid_request_error', 'param': None, 'code': 'model_not_found'}}
33
+ LLM Error (Synthesis): Error code: 404 - {'error': {'message': 'The model `gpt-4-turbo-preview` does not exist or you do not have access to it.', 'type': 'invalid_request_error', 'param': None, 'code': 'model_not_found'}}
34
+ LLM Error (Synthesis): Error code: 404 - {'error': {'message': 'The model `gpt-4-turbo-preview` does not exist or you do not have access to it.', 'type': 'invalid_request_error', 'param': None, 'code': 'model_not_found'}}
35
+ LLM Error (Synthesis): Error code: 404 - {'error': {'message': 'The model `gpt-4-turbo-preview` does not exist or you do not have access to it.', 'type': 'invalid_request_error', 'param': None, 'code': 'model_not_found'}}
36
+
37
+ --- INIT ---
38
+ OpenAI Key: True
39
+ HF Key: hf_Js
40
+ AsyncInferenceClient: <class 'huggingface_hub.inference._generated._async_client.AsyncInferenceClient'>
41
+
42
+ --- INIT ---
43
+ OpenAI Key: True
44
+ HF Key: hf_Js
45
+ AsyncInferenceClient: <class 'huggingface_hub.inference._generated._async_client.AsyncInferenceClient'>
46
+
47
+ --- INIT ---
48
+ OpenAI Key: True
49
+ HF Key: hf_Js
50
+ AsyncInferenceClient: <class 'huggingface_hub.inference._generated._async_client.AsyncInferenceClient'>
51
+
52
+ --- INIT ---
53
+ OpenAI Key: True
54
+ HF Key: hf_Js
55
+ AsyncInferenceClient: <class 'huggingface_hub.inference._generated._async_client.AsyncInferenceClient'>
56
+
57
+ --- INIT ---
58
+ OpenAI Key: True
59
+ HF Key: hf_Js
60
+ AsyncInferenceClient: <class 'huggingface_hub.inference._generated._async_client.AsyncInferenceClient'>
61
+ LLM Error (Synthesis): (Request ID: Root=1-696658c4-788fc3276bd8d2ba39479c5d;254be642-c222-4663-84d5-f8ba42f2ebbe)
62
+
63
+ 403 Forbidden: This authentication method does not have sufficient permissions to call Inference Providers on behalf of user Abhi18singh.
64
+ Cannot access content at: https://router.huggingface.co/v1/chat/completions.
65
+ Make sure your token has the correct permissions.
66
+ LLM Error (Synthesis): (Request ID: Root=1-696658ef-5a3811376f5b88c367c230fc;3c7bda51-0042-48c4-9c9d-9cd4f4e4d6d2)
67
+
68
+ 403 Forbidden: This authentication method does not have sufficient permissions to call Inference Providers on behalf of user Abhi18singh.
69
+ Cannot access content at: https://router.huggingface.co/v1/chat/completions.
70
+ Make sure your token has the correct permissions.
71
+ LLM Error (Synthesis): (Request ID: Root=1-69665998-7a2fa3962140a6b8222dd600;b74da982-0738-479c-9651-854274a658d2)
72
+
73
+ 403 Forbidden: This authentication method does not have sufficient permissions to call Inference Providers on behalf of user Abhi18singh.
74
+ Cannot access content at: https://router.huggingface.co/v1/chat/completions.
75
+ Make sure your token has the correct permissions.
76
+ LLM Error (Synthesis): (Request ID: Root=1-69665a20-1573ab002b04c9556e6c0c59;fa51d8f9-7c0a-4211-aacd-105bec200581)
77
+
78
+ 403 Forbidden: This authentication method does not have sufficient permissions to call Inference Providers on behalf of user Abhi18singh.
79
+ Cannot access content at: https://router.huggingface.co/v1/chat/completions.
80
+ Make sure your token has the correct permissions.
81
+ LLM Error (Synthesis): (Request ID: Root=1-69665a5e-0646812e1eec86ba4943610e;25d67883-df55-4879-869e-639ffc2a2eb8)
82
+
83
+ 403 Forbidden: This authentication method does not have sufficient permissions to call Inference Providers on behalf of user Abhi18singh.
84
+ Cannot access content at: https://router.huggingface.co/v1/chat/completions.
85
+ Make sure your token has the correct permissions.
86
+
87
+ --- INIT ---
88
+ OpenAI Key: True
89
+ HF Key: hf_Js
90
+ AsyncInferenceClient: <class 'huggingface_hub.inference._generated._async_client.AsyncInferenceClient'>
91
+
92
+ --- INIT ---
93
+ OpenAI Key: True
94
+ HF Key: hf_Js
95
+ AsyncInferenceClient: <class 'huggingface_hub.inference._generated._async_client.AsyncInferenceClient'>
96
+
97
+ --- INIT ---
98
+ OpenAI Key: True
99
+ HF Key: hf_Js
100
+ AsyncInferenceClient: <class 'huggingface_hub.inference._generated._async_client.AsyncInferenceClient'>
101
+
102
+ --- INIT ---
103
+ OpenAI Key: True
104
+ HF Key: hf_Js
105
+ AsyncInferenceClient: <class 'huggingface_hub.inference._generated._async_client.AsyncInferenceClient'>
106
+
107
+ --- INIT ---
108
+ OpenAI Key: True
109
+ HF Key: hf_Js
110
+ AsyncInferenceClient: <class 'huggingface_hub.inference._generated._async_client.AsyncInferenceClient'>
111
+
112
+ --- INIT ---
113
+ OpenAI Key: True
114
+ HF Key: hf_Js
115
+ AsyncInferenceClient: <class 'huggingface_hub.inference._generated._async_client.AsyncInferenceClient'>
116
+
117
+ --- INIT ---
118
+ OpenAI Key: True
119
+ HF Key: hf_Js
120
+ AsyncInferenceClient: <class 'huggingface_hub.inference._generated._async_client.AsyncInferenceClient'>
121
+
122
+ --- INIT ---
123
+ OpenAI Key: True
124
+ HF Key: hf_Js
125
+ AsyncInferenceClient: <class 'huggingface_hub.inference._generated._async_client.AsyncInferenceClient'>
126
+
127
+ --- INIT ---
128
+ OpenAI Key: True
129
+ HF Key: hf_Js
130
+ AsyncInferenceClient: <class 'huggingface_hub.inference._generated._async_client.AsyncInferenceClient'>
131
+
132
+ --- INIT ---
133
+ OpenAI Key: True
134
+ HF Key: hf_Js
135
+ AsyncInferenceClient: <class 'huggingface_hub.inference._generated._async_client.AsyncInferenceClient'>
136
+
137
+ --- INIT ---
138
+ OpenAI Key: True
139
+ HF Key: hf_Js
140
+ AsyncInferenceClient: <class 'huggingface_hub.inference._generated._async_client.AsyncInferenceClient'>
141
+
142
+ --- INIT ---
143
+ OpenAI Key: True
144
+ HF Key: hf_Js
145
+ AsyncInferenceClient: <class 'huggingface_hub.inference._generated._async_client.AsyncInferenceClient'>
146
+
147
+ --- INIT ---
148
+ OpenAI Key: True
149
+ HF Key: hf_Js
150
+ AsyncInferenceClient: <class 'huggingface_hub.inference._generated._async_client.AsyncInferenceClient'>
151
+
152
+ --- INIT ---
153
+ OpenAI Key: True
154
+ HF Key: hf_Js
155
+ Groq Key: gsk_f
156
+
157
+ --- INIT ---
158
+ OpenAI Key: True
159
+ HF Key: hf_Js
160
+ Groq Key: gsk_f
161
+
162
+ --- INIT ---
163
+ OpenAI Key: True
164
+ HF Key: hf_Js
165
+ Groq Key: gsk_f
166
+
167
+ --- INIT ---
168
+ OpenAI Key: True
169
+ HF Key: hf_Js
170
+ Groq Key: gsk_f
171
+
172
+ --- INIT ---
173
+ OpenAI Key: True
174
+ HF Key: hf_Js
175
+ Groq Key: gsk_f
176
+
177
+ --- INIT ---
178
+ OpenAI Key: True
179
+ HF Key: hf_Js
180
+ Groq Key: gsk_f
181
+
182
+ --- INIT ---
183
+ OpenAI Key: True
184
+ HF Key: hf_Js
185
+ Groq Key: gsk_f
186
+
187
+ --- INIT ---
188
+ OpenAI Key: True
189
+ HF Key: hf_Js
190
+ Groq Key: gsk_f
191
+
192
+ --- INIT ---
193
+ OpenAI Key: True
194
+ HF Key: hf_Js
195
+ Groq Key: gsk_f
196
+
197
+ --- INIT ---
198
+ OpenAI Key: True
199
+ HF Key: hf_Js
200
+ Groq Key: gsk_f
201
+ LLM Error (Synthesis): Error code: 400 - {'error': {'message': 'The model `llama3-70b-8192` has been decommissioned and is no longer supported. Please refer to https://console.groq.com/docs/deprecations for a recommendation on which model to use instead.', 'type': 'invalid_request_error', 'code': 'model_decommissioned'}}
202
+ LLM Error (Synthesis): Error code: 400 - {'error': {'message': 'The model `llama3-70b-8192` has been decommissioned and is no longer supported. Please refer to https://console.groq.com/docs/deprecations for a recommendation on which model to use instead.', 'type': 'invalid_request_error', 'code': 'model_decommissioned'}}
203
+ LLM Error (Synthesis): Error code: 400 - {'error': {'message': 'The model `llama3-70b-8192` has been decommissioned and is no longer supported. Please refer to https://console.groq.com/docs/deprecations for a recommendation on which model to use instead.', 'type': 'invalid_request_error', 'code': 'model_decommissioned'}}
204
+
205
+ --- INIT ---
206
+ OpenAI Key: True
207
+ HF Key: hf_Js
208
+ Groq Key: gsk_f
209
+ Gemini Key: AIzaS
210
+
211
+ --- INIT ---
212
+ OpenAI Key: True
213
+ HF Key: hf_Js
214
+ Groq Key: gsk_f
215
+ Gemini Key: AIzaS
216
+
217
+ --- INIT ---
218
+ OpenAI Key: True
219
+ HF Key: hf_Js
220
+ Groq Key: gsk_f
221
+ Gemini Key: AIzaS
222
+ LLM Error (Synthesis): Error code: 400 - {'error': {'message': 'The model `llama3-70b-8192` has been decommissioned and is no longer supported. Please refer to https://console.groq.com/docs/deprecations for a recommendation on which model to use instead.', 'type': 'invalid_request_error', 'code': 'model_decommissioned'}}
223
+
224
+ --- INIT ---
225
+ OpenAI Key: True
226
+ HF Key: hf_Js
227
+ Groq Key: gsk_f
228
+ Gemini Key: AIzaS
229
+ LLM Error (Synthesis): 404 models/gemini-1.5-flash is not found for API version v1beta, or is not supported for generateContent. Call ListModels to see the list of available models and their supported methods.
230
+
231
+ --- INIT ---
232
+ OpenAI Key: True
233
+ HF Key: hf_Js
234
+ Groq Key: gsk_f
235
+ Gemini Key: AIzaS
236
+
237
+ --- INIT ---
238
+ OpenAI Key: True
239
+ HF Key: hf_Js
240
+ Groq Key: gsk_f
241
+ Gemini Key: AIzaS
242
+
243
+ --- INIT ---
244
+ OpenAI Key: True
245
+ HF Key: hf_Js
246
+ Groq Key: gsk_f
247
+ Gemini Key: AIzaS
248
+ LLM Error (Synthesis): 429 You exceeded your current quota, please check your plan and billing details. For more information on this error, head to: https://ai.google.dev/gemini-api/docs/rate-limits. To monitor your current usage, head to: https://ai.dev/rate-limit.
249
+ * Quota exceeded for metric: generativelanguage.googleapis.com/generate_content_free_tier_input_token_count, limit: 0, model: gemini-2.5-pro
250
+ * Quota exceeded for metric: generativelanguage.googleapis.com/generate_content_free_tier_requests, limit: 0, model: gemini-2.5-pro
251
+ * Quota exceeded for metric: generativelanguage.googleapis.com/generate_content_free_tier_requests, limit: 0, model: gemini-2.5-pro
252
+ * Quota exceeded for metric: generativelanguage.googleapis.com/generate_content_free_tier_input_token_count, limit: 0, model: gemini-2.5-pro
253
+ Please retry in 24.311506491s. [links {
254
+ description: "Learn more about Gemini API quotas"
255
+ url: "https://ai.google.dev/gemini-api/docs/rate-limits"
256
+ }
257
+ , violations {
258
+ quota_metric: "generativelanguage.googleapis.com/generate_content_free_tier_input_token_count"
259
+ quota_id: "GenerateContentInputTokensPerModelPerMinute-FreeTier"
260
+ quota_dimensions {
261
+ key: "model"
262
+ value: "gemini-2.5-pro"
263
+ }
264
+ quota_dimensions {
265
+ key: "location"
266
+ value: "global"
267
+ }
268
+ }
269
+ violations {
270
+ quota_metric: "generativelanguage.googleapis.com/generate_content_free_tier_requests"
271
+ quota_id: "GenerateRequestsPerMinutePerProjectPerModel-FreeTier"
272
+ quota_dimensions {
273
+ key: "model"
274
+ value: "gemini-2.5-pro"
275
+ }
276
+ quota_dimensions {
277
+ key: "location"
278
+ value: "global"
279
+ }
280
+ }
281
+ violations {
282
+ quota_metric: "generativelanguage.googleapis.com/generate_content_free_tier_requests"
283
+ quota_id: "GenerateRequestsPerDayPerProjectPerModel-FreeTier"
284
+ quota_dimensions {
285
+ key: "model"
286
+ value: "gemini-2.5-pro"
287
+ }
288
+ quota_dimensions {
289
+ key: "location"
290
+ value: "global"
291
+ }
292
+ }
293
+ violations {
294
+ quota_metric: "generativelanguage.googleapis.com/generate_content_free_tier_input_token_count"
295
+ quota_id: "GenerateContentInputTokensPerModelPerDay-FreeTier"
296
+ quota_dimensions {
297
+ key: "model"
298
+ value: "gemini-2.5-pro"
299
+ }
300
+ quota_dimensions {
301
+ key: "location"
302
+ value: "global"
303
+ }
304
+ }
305
+ , retry_delay {
306
+ seconds: 24
307
+ }
308
+ ]
309
+ LLM Error (Synthesis): 429 You exceeded your current quota, please check your plan and billing details. For more information on this error, head to: https://ai.google.dev/gemini-api/docs/rate-limits. To monitor your current usage, head to: https://ai.dev/rate-limit.
310
+ * Quota exceeded for metric: generativelanguage.googleapis.com/generate_content_free_tier_input_token_count, limit: 0, model: gemini-2.5-pro
311
+ * Quota exceeded for metric: generativelanguage.googleapis.com/generate_content_free_tier_requests, limit: 0, model: gemini-2.5-pro
312
+ * Quota exceeded for metric: generativelanguage.googleapis.com/generate_content_free_tier_requests, limit: 0, model: gemini-2.5-pro
313
+ * Quota exceeded for metric: generativelanguage.googleapis.com/generate_content_free_tier_input_token_count, limit: 0, model: gemini-2.5-pro
314
+ Please retry in 42.395475103s. [links {
315
+ description: "Learn more about Gemini API quotas"
316
+ url: "https://ai.google.dev/gemini-api/docs/rate-limits"
317
+ }
318
+ , violations {
319
+ quota_metric: "generativelanguage.googleapis.com/generate_content_free_tier_input_token_count"
320
+ quota_id: "GenerateContentInputTokensPerModelPerDay-FreeTier"
321
+ quota_dimensions {
322
+ key: "model"
323
+ value: "gemini-2.5-pro"
324
+ }
325
+ quota_dimensions {
326
+ key: "location"
327
+ value: "global"
328
+ }
329
+ }
330
+ violations {
331
+ quota_metric: "generativelanguage.googleapis.com/generate_content_free_tier_requests"
332
+ quota_id: "GenerateRequestsPerDayPerProjectPerModel-FreeTier"
333
+ quota_dimensions {
334
+ key: "model"
335
+ value: "gemini-2.5-pro"
336
+ }
337
+ quota_dimensions {
338
+ key: "location"
339
+ value: "global"
340
+ }
341
+ }
342
+ violations {
343
+ quota_metric: "generativelanguage.googleapis.com/generate_content_free_tier_requests"
344
+ quota_id: "GenerateRequestsPerMinutePerProjectPerModel-FreeTier"
345
+ quota_dimensions {
346
+ key: "model"
347
+ value: "gemini-2.5-pro"
348
+ }
349
+ quota_dimensions {
350
+ key: "location"
351
+ value: "global"
352
+ }
353
+ }
354
+ violations {
355
+ quota_metric: "generativelanguage.googleapis.com/generate_content_free_tier_input_token_count"
356
+ quota_id: "GenerateContentInputTokensPerModelPerMinute-FreeTier"
357
+ quota_dimensions {
358
+ key: "model"
359
+ value: "gemini-2.5-pro"
360
+ }
361
+ quota_dimensions {
362
+ key: "location"
363
+ value: "global"
364
+ }
365
+ }
366
+ , retry_delay {
367
+ seconds: 42
368
+ }
369
+ ]
370
+
371
+ --- INIT ---
372
+ OpenAI Key: True
373
+ HF Key: hf_Js
374
+ Groq Key: gsk_f
375
+ Gemini Key: AIzaS
376
+
377
+ --- INIT ---
378
+ OpenAI Key: True
379
+ HF Key: hf_Js
380
+ Groq Key: gsk_f
381
+ Gemini Key: AIzaS
382
+ LLM Error (Synthesis): 404 models/gemini-1.5-flash is not found for API version v1beta, or is not supported for generateContent. Call ListModels to see the list of available models and their supported methods.
383
+ LLM Error (Synthesis): 404 models/gemini-1.5-flash is not found for API version v1beta, or is not supported for generateContent. Call ListModels to see the list of available models and their supported methods.
384
+
385
+ --- INIT ---
386
+ OpenAI Key: True
387
+ HF Key: hf_Js
388
+ Groq Key: gsk_f
389
+ Gemini Key: AIzaS
390
+
391
+ --- INIT ---
392
+ OpenAI Key: True
393
+ HF Key: hf_Js
394
+ Groq Key: gsk_f
395
+ Gemini Key: AIzaS
396
+
397
+ --- INIT ---
398
+ OpenAI Key: True
399
+ HF Key: hf_Js
400
+ Groq Key: gsk_f
401
+ Gemini Key: AIzaS
402
+
403
+ --- INIT ---
404
+ OpenAI Key: True
405
+ HF Key: hf_Js
406
+ Groq Key: gsk_f
407
+ Gemini Key: AIzaS
408
+ LLM Error (Synthesis): 429 You exceeded your current quota, please check your plan and billing details. For more information on this error, head to: https://ai.google.dev/gemini-api/docs/rate-limits. To monitor your current usage, head to: https://ai.dev/rate-limit.
409
+ * Quota exceeded for metric: generativelanguage.googleapis.com/generate_content_free_tier_input_token_count, limit: 0, model: gemini-2.0-flash
410
+ * Quota exceeded for metric: generativelanguage.googleapis.com/generate_content_free_tier_requests, limit: 0, model: gemini-2.0-flash
411
+ * Quota exceeded for metric: generativelanguage.googleapis.com/generate_content_free_tier_requests, limit: 0, model: gemini-2.0-flash
412
+ Please retry in 50.635719043s. [links {
413
+ description: "Learn more about Gemini API quotas"
414
+ url: "https://ai.google.dev/gemini-api/docs/rate-limits"
415
+ }
416
+ , violations {
417
+ quota_metric: "generativelanguage.googleapis.com/generate_content_free_tier_input_token_count"
418
+ quota_id: "GenerateContentInputTokensPerModelPerMinute-FreeTier"
419
+ quota_dimensions {
420
+ key: "model"
421
+ value: "gemini-2.0-flash"
422
+ }
423
+ quota_dimensions {
424
+ key: "location"
425
+ value: "global"
426
+ }
427
+ }
428
+ violations {
429
+ quota_metric: "generativelanguage.googleapis.com/generate_content_free_tier_requests"
430
+ quota_id: "GenerateRequestsPerMinutePerProjectPerModel-FreeTier"
431
+ quota_dimensions {
432
+ key: "model"
433
+ value: "gemini-2.0-flash"
434
+ }
435
+ quota_dimensions {
436
+ key: "location"
437
+ value: "global"
438
+ }
439
+ }
440
+ violations {
441
+ quota_metric: "generativelanguage.googleapis.com/generate_content_free_tier_requests"
442
+ quota_id: "GenerateRequestsPerDayPerProjectPerModel-FreeTier"
443
+ quota_dimensions {
444
+ key: "model"
445
+ value: "gemini-2.0-flash"
446
+ }
447
+ quota_dimensions {
448
+ key: "location"
449
+ value: "global"
450
+ }
451
+ }
452
+ , retry_delay {
453
+ seconds: 50
454
+ }
455
+ ]
456
+ LLM Error (Synthesis): 429 You exceeded your current quota, please check your plan and billing details. For more information on this error, head to: https://ai.google.dev/gemini-api/docs/rate-limits. To monitor your current usage, head to: https://ai.dev/rate-limit.
457
+ * Quota exceeded for metric: generativelanguage.googleapis.com/generate_content_free_tier_requests, limit: 0, model: gemini-2.0-flash
458
+ * Quota exceeded for metric: generativelanguage.googleapis.com/generate_content_free_tier_requests, limit: 0, model: gemini-2.0-flash
459
+ * Quota exceeded for metric: generativelanguage.googleapis.com/generate_content_free_tier_input_token_count, limit: 0, model: gemini-2.0-flash
460
+ Please retry in 6.198648352s. [links {
461
+ description: "Learn more about Gemini API quotas"
462
+ url: "https://ai.google.dev/gemini-api/docs/rate-limits"
463
+ }
464
+ , violations {
465
+ quota_metric: "generativelanguage.googleapis.com/generate_content_free_tier_requests"
466
+ quota_id: "GenerateRequestsPerDayPerProjectPerModel-FreeTier"
467
+ quota_dimensions {
468
+ key: "model"
469
+ value: "gemini-2.0-flash"
470
+ }
471
+ quota_dimensions {
472
+ key: "location"
473
+ value: "global"
474
+ }
475
+ }
476
+ violations {
477
+ quota_metric: "generativelanguage.googleapis.com/generate_content_free_tier_requests"
478
+ quota_id: "GenerateRequestsPerMinutePerProjectPerModel-FreeTier"
479
+ quota_dimensions {
480
+ key: "model"
481
+ value: "gemini-2.0-flash"
482
+ }
483
+ quota_dimensions {
484
+ key: "location"
485
+ value: "global"
486
+ }
487
+ }
488
+ violations {
489
+ quota_metric: "generativelanguage.googleapis.com/generate_content_free_tier_input_token_count"
490
+ quota_id: "GenerateContentInputTokensPerModelPerMinute-FreeTier"
491
+ quota_dimensions {
492
+ key: "model"
493
+ value: "gemini-2.0-flash"
494
+ }
495
+ quota_dimensions {
496
+ key: "location"
497
+ value: "global"
498
+ }
499
+ }
500
+ , retry_delay {
501
+ seconds: 6
502
+ }
503
+ ]
504
+
505
+ --- INIT ---
506
+ OpenAI Key: True
507
+ HF Key: hf_Js
508
+ Groq Key: gsk_f
509
+ Gemini Key: AIzaS
510
+
511
+ --- INIT ---
512
+ OpenAI Key: True
513
+ HF Key: hf_Js
514
+ Groq Key: gsk_f
515
+ Gemini Key: AIzaS
516
+
517
+ --- INIT ---
518
+ OpenAI Key: True
519
+ HF Key: hf_Js
520
+ Groq Key: gsk_f
521
+ Gemini Key: AIzaS
522
+ LLM Error (Synthesis): 429 You exceeded your current quota, please check your plan and billing details. For more information on this error, head to: https://ai.google.dev/gemini-api/docs/rate-limits. To monitor your current usage, head to: https://ai.dev/rate-limit.
523
+ * Quota exceeded for metric: generativelanguage.googleapis.com/generate_content_free_tier_requests, limit: 20, model: gemini-2.5-flash
524
+ Please retry in 9.804671677s. [links {
525
+ description: "Learn more about Gemini API quotas"
526
+ url: "https://ai.google.dev/gemini-api/docs/rate-limits"
527
+ }
528
+ , violations {
529
+ quota_metric: "generativelanguage.googleapis.com/generate_content_free_tier_requests"
530
+ quota_id: "GenerateRequestsPerDayPerProjectPerModel-FreeTier"
531
+ quota_dimensions {
532
+ key: "model"
533
+ value: "gemini-2.5-flash"
534
+ }
535
+ quota_dimensions {
536
+ key: "location"
537
+ value: "global"
538
+ }
539
+ quota_value: 20
540
+ }
541
+ , retry_delay {
542
+ seconds: 9
543
+ }
544
+ ]
545
+ LLM Error (Synthesis): 429 You exceeded your current quota, please check your plan and billing details. For more information on this error, head to: https://ai.google.dev/gemini-api/docs/rate-limits. To monitor your current usage, head to: https://ai.dev/rate-limit.
546
+ * Quota exceeded for metric: generativelanguage.googleapis.com/generate_content_free_tier_requests, limit: 20, model: gemini-2.5-flash
547
+ Please retry in 29.659097469s. [links {
548
+ description: "Learn more about Gemini API quotas"
549
+ url: "https://ai.google.dev/gemini-api/docs/rate-limits"
550
+ }
551
+ , violations {
552
+ quota_metric: "generativelanguage.googleapis.com/generate_content_free_tier_requests"
553
+ quota_id: "GenerateRequestsPerDayPerProjectPerModel-FreeTier"
554
+ quota_dimensions {
555
+ key: "model"
556
+ value: "gemini-2.5-flash"
557
+ }
558
+ quota_dimensions {
559
+ key: "location"
560
+ value: "global"
561
+ }
562
+ quota_value: 20
563
+ }
564
+ , retry_delay {
565
+ seconds: 29
566
+ }
567
+ ]
568
+
569
+ --- INIT ---
570
+ OpenAI Key: True
571
+ HF Key: hf_Js
572
+ Groq Key: gsk_f
573
+ Gemini Key: AIzaS
574
+ LLM Error (Synthesis): 429 You exceeded your current quota, please check your plan and billing details. For more information on this error, head to: https://ai.google.dev/gemini-api/docs/rate-limits. To monitor your current usage, head to: https://ai.dev/rate-limit.
575
+ * Quota exceeded for metric: generativelanguage.googleapis.com/generate_content_free_tier_requests, limit: 20, model: gemini-2.5-flash
576
+ Please retry in 3.690783496s. [links {
577
+ description: "Learn more about Gemini API quotas"
578
+ url: "https://ai.google.dev/gemini-api/docs/rate-limits"
579
+ }
580
+ , violations {
581
+ quota_metric: "generativelanguage.googleapis.com/generate_content_free_tier_requests"
582
+ quota_id: "GenerateRequestsPerDayPerProjectPerModel-FreeTier"
583
+ quota_dimensions {
584
+ key: "model"
585
+ value: "gemini-2.5-flash"
586
+ }
587
+ quota_dimensions {
588
+ key: "location"
589
+ value: "global"
590
+ }
591
+ quota_value: 20
592
+ }
593
+ , retry_delay {
594
+ seconds: 3
595
+ }
596
+ ]
597
+
598
+ --- INIT ---
599
+ OpenAI Key: True
600
+ HF Key: hf_Js
601
+ Groq Key: gsk_f
602
+ Gemini Key: AIzaS
603
+ LLM Error (Synthesis): 404 models/gemini-1.5-flash is not found for API version v1beta, or is not supported for generateContent. Call ListModels to see the list of available models and their supported methods.
604
+
605
+ --- INIT ---
606
+ OpenAI Key: True
607
+ HF Key: hf_Js
608
+ Groq Key: gsk_f
609
+ Gemini Key: AIzaS
610
+
611
+ --- INIT ---
612
+ OpenAI Key: True
613
+ HF Key: hf_Js
614
+ Groq Key: gsk_f
615
+ Gemini Key: AIzaS
616
+ LLM Error (Synthesis): 404 models/gemini-1.5-flash is not found for API version v1beta, or is not supported for generateContent. Call ListModels to see the list of available models and their supported methods.
617
+
618
+ --- INIT ---
619
+ OpenAI Key: True
620
+ HF Key: hf_Js
621
+ Groq Key: gsk_f
622
+ Gemini Key: AIzaS
623
+
624
+ --- INIT ---
625
+ OpenAI Key: True
626
+ HF Key: hf_Js
627
+ Groq Key: gsk_f
628
+ Gemini Key: AIzaS
629
+
630
+ --- INIT ---
631
+ OpenAI Key: True
632
+ HF Key: hf_Js
633
+ Groq Key: gsk_f
634
+ Gemini Key: AIzaS
635
+
636
+ --- INIT ---
637
+ OpenAI Key: True
638
+ HF Key: hf_Js
639
+ Groq Key: gsk_f
640
+ Gemini Key: AIzaS
641
+ LLM Error (Synthesis): 404 models/gemini-1.5-flash is not found for API version v1beta, or is not supported for generateContent. Call ListModels to see the list of available models and their supported methods.
642
+
643
+ --- INIT ---
644
+ OpenAI Key: True
645
+ HF Key: hf_Js
646
+ Groq Key: gsk_f
647
+ Gemini Key: AIzaS
648
+
649
+ --- INIT ---
650
+ OpenAI Key: True
651
+ HF Key: hf_Js
652
+ Groq Key: gsk_f
653
+ Gemini Key: AIzaS
654
+ LLM Error (Synthesis): 429 You exceeded your current quota, please check your plan and billing details. For more information on this error, head to: https://ai.google.dev/gemini-api/docs/rate-limits. To monitor your current usage, head to: https://ai.dev/rate-limit.
655
+ * Quota exceeded for metric: generativelanguage.googleapis.com/generate_content_free_tier_requests, limit: 0, model: gemini-2.0-flash-exp
656
+ * Quota exceeded for metric: generativelanguage.googleapis.com/generate_content_free_tier_requests, limit: 0, model: gemini-2.0-flash-exp
657
+ * Quota exceeded for metric: generativelanguage.googleapis.com/generate_content_free_tier_input_token_count, limit: 0, model: gemini-2.0-flash-exp
658
+ Please retry in 36.195734558s. [links {
659
+ description: "Learn more about Gemini API quotas"
660
+ url: "https://ai.google.dev/gemini-api/docs/rate-limits"
661
+ }
662
+ , violations {
663
+ quota_metric: "generativelanguage.googleapis.com/generate_content_free_tier_requests"
664
+ quota_id: "GenerateRequestsPerDayPerProjectPerModel-FreeTier"
665
+ quota_dimensions {
666
+ key: "model"
667
+ value: "gemini-2.0-flash-exp"
668
+ }
669
+ quota_dimensions {
670
+ key: "location"
671
+ value: "global"
672
+ }
673
+ }
674
+ violations {
675
+ quota_metric: "generativelanguage.googleapis.com/generate_content_free_tier_requests"
676
+ quota_id: "GenerateRequestsPerMinutePerProjectPerModel-FreeTier"
677
+ quota_dimensions {
678
+ key: "model"
679
+ value: "gemini-2.0-flash-exp"
680
+ }
681
+ quota_dimensions {
682
+ key: "location"
683
+ value: "global"
684
+ }
685
+ }
686
+ violations {
687
+ quota_metric: "generativelanguage.googleapis.com/generate_content_free_tier_input_token_count"
688
+ quota_id: "GenerateContentInputTokensPerModelPerMinute-FreeTier"
689
+ quota_dimensions {
690
+ key: "model"
691
+ value: "gemini-2.0-flash-exp"
692
+ }
693
+ quota_dimensions {
694
+ key: "location"
695
+ value: "global"
696
+ }
697
+ }
698
+ , retry_delay {
699
+ seconds: 36
700
+ }
701
+ ]
702
+
703
+ --- INIT ---
704
+ OpenAI Key: True
705
+ HF Key: hf_Js
706
+ Groq Key: gsk_f
707
+ Gemini Key: AIzaS
708
+
709
+ --- INIT ---
710
+ OpenAI Key: True
711
+ HF Key: hf_Js
712
+ Groq Key: gsk_f
713
+ Gemini Key: AIzaS
714
+
715
+ --- INIT ---
716
+ OpenAI Key: True
717
+ HF Key: hf_Js
718
+ Groq Key: gsk_f
719
+ Gemini Key: AIzaS
720
+
721
+ --- INIT ---
722
+ OpenAI Key: True
723
+ HF Key: hf_Js
724
+ Groq Key: gsk_f
725
+ Gemini Key: AIzaS
726
+
727
+ --- INIT ---
728
+ OpenAI Key: True
729
+ HF Key: hf_Js
730
+ Groq Key: gsk_f
731
+ Gemini Key: AIzaS
732
+
733
+ --- INIT ---
734
+ OpenAI Key: True
735
+ HF Key: hf_Js
736
+ Groq Key: gsk_f
737
+ Gemini Key: AIzaS
738
+
739
+ --- INIT ---
740
+ OpenAI Key: True
741
+ HF Key: hf_Js
742
+ Groq Key: gsk_f
743
+ Gemini Key: AIzaS
744
+
745
+ --- INIT ---
746
+ OpenAI Key: True
747
+ HF Key: hf_Js
748
+ Groq Key: gsk_f
749
+ Gemini Key: AIzaS
750
+
751
+ --- INIT ---
752
+ OpenAI Key: True
753
+ HF Key: hf_Js
754
+ Groq Key: gsk_f
755
+ Gemini Key: AIzaS
756
+
757
+ --- INIT ---
758
+ OpenAI Key: True
759
+ HF Key: hf_Js
760
+ Groq Key: gsk_f
761
+ Gemini Key: AIzaS
762
+
763
+ --- INIT ---
764
+ OpenAI Key: True
765
+ HF Key: hf_Js
766
+ Groq Key: gsk_f
767
+ Gemini Key: AIzaS
768
+
769
+ --- INIT ---
770
+ OpenAI Key: True
771
+ HF Key: hf_Js
772
+ Groq Key: gsk_f
773
+ Gemini Key: AIzaS
774
+
775
+ --- INIT ---
776
+ OpenAI Key: True
777
+ HF Key: hf_Js
778
+ Groq Key: gsk_f
779
+ Gemini Key: AIzaS
780
+
781
+ --- INIT ---
782
+ OpenAI Key: True
783
+ HF Key: hf_Js
784
+ Groq Key: gsk_f
785
+ Gemini Key: AIzaS
786
+ Provider Selected: Groq (llama-3.3-70b-versatile)
787
+ Provider Selected: Groq (llama-3.3-70b-versatile)
788
+ Provider Selected: Groq (llama-3.3-70b-versatile)
789
+
790
+ --- INIT ---
791
+ OpenAI Key: True
792
+ HF Key: hf_Js
793
+ Groq Key: gsk_f
794
+ Gemini Key: AIzaS
795
+ Provider Selected: Groq (llama-3.3-70b-versatile)
796
+
797
+ --- INIT ---
798
+ OpenAI Key: True
799
+ HF Key: hf_Js
800
+ Groq Key: gsk_f
801
+ Gemini Key: AIzaS
802
+ Provider Selected: Groq (llama-3.3-70b-versatile)
803
+
804
+ --- INIT ---
805
+ OpenAI Key: True
806
+ HF Key: hf_Js
807
+ Groq Key: gsk_f
808
+ Gemini Key: AIzaS
809
+ Provider Selected: Groq (llama-3.3-70b-versatile)
810
+
811
+ --- INIT ---
812
+ OpenAI Key: True
813
+ HF Key: hf_Js
814
+ Groq Key: gsk_f
815
+ Gemini Key: AIzaS
816
+ Provider Selected: Groq (llama-3.3-70b-versatile)
817
+
818
+ --- INIT ---
819
+ OpenAI Key: True
820
+ HF Key: hf_Js
821
+ Groq Key: gsk_f
822
+ Gemini Key: AIzaS
823
+ Provider Selected: Groq (llama-3.3-70b-versatile)
backend/debug_env.py ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from dotenv import load_dotenv
3
+ load_dotenv()
4
+
5
+ print("--- ENV DEBUG ---")
6
+ hf_key = os.getenv("HUGGINGFACE_API_KEY")
7
+ print(f"HF Key present: {bool(hf_key)}")
8
+ print(f"HF Key start: {hf_key[:4] if hf_key else 'None'}")
9
+
10
+ print("\n--- LIBRARY DEBUG ---")
11
+ try:
12
+ import huggingface_hub
13
+ print(f"huggingface_hub version: {huggingface_hub.__version__}")
14
+ except ImportError:
15
+ print("huggingface_hub NOT installed")
16
+
17
+ try:
18
+ from huggingface_hub import AsyncInferenceClient
19
+ print("AsyncInferenceClient imported successfully")
20
+ except ImportError as e:
21
+ print(f"AsyncInferenceClient import FAILED: {e}")
22
+
23
+ try:
24
+ from huggingface_hub import InferenceClient
25
+ print("InferenceClient imported successfully")
26
+ except ImportError as e:
27
+ print(f"InferenceClient import FAILED: {e}")
28
+
29
+ print("\n--- VECTOR DB DEBUG ---")
30
+ try:
31
+ import faiss
32
+ print(f"FAISS imported successfully")
33
+ except ImportError:
34
+ print("FAISS NOT installed")
35
+
36
+ try:
37
+ from sentence_transformers import SentenceTransformer
38
+ print("SentenceTransformer imported successfully")
39
+ except ImportError:
40
+ print("SentenceTransformer NOT installed")
41
+
42
+ print("\n--- APP CONFIG DEBUG ---")
43
+ try:
44
+ from app.core.config import settings
45
+ print(f"Settings HF Key: {bool(settings.HUGGINGFACE_API_KEY)}")
46
+ except Exception as e:
47
+ print(f"Could not load settings: {e}")
backend/list_available_models.py ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import google.generativeai as genai
2
+ import os
3
+ from dotenv import load_dotenv
4
+
5
+ load_dotenv()
6
+ api_key = os.getenv("GEMINI_API_KEY")
7
+
8
+ if not api_key:
9
+ print("No API Key found")
10
+ exit()
11
+
12
+ print(f"Checking models for key: {api_key[:10]}...")
13
+ genai.configure(api_key=api_key)
14
+
15
+ try:
16
+ print("List of available models:")
17
+ for m in genai.list_models():
18
+ if 'generateContent' in m.supported_generation_methods:
19
+ print(f"- {m.name}")
20
+ except Exception as e:
21
+ print(f"Error listing models: {e}")
backend/list_models.py ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import google.generativeai as genai
2
+ from app.core.config import settings
3
+
4
+ genai.configure(api_key=settings.GEMINI_API_KEY)
5
+
6
+ print("Listing models...")
7
+ try:
8
+ for m in genai.list_models():
9
+ if 'generateContent' in m.supported_generation_methods:
10
+ print(m.name)
11
+ except Exception as e:
12
+ print(f"Error listing models: {e}")
backend/my_models.txt ADDED
Binary file (2.36 kB). View file
 
backend/rag_summarizer.py ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from dotenv import load_dotenv
2
+ import os
3
+ import google.generativeai as genai
4
+ from tavily import TavilyClient
5
+
6
+ # =====================
7
+ # LOAD ENV
8
+ # =====================
9
+ load_dotenv()
10
+
11
+ GEMINI_KEY = os.getenv("GEMINI_API_KEY")
12
+ TAVILY_KEY = os.getenv("TAVILY_API_KEY")
13
+
14
+ print(f"Gemini Key: {GEMINI_KEY[:10]}...")
15
+
16
+ if not GEMINI_KEY:
17
+ print("❌ Gemini API key not loaded")
18
+ exit(1)
19
+
20
+ # =====================
21
+ # INIT SERVICES
22
+ # =====================
23
+ genai.configure(api_key=GEMINI_KEY)
24
+ model = genai.GenerativeModel("gemini-1.5-flash")
25
+
26
+ # Mock Tavily if key missing (for testing Gemini specifically)
27
+ if TAVILY_KEY:
28
+ tavily = TavilyClient(api_key=TAVILY_KEY)
29
+ else:
30
+ tavily = None
31
+ print("Warning: Tavily Key missing, using mock data")
32
+
33
+ # =====================
34
+ # USER QUERY
35
+ # =====================
36
+ query = "AI in healthcare current and future applications"
37
+
38
+ # =====================
39
+ # SEARCH (TAVILY)
40
+ # =====================
41
+ if tavily:
42
+ search_results = tavily.search(
43
+ query=query,
44
+ max_results=5,
45
+ include_raw_content=True
46
+ )
47
+ context = "\n\n".join(
48
+ [res["content"] for res in search_results["results"]]
49
+ )
50
+ else:
51
+ context = "Artificial Intelligence involves using computers to simulate human intelligence. AI is used in healthcare for diagnosis and treatment planning."
52
+
53
+ # =====================
54
+ # PROMPT
55
+ # =====================
56
+ PROMPT = f"""
57
+ You are an expert research assistant.
58
+
59
+ Task:
60
+ - Generate a concise, accurate, and well-structured answer
61
+ - Use ONLY the information from the sources below
62
+ - If unsure, say "Information not found in sources"
63
+
64
+ Sources:
65
+ {context}
66
+
67
+ Instructions:
68
+ - Summarize in 6–8 bullet points
69
+ - Use simple professional language
70
+ - Focus on real-world applications and future impact
71
+ """
72
+
73
+ # =====================
74
+ # GENERATE ANSWER
75
+ # =====================
76
+ try:
77
+ response = model.generate_content(PROMPT)
78
+ print("\n✅ FINAL ANSWER:\n")
79
+ print(response.text)
80
+ except Exception as e:
81
+ print(f"\n❌ ERROR: {e}")
debug.log ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ --- INIT ---
3
+ OpenAI Key: True
4
+ HF Key: None
5
+ Groq Key: None
6
+ Gemini Key: None
7
+
8
+ --- INIT ---
9
+ OpenAI Key: True
10
+ HF Key: None
11
+ Groq Key: None
12
+ Gemini Key: None
13
+
14
+ --- INIT ---
15
+ OpenAI Key: True
16
+ HF Key: None
17
+ Groq Key: None
18
+ Gemini Key: None
frontend/.gitignore ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2
+
3
+ # dependencies
4
+ /node_modules
5
+ /.pnp
6
+ .pnp.*
7
+ .yarn/*
8
+ !.yarn/patches
9
+ !.yarn/plugins
10
+ !.yarn/releases
11
+ !.yarn/versions
12
+
13
+ # testing
14
+ /coverage
15
+
16
+ # next.js
17
+ /.next/
18
+ /out/
19
+
20
+ # production
21
+ /build
22
+
23
+ # misc
24
+ .DS_Store
25
+ *.pem
26
+
27
+ # debug
28
+ npm-debug.log*
29
+ yarn-debug.log*
30
+ yarn-error.log*
31
+ .pnpm-debug.log*
32
+
33
+ # env files (can opt-in for committing if needed)
34
+ .env*
35
+
36
+ # vercel
37
+ .vercel
38
+
39
+ # typescript
40
+ *.tsbuildinfo
41
+ next-env.d.ts
frontend/README.md ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
2
+
3
+ ## Getting Started
4
+
5
+ First, run the development server:
6
+
7
+ ```bash
8
+ npm run dev
9
+ # or
10
+ yarn dev
11
+ # or
12
+ pnpm dev
13
+ # or
14
+ bun dev
15
+ ```
16
+
17
+ Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
18
+
19
+ You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
20
+
21
+ This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
22
+
23
+ ## Learn More
24
+
25
+ To learn more about Next.js, take a look at the following resources:
26
+
27
+ - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
28
+ - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
29
+
30
+ You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
31
+
32
+ ## Deploy on Vercel
33
+
34
+ The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
35
+
36
+ Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
frontend/app/favicon.ico ADDED
frontend/app/globals.css ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @import "tailwindcss";
2
+
3
+
4
+ @theme {
5
+ --color-background: var(--background);
6
+ --color-foreground: var(--foreground);
7
+ --color-card: var(--card);
8
+ --color-card-foreground: var(--card-foreground);
9
+ --color-popover: var(--popover);
10
+ --color-popover-foreground: var(--popover-foreground);
11
+ --color-primary: var(--primary);
12
+ --color-primary-foreground: var(--primary-foreground);
13
+ --color-secondary: var(--secondary);
14
+ --color-secondary-foreground: var(--secondary-foreground);
15
+ --color-muted: var(--muted);
16
+ --color-muted-foreground: var(--muted-foreground);
17
+ --color-accent: var(--accent);
18
+ --color-accent-foreground: var(--accent-foreground);
19
+ --color-destructive: var(--destructive);
20
+ --color-destructive-foreground: var(--destructive-foreground);
21
+ --color-border: var(--border);
22
+ --color-input: var(--input);
23
+ --color-ring: var(--ring);
24
+
25
+ --radius-lg: var(--radius);
26
+ --radius-md: calc(var(--radius) - 2px);
27
+ --radius-sm: calc(var(--radius) - 4px);
28
+ }
29
+
30
+ :root {
31
+ --background: 0 0% 100%;
32
+ --foreground: 222.2 84% 4.9%;
33
+
34
+ --card: 0 0% 100%;
35
+ --card-foreground: 222.2 84% 4.9%;
36
+
37
+ --popover: 0 0% 100%;
38
+ --popover-foreground: 222.2 84% 4.9%;
39
+
40
+ --primary: 222.2 47.4% 11.2%;
41
+ --primary-foreground: 210 40% 98%;
42
+
43
+ --secondary: 210 40% 96.1%;
44
+ --secondary-foreground: 222.2 47.4% 11.2%;
45
+
46
+ --muted: 210 40% 96.1%;
47
+ --muted-foreground: 215.4 16.3% 46.9%;
48
+
49
+ --accent: 210 40% 96.1%;
50
+ --accent-foreground: 222.2 47.4% 11.2%;
51
+
52
+ --destructive: 0 84.2% 60.2%;
53
+ --destructive-foreground: 210 40% 98%;
54
+
55
+ --border: 214.3 31.8% 91.4%;
56
+ --input: 214.3 31.8% 91.4%;
57
+ --ring: 222.2 84% 4.9%;
58
+
59
+ --radius: 0.5rem;
60
+ }
61
+
62
+ .dark {
63
+ --background: 222.2 84% 4.9%;
64
+ --foreground: 210 40% 98%;
65
+
66
+ --card: 222.2 84% 4.9%;
67
+ --card-foreground: 210 40% 98%;
68
+
69
+ --popover: 222.2 84% 4.9%;
70
+ --popover-foreground: 210 40% 98%;
71
+
72
+ --primary: 210 40% 98%;
73
+ --primary-foreground: 222.2 47.4% 11.2%;
74
+
75
+ --secondary: 217.2 32.6% 17.5%;
76
+ --secondary-foreground: 210 40% 98%;
77
+
78
+ --muted: 217.2 32.6% 17.5%;
79
+ --muted-foreground: 215 20.2% 65.1%;
80
+
81
+ --accent: 217.2 32.6% 17.5%;
82
+ --accent-foreground: 210 40% 98%;
83
+
84
+ --destructive: 0 62.8% 30.6%;
85
+ --destructive-foreground: 210 40% 98%;
86
+
87
+ --border: 217.2 32.6% 17.5%;
88
+ --input: 217.2 32.6% 17.5%;
89
+ --ring: 212.7 26.8% 83.9%;
90
+ }
91
+
92
+ @layer base {
93
+ * {
94
+ @apply border-border;
95
+ }
96
+
97
+ body {
98
+ @apply bg-background text-foreground;
99
+ }
100
+ }
frontend/app/layout.tsx ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { Metadata } from "next";
2
+ import { Geist, Geist_Mono } from "next/font/google";
3
+ import "./globals.css";
4
+
5
+ const geistSans = Geist({
6
+ variable: "--font-geist-sans",
7
+ subsets: ["latin"],
8
+ });
9
+
10
+ const geistMono = Geist_Mono({
11
+ variable: "--font-geist-mono",
12
+ subsets: ["latin"],
13
+ });
14
+
15
+ export const metadata: Metadata = {
16
+ title: "Create Next App",
17
+ description: "Generated by create next app",
18
+ };
19
+
20
+ export default function RootLayout({
21
+ children,
22
+ }: Readonly<{
23
+ children: React.ReactNode;
24
+ }>) {
25
+ return (
26
+ <html lang="en" suppressHydrationWarning>
27
+ <body
28
+ className={`${geistSans.variable} ${geistMono.variable} antialiased`}
29
+ suppressHydrationWarning
30
+ >
31
+ {children}
32
+ </body>
33
+ </html>
34
+ );
35
+ }
frontend/app/page.tsx ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ import ChatLayout from "../components/Chat/ChatLayout";
2
+
3
+ export default function Home() {
4
+ return <ChatLayout />;
5
+ }
frontend/components/Chat/ChatInput.tsx ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ import { SendHorizontal, Paperclip } from "lucide-react";
4
+ import { useRef, useEffect } from "react";
5
+
6
+ interface ChatInputProps {
7
+ value: string;
8
+ onChange: (val: string) => void;
9
+ onSubmit: () => void;
10
+ isLoading: boolean;
11
+ onFileSelect?: (file: File) => void;
12
+ attachedFile?: File | null;
13
+ }
14
+
15
+ export default function ChatInput({ value, onChange, onSubmit, isLoading, onFileSelect, attachedFile }: ChatInputProps) {
16
+ const textareaRef = useRef<HTMLTextAreaElement>(null);
17
+ const fileInputRef = useRef<HTMLInputElement>(null);
18
+
19
+ // Auto-resize textarea
20
+ useEffect(() => {
21
+ if (textareaRef.current) {
22
+ textareaRef.current.style.height = "auto";
23
+ textareaRef.current.style.height = `${Math.min(textareaRef.current.scrollHeight, 200)}px`;
24
+ }
25
+ }, [value]);
26
+
27
+ const handleKeyDown = (e: React.KeyboardEvent) => {
28
+ if (e.key === "Enter" && !e.shiftKey) {
29
+ e.preventDefault();
30
+ onSubmit();
31
+ }
32
+ };
33
+
34
+ return (
35
+ <div className="w-full max-w-3xl mx-auto px-4 pb-6">
36
+ <div className="relative flex items-end gap-2 bg-white border border-gray-200 rounded-xl p-3 shadow-lg focus-within:ring-2 focus-within:ring-indigo-500/50 focus-within:border-indigo-500 transition-all">
37
+ <input
38
+ type="file"
39
+ ref={fileInputRef}
40
+ className="hidden"
41
+ onChange={(e) => {
42
+ if (e.target.files?.[0] && onFileSelect) onFileSelect(e.target.files[0]);
43
+ }}
44
+ />
45
+ <button
46
+ onClick={() => fileInputRef.current?.click()}
47
+ className={`p-2 transition-colors ${attachedFile ? "text-indigo-600 bg-indigo-50 rounded" : "text-gray-400 hover:text-gray-600"}`}
48
+ title="Attach file (PDF, Excel, Txt)"
49
+ >
50
+ <Paperclip size={20} />
51
+ </button>
52
+
53
+ <textarea
54
+ ref={textareaRef}
55
+ value={value}
56
+ onChange={(e) => onChange(e.target.value)}
57
+ onKeyDown={handleKeyDown}
58
+ placeholder="Ask anything..."
59
+ className="flex-1 bg-transparent border-0 focus:ring-0 text-gray-900 placeholder-gray-400 resize-none max-h-[200px] min-h-[24px] py-2"
60
+ disabled={isLoading}
61
+ rows={1}
62
+ />
63
+
64
+ <button
65
+ onClick={onSubmit}
66
+ disabled={isLoading || !value.trim()}
67
+ className={`p-2 rounded-lg transition-all ${value.trim() && !isLoading
68
+ ? "bg-indigo-600 text-white hover:bg-indigo-700"
69
+ : "bg-gray-100 text-gray-300 cursor-not-allowed"
70
+ }`}
71
+ >
72
+ <SendHorizontal size={20} />
73
+ </button>
74
+ </div>
75
+ {attachedFile && (
76
+ <div className="mt-2 ml-1 text-xs text-indigo-600 flex items-center gap-1 font-medium animate-fade-in">
77
+ <span>📄 Attached: {attachedFile.name} (will be analyzed)</span>
78
+ </div>
79
+ )}
80
+ <div className="text-center mt-2">
81
+ <p className="text-xs text-gray-400">
82
+ Trust-First Copilot can make mistakes. Check important info.
83
+ </p>
84
+ </div>
85
+ </div>
86
+ );
87
+ }
frontend/components/Chat/ChatLayout.tsx ADDED
@@ -0,0 +1,237 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ import { useState, useEffect } from "react";
4
+ import Sidebar from "./Sidebar";
5
+ import MessageBubble from "./MessageBubble";
6
+ import ChatInput from "./ChatInput";
7
+ import { Menu } from "lucide-react";
8
+
9
+ interface Message {
10
+ role: "user" | "assistant";
11
+ content: string;
12
+ sources?: any[];
13
+ intent?: string;
14
+ thought_process?: string;
15
+ }
16
+
17
+ interface Session {
18
+ id: string;
19
+ title: string;
20
+ date: string;
21
+ }
22
+
23
+ export default function ChatLayout() {
24
+ const [isSidebarOpen, setIsSidebarOpen] = useState(true);
25
+ const [query, setQuery] = useState("");
26
+ const [attachedFile, setAttachedFile] = useState<File | null>(null);
27
+ const [messages, setMessages] = useState<Message[]>([]);
28
+ const [isLoading, setIsLoading] = useState(false);
29
+ const [sessions, setSessions] = useState<Session[]>([]);
30
+ const [currentSessionId, setCurrentSessionId] = useState<string | null>(null);
31
+
32
+ // Load history from local storage on mount
33
+ useEffect(() => {
34
+ const saved = localStorage.getItem("chat_sessions");
35
+ if (saved) {
36
+ setSessions(JSON.parse(saved));
37
+ }
38
+ }, []);
39
+
40
+ const handleNewChat = () => {
41
+ setMessages([]);
42
+ setCurrentSessionId(null);
43
+ };
44
+
45
+ const handleSearch = async () => {
46
+ if (!query.trim()) return;
47
+
48
+ // Add User Message
49
+ const userMsg: Message = { role: "user", content: query };
50
+ setMessages((prev) => [...prev, userMsg]);
51
+ setQuery("");
52
+ setIsLoading(true);
53
+
54
+ try {
55
+ let finalQuery = userMsg.content;
56
+
57
+ // Upload File if exists
58
+ if (attachedFile) {
59
+ const formData = new FormData();
60
+ formData.append("file", attachedFile);
61
+
62
+ // Optimistic UI update: Show file as uploaded
63
+ setMessages((prev) => [...prev, { role: "assistant", content: `📂 Analyzing ${attachedFile.name}...`, isLoading: true }]);
64
+
65
+ const uploadRes = await fetch(`${process.env.NEXT_PUBLIC_API_BASE || "http://localhost:8000"}/api/v1/upload`, {
66
+ method: "POST",
67
+ body: formData,
68
+ });
69
+ const uploadData = await uploadRes.json();
70
+
71
+ // Remove the loading message logic would be complex, simplfying:
72
+ // Append file context to query
73
+ finalQuery = `[Context from uploaded file ${attachedFile.name}]:\n${uploadData.content}\n\nUser Question: ${userMsg.content}`;
74
+ setAttachedFile(null); // Clear file
75
+ }
76
+
77
+ const response = await fetch(`${process.env.NEXT_PUBLIC_API_BASE || "http://localhost:8000"}/api/v1/query`, {
78
+ method: "POST",
79
+ headers: { "Content-Type": "application/json" },
80
+ body: JSON.stringify({ query: finalQuery }),
81
+ });
82
+
83
+ const data = await response.json();
84
+
85
+ // Add Assistant Message
86
+ const aiMsg: Message = {
87
+ role: "assistant",
88
+ content: data.answer,
89
+ sources: data.sources,
90
+ intent: data.intent,
91
+ thought_process: data.thought_process,
92
+ };
93
+ setMessages((prev) => [...prev, aiMsg]);
94
+
95
+ // Save Session (Simple Logic)
96
+ if (!currentSessionId) {
97
+ const newId = Date.now().toString();
98
+ const newSession = {
99
+ id: newId,
100
+ title: userMsg.content.slice(0, 30) + "...",
101
+ date: new Date().toLocaleDateString(),
102
+ };
103
+ setSessions((prev) => [newSession, ...prev]);
104
+ setCurrentSessionId(newId);
105
+ localStorage.setItem("chat_sessions", JSON.stringify([newSession, ...sessions]));
106
+ }
107
+
108
+ } catch (error) {
109
+ console.error("Error:", error);
110
+ setMessages((prev) => [
111
+ ...prev,
112
+ { role: "assistant", content: "Sorry, something went wrong. Please try again." },
113
+ ]);
114
+ } finally {
115
+ setIsLoading(false);
116
+ }
117
+ };
118
+
119
+ const handleChallenge = async (msgIndex: number) => {
120
+ const targetMsg = messages[msgIndex];
121
+ if (!targetMsg || targetMsg.role !== "assistant") return;
122
+
123
+ // Find the preceding user message for context (simple heuristic: index - 1)
124
+ const userQuery = messages[msgIndex - 1]?.content || "Unknown context";
125
+ const sourcesText = targetMsg.sources
126
+ ? targetMsg.sources.map(s => `Title: ${s.title}\nContent: ${s.snippet}`).join("\n\n")
127
+ : "No sources.";
128
+
129
+ setIsLoading(true);
130
+
131
+ try {
132
+ const response = await fetch(`${process.env.NEXT_PUBLIC_API_BASE || "http://localhost:8000"}/api/v1/challenge`, {
133
+ method: "POST",
134
+ headers: { "Content-Type": "application/json" },
135
+ body: JSON.stringify({
136
+ original_query: userQuery,
137
+ original_answer: targetMsg.content,
138
+ sources_text: sourcesText
139
+ }),
140
+ });
141
+
142
+ const data = await response.json();
143
+
144
+ // Add Critique Message
145
+ const critiqueMsg: Message = {
146
+ role: "assistant",
147
+ content: data.answer,
148
+ sources: [],
149
+ intent: "CRITIQUE",
150
+ thought_process: "Devil's Advocate Mode: Analyzing potential flaws in the previous answer."
151
+ };
152
+ setMessages((prev) => [...prev, critiqueMsg]);
153
+
154
+ } catch (error) {
155
+ console.error("Challenge Error:", error);
156
+ } finally {
157
+ setIsLoading(false);
158
+ }
159
+ };
160
+
161
+ return (
162
+ <div className="flex h-screen bg-white text-gray-900 font-sans overflow-hidden">
163
+ {/* Sidebar */}
164
+ <Sidebar
165
+ isOpen={isSidebarOpen}
166
+ sessions={sessions}
167
+ currentSessionId={currentSessionId}
168
+ onNewChat={handleNewChat}
169
+ onSelectSession={(id) => console.log("Select session:", id)} // Placeholder for loading specific session
170
+ onDeleteSession={(id, e) => {
171
+ e.stopPropagation();
172
+ const newSessions = sessions.filter(s => s.id !== id);
173
+ setSessions(newSessions);
174
+ localStorage.setItem("chat_sessions", JSON.stringify(newSessions));
175
+ if (currentSessionId === id) handleNewChat();
176
+ }}
177
+ />
178
+
179
+ {/* Main Content */}
180
+ <div className="flex-1 flex flex-col h-full relative">
181
+ {/* Header / Mobile Toggle */}
182
+ <div className="absolute top-4 left-4 z-20">
183
+ <button
184
+ onClick={() => setIsSidebarOpen(!isSidebarOpen)}
185
+ className="p-2 text-gray-400 hover:text-gray-900 rounded-lg hover:bg-gray-100 transition-colors"
186
+ >
187
+ <Menu size={20} />
188
+ </button>
189
+ </div>
190
+
191
+ {/* Chat Area */}
192
+ <div className="flex-1 overflow-y-auto scroll-smooth">
193
+ {messages.length === 0 ? (
194
+ <div className="h-full flex flex-col items-center justify-center p-8 text-center opacity-50">
195
+ <div className="w-16 h-16 bg-gray-100 rounded-2xl flex items-center justify-center mb-6">
196
+ <span className="text-3xl">✨</span>
197
+ </div>
198
+ <h2 className="text-2xl font-semibold mb-2 text-gray-900">Trust-First Copilot</h2>
199
+ <p className="max-w-md text-gray-500">
200
+ Ask anything. I check reliable sources before answering.
201
+ </p>
202
+ </div>
203
+ ) : (
204
+ <div className="pb-32">
205
+ {messages.map((msg, idx) => (
206
+ <MessageBubble
207
+ key={idx}
208
+ role={msg.role}
209
+ content={msg.content}
210
+ sources={msg.sources}
211
+ intent={msg.intent}
212
+ thought_process={msg.thought_process}
213
+ onChallenge={msg.role === "assistant" ? () => handleChallenge(idx) : undefined}
214
+ />
215
+ ))}
216
+ {isLoading && (
217
+ <MessageBubble role="assistant" content="" isLoading={true} />
218
+ )}
219
+ </div>
220
+ )}
221
+ </div>
222
+
223
+ {/* Input Area */}
224
+ <div className="flex-shrink-0 bg-white pt-10">
225
+ <ChatInput
226
+ value={query}
227
+ onChange={setQuery}
228
+ onSubmit={handleSearch}
229
+ isLoading={isLoading}
230
+ onFileSelect={setAttachedFile}
231
+ attachedFile={attachedFile}
232
+ />
233
+ </div>
234
+ </div>
235
+ </div>
236
+ );
237
+ }
frontend/components/Chat/MessageBubble.tsx ADDED
@@ -0,0 +1,121 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ import ReactMarkdown from "react-markdown";
4
+ import { Copy, ThumbsUp, ThumbsDown, User, Bot, Loader2 } from "lucide-react";
5
+ import { SourceList } from "../ui/SourceList";
6
+ import { cleanMessageContent, linkifySources } from "@/lib/utils";
7
+
8
+ interface MessageBubbleProps {
9
+ role: "user" | "assistant";
10
+ content: string;
11
+ sources?: any[];
12
+ isLoading?: boolean;
13
+ intent?: string;
14
+ thought_process?: string;
15
+ onChallenge?: () => void;
16
+ }
17
+
18
+ export default function MessageBubble({ role, content, sources, isLoading, intent, thought_process, onChallenge }: MessageBubbleProps) {
19
+ const isUser = role === "user";
20
+
21
+ // Intent Badge Config
22
+ const getIntentBadge = (intent?: string) => {
23
+ if (!intent) return null;
24
+ if (intent === "SEARCH_REQUIRED") return <span className="text-[10px] uppercase font-bold tracking-wider px-2 py-0.5 rounded-full bg-blue-100 text-blue-700">Web Search</span>;
25
+ if (intent === "CHAT_ONLY") return <span className="text-[10px] uppercase font-bold tracking-wider px-2 py-0.5 rounded-full bg-emerald-100 text-emerald-700">Direct Chat</span>;
26
+ if (intent === "CODING_TASK") return <span className="text-[10px] uppercase font-bold tracking-wider px-2 py-0.5 rounded-full bg-purple-100 text-purple-700">Coding</span>;
27
+ return <span className="text-[10px] uppercase font-bold tracking-wider px-2 py-0.5 rounded-full bg-gray-100 text-gray-700">{intent}</span>;
28
+ };
29
+
30
+ return (
31
+ <div className={`w-full py-6 ${isUser ? "" : "bg-gray-50 dark:bg-transparent"}`}>
32
+ <div className="max-w-3xl mx-auto px-4 flex gap-6">
33
+ {/* Avatar */}
34
+ <div className="flex-shrink-0 mt-1">
35
+ {isUser ? (
36
+ <div className="w-8 h-8 rounded-full bg-gray-200 flex items-center justify-center">
37
+ <User size={18} className="text-gray-500" />
38
+ </div>
39
+ ) : (
40
+ <div className="w-8 h-8 rounded-full bg-indigo-600 flex items-center justify-center shadow-lg shadow-indigo-500/20">
41
+ <Bot size={18} className="text-white" />
42
+ </div>
43
+ )}
44
+ </div>
45
+
46
+ {/* Content */}
47
+ <div className="flex-1 overflow-hidden">
48
+ <div className="flex items-center gap-3 mb-1">
49
+ <span className="font-semibold text-sm text-gray-900">
50
+ {isUser ? "You" : "Copilot"}
51
+ </span>
52
+ {!isUser && !isLoading && getIntentBadge(intent)}
53
+ </div>
54
+
55
+ <div className="prose prose-sm max-w-none text-gray-800 leading-relaxed">
56
+ {isLoading ? (
57
+ <div className="flex items-center gap-2 text-gray-500 animate-pulse">
58
+ <Loader2 size={16} className="animate-spin" />
59
+ Thinking...
60
+ </div>
61
+ ) : (
62
+ <ReactMarkdown
63
+ components={{
64
+ a: ({ node, ...props }) => <a {...props} className="text-blue-600 hover:underline" target="_blank" rel="noopener noreferrer" />
65
+ }}
66
+ >
67
+ {isUser ? content : linkifySources(cleanMessageContent(content), sources)}
68
+ </ReactMarkdown>
69
+ )}
70
+ </div>
71
+
72
+ {/* Sources for Assistant */}
73
+ {!isUser && sources && sources.length > 0 && (
74
+ <div className="mt-4 pt-4 border-t border-gray-100">
75
+ <SourceList sources={sources} />
76
+ </div>
77
+ )}
78
+
79
+ {/* Reasoning for Assistant (AI-OS Feature) */}
80
+ {!isUser && thought_process && (
81
+ <div className="mt-4">
82
+ <details className="group">
83
+ <summary className="flex items-center gap-2 text-xs font-semibold text-gray-400 cursor-pointer hover:text-gray-600 transition-colors select-none">
84
+ <span>🧠 View Thought Process</span>
85
+ </summary>
86
+ <div className="mt-2 text-xs text-gray-600 font-mono bg-gray-100 p-3 rounded-lg leading-relaxed whitespace-pre-wrap">
87
+ {thought_process}
88
+ </div>
89
+ </details>
90
+ </div>
91
+ )}
92
+
93
+ {/* Actions for Assistant */}
94
+ {!isUser && !isLoading && (
95
+ <div className="flex items-center gap-2 mt-4 text-gray-400">
96
+ <button className="p-1 hover:text-gray-600 transition-colors" title="Copy">
97
+ <Copy size={14} />
98
+ </button>
99
+ <button className="p-1 hover:text-gray-600 transition-colors" title="Good response">
100
+ <ThumbsUp size={14} />
101
+ </button>
102
+ <button className="p-1 hover:text-gray-600 transition-colors" title="Bad response">
103
+ <ThumbsDown size={14} />
104
+ </button>
105
+ {/* "Disagree with Me" Button */}
106
+ {onChallenge && (
107
+ <button
108
+ onClick={onChallenge}
109
+ className="ml-2 flex items-center gap-1 text-xs font-medium text-amber-600 hover:text-amber-700 bg-amber-50 px-2 py-1 rounded border border-amber-200 transition-colors"
110
+ title="Critique this answer"
111
+ >
112
+ <span>⚖️ Challenge</span>
113
+ </button>
114
+ )}
115
+ </div>
116
+ )}
117
+ </div>
118
+ </div>
119
+ </div>
120
+ );
121
+ }
frontend/components/Chat/Sidebar.tsx ADDED
@@ -0,0 +1,90 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ import { MessageSquare, Plus, Trash2 } from "lucide-react";
4
+ import { motion } from "framer-motion";
5
+
6
+ interface SidebarProps {
7
+ isOpen: boolean;
8
+ sessions: { id: string; title: string; date: string }[];
9
+ currentSessionId: string | null;
10
+ onNewChat: () => void;
11
+ onSelectSession: (id: string) => void;
12
+ onDeleteSession: (id: string, e: React.MouseEvent) => void;
13
+ }
14
+
15
+ export default function Sidebar({
16
+ isOpen,
17
+ sessions,
18
+ currentSessionId,
19
+ onNewChat,
20
+ onSelectSession,
21
+ onDeleteSession,
22
+ }: SidebarProps) {
23
+ return (
24
+ <motion.div
25
+ initial={{ width: 0, opacity: 0 }}
26
+ animate={{ width: isOpen ? 260 : 0, opacity: isOpen ? 1 : 0 }}
27
+ className="h-screen bg-gray-50 border-r border-gray-200 flex flex-col flex-shrink-0 overflow-hidden"
28
+ >
29
+ {/* New Chat Button */}
30
+ <div className="p-4">
31
+ <button
32
+ onClick={onNewChat}
33
+ className="w-full flex items-center gap-2 px-4 py-3 bg-white hover:bg-gray-100 text-gray-900 rounded-lg transition-colors border border-gray-200 text-sm font-medium shadow-sm"
34
+ >
35
+ <Plus size={16} />
36
+ New Chat
37
+ </button>
38
+ </div>
39
+
40
+ {/* Session List */}
41
+ <div className="flex-1 overflow-y-auto px-2 pb-4">
42
+ <div className="text-xs font-semibold text-gray-500 mb-2 px-2 mt-2">
43
+ Recent
44
+ </div>
45
+ <div className="space-y-1">
46
+ {sessions.map((session) => (
47
+ <button
48
+ key={session.id}
49
+ onClick={() => onSelectSession(session.id)}
50
+ className={`group w-full flex items-center justify-between gap-2 px-3 py-3 rounded-lg text-sm text-left transition-colors ${currentSessionId === session.id
51
+ ? "bg-white text-gray-900 shadow-sm border border-gray-100"
52
+ : "text-gray-600 hover:bg-gray-100"
53
+ }`}
54
+ >
55
+ <div className="flex items-center gap-2 overflow-hidden">
56
+ <MessageSquare size={14} className="text-gray-400 flex-shrink-0" />
57
+ <span className="truncate">{session.title}</span>
58
+ </div>
59
+
60
+ <div
61
+ onClick={(e) => onDeleteSession(session.id, e)}
62
+ className={`text-gray-400 hover:text-red-500 opacity-0 group-hover:opacity-100 transition-opacity p-1`}
63
+ >
64
+ <Trash2 size={14} />
65
+ </div>
66
+ </button>
67
+ ))}
68
+ {sessions.length === 0 && (
69
+ <div className="px-4 py-4 text-xs text-gray-400 text-center italic">
70
+ No recent chats
71
+ </div>
72
+ )}
73
+ </div>
74
+ </div>
75
+
76
+ {/* User Profile / Footer (Optional) */}
77
+ <div className="p-4 border-t border-gray-200">
78
+ <div className="flex items-center gap-3 px-2 py-2 text-gray-700 text-sm">
79
+ <div className="w-8 h-8 rounded-full bg-indigo-600 flex items-center justify-center text-white font-bold">
80
+ U
81
+ </div>
82
+ <div className="flex-col flex">
83
+ <span className="font-medium text-gray-900">User</span>
84
+ <span className="text-xs text-gray-500">Pro Plan</span>
85
+ </div>
86
+ </div>
87
+ </div>
88
+ </motion.div>
89
+ );
90
+ }
frontend/components/ui/SearchBar.tsx ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ import { Search, ArrowRight } from "lucide-react";
4
+ import { useState } from "react";
5
+ import { cn } from "@/lib/utils";
6
+
7
+ interface SearchBarProps {
8
+ onSearch: (query: string) => void;
9
+ isLoading: boolean;
10
+ centered?: boolean;
11
+ }
12
+
13
+ export function SearchBar({ onSearch, isLoading, centered = false }: SearchBarProps) {
14
+ const [query, setQuery] = useState("");
15
+
16
+ const handleSubmit = (e: React.FormEvent) => {
17
+ e.preventDefault();
18
+ if (query.trim()) {
19
+ onSearch(query);
20
+ }
21
+ };
22
+
23
+ return (
24
+ <div className={cn("w-full transition-all duration-500", centered ? "max-w-2xl" : "max-w-4xl")}>
25
+ <form onSubmit={handleSubmit} className="relative group">
26
+ <div className="absolute inset-y-0 left-4 flex items-center pointer-events-none text-muted-foreground">
27
+ <Search className="w-5 h-5" />
28
+ </div>
29
+ <input
30
+ type="text"
31
+ value={query}
32
+ onChange={(e) => setQuery(e.target.value)}
33
+ placeholder="Ask anything..."
34
+ className="w-full py-4 pl-12 pr-12 text-lg bg-secondary/50 border-none rounded-2xl ring-1 ring-black/5 focus:ring-2 focus:ring-primary/20 focus:bg-background transition-all shadow-sm group-hover:shadow-md"
35
+ disabled={isLoading}
36
+ />
37
+ <button
38
+ type="submit"
39
+ disabled={!query.trim() || isLoading}
40
+ className="absolute inset-y-2 right-2 flex items-center justify-center p-2 rounded-xl bg-primary text-primary-foreground disabled:opacity-50 disabled:cursor-not-allowed hover:opacity-90 transition-opacity"
41
+ >
42
+ {isLoading ? (
43
+ <div className="w-5 h-5 border-2 border-current border-t-transparent rounded-full animate-spin" />
44
+ ) : (
45
+ <ArrowRight className="w-5 h-5" />
46
+ )}
47
+ </button>
48
+ </form>
49
+ {centered && (
50
+ <div className="mt-8 flex gap-2 justify-center text-sm text-muted-foreground">
51
+ <span className="px-3 py-1 bg-secondary rounded-full cursor-pointer hover:bg-secondary/80">Market Trends</span>
52
+ <span className="px-3 py-1 bg-secondary rounded-full cursor-pointer hover:bg-secondary/80">Code Debugging</span>
53
+ <span className="px-3 py-1 bg-secondary rounded-full cursor-pointer hover:bg-secondary/80">Legal Research</span>
54
+ </div>
55
+ )}
56
+ </div>
57
+ );
58
+ }
frontend/components/ui/SourceList.tsx ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { ExternalLink, FileText } from "lucide-react";
2
+
3
+ interface Source {
4
+ title: string;
5
+ url: string;
6
+ snippet: string;
7
+ }
8
+
9
+ interface SourceListProps {
10
+ sources: Source[];
11
+ }
12
+
13
+ export function SourceList({ sources }: SourceListProps) {
14
+ if (!sources || sources.length === 0) return null;
15
+
16
+ return (
17
+ <div className="mb-8">
18
+ <h3 className="text-sm font-semibold text-muted-foreground mb-3 flex items-center gap-2">
19
+ <FileText className="w-4 h-4" /> Sources
20
+ </h3>
21
+ <div className="flex gap-3 overflow-x-auto pb-4 scrollbar-hide">
22
+ {sources.map((source, idx) => (
23
+ <a
24
+ key={idx}
25
+ href={source.url}
26
+ target="_blank"
27
+ rel="noopener noreferrer"
28
+ className="flex-shrink-0 w-64 p-3 bg-card border rounded-xl hover:bg-secondary/50 transition-colors group cursor-pointer"
29
+ >
30
+ <div className="text-xs text-muted-foreground mb-1 truncate">{new URL(source.url).hostname}</div>
31
+ <div className="font-medium text-sm line-clamp-2 mb-2 group-hover:text-primary transition-colors">
32
+ {source.title}
33
+ </div>
34
+ <div className="text-xs text-muted-foreground line-clamp-2">
35
+ {source.snippet}
36
+ </div>
37
+ </a>
38
+ ))}
39
+ </div>
40
+ </div>
41
+ );
42
+ }
frontend/env_example.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ NEXT_PUBLIC_API_BASE=https://YOUR-BACKEND-URL.onrender.com
frontend/eslint.config.mjs ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { defineConfig, globalIgnores } from "eslint/config";
2
+ import nextVitals from "eslint-config-next/core-web-vitals";
3
+ import nextTs from "eslint-config-next/typescript";
4
+
5
+ const eslintConfig = defineConfig([
6
+ ...nextVitals,
7
+ ...nextTs,
8
+ // Override default ignores of eslint-config-next.
9
+ globalIgnores([
10
+ // Default ignores of eslint-config-next:
11
+ ".next/**",
12
+ "out/**",
13
+ "build/**",
14
+ "next-env.d.ts",
15
+ ]),
16
+ ]);
17
+
18
+ export default eslintConfig;
frontend/lib/utils.ts ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { clsx, type ClassValue } from "clsx";
2
+ import { twMerge } from "tailwind-merge";
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs));
6
+ }
7
+
8
+ export function cleanMessageContent(content: string): string {
9
+ if (!content) return "";
10
+
11
+ // Remove "Assumptions & Risks" section and everything until the next header or end
12
+ let cleaned = content.replace(/### ⚠️ Assumptions & Risks[\s\S]*?(?=### |$)/g, "");
13
+
14
+ // Remove "Unknowns / Limitations" section and everything until the next header or end
15
+ cleaned = cleaned.replace(/### ❌ Unknowns \/ Limitations[\s\S]*?(?=### |$)/g, "");
16
+
17
+ return cleaned.trim();
18
+ }
19
+
20
+ export function linkifySources(content: string, sources: any[] = []): string {
21
+ if (!content || !sources.length) return content;
22
+
23
+ let linkedContent = content;
24
+
25
+ sources.forEach((source, index) => {
26
+ const sourceNum = index + 1;
27
+ const url = source.url || "#";
28
+ // Regex to find "Source X" or "(Source X)" case insensitive
29
+ // We use a complex regex to avoid double linking if run multiple times or overlapping
30
+ // Simplified: Look for "Source X" that isn't already inside a markdown link
31
+ const regex = new RegExp(`(?<!\\[)Source ${sourceNum}(?!\\])`, 'gi');
32
+
33
+ linkedContent = linkedContent.replace(regex, `[Source ${sourceNum}](${url})`);
34
+ });
35
+
36
+ return linkedContent;
37
+ }
frontend/next.config.ts ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ import type { NextConfig } from "next";
2
+
3
+ const nextConfig: NextConfig = {
4
+ /* config options here */
5
+ };
6
+
7
+ export default nextConfig;
frontend/package-lock.json ADDED
The diff for this file is too large to render. See raw diff
 
frontend/package.json ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "frontend",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "scripts": {
6
+ "dev": "next dev",
7
+ "build": "next build",
8
+ "start": "next start",
9
+ "lint": "eslint"
10
+ },
11
+ "dependencies": {
12
+ "clsx": "^2.1.1",
13
+ "framer-motion": "^12.26.2",
14
+ "lucide-react": "^0.562.0",
15
+ "next": "16.1.1",
16
+ "react": "19.2.3",
17
+ "react-dom": "19.2.3",
18
+ "react-markdown": "^10.1.0",
19
+ "tailwind-merge": "^3.4.0",
20
+ "tailwindcss-animate": "^1.0.7"
21
+ },
22
+ "devDependencies": {
23
+ "@tailwindcss/postcss": "^4",
24
+ "@types/node": "^20",
25
+ "@types/react": "^19",
26
+ "@types/react-dom": "^19",
27
+ "eslint": "^9",
28
+ "eslint-config-next": "16.1.1",
29
+ "tailwindcss": "^4",
30
+ "typescript": "^5"
31
+ }
32
+ }
frontend/postcss.config.mjs ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ const config = {
2
+ plugins: {
3
+ "@tailwindcss/postcss": {},
4
+ },
5
+ };
6
+
7
+ export default config;
frontend/public/file.svg ADDED
frontend/public/globe.svg ADDED