KURUPRASATH-J commited on
Commit
a178433
·
verified ·
1 Parent(s): 935be70

Upload 12 files

Browse files
Files changed (13) hide show
  1. .gitattributes +3 -0
  2. Dockerfile +30 -0
  3. app.py +748 -0
  4. avatar.jpg +0 -0
  5. favicon.jpg +3 -0
  6. index.html +203 -0
  7. juno avatar.jpg +3 -0
  8. juno logo.jpg +0 -0
  9. juno.jpg +3 -0
  10. prompts.py +689 -0
  11. requirements.txt +17 -0
  12. script.js +858 -0
  13. styles.css +1179 -0
.gitattributes CHANGED
@@ -33,3 +33,6 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ favicon.jpg filter=lfs diff=lfs merge=lfs -text
37
+ juno[[:space:]]avatar.jpg filter=lfs diff=lfs merge=lfs -text
38
+ juno.jpg filter=lfs diff=lfs merge=lfs -text
Dockerfile ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.9-slim
2
+
3
+ WORKDIR /code
4
+
5
+ # Install system dependencies including tesseract for OCR
6
+ RUN apt-get update && apt-get install -y \
7
+ tesseract-ocr \
8
+ tesseract-ocr-eng \
9
+ poppler-utils \
10
+ git \
11
+ && rm -rf /var/lib/apt/lists/*
12
+
13
+ # Copy requirements and install Python dependencies
14
+ COPY ./requirements.txt /code/requirements.txt
15
+ RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
16
+
17
+ # Copy application files
18
+ COPY . /code
19
+
20
+ # Create necessary directories
21
+ RUN mkdir -p /code/static /code/templates
22
+
23
+ # Expose port
24
+ EXPOSE 7860
25
+
26
+ # Set environment variable for Hugging Face Spaces
27
+ ENV PYTHONPATH=/code
28
+
29
+ # Command to run the application
30
+ CMD ["python", "app.py"]
app.py ADDED
@@ -0,0 +1,748 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ import uuid
4
+ import time
5
+ import random
6
+ from datetime import datetime, timedelta
7
+ from flask import Flask, request, jsonify, send_from_directory
8
+ from flask_cors import CORS
9
+ from dotenv import load_dotenv
10
+ import google.generativeai as genai
11
+ from google.api_core.exceptions import ResourceExhausted, GoogleAPIError
12
+ from langchain.text_splitter import RecursiveCharacterTextSplitter
13
+ from langchain.vectorstores import Chroma
14
+ from langchain.embeddings import HuggingFaceEmbeddings
15
+ from langchain.schema import Document
16
+ import PyPDF2
17
+ import io
18
+ import base64
19
+ from typing import List, Dict, Any
20
+ import requests
21
+ from bs4 import BeautifulSoup
22
+ import re
23
+ import pytesseract
24
+ from PIL import Image
25
+
26
+ # Import Juno AI Prompts System
27
+ from prompts import juno_prompts, get_main_conversation_prompt, get_document_summary_prompt, get_rag_prompt, get_streaming_prompt, get_fallback_responses
28
+
29
+ # Load environment variables
30
+ load_dotenv()
31
+
32
+ app = Flask(__name__)
33
+ CORS(app)
34
+
35
+ # Configure Gemini
36
+ genai.configure(api_key=os.getenv('GEMINI_API_KEY'))
37
+
38
+ class ChatbotWithMemoryAndRAG:
39
+ def __init__(self):
40
+ self.embeddings = HuggingFaceEmbeddings(
41
+ model_name="sentence-transformers/all-MiniLM-L6-v2"
42
+ )
43
+
44
+ self.text_splitter = RecursiveCharacterTextSplitter(
45
+ chunk_size=1000,
46
+ chunk_overlap=200,
47
+ length_function=len
48
+ )
49
+
50
+ self.vectorstore = None
51
+ self.chat_history = []
52
+ self.memory = {}
53
+ self.session_id = str(uuid.uuid4())
54
+ self.last_rate_limit = None
55
+ self.consecutive_rate_limits = 0
56
+
57
+ # Initialize Juno AI Prompts System
58
+ self.prompts = juno_prompts
59
+
60
+ print(f"🤖 Juno AI initialized with session ID: {self.session_id}")
61
+
62
+ def _retry_with_backoff(self, func, max_retries=5, base_delay=2):
63
+ """Improved retry function with progressive backoff for rate limit handling"""
64
+
65
+ # If we recently hit rate limits, wait longer before trying
66
+ if self.last_rate_limit and datetime.now() - self.last_rate_limit < timedelta(seconds=30):
67
+ additional_wait = min(self.consecutive_rate_limits * 5, 30) # Up to 30 seconds
68
+ print(f"Recent rate limits detected, waiting additional {additional_wait}s")
69
+ time.sleep(additional_wait)
70
+
71
+ for attempt in range(max_retries):
72
+ try:
73
+ result = func()
74
+ # Reset rate limit tracking on success
75
+ self.consecutive_rate_limits = 0
76
+ self.last_rate_limit = None
77
+ return result
78
+
79
+ except ResourceExhausted as e:
80
+ self.last_rate_limit = datetime.now()
81
+ self.consecutive_rate_limits += 1
82
+
83
+ if attempt == max_retries - 1:
84
+ print(f"Max retries ({max_retries}) exceeded for rate limit")
85
+ raise e
86
+
87
+ # Progressive backoff with jitter: 2s, 6s, 14s, 30s, 62s
88
+ delay = base_delay * (2 ** attempt) + random.uniform(1, 3) # Add jitter
89
+ delay = min(delay, 60) # Cap at 60 seconds
90
+
91
+ print(f"Rate limit hit (attempt {attempt + 1}/{max_retries}), waiting {delay:.1f}s...")
92
+ time.sleep(delay)
93
+
94
+ except GoogleAPIError as e:
95
+ print(f"Google API Error: {e}")
96
+ if "quota" in str(e).lower() or "rate" in str(e).lower():
97
+ # Treat as rate limit
98
+ self.last_rate_limit = datetime.now()
99
+ self.consecutive_rate_limits += 1
100
+
101
+ if attempt == max_retries - 1:
102
+ raise ResourceExhausted("API quota exceeded")
103
+
104
+ delay = base_delay * (2 ** attempt) + random.uniform(1, 3)
105
+ delay = min(delay, 60)
106
+ print(f"API quota issue, waiting {delay:.1f}s...")
107
+ time.sleep(delay)
108
+ else:
109
+ raise e
110
+
111
+ except Exception as e:
112
+ # For non-rate-limit errors, don't retry
113
+ print(f"Non-retryable error: {e}")
114
+ raise e
115
+
116
+ def _fallback_response(self, user_message):
117
+ """Generate a fallback response when API is unavailable using Juno AI prompts"""
118
+
119
+ # Use Juno AI fallback response templates
120
+ fallback_templates = get_fallback_responses()
121
+
122
+ # Select a template and personalize it
123
+ template = random.choice(fallback_templates)
124
+ response = template.format(
125
+ user_message_preview=user_message[:50]
126
+ )
127
+
128
+ # Add to chat history so conversation continues
129
+ self.chat_history.append({
130
+ "user": user_message,
131
+ "bot": response,
132
+ "timestamp": datetime.now().isoformat(),
133
+ "fallback": True
134
+ })
135
+
136
+ return response
137
+
138
+ def extract_text_from_pdf(self, pdf_content):
139
+ """Extract text content from PDF bytes with OCR fallback"""
140
+ try:
141
+ pdf_reader = PyPDF2.PdfReader(io.BytesIO(pdf_content))
142
+ text = ""
143
+
144
+ for page in pdf_reader.pages:
145
+ page_text = page.extract_text()
146
+ # Check if extracted text is substantial
147
+ if page_text and len(page_text.strip()) > 10: # Heuristic to check for actual content
148
+ text += page_text + "\n"
149
+ else:
150
+ # Attempt OCR if text extraction is poor
151
+ try:
152
+ # Iterate through images on the page for OCR
153
+ for image_file_object in page.images:
154
+ img = Image.open(io.BytesIO(image_file_object.data))
155
+ ocr_text = pytesseract.image_to_string(img)
156
+ if ocr_text:
157
+ text += ocr_text + "\n"
158
+ except Exception as ocr_error:
159
+ # OCR can fail if no images, etc. Silently pass.
160
+ print(f"OCR fallback failed for a page: {ocr_error}")
161
+ pass
162
+
163
+ return text
164
+ except Exception as e:
165
+ return f"Error extracting PDF: {str(e)}"
166
+
167
+ def process_document(self, text_content, filename="document"):
168
+ """Process document text and create vector store"""
169
+ try:
170
+ # Split text into chunks
171
+ chunks = self.text_splitter.split_text(text_content)
172
+
173
+ # Create documents
174
+ documents = [
175
+ Document(
176
+ page_content=chunk,
177
+ metadata={"source": filename, "chunk_id": i}
178
+ )
179
+ for i, chunk in enumerate(chunks)
180
+ ]
181
+
182
+ # Create or update vector store
183
+ if self.vectorstore is None:
184
+ self.vectorstore = Chroma.from_documents(
185
+ documents=documents,
186
+ embedding=self.embeddings,
187
+ collection_name=f"collection_{self.session_id}"
188
+ )
189
+ else:
190
+ self.vectorstore.add_documents(documents)
191
+
192
+ return f"Successfully processed {len(chunks)} chunks from {filename}"
193
+ except Exception as e:
194
+ return f"Error processing document: {str(e)}"
195
+
196
+ def retrieve_relevant_context(self, query, k=3):
197
+ """Retrieve relevant context from vector store"""
198
+ if self.vectorstore is None:
199
+ return ""
200
+
201
+ try:
202
+ docs = self.vectorstore.similarity_search(query, k=k)
203
+ context = "\n".join([doc.page_content for doc in docs])
204
+ return context
205
+ except Exception as e:
206
+ return ""
207
+
208
+ def summarize_text(self, text, max_length=500):
209
+ """Summarize long text using Juno AI prompts with improved rate limit handling"""
210
+ def _summarize():
211
+ model = genai.GenerativeModel('gemini-1.5-flash')
212
+
213
+ # Use Juno AI document summarization prompt
214
+ prompt = self.prompts.get_document_summarization_prompt(text, max_length)
215
+
216
+ response = model.generate_content(prompt)
217
+ return response.text
218
+
219
+ try:
220
+ return self._retry_with_backoff(_summarize)
221
+ except (ResourceExhausted, GoogleAPIError):
222
+ return f"📄 Document uploaded successfully ({len(text)} characters). \n\n✨ **Juno AI Note:** Summary temporarily unavailable due to high API usage, but the document content is fully searchable and ready for your questions!"
223
+ except Exception as e:
224
+ return f"Error summarizing text: {str(e)}"
225
+
226
+ def generate_response(self, user_message, context=""):
227
+ """Generate response using Juno AI prompts with improved rate limit handling"""
228
+ def _generate():
229
+ model = genai.GenerativeModel('gemini-1.5-flash')
230
+
231
+ # Build conversation context for Juno AI
232
+ conversation_history = []
233
+ if self.chat_history:
234
+ recent_history = self.chat_history[-3:] # Last 3 exchanges
235
+ for exchange in recent_history:
236
+ if not exchange.get('fallback', False): # Skip fallback responses
237
+ conversation_history.append({
238
+ 'user': exchange['user'],
239
+ 'bot': exchange['bot'],
240
+ 'timestamp': exchange.get('timestamp', '')
241
+ })
242
+
243
+ # Use Juno AI conversation prompt with full context
244
+ prompt = self.prompts.get_conversation_prompt(
245
+ user_message=user_message,
246
+ context=context,
247
+ conversation_history=conversation_history,
248
+ memory_context=self.memory
249
+ )
250
+
251
+ response = model.generate_content(prompt)
252
+ return response.text
253
+
254
+ try:
255
+ bot_response = self._retry_with_backoff(_generate)
256
+
257
+ # Update chat history
258
+ self.chat_history.append({
259
+ "user": user_message,
260
+ "bot": bot_response,
261
+ "timestamp": datetime.now().isoformat()
262
+ })
263
+
264
+ # Update memory with important information
265
+ self.update_memory(user_message, bot_response)
266
+
267
+ return bot_response
268
+
269
+ except (ResourceExhausted, GoogleAPIError):
270
+ return self._fallback_response(user_message)
271
+ except Exception as e:
272
+ return f"Error generating response: {str(e)}"
273
+
274
+ def update_memory(self, user_message, bot_response):
275
+ """Update session memory with important information"""
276
+ # Simple memory update - in production, you'd want more sophisticated extraction
277
+ current_time = datetime.now().isoformat()
278
+
279
+ if "memory" not in self.memory:
280
+ self.memory["memory"] = []
281
+
282
+ self.memory["memory"].append({
283
+ "user": user_message,
284
+ "bot": bot_response,
285
+ "timestamp": current_time
286
+ })
287
+
288
+ # Keep only last 10 interactions in memory
289
+ if len(self.memory["memory"]) > 10:
290
+ self.memory["memory"] = self.memory["memory"][-10:]
291
+
292
+ def scrape_web_content(self, url):
293
+ """Scrape content from a web URL"""
294
+ try:
295
+ headers = {
296
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
297
+ }
298
+
299
+ response = requests.get(url, headers=headers, timeout=10)
300
+ response.raise_for_status()
301
+
302
+ soup = BeautifulSoup(response.content, 'html.parser')
303
+
304
+ # Remove script and style elements
305
+ for script in soup(["script", "style"]):
306
+ script.decompose()
307
+
308
+ # Get text content
309
+ text = soup.get_text()
310
+
311
+ # Clean up text
312
+ lines = (line.strip() for line in text.splitlines())
313
+ chunks = (phrase.strip() for line in lines for phrase in line.split(" "))
314
+ text = ' '.join(chunk for chunk in chunks if chunk)
315
+
316
+ return text[:10000] # Limit to 10000 characters
317
+ except Exception as e:
318
+ return f"Error scraping URL: {str(e)}"
319
+
320
+ def analyze_web_content(self, url, content):
321
+ """Analyze scraped web content using Juno AI prompts"""
322
+ def _analyze():
323
+ model = genai.GenerativeModel('gemini-1.5-flash')
324
+
325
+ # Use Juno AI web content analysis prompt
326
+ prompt = self.prompts.get_web_content_analysis_prompt(url, content)
327
+
328
+ response = model.generate_content(prompt)
329
+ return response.text
330
+
331
+ try:
332
+ return self._retry_with_backoff(_analyze)
333
+ except (ResourceExhausted, GoogleAPIError):
334
+ return f"🌐 **Web Content Scraped Successfully**\n\n**URL:** {url}\n**Content Length:** {len(content)} characters\n\n**Juno AI Note:** Analysis temporarily unavailable due to high API usage, but the content has been processed and is ready for your questions!"
335
+ except Exception as e:
336
+ return f"Error analyzing web content: {str(e)}"
337
+
338
+ def generate_rag_response(self, user_query, context, sources=None):
339
+ """Generate RAG response using Juno AI prompts"""
340
+ def _generate_rag():
341
+ model = genai.GenerativeModel('gemini-1.5-flash')
342
+
343
+ # Split context into chunks for better handling
344
+ context_chunks = [context[i:i+2000] for i in range(0, len(context), 2000)]
345
+
346
+ # Use Juno AI RAG prompt
347
+ prompt = self.prompts.get_rag_response_prompt(
348
+ user_query=user_query,
349
+ retrieved_chunks=context_chunks[:3], # Top 3 chunks
350
+ source_info=sources
351
+ )
352
+
353
+ response = model.generate_content(prompt)
354
+ return response.text
355
+
356
+ try:
357
+ return self._retry_with_backoff(_generate_rag)
358
+ except (ResourceExhausted, GoogleAPIError):
359
+ return self._fallback_response(user_query)
360
+ except Exception as e:
361
+ return f"Error generating RAG response: {str(e)}"
362
+
363
+ def save_conversation(self, conversation_id, title=""):
364
+ """Save current conversation to memory"""
365
+ if not title:
366
+ title = f"Chat {datetime.now().strftime('%Y-%m-%d %H:%M')}"
367
+
368
+ conversation_data = {
369
+ "id": conversation_id,
370
+ "title": title,
371
+ "messages": self.chat_history,
372
+ "created_at": datetime.now().isoformat(),
373
+ "last_updated": datetime.now().isoformat()
374
+ }
375
+
376
+ if "conversations" not in self.memory:
377
+ self.memory["conversations"] = {}
378
+
379
+ self.memory["conversations"][conversation_id] = conversation_data
380
+ return conversation_data
381
+
382
+ def load_conversation(self, conversation_id):
383
+ """Load a specific conversation"""
384
+ if "conversations" in self.memory and conversation_id in self.memory["conversations"]:
385
+ conversation = self.memory["conversations"][conversation_id]
386
+ self.chat_history = conversation["messages"]
387
+ return conversation
388
+ return None
389
+
390
+ def delete_conversation(self, conversation_id):
391
+ """Delete a specific conversation"""
392
+ if "conversations" in self.memory and conversation_id in self.memory["conversations"]:
393
+ del self.memory["conversations"][conversation_id]
394
+ return True
395
+ return False
396
+
397
+ def rename_conversation(self, conversation_id, new_title):
398
+ """Rename a conversation"""
399
+ if "conversations" in self.memory and conversation_id in self.memory["conversations"]:
400
+ self.memory["conversations"][conversation_id]["title"] = new_title
401
+ self.memory["conversations"][conversation_id]["last_updated"] = datetime.now().isoformat()
402
+ return True
403
+ return False
404
+
405
+ def generate_streaming_response(self, user_message, context=""):
406
+ """Generate streaming response using Juno AI prompts with improved rate limit handling"""
407
+ def _generate_stream():
408
+ model = genai.GenerativeModel('gemini-1.5-flash')
409
+
410
+ # Use Juno AI streaming prompt (optimized for speed)
411
+ prompt = self.prompts.get_streaming_response_prompt(user_message, context)
412
+
413
+ response = model.generate_content(prompt, stream=True)
414
+ return response
415
+
416
+ try:
417
+ return self._retry_with_backoff(_generate_stream, max_retries=3, base_delay=1)
418
+ except (ResourceExhausted, GoogleAPIError):
419
+ return None
420
+ except Exception as e:
421
+ return None
422
+
423
+ # Initialize Juno AI chatbot
424
+ chatbot = ChatbotWithMemoryAndRAG()
425
+
426
+ @app.route('/')
427
+ def serve_frontend():
428
+ return send_from_directory('.', 'index.html')
429
+
430
+ @app.route('/<path:filename>')
431
+ def serve_static(filename):
432
+ return send_from_directory('.', filename)
433
+
434
+ @app.route('/api/chat', methods=['POST'])
435
+ def chat():
436
+ try:
437
+ data = request.json
438
+ user_message = data.get('message', '')
439
+
440
+ if not user_message:
441
+ return jsonify({'error': 'No message provided'}), 400
442
+
443
+ # Retrieve relevant context
444
+ context = chatbot.retrieve_relevant_context(user_message)
445
+
446
+ # Generate response using Juno AI prompts
447
+ if context:
448
+ # Use RAG response for document-based queries
449
+ bot_response = chatbot.generate_rag_response(user_message, context)
450
+ else:
451
+ # Use regular conversation response
452
+ bot_response = chatbot.generate_response(user_message, context)
453
+
454
+ return jsonify({
455
+ 'response': bot_response,
456
+ 'has_context': bool(context),
457
+ 'session_id': chatbot.session_id
458
+ })
459
+ except Exception as e:
460
+ return jsonify({'error': str(e)}), 500
461
+
462
+ @app.route('/api/upload', methods=['POST'])
463
+ def upload_document():
464
+ try:
465
+ if 'file' not in request.files:
466
+ return jsonify({'error': 'No file provided'}), 400
467
+
468
+ file = request.files['file']
469
+ if file.filename == '':
470
+ return jsonify({'error': 'No file selected'}), 400
471
+
472
+ if file and file.filename.lower().endswith('.pdf'):
473
+ # Read PDF content
474
+ pdf_content = file.read()
475
+
476
+ # Extract text
477
+ text_content = chatbot.extract_text_from_pdf(pdf_content)
478
+
479
+ if text_content.startswith("Error"):
480
+ return jsonify({'error': text_content}), 400
481
+
482
+ # Process document
483
+ result = chatbot.process_document(text_content, file.filename)
484
+
485
+ # Generate Juno AI summary
486
+ summary = chatbot.summarize_text(text_content)
487
+
488
+ return jsonify({
489
+ 'message': result,
490
+ 'summary': summary,
491
+ 'filename': file.filename,
492
+ 'text_length': len(text_content)
493
+ })
494
+ else:
495
+ return jsonify({'error': 'Only PDF files are supported'}), 400
496
+ except Exception as e:
497
+ return jsonify({'error': str(e)}), 500
498
+
499
+ @app.route('/api/summarize', methods=['POST'])
500
+ def summarize_document():
501
+ try:
502
+ data = request.json
503
+ text = data.get('text', '')
504
+ max_length = data.get('max_length', 500)
505
+
506
+ if not text:
507
+ return jsonify({'error': 'No text provided'}), 400
508
+
509
+ # Use Juno AI summarization
510
+ summary = chatbot.summarize_text(text, max_length)
511
+
512
+ return jsonify({
513
+ 'summary': summary,
514
+ 'original_length': len(text),
515
+ 'summary_length': len(summary)
516
+ })
517
+ except Exception as e:
518
+ return jsonify({'error': str(e)}), 500
519
+
520
+ @app.route('/api/memory', methods=['GET'])
521
+ def get_memory():
522
+ return jsonify({
523
+ 'memory': chatbot.memory,
524
+ 'chat_history_length': len(chatbot.chat_history),
525
+ 'has_vectorstore': chatbot.vectorstore is not None,
526
+ 'session_id': chatbot.session_id
527
+ })
528
+
529
+ @app.route('/api/clear', methods=['POST'])
530
+ def clear_session():
531
+ global chatbot
532
+ chatbot = ChatbotWithMemoryAndRAG()
533
+ return jsonify({'message': 'Juno AI session cleared successfully'})
534
+
535
+ @app.route('/api/scrape', methods=['POST'])
536
+ def scrape_url():
537
+ try:
538
+ data = request.json
539
+ url = data.get('url', '')
540
+
541
+ if not url:
542
+ return jsonify({'error': 'No URL provided'}), 400
543
+
544
+ # Validate URL format
545
+ if not re.match(r'^https?://', url):
546
+ url = 'https://' + url
547
+
548
+ content = chatbot.scrape_web_content(url)
549
+
550
+ if content.startswith("Error"):
551
+ return jsonify({'error': content}), 400
552
+
553
+ # Process the scraped content
554
+ result = chatbot.process_document(content, f"Web: {url}")
555
+
556
+ # Use Juno AI web content analysis
557
+ analysis = chatbot.analyze_web_content(url, content)
558
+
559
+ return jsonify({
560
+ 'message': result,
561
+ 'summary': analysis,
562
+ 'url': url,
563
+ 'content_length': len(content)
564
+ })
565
+ except Exception as e:
566
+ return jsonify({'error': str(e)}), 500
567
+
568
+ @app.route('/api/chat/stream', methods=['POST'])
569
+ def chat_stream():
570
+ try:
571
+ data = request.json
572
+ user_message = data.get('message', '')
573
+
574
+ if not user_message:
575
+ return jsonify({'error': 'No message provided'}), 400
576
+
577
+ # Retrieve relevant context
578
+ context = chatbot.retrieve_relevant_context(user_message)
579
+
580
+ # Generate streaming response using Juno AI prompts
581
+ streaming_response = chatbot.generate_streaming_response(user_message, context)
582
+
583
+ if streaming_response is None:
584
+ # Fallback to regular response if streaming fails
585
+ if context:
586
+ bot_response = chatbot.generate_rag_response(user_message, context)
587
+ else:
588
+ bot_response = chatbot.generate_response(user_message, context)
589
+ return jsonify({
590
+ 'response': bot_response,
591
+ 'has_context': bool(context),
592
+ 'session_id': chatbot.session_id,
593
+ 'streaming': False
594
+ })
595
+
596
+ # Collect streaming response
597
+ full_response = ""
598
+ response_chunks = []
599
+
600
+ try:
601
+ for chunk in streaming_response:
602
+ if chunk.text:
603
+ full_response += chunk.text
604
+ response_chunks.append(chunk.text)
605
+ except (ResourceExhausted, GoogleAPIError):
606
+ # If rate limited during streaming, fallback to regular response
607
+ if context:
608
+ bot_response = chatbot.generate_rag_response(user_message, context)
609
+ else:
610
+ bot_response = chatbot.generate_response(user_message, context)
611
+ return jsonify({
612
+ 'response': bot_response,
613
+ 'has_context': bool(context),
614
+ 'session_id': chatbot.session_id,
615
+ 'streaming': False
616
+ })
617
+
618
+ # Update chat history
619
+ chatbot.chat_history.append({
620
+ "user": user_message,
621
+ "bot": full_response,
622
+ "timestamp": datetime.now().isoformat()
623
+ })
624
+
625
+ # Update memory
626
+ chatbot.update_memory(user_message, full_response)
627
+
628
+ return jsonify({
629
+ 'response': full_response,
630
+ 'chunks': response_chunks,
631
+ 'has_context': bool(context),
632
+ 'session_id': chatbot.session_id,
633
+ 'streaming': True
634
+ })
635
+ except Exception as e:
636
+ return jsonify({'error': str(e)}), 500
637
+
638
+ @app.route('/api/conversations', methods=['GET'])
639
+ def get_conversations():
640
+ try:
641
+ conversations = []
642
+ if "conversations" in chatbot.memory:
643
+ for conv_id, conv_data in chatbot.memory["conversations"].items():
644
+ conversations.append({
645
+ 'id': conv_id,
646
+ 'title': conv_data['title'],
647
+ 'created_at': conv_data['created_at'],
648
+ 'last_updated': conv_data['last_updated'],
649
+ 'message_count': len(conv_data['messages'])
650
+ })
651
+
652
+ # Sort by last updated, newest first
653
+ conversations.sort(key=lambda x: x['last_updated'], reverse=True)
654
+
655
+ return jsonify({'conversations': conversations})
656
+ except Exception as e:
657
+ return jsonify({'error': str(e)}), 500
658
+
659
+ @app.route('/api/conversations', methods=['POST'])
660
+ def save_conversation():
661
+ try:
662
+ data = request.json
663
+ conversation_id = data.get('id', str(uuid.uuid4()))
664
+ title = data.get('title', '')
665
+
666
+ conversation = chatbot.save_conversation(conversation_id, title)
667
+
668
+ return jsonify({
669
+ 'message': 'Conversation saved successfully',
670
+ 'conversation': conversation
671
+ })
672
+ except Exception as e:
673
+ return jsonify({'error': str(e)}), 500
674
+
675
+ @app.route('/api/conversations/<conversation_id>', methods=['GET'])
676
+ def load_conversation(conversation_id):
677
+ try:
678
+ conversation = chatbot.load_conversation(conversation_id)
679
+ if conversation:
680
+ return jsonify({
681
+ 'message': 'Conversation loaded successfully',
682
+ 'conversation': conversation
683
+ })
684
+ else:
685
+ return jsonify({'error': 'Conversation not found'}), 404
686
+ except Exception as e:
687
+ return jsonify({'error': str(e)}), 500
688
+
689
+ @app.route('/api/conversations/<conversation_id>', methods=['DELETE'])
690
+ def delete_conversation(conversation_id):
691
+ try:
692
+ success = chatbot.delete_conversation(conversation_id)
693
+ if success:
694
+ return jsonify({'message': 'Conversation deleted successfully'})
695
+ else:
696
+ return jsonify({'error': 'Conversation not found'}), 404
697
+ except Exception as e:
698
+ return jsonify({'error': str(e)}), 500
699
+
700
+ @app.route('/api/conversations/<conversation_id>/rename', methods=['PUT'])
701
+ def rename_conversation(conversation_id):
702
+ try:
703
+ data = request.json
704
+ new_title = data.get('title', '')
705
+
706
+ if not new_title:
707
+ return jsonify({'error': 'No title provided'}), 400
708
+
709
+ success = chatbot.rename_conversation(conversation_id, new_title)
710
+ if success:
711
+ return jsonify({'message': 'Conversation renamed successfully'})
712
+ else:
713
+ return jsonify({'error': 'Conversation not found'}), 404
714
+ except Exception as e:
715
+ return jsonify({'error': str(e)}), 500
716
+
717
+ @app.route('/api/messages/<int:message_index>/edit', methods=['PUT'])
718
+ def edit_message(message_index):
719
+ try:
720
+ data = request.json
721
+ new_message = data.get('message', '')
722
+
723
+ if not new_message:
724
+ return jsonify({'error': 'No message provided'}), 400
725
+
726
+ if 0 <= message_index < len(chatbot.chat_history):
727
+ # Update the user message
728
+ chatbot.chat_history[message_index]['user'] = new_message
729
+ chatbot.chat_history[message_index]['edited'] = True
730
+ chatbot.chat_history[message_index]['edited_at'] = datetime.now().isoformat()
731
+
732
+ # Remove subsequent messages (bot response and after)
733
+ chatbot.chat_history = chatbot.chat_history[:message_index + 1]
734
+
735
+ return jsonify({
736
+ 'message': 'Message edited successfully',
737
+ 'updated_history': chatbot.chat_history
738
+ })
739
+ else:
740
+ return jsonify({'error': 'Invalid message index'}), 400
741
+ except Exception as e:
742
+ return jsonify({'error': str(e)}), 500
743
+
744
+ if __name__ == '__main__':
745
+ print("🚀 Starting Juno AI Server...")
746
+ print("🤖 Advanced AI Assistant with Document Processing, Web Scraping, and Memory")
747
+ print("🌟 Powered by Juno AI Prompts System")
748
+ app.run(debug=False, host='0.0.0.0', port=7860)
avatar.jpg ADDED
favicon.jpg ADDED

Git LFS Details

  • SHA256: b0c7bb0ed06e6218708181558e9bcb097d59191e271556430bbb2a1ed766a62b
  • Pointer size: 131 Bytes
  • Size of remote file: 704 kB
index.html ADDED
@@ -0,0 +1,203 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>JUNO AI </title>
7
+ <link rel="stylesheet" href="styles.css">
8
+ <link rel="preconnect" href="https://fonts.googleapis.com">
9
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
11
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
12
+ </head>
13
+ <body data-theme="light">
14
+ <div class="app-container">
15
+ <!-- Header -->
16
+ <header class="header">
17
+ <div class="header-content">
18
+ <div class="logo">
19
+ <img src="juno.jpg" alt="Juno Logo" class="logo-image">
20
+ <h1>JUNO AI</h1>
21
+ </div>
22
+ <div class="header-actions">
23
+ <button class="btn-secondary" id="memoryBtn">
24
+ <i class="fas fa-brain"></i> Memory
25
+ </button>
26
+ <button class="btn-secondary" id="clearBtn">
27
+ <i class="fas fa-broom"></i> Clear
28
+ </button>
29
+ <button class="btn-secondary" id="themeToggleBtn">
30
+ <i class="fas fa-moon"></i>
31
+ </button>
32
+ <button class="btn-secondary" id="conversationsBtn">
33
+ <i class="fas fa-bars"></i> Menu
34
+ </button>
35
+ </div>
36
+ </div>
37
+ </header>
38
+
39
+ <!-- Main Content -->
40
+ <div class="main-content">
41
+ <div class="chat-container">
42
+ <!-- Chat Header -->
43
+ <div class="chat-header">
44
+ <div class="status-indicator">
45
+ <span class="status-dot active"></span>
46
+ <span>JUNO AI is Ready</span>
47
+ </div>
48
+ <div class="document-status" id="documentStatus">
49
+ <i class="fas fa-file-pdf" style="color: var(--text-muted);"></i>
50
+ <span>No documents loaded</span>
51
+ </div>
52
+ </div>
53
+
54
+ <!-- Messages Container -->
55
+ <div class="messages-container" id="messagesContainer">
56
+ <div class="welcome-message" id="welcomeMessage">
57
+ <div class="welcome-icon">
58
+ <img src="juno logo.jpg" alt="Juno Logo" class="welcome-logo">
59
+ </div>
60
+ <h2>Welcome to JUNO AI</h2>
61
+ <p>Upload documents, ask questions, or start a conversation!</p>
62
+
63
+ <div class="feature-grid">
64
+ <div class="feature-item">
65
+ <i class="fas fa-file-upload"></i>
66
+ <span>Upload PDFs</span>
67
+ </div>
68
+ <div class="feature-item">
69
+ <i class="fas fa-brain"></i>
70
+ <span>Smart Memory</span>
71
+ </div>
72
+ <div class="feature-item">
73
+ <i class="fas fa-globe"></i>
74
+ <span>Web Scraping</span>
75
+ </div>
76
+ <div class="feature-item">
77
+ <i class="fas fa-microphone"></i>
78
+ <span>Voice Input</span>
79
+ </div>
80
+ </div>
81
+
82
+ <!-- Quick Actions -->
83
+ <div class="quick-actions">
84
+ <button class="quick-btn" onclick="chatbot.fillInput('Summarize this document')">
85
+ <i class="fas fa-file-text"></i> Summarize Doc
86
+ </button>
87
+ <button class="quick-btn" onclick="chatbot.fillInput('Extract key insights from this content')">
88
+ <i class="fas fa-lightbulb"></i> Key Insights
89
+ </button>
90
+ <button class="quick-btn" onclick="chatbot.fillInput('What is the main topic of this document?')">
91
+ <i class="fas fa-bullseye"></i> Main Topic
92
+ </button>
93
+ <button class="quick-btn" onclick="chatbot.fillInput('List the important points mentioned')">
94
+ <i class="fas fa-list"></i> Key Points
95
+ </button>
96
+ </div>
97
+ </div>
98
+ </div>
99
+
100
+ <!-- Input Area -->
101
+ <div class="input-area">
102
+ <div class="message-input-container">
103
+ <div class="input-wrapper">
104
+ <div class="input-actions-left">
105
+ <button class="action-btn upload-btn" id="attachBtn" title="Upload PDF">
106
+ <i class="fas fa-paperclip"></i>
107
+ </button>
108
+ <button class="action-btn scrape-btn" id="scrapeBtn" title="Scrape Website">
109
+ <i class="fas fa-globe"></i>
110
+ </button>
111
+ <button class="action-btn voice-btn" id="voiceBtn" title="Voice Input">
112
+ <i class="fas fa-microphone"></i>
113
+ </button>
114
+ </div>
115
+ <div class="input-field-wrapper">
116
+ <textarea id="messageInput" placeholder="Type your message... (Shift+Enter for new line)" rows="1"></textarea>
117
+ </div>
118
+ <div class="input-actions-right">
119
+ <button class="action-btn send-btn" id="sendBtn">
120
+ <i class="fas fa-paper-plane"></i>
121
+ </button>
122
+ </div>
123
+ </div>
124
+ </div>
125
+ </div>
126
+ </div>
127
+
128
+ <!-- Sidebar for Conversations -->
129
+ <div class="sidebar" id="sidebar">
130
+ <div class="sidebar-header">
131
+ <h3>Conversations</h3>
132
+ <button class="btn-icon" id="closeSidebarBtn">
133
+ <i class="fas fa-times"></i>
134
+ </button>
135
+ </div>
136
+ <div class="sidebar-content">
137
+ <div class="sidebar-actions">
138
+ <button class="btn-secondary" id="newConvBtn">
139
+ <i class="fas fa-plus"></i> New Conversation
140
+ </button>
141
+ <button class="btn-secondary" id="saveConvBtn">
142
+ <i class="fas fa-save"></i> Save Current
143
+ </button>
144
+ </div>
145
+ <div class="conversations-list" id="conversationsList">
146
+ <!-- Conversations will be loaded here -->
147
+ </div>
148
+ </div>
149
+ </div>
150
+ </div>
151
+
152
+ <!-- Voice Modal -->
153
+ <div class="voice-modal" id="voiceModal">
154
+ <div class="voice-content">
155
+ <div class="voice-animation">
156
+ <div class="voice-wave"></div>
157
+ <div class="voice-wave"></div>
158
+ <div class="voice-wave"></div>
159
+ <div class="voice-wave"></div>
160
+ <div class="voice-wave"></div>
161
+ </div>
162
+ <div class="voice-title">Listening...</div>
163
+ <div class="voice-subtitle">Speak clearly into your microphone</div>
164
+ <button class="stop-voice-btn" id="stopVoiceBtn">
165
+ <i class="fas fa-stop"></i> Stop Recording
166
+ </button>
167
+ </div>
168
+ </div>
169
+
170
+ <!-- Memory Modal -->
171
+ <div class="modal" id="memoryModal">
172
+ <div class="modal-content">
173
+ <div class="modal-header">
174
+ <h3>Memory & Session Data</h3>
175
+ <button class="btn-icon" id="closeModalBtn">
176
+ <i class="fas fa-times"></i>
177
+ </button>
178
+ </div>
179
+ <div class="modal-body">
180
+ <div id="memoryContent">
181
+ Loading memory data...
182
+ </div>
183
+ </div>
184
+ </div>
185
+ </div>
186
+
187
+ <!-- Advanced Loading Overlay -->
188
+ <div class="loading-overlay" id="loadingOverlay">
189
+ <div class="loading-spinner">
190
+ <div class="spinner-ring"></div>
191
+ <div class="spinner-ring"></div>
192
+ <div class="spinner-ring"></div>
193
+ </div>
194
+ <div class="loading-text">Processing...</div>
195
+ </div>
196
+ </div>
197
+
198
+ <!-- Hidden file input -->
199
+ <input type="file" id="fileInput" accept=".pdf" style="display: none;">
200
+
201
+ <script src="script.js"></script>
202
+ </body>
203
+ </html>
juno avatar.jpg ADDED

Git LFS Details

  • SHA256: 9303e0f55d44085755426c9af94a88e047e0a25b23368d886b3e10b65056382a
  • Pointer size: 132 Bytes
  • Size of remote file: 1.49 MB
juno logo.jpg ADDED
juno.jpg ADDED

Git LFS Details

  • SHA256: 851c58a81218999b651866fde006cc0906206c8b49c3b62cf9d3072f3bdfbebf
  • Pointer size: 131 Bytes
  • Size of remote file: 143 kB
prompts.py ADDED
@@ -0,0 +1,689 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Juno AI - Comprehensive Prompt System
3
+ =====================================
4
+
5
+ This module contains all prompts and prompt templates for Juno AI,
6
+ an advanced conversational AI assistant with document processing,
7
+ web scraping, memory management, and RAG capabilities.
8
+
9
+ Features Covered:
10
+ - Core AI Personality & Branding
11
+ - Document Analysis & Processing
12
+ - Web Content Integration
13
+ - Memory & Context Management
14
+ - Conversation Management
15
+ - Summarization Capabilities
16
+ - RAG (Retrieval Augmented Generation)
17
+ - Streaming Responses
18
+ - Error Handling & Fallbacks
19
+ - Professional Communication
20
+
21
+ Author: Juno AI Development Team
22
+ Version: 1.0
23
+ """
24
+
25
+ from typing import List, Dict, Any, Optional
26
+ from datetime import datetime
27
+ import json
28
+
29
+ class JunoAIPrompts:
30
+ """
31
+ Centralized prompt management system for Juno AI.
32
+ Contains all prompts, templates, and prompt generation methods.
33
+ """
34
+
35
+ def __init__(self):
36
+ self.version = "1.0"
37
+ self.ai_name = "Juno AI"
38
+ self.personality = self._load_personality_traits()
39
+
40
+ def _load_personality_traits(self) -> Dict[str, str]:
41
+ """Define Juno AI's core personality traits"""
42
+ return {
43
+ "helpful": "Always eager to assist and provide valuable insights",
44
+ "intelligent": "Demonstrates deep understanding and analytical thinking",
45
+ "professional": "Maintains professional tone while being approachable",
46
+ "adaptive": "Adapts communication style to user needs and context",
47
+ "reliable": "Provides accurate, well-sourced information",
48
+ "innovative": "Offers creative solutions and fresh perspectives",
49
+ "empathetic": "Understands user needs and responds thoughtfully"
50
+ }
51
+
52
+ # ==========================================
53
+ # CORE AI ASSISTANT PROMPTS
54
+ # ==========================================
55
+
56
+ def get_core_system_prompt(self) -> str:
57
+ """
58
+ Core system prompt that defines Juno AI's personality and capabilities
59
+ """
60
+ return f"""You are {self.ai_name}, an advanced AI assistant created to help users with a wide range of tasks through intelligent conversation, document analysis, and information processing.
61
+
62
+ CORE IDENTITY & PERSONALITY:
63
+ - You are helpful, intelligent, and professional while maintaining a warm, approachable demeanor
64
+ - You demonstrate deep analytical thinking and provide thoughtful, well-reasoned responses
65
+ - You adapt your communication style to match the user's needs and expertise level
66
+ - You are curious about learning and helping users discover insights
67
+ - You maintain professionalism while being conversational and engaging
68
+
69
+ KEY CAPABILITIES:
70
+ 🧠 Intelligent Conversation: Engage in natural, contextual conversations with memory retention
71
+ 📄 Document Analysis: Process, analyze, and extract insights from uploaded documents
72
+ 🌐 Web Integration: Scrape and analyze web content for real-time information
73
+ 💭 Memory Management: Remember important details across conversations
74
+ 🔍 Smart Search: Use RAG to find relevant information from uploaded content
75
+ 📊 Summarization: Create comprehensive summaries of long-form content
76
+ 🎯 Task Management: Help with various professional and personal tasks
77
+
78
+ COMMUNICATION STYLE:
79
+ - Be clear, concise, and informative
80
+ - Use appropriate formatting for readability
81
+ - Provide specific examples when helpful
82
+ - Ask clarifying questions when needed
83
+ - Acknowledge uncertainty when appropriate
84
+ - Maintain continuity across conversation turns
85
+
86
+ CONTEXT AWARENESS:
87
+ - Always consider previous conversation history
88
+ - Reference uploaded documents and web content when relevant
89
+ - Use memory to provide personalized responses
90
+ - Maintain context across multiple interaction sessions
91
+
92
+ Remember: You are not just answering questions - you are having a meaningful conversation and building a helpful relationship with the user."""
93
+
94
+ def get_conversation_prompt(self,
95
+ user_message: str,
96
+ context: str = "",
97
+ conversation_history: List[Dict] = None,
98
+ memory_context: Dict = None,
99
+ user_preferences: Dict = None) -> str:
100
+ """
101
+ Generate a comprehensive conversation prompt with all available context
102
+ """
103
+
104
+ # Build conversation history section
105
+ history_section = ""
106
+ if conversation_history:
107
+ recent_history = conversation_history[-5:] # Last 5 exchanges
108
+ history_section = "\n".join([
109
+ f"User: {exchange.get('user', '')}\nAssistant: {exchange.get('bot', '')}"
110
+ for exchange in recent_history
111
+ if not exchange.get('fallback', False)
112
+ ])
113
+
114
+ # Build memory context section
115
+ memory_section = ""
116
+ if memory_context and memory_context.get('memory'):
117
+ recent_memory = memory_context['memory'][-3:]
118
+ memory_section = json.dumps(recent_memory, indent=2)
119
+
120
+ # Build user preferences section
121
+ preferences_section = ""
122
+ if user_preferences:
123
+ preferences_section = json.dumps(user_preferences, indent=2)
124
+
125
+ # Build document context section
126
+ context_section = ""
127
+ if context:
128
+ context_section = f"\n\nRELEVANT DOCUMENT CONTEXT:\n{context[:2000]}"
129
+
130
+ return f"""{self.get_core_system_prompt()}
131
+
132
+ CONVERSATION CONTEXT:
133
+ {f"Previous conversation history:\n{history_section}\n" if history_section else ""}
134
+ {f"Session memory:\n{memory_section}\n" if memory_section else ""}
135
+ {f"User preferences:\n{preferences_section}\n" if preferences_section else ""}
136
+ {context_section}
137
+
138
+ CURRENT USER MESSAGE: {user_message}
139
+
140
+ RESPONSE INSTRUCTIONS:
141
+ - Provide a helpful, accurate response based on all available context
142
+ - Reference relevant information from documents or previous conversations when applicable
143
+ - Maintain conversational flow and continuity
144
+ - Be specific and actionable in your advice
145
+ - Use formatting (lists, headers, etc.) to improve readability when appropriate
146
+ - If you need clarification, ask thoughtful follow-up questions
147
+ - Do NOT include your name or "Assistant:" in your response - respond directly and naturally"""
148
+
149
+ # ==========================================
150
+ # DOCUMENT PROCESSING PROMPTS
151
+ # ==========================================
152
+
153
+ def get_document_analysis_prompt(self, document_text: str, filename: str = "document") -> str:
154
+ """
155
+ Prompt for analyzing uploaded documents
156
+ """
157
+ return f"""Analyze the following document and provide comprehensive insights.
158
+
159
+ DOCUMENT: {filename}
160
+ CONTENT:
161
+ {document_text[:8000]}
162
+
163
+ ANALYSIS REQUIREMENTS:
164
+ 1. **Document Summary**: Provide a clear, comprehensive summary of the main content
165
+ 2. **Key Points**: Extract the most important points, insights, or findings
166
+ 3. **Document Type**: Identify the type of document (report, article, manual, etc.)
167
+ 4. **Main Topics**: List the primary topics or themes covered
168
+ 5. **Important Details**: Highlight any critical information, data, or recommendations
169
+ 6. **Potential Use Cases**: Suggest how this information could be applied or used
170
+
171
+ RESPONSE FORMAT:
172
+ Structure your analysis clearly with headers and bullet points for easy reading.
173
+ Be thorough but concise, focusing on the most valuable insights."""
174
+
175
+ def get_document_summarization_prompt(self, text: str, max_length: int = 500, focus_area: str = "") -> str:
176
+ """
177
+ Prompt for document summarization
178
+ """
179
+ focus_instruction = f"\nFocus particularly on: {focus_area}" if focus_area else ""
180
+
181
+ return f"""Create a comprehensive summary of the following text.
182
+
183
+ TARGET LENGTH: Approximately {max_length} words
184
+ {focus_instruction}
185
+
186
+ CONTENT TO SUMMARIZE:
187
+ {text[:10000]}
188
+
189
+ SUMMARIZATION REQUIREMENTS:
190
+ - Capture all key points and main arguments
191
+ - Maintain the original meaning and context
192
+ - Include important details, data, and insights
193
+ - Use clear, professional language
194
+ - Structure with headers or bullet points if helpful
195
+ - Ensure the summary is standalone and comprehensive"""
196
+
197
+ def get_document_qa_prompt(self, question: str, document_context: str, document_title: str = "") -> str:
198
+ """
199
+ Prompt for answering questions about specific documents
200
+ """
201
+ title_section = f"DOCUMENT: {document_title}\n" if document_title else ""
202
+
203
+ return f"""Answer the following question based on the provided document context.
204
+
205
+ {title_section}QUESTION: {question}
206
+
207
+ RELEVANT DOCUMENT CONTENT:
208
+ {document_context}
209
+
210
+ RESPONSE REQUIREMENTS:
211
+ - Answer directly and specifically based on the document content
212
+ - Quote or reference specific sections when relevant
213
+ - If the answer isn't in the document, clearly state this
214
+ - Provide additional context or explanations when helpful
215
+ - Use clear formatting for readability"""
216
+
217
+ # ==========================================
218
+ # WEB SCRAPING & CONTENT PROMPTS
219
+ # ==========================================
220
+
221
+ def get_web_content_analysis_prompt(self, url: str, content: str) -> str:
222
+ """
223
+ Prompt for analyzing scraped web content
224
+ """
225
+ return f"""Analyze the following web content and provide comprehensive insights.
226
+
227
+ SOURCE URL: {url}
228
+ SCRAPED CONTENT:
229
+ {content[:8000]}
230
+
231
+ ANALYSIS REQUIREMENTS:
232
+ 1. **Content Summary**: Provide a clear summary of the web page content
233
+ 2. **Key Information**: Extract the most important information and insights
234
+ 3. **Content Type**: Identify the type of content (article, blog, news, product page, etc.)
235
+ 4. **Main Topics**: List the primary topics or themes
236
+ 5. **Credibility Assessment**: Comment on the source credibility and information quality
237
+ 6. **Relevance**: Explain potential use cases for this information
238
+
239
+ RESPONSE FORMAT:
240
+ Use clear headers and bullet points for easy scanning.
241
+ Focus on providing actionable insights from the web content."""
242
+
243
+ def get_web_content_integration_prompt(self, user_question: str, web_content: str, url: str) -> str:
244
+ """
245
+ Prompt for integrating web content into responses
246
+ """
247
+ return f"""Answer the user's question using the provided web content as a primary source.
248
+
249
+ USER QUESTION: {user_question}
250
+
251
+ WEB SOURCE: {url}
252
+ CONTENT:
253
+ {web_content[:4000]}
254
+
255
+ RESPONSE REQUIREMENTS:
256
+ - Answer the question using information from the web content
257
+ - Reference the source appropriately
258
+ - Provide additional context or explanations when helpful
259
+ - If the web content doesn't fully answer the question, state what's missing
260
+ - Maintain objectivity and cite the source clearly"""
261
+
262
+ # ==========================================
263
+ # MEMORY & CONTEXT MANAGEMENT PROMPTS
264
+ # ==========================================
265
+
266
+ def get_memory_extraction_prompt(self, user_message: str, bot_response: str) -> str:
267
+ """
268
+ Prompt for extracting important information for memory storage
269
+ """
270
+ return f"""Analyze this conversation exchange and extract important information that should be remembered for future interactions.
271
+
272
+ USER MESSAGE: {user_message}
273
+ AI RESPONSE: {bot_response}
274
+
275
+ EXTRACTION CRITERIA:
276
+ - User preferences, interests, or goals mentioned
277
+ - Important facts or context about the user
278
+ - Project details or ongoing tasks
279
+ - Specific requests or requirements
280
+ - Any information that would improve future interactions
281
+
282
+ Return a JSON object with extracted information:
283
+ {{
284
+ "user_preferences": [...],
285
+ "important_facts": [...],
286
+ "ongoing_projects": [...],
287
+ "context_tags": [...]
288
+ }}
289
+
290
+ Only extract genuinely important information that would be useful to remember."""
291
+
292
+ def get_context_integration_prompt(self, current_message: str, relevant_memories: List[Dict]) -> str:
293
+ """
294
+ Prompt for integrating remembered context into responses
295
+ """
296
+ memory_context = json.dumps(relevant_memories, indent=2)
297
+
298
+ return f"""Respond to the current message while incorporating relevant context from previous interactions.
299
+
300
+ CURRENT MESSAGE: {current_message}
301
+
302
+ RELEVANT CONTEXT FROM PREVIOUS INTERACTIONS:
303
+ {memory_context}
304
+
305
+ INTEGRATION INSTRUCTIONS:
306
+ - Reference previous conversations naturally when relevant
307
+ - Show continuity and memory of past interactions
308
+ - Build upon previous discussions when appropriate
309
+ - Don't over-reference past conversations unless directly relevant
310
+ - Maintain a natural conversation flow"""
311
+
312
+ # ==========================================
313
+ # RAG (RETRIEVAL AUGMENTED GENERATION) PROMPTS
314
+ # ==========================================
315
+
316
+ def get_rag_response_prompt(self,
317
+ user_query: str,
318
+ retrieved_chunks: List[str],
319
+ source_info: List[str] = None) -> str:
320
+ """
321
+ Prompt for generating responses using retrieved document chunks
322
+ """
323
+
324
+ # Combine retrieved chunks
325
+ context = "\n\n---\n\n".join(retrieved_chunks[:3]) # Top 3 chunks
326
+
327
+ # Add source information if available
328
+ source_section = ""
329
+ if source_info:
330
+ sources = ", ".join(set(source_info[:3]))
331
+ source_section = f"\nSOURCES: {sources}\n"
332
+
333
+ return f"""Answer the user's question using the retrieved information from uploaded documents.
334
+
335
+ USER QUESTION: {user_query}
336
+ {source_section}
337
+ RETRIEVED INFORMATION:
338
+ {context}
339
+
340
+ RESPONSE REQUIREMENTS:
341
+ - Answer the question using the retrieved information as the primary source
342
+ - Synthesize information from multiple chunks when relevant
343
+ - Clearly indicate when information comes from the uploaded documents
344
+ - If the retrieved information doesn't fully answer the question, state what's missing
345
+ - Provide specific details and examples from the source material
346
+ - Maintain accuracy and don't add information not present in the sources"""
347
+
348
+ def get_rag_no_context_prompt(self, user_query: str) -> str:
349
+ """
350
+ Prompt when no relevant context is found in uploaded documents
351
+ """
352
+ return f"""The user has asked a question but no relevant information was found in their uploaded documents.
353
+
354
+ USER QUESTION: {user_query}
355
+
356
+ RESPONSE REQUIREMENTS:
357
+ - Acknowledge that no relevant information was found in uploaded documents
358
+ - Provide a helpful general response based on your knowledge
359
+ - Suggest how the user might find more specific information
360
+ - Offer to help analyze relevant documents if they upload them"""
361
+
362
+ # ==========================================
363
+ # CONVERSATION MANAGEMENT PROMPTS
364
+ # ==========================================
365
+
366
+ def get_conversation_starter_prompt(self, user_context: Dict = None) -> str:
367
+ """
368
+ Prompt for starting new conversations
369
+ """
370
+ context_section = ""
371
+ if user_context:
372
+ context_section = f"User Context: {json.dumps(user_context, indent=2)}\n"
373
+
374
+ return f"""Generate a welcoming message to start a new conversation.
375
+
376
+ {context_section}REQUIREMENTS:
377
+ - Be welcoming and professional
378
+ - Introduce your capabilities briefly
379
+ - Invite the user to ask questions or share what they need help with
380
+ - Reference any available context appropriately
381
+ - Keep it concise but engaging"""
382
+
383
+ def get_conversation_summary_prompt(self, conversation_messages: List[Dict]) -> str:
384
+ """
385
+ Prompt for summarizing conversations
386
+ """
387
+ messages_text = "\n\n".join([
388
+ f"User: {msg.get('user', '')}\nAssistant: {msg.get('bot', '')}"
389
+ for msg in conversation_messages[-10:] # Last 10 exchanges
390
+ ])
391
+
392
+ return f"""Create a comprehensive summary of this conversation.
393
+
394
+ CONVERSATION:
395
+ {messages_text}
396
+
397
+ SUMMARY REQUIREMENTS:
398
+ - Capture the main topics discussed
399
+ - Note key questions asked and answers provided
400
+ - Include important decisions or conclusions reached
401
+ - Highlight any ongoing tasks or follow-up items
402
+ - Keep it concise but comprehensive"""
403
+
404
+ # ==========================================
405
+ # SPECIALIZED TASK PROMPTS
406
+ # ==========================================
407
+
408
+ def get_comparison_analysis_prompt(self, items_to_compare: List[str], comparison_criteria: str = "") -> str:
409
+ """
410
+ Prompt for comparative analysis
411
+ """
412
+ criteria_section = f"\nComparison Criteria: {comparison_criteria}" if comparison_criteria else ""
413
+ items_list = "\n".join([f"- {item}" for item in items_to_compare])
414
+
415
+ return f"""Provide a comprehensive comparison analysis.
416
+
417
+ ITEMS TO COMPARE:
418
+ {items_list}
419
+ {criteria_section}
420
+
421
+ ANALYSIS REQUIREMENTS:
422
+ - Create a structured comparison covering key aspects
423
+ - Highlight similarities and differences
424
+ - Provide pros and cons for each item
425
+ - Include recommendations based on different use cases
426
+ - Use clear formatting (tables, lists, headers) for readability"""
427
+
428
+ def get_research_synthesis_prompt(self, research_sources: List[Dict], research_question: str) -> str:
429
+ """
430
+ Prompt for synthesizing research from multiple sources
431
+ """
432
+ sources_text = "\n\n".join([
433
+ f"SOURCE: {source.get('title', 'Unknown')}\n{source.get('content', '')[:2000]}"
434
+ for source in research_sources[:5] # Up to 5 sources
435
+ ])
436
+
437
+ return f"""Synthesize the following research sources to answer the research question.
438
+
439
+ RESEARCH QUESTION: {research_question}
440
+
441
+ SOURCES:
442
+ {sources_text}
443
+
444
+ SYNTHESIS REQUIREMENTS:
445
+ - Integrate information from multiple sources
446
+ - Identify common themes and conflicting viewpoints
447
+ - Provide a balanced, comprehensive answer
448
+ - Note any gaps or limitations in the available sources
449
+ - Use appropriate citations or source references
450
+ - Structure the response logically with clear sections"""
451
+
452
+ def get_problem_solving_prompt(self, problem_description: str, constraints: str = "", goal: str = "") -> str:
453
+ """
454
+ Prompt for structured problem-solving
455
+ """
456
+ constraints_section = f"\nConstraints: {constraints}" if constraints else ""
457
+ goal_section = f"\nGoal: {goal}" if goal else ""
458
+
459
+ return f"""Help solve the following problem using a structured approach.
460
+
461
+ PROBLEM: {problem_description}
462
+ {constraints_section}
463
+ {goal_section}
464
+
465
+ PROBLEM-SOLVING APPROACH:
466
+ 1. **Problem Analysis**: Break down the problem into components
467
+ 2. **Root Cause Analysis**: Identify underlying causes
468
+ 3. **Solution Options**: Generate multiple potential solutions
469
+ 4. **Evaluation**: Assess pros and cons of each option
470
+ 5. **Recommendation**: Provide the best solution(s) with rationale
471
+ 6. **Implementation Plan**: Outline next steps"""
472
+
473
+ # ==========================================
474
+ # ERROR HANDLING & FALLBACK PROMPTS
475
+ # ==========================================
476
+
477
+ def get_fallback_response_templates(self) -> List[str]:
478
+ """
479
+ Templates for fallback responses when API is overloaded
480
+ """
481
+ return [
482
+ "I'm currently experiencing high API demand, but I'm here and ready to help. Your message about '{user_message_preview}' is important to me. Please try again in a moment while I catch up with the processing queue.",
483
+
484
+ "The AI processing system is temporarily overloaded, but don't worry - I've received your message and I'm working on getting back to full capacity. Your question deserves a thoughtful response, so please retry in 30-60 seconds.",
485
+
486
+ "I'm having a brief moment of high computational demand. While I process your message about '{user_message_preview}', please know that I'm committed to providing you with a helpful response once the system stabilizes.",
487
+
488
+ "System overload detected, but I want to acknowledge your message: '{user_message_preview}'. I'm designed to provide thoughtful, comprehensive responses, so please give me a moment to clear the processing backlog and try again.",
489
+
490
+ "I'm experiencing temporary processing constraints due to high usage, but I'm still here with you. Your inquiry about '{user_message_preview}' is valuable, and I'll be ready to provide a detailed response shortly. Please retry in a minute."
491
+ ]
492
+
493
+ def get_error_explanation_prompt(self, error_type: str, user_context: str = "") -> str:
494
+ """
495
+ Prompt for explaining errors to users
496
+ """
497
+ return f"""Explain the following error to the user in a helpful, non-technical way.
498
+
499
+ ERROR TYPE: {error_type}
500
+ USER CONTEXT: {user_context}
501
+
502
+ EXPLANATION REQUIREMENTS:
503
+ - Use clear, non-technical language
504
+ - Explain what happened and why
505
+ - Provide actionable steps to resolve the issue
506
+ - Maintain a helpful, apologetic tone
507
+ - Offer alternative solutions when possible
508
+ - Reassure the user that you're still available to help"""
509
+
510
+ def get_clarification_request_prompt(self, unclear_request: str, possible_interpretations: List[str]) -> str:
511
+ """
512
+ Prompt for requesting clarification from users
513
+ """
514
+ interpretations_list = "\n".join([f"- {interp}" for interp in possible_interpretations])
515
+
516
+ return f"""The user's request needs clarification to provide the most helpful response.
517
+
518
+ USER REQUEST: {unclear_request}
519
+
520
+ POSSIBLE INTERPRETATIONS:
521
+ {interpretations_list}
522
+
523
+ CLARIFICATION REQUIREMENTS:
524
+ - Politely acknowledge the request
525
+ - Explain why clarification would help provide a better response
526
+ - Present the possible interpretations as options
527
+ - Ask specific questions to narrow down the intent
528
+ - Maintain an encouraging, helpful tone"""
529
+
530
+ # ==========================================
531
+ # STREAMING RESPONSE PROMPTS
532
+ # ==========================================
533
+
534
+ def get_streaming_response_prompt(self, user_message: str, context: str = "") -> str:
535
+ """
536
+ Optimized prompt for streaming responses (shorter to reduce latency)
537
+ """
538
+ context_section = f"\nContext: {context[:1500]}" if context else ""
539
+
540
+ return f"""You are a helpful AI assistant. Respond naturally and conversationally.
541
+
542
+ {context_section}
543
+
544
+ User: {user_message}
545
+
546
+ Requirements:
547
+ - Be helpful and accurate
548
+ - Use available context when relevant
549
+ - Maintain conversational flow
550
+ - Format for readability
551
+ - Respond directly without prefixes"""
552
+
553
+ # ==========================================
554
+ # UTILITY METHODS
555
+ # ==========================================
556
+
557
+ def format_prompt_with_variables(self, template: str, **variables) -> str:
558
+ """
559
+ Format a prompt template with provided variables
560
+ """
561
+ try:
562
+ return template.format(**variables)
563
+ except KeyError as e:
564
+ raise ValueError(f"Missing required variable for prompt template: {e}")
565
+
566
+ def get_prompt_by_category(self, category: str, prompt_type: str, **kwargs) -> str:
567
+ """
568
+ Get a specific prompt by category and type
569
+ """
570
+ method_name = f"get_{category}_{prompt_type}_prompt"
571
+ if hasattr(self, method_name):
572
+ method = getattr(self, method_name)
573
+ return method(**kwargs)
574
+ else:
575
+ raise ValueError(f"Prompt not found: {category}.{prompt_type}")
576
+
577
+ def get_all_prompt_categories(self) -> List[str]:
578
+ """
579
+ Get list of all available prompt categories
580
+ """
581
+ return [
582
+ "core_system",
583
+ "conversation",
584
+ "document_analysis",
585
+ "document_summarization",
586
+ "document_qa",
587
+ "web_content_analysis",
588
+ "web_content_integration",
589
+ "memory_extraction",
590
+ "context_integration",
591
+ "rag_response",
592
+ "rag_no_context",
593
+ "conversation_starter",
594
+ "conversation_summary",
595
+ "comparison_analysis",
596
+ "research_synthesis",
597
+ "problem_solving",
598
+ "error_explanation",
599
+ "clarification_request",
600
+ "streaming_response"
601
+ ]
602
+
603
+ def validate_prompt_inputs(self, **inputs) -> Dict[str, Any]:
604
+ """
605
+ Validate and sanitize prompt inputs
606
+ """
607
+ validated = {}
608
+
609
+ for key, value in inputs.items():
610
+ if isinstance(value, str):
611
+ # Truncate very long strings to prevent token issues
612
+ if len(value) > 10000:
613
+ validated[key] = value[:10000] + "\n\n[Content truncated for processing...]"
614
+ else:
615
+ validated[key] = value
616
+ elif isinstance(value, (list, dict)):
617
+ validated[key] = value
618
+ else:
619
+ validated[key] = str(value)
620
+
621
+ return validated
622
+
623
+ # ==========================================
624
+ # GLOBAL INSTANCE & CONVENIENCE FUNCTIONS
625
+ # ==========================================
626
+
627
+ # Create global instance for easy access
628
+ juno_prompts = JunoAIPrompts()
629
+
630
+ # Convenience functions for common prompt types
631
+ def get_main_conversation_prompt(user_message: str, **kwargs) -> str:
632
+ """Get the main conversation prompt with all context"""
633
+ return juno_prompts.get_conversation_prompt(user_message, **kwargs)
634
+
635
+ def get_document_summary_prompt(text: str, max_length: int = 500) -> str:
636
+ """Get document summarization prompt"""
637
+ return juno_prompts.get_document_summarization_prompt(text, max_length)
638
+
639
+ def get_rag_prompt(user_query: str, retrieved_chunks: List[str]) -> str:
640
+ """Get RAG response prompt"""
641
+ return juno_prompts.get_rag_response_prompt(user_query, retrieved_chunks)
642
+
643
+ def get_streaming_prompt(user_message: str, context: str = "") -> str:
644
+ """Get optimized streaming response prompt"""
645
+ return juno_prompts.get_streaming_response_prompt(user_message, context)
646
+
647
+ def get_fallback_responses() -> List[str]:
648
+ """Get fallback response templates"""
649
+ return juno_prompts.get_fallback_response_templates()
650
+
651
+ # ==========================================
652
+ # EXAMPLE USAGE
653
+ # ==========================================
654
+
655
+ if __name__ == "__main__":
656
+ # Example usage of the Juno AI Prompts system
657
+ prompts = JunoAIPrompts()
658
+
659
+ # Example 1: Basic conversation prompt
660
+ conversation_prompt = prompts.get_conversation_prompt(
661
+ user_message="How can you help me analyze documents?",
662
+ context="User has uploaded a research paper about AI",
663
+ conversation_history=[
664
+ {"user": "Hello", "bot": "Hi! How can I help you today?"}
665
+ ]
666
+ )
667
+ print("CONVERSATION PROMPT:")
668
+ print(conversation_prompt[:500] + "...")
669
+ print("\n" + "="*50 + "\n")
670
+
671
+ # Example 2: Document analysis prompt
672
+ doc_analysis_prompt = prompts.get_document_analysis_prompt(
673
+ document_text="This is a sample document about AI technology...",
674
+ filename="ai_research_paper.pdf"
675
+ )
676
+ print("DOCUMENT ANALYSIS PROMPT:")
677
+ print(doc_analysis_prompt[:500] + "...")
678
+ print("\n" + "="*50 + "\n")
679
+
680
+ # Example 3: RAG prompt
681
+ rag_prompt = prompts.get_rag_response_prompt(
682
+ user_query="What are the benefits of AI?",
683
+ retrieved_chunks=[
684
+ "AI provides automation capabilities...",
685
+ "Machine learning improves over time..."
686
+ ]
687
+ )
688
+ print("RAG PROMPT:")
689
+ print(rag_prompt[:500] + "...")
requirements.txt ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ python-dotenv
2
+ google-generativeai
3
+ langchain
4
+ langchain-google-genai
5
+ chromadb
6
+ pypdf2
7
+ sentence-transformers
8
+ flask
9
+ flask-cors
10
+ faiss-cpu
11
+ tiktoken
12
+ requests
13
+ beautifulsoup4
14
+ pytesseract
15
+ Pillow
16
+ opencv-python-headless
17
+ pdf2image
script.js ADDED
@@ -0,0 +1,858 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ class ChatbotUI {
2
+ constructor() {
3
+ this.apiBase = 'http://localhost:5000/api';
4
+ this.isTyping = false;
5
+ this.hasDocuments = false;
6
+ this.conversations = [];
7
+ this.currentConversationId = null;
8
+ this.isStreaming = false;
9
+
10
+ // Speech recognition properties
11
+ this.recognition = null;
12
+ this.isRecording = false;
13
+ this.isListening = false;
14
+
15
+ this.initializeElements();
16
+ this.attachEventListeners();
17
+ this.applyInitialTheme();
18
+ this.loadConversations();
19
+ this.initializeSpeechRecognition();
20
+ }
21
+
22
+ initializeElements() {
23
+ // Main elements
24
+ this.messagesContainer = document.getElementById('messagesContainer');
25
+ this.messageInput = document.getElementById('messageInput');
26
+ this.sendBtn = document.getElementById('sendBtn');
27
+ this.fileInput = document.getElementById('fileInput');
28
+ this.documentStatus = document.getElementById('documentStatus');
29
+ this.welcomeMessage = document.getElementById('welcomeMessage');
30
+
31
+ // Buttons
32
+ this.attachBtn = document.getElementById('attachBtn');
33
+ this.memoryBtn = document.getElementById('memoryBtn');
34
+ this.clearBtn = document.getElementById('clearBtn');
35
+ this.themeToggleBtn = document.getElementById('themeToggleBtn');
36
+ this.voiceBtn = document.getElementById('voiceBtn');
37
+ this.stopVoiceBtn = document.getElementById('stopVoiceBtn');
38
+ this.conversationsBtn = document.getElementById('conversationsBtn');
39
+
40
+ // Modal and sidebar
41
+ this.memoryModal = document.getElementById('memoryModal');
42
+ this.closeModalBtn = document.getElementById('closeModalBtn');
43
+ this.memoryContent = document.getElementById('memoryContent');
44
+ this.sidebar = document.getElementById('sidebar');
45
+ this.closeSidebarBtn = document.getElementById('closeSidebarBtn');
46
+
47
+ // Voice modal and loading
48
+ this.voiceModal = document.getElementById('voiceModal');
49
+ this.loadingOverlay = document.getElementById('loadingOverlay');
50
+ }
51
+
52
+ initializeSpeechRecognition() {
53
+ // Check if speech recognition is supported
54
+ if ('webkitSpeechRecognition' in window || 'SpeechRecognition' in window) {
55
+ const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
56
+ this.recognition = new SpeechRecognition();
57
+
58
+ // Configure recognition settings
59
+ this.recognition.continuous = true;
60
+ this.recognition.interimResults = true;
61
+ this.recognition.lang = 'en-US';
62
+
63
+ // Event handlers
64
+ this.recognition.onstart = () => {
65
+ console.log('🎤 Speech recognition started');
66
+ this.isListening = true;
67
+ this.voiceBtn.classList.add('voice-recording');
68
+ this.showVoiceModal();
69
+ };
70
+
71
+ this.recognition.onresult = (event) => {
72
+ let finalTranscript = '';
73
+ let interimTranscript = '';
74
+
75
+ for (let i = event.resultIndex; i < event.results.length; i++) {
76
+ const transcript = event.results[i][0].transcript;
77
+ if (event.results[i].isFinal) {
78
+ finalTranscript += transcript;
79
+ } else {
80
+ interimTranscript += transcript;
81
+ }
82
+ }
83
+
84
+ // Update input field with transcription
85
+ this.messageInput.value = finalTranscript + interimTranscript;
86
+ this.autoResizeInput();
87
+ this.updateSendButton();
88
+ };
89
+
90
+ this.recognition.onerror = (event) => {
91
+ console.error('🎤 Speech recognition error:', event.error);
92
+ this.showNotification(`Voice recognition error: ${event.error}`, 'error');
93
+ this.stopVoiceRecording();
94
+ };
95
+
96
+ this.recognition.onend = () => {
97
+ console.log('🎤 Speech recognition ended');
98
+ this.isListening = false;
99
+ this.stopVoiceRecording();
100
+ };
101
+ } else {
102
+ console.warn('🎤 Speech recognition not supported in this browser');
103
+ }
104
+ }
105
+
106
+ attachEventListeners() {
107
+ // Send message events
108
+ this.sendBtn.addEventListener('click', () => this.sendMessage());
109
+
110
+ // Shift+Enter for new line
111
+ this.messageInput.addEventListener('keydown', (e) => {
112
+ if (e.key === 'Enter') {
113
+ if (e.shiftKey) {
114
+ return;
115
+ } else {
116
+ e.preventDefault();
117
+ this.sendMessage();
118
+ }
119
+ }
120
+ });
121
+
122
+ // File upload events
123
+ this.attachBtn.addEventListener('click', () => this.fileInput.click());
124
+ this.fileInput.addEventListener('change', (e) => this.handleFileUpload(e));
125
+
126
+ // Voice events
127
+ this.voiceBtn.addEventListener('click', () => this.toggleVoiceRecording());
128
+ this.stopVoiceBtn.addEventListener('click', () => this.stopVoiceRecording());
129
+
130
+ // Close voice modal on click outside
131
+ this.voiceModal.addEventListener('click', (e) => {
132
+ if (e.target === this.voiceModal) {
133
+ this.stopVoiceRecording();
134
+ }
135
+ });
136
+
137
+ // Conversations button (Menu button)
138
+ this.conversationsBtn.addEventListener('click', () => this.toggleSidebar());
139
+
140
+ // Modal events
141
+ this.memoryBtn.addEventListener('click', () => this.showMemory());
142
+ this.closeModalBtn.addEventListener('click', () => this.hideMemory());
143
+ this.memoryModal.addEventListener('click', (e) => {
144
+ if (e.target === this.memoryModal) this.hideMemory();
145
+ });
146
+
147
+ // Clear session
148
+ this.clearBtn.addEventListener('click', () => this.clearSession());
149
+
150
+ // Sidebar events
151
+ this.closeSidebarBtn.addEventListener('click', () => this.closeSidebar());
152
+
153
+ // Auto-resize textarea
154
+ this.messageInput.addEventListener('input', () => {
155
+ this.autoResizeInput();
156
+ this.updateSendButton();
157
+ });
158
+
159
+ // Theme toggle
160
+ this.themeToggleBtn.addEventListener('click', () => this.toggleTheme());
161
+
162
+ // Web scraping button
163
+ const scrapeBtn = document.getElementById('scrapeBtn');
164
+ if (scrapeBtn) {
165
+ scrapeBtn.addEventListener('click', () => this.scrapeWebsite());
166
+ }
167
+
168
+ // Save conversation button
169
+ const saveConvBtn = document.getElementById('saveConvBtn');
170
+ if (saveConvBtn) {
171
+ saveConvBtn.addEventListener('click', () => this.saveConversation());
172
+ }
173
+
174
+ // New conversation button
175
+ const newConvBtn = document.getElementById('newConvBtn');
176
+ if (newConvBtn) {
177
+ newConvBtn.addEventListener('click', () => this.newConversation());
178
+ }
179
+ }
180
+
181
+ // Quick Action Button Helper
182
+ fillInput(text) {
183
+ this.messageInput.value = text;
184
+ this.updateSendButton();
185
+ this.autoResizeInput();
186
+ this.messageInput.focus();
187
+ }
188
+
189
+ // Toggle sidebar method
190
+ toggleSidebar() {
191
+ this.sidebar.classList.toggle('active');
192
+ if (this.sidebar.classList.contains('active')) {
193
+ this.loadConversations(); // Refresh conversations when opening
194
+ }
195
+ }
196
+
197
+ // Loading overlay methods
198
+ showLoading() {
199
+ if (this.loadingOverlay) {
200
+ this.loadingOverlay.classList.add('active');
201
+ }
202
+ }
203
+
204
+ hideLoading() {
205
+ if (this.loadingOverlay) {
206
+ this.loadingOverlay.classList.remove('active');
207
+ }
208
+ }
209
+
210
+ // Voice Recording Methods
211
+ toggleVoiceRecording() {
212
+ if (!this.recognition) {
213
+ this.showNotification('Speech recognition not supported in this browser', 'error');
214
+ return;
215
+ }
216
+
217
+ if (this.isRecording) {
218
+ this.stopVoiceRecording();
219
+ } else {
220
+ this.startVoiceRecording();
221
+ }
222
+ }
223
+
224
+ startVoiceRecording() {
225
+ if (this.isRecording || !this.recognition) return;
226
+
227
+ try {
228
+ this.isRecording = true;
229
+ this.recognition.start();
230
+ console.log('🎤 Starting voice recording...');
231
+ } catch (error) {
232
+ console.error('🎤 Error starting voice recording:', error);
233
+ this.showNotification('Failed to start voice recording', 'error');
234
+ this.isRecording = false;
235
+ }
236
+ }
237
+
238
+ stopVoiceRecording() {
239
+ if (!this.isRecording && !this.isListening) return;
240
+
241
+ try {
242
+ if (this.recognition) {
243
+ this.recognition.stop();
244
+ }
245
+
246
+ this.isRecording = false;
247
+ this.isListening = false;
248
+ this.voiceBtn.classList.remove('voice-recording');
249
+ this.hideVoiceModal();
250
+
251
+ console.log('🎤 Voice recording stopped');
252
+
253
+ // Auto-send if there's content and user preference
254
+ if (this.messageInput.value.trim()) {
255
+ this.updateSendButton();
256
+ this.messageInput.focus();
257
+ }
258
+ } catch (error) {
259
+ console.error('🎤 Error stopping voice recording:', error);
260
+ }
261
+ }
262
+
263
+ showVoiceModal() {
264
+ this.voiceModal.classList.add('active');
265
+ document.body.style.overflow = 'hidden';
266
+ }
267
+
268
+ hideVoiceModal() {
269
+ this.voiceModal.classList.remove('active');
270
+ document.body.style.overflow = 'auto';
271
+ }
272
+
273
+ async sendMessage() {
274
+ const message = this.messageInput.value.trim();
275
+ if (!message || this.isTyping) return;
276
+
277
+ // Use streaming for better UX
278
+ await this.sendMessageWithStreaming(message);
279
+ }
280
+
281
+ // Conversation Management
282
+ async loadConversations() {
283
+ try {
284
+ const response = await fetch(`${this.apiBase}/conversations`);
285
+ const data = await response.json();
286
+ if (response.ok) {
287
+ this.conversations = data.conversations;
288
+ this.updateConversationsList();
289
+ }
290
+ } catch (error) {
291
+ console.error('Error loading conversations:', error);
292
+ }
293
+ }
294
+
295
+ updateConversationsList() {
296
+ const conversationsList = document.getElementById('conversationsList');
297
+ if (!conversationsList) return;
298
+
299
+ conversationsList.innerHTML = '';
300
+ this.conversations.forEach(conversation => {
301
+ const conversationItem = document.createElement('div');
302
+ conversationItem.className = 'conversation-item';
303
+ if (conversation.id === this.currentConversationId) {
304
+ conversationItem.classList.add('active');
305
+ }
306
+
307
+ conversationItem.innerHTML = `
308
+ <div class="conversation-info">
309
+ <div class="conversation-title">${conversation.title}</div>
310
+ <div class="conversation-meta">${conversation.message_count} messages • ${new Date(conversation.last_updated).toLocaleDateString()}</div>
311
+ </div>
312
+ <div class="conversation-actions">
313
+ <button class="btn-icon" onclick="chatbot.loadConversation('${conversation.id}')" title="Load">
314
+ <i class="fas fa-folder-open"></i>
315
+ </button>
316
+ <button class="btn-icon" onclick="chatbot.deleteConversation('${conversation.id}')" title="Delete">
317
+ <i class="fas fa-trash"></i>
318
+ </button>
319
+ </div>
320
+ `;
321
+
322
+ conversationsList.appendChild(conversationItem);
323
+ });
324
+ }
325
+
326
+ async sendMessageWithStreaming(message) {
327
+ this.addMessage(message, 'user');
328
+ this.messageInput.value = '';
329
+ this.autoResizeInput();
330
+ this.updateSendButton();
331
+ this.showTypingIndicator();
332
+
333
+ try {
334
+ const response = await fetch(`${this.apiBase}/chat/stream`, {
335
+ method: 'POST',
336
+ headers: {
337
+ 'Content-Type': 'application/json',
338
+ },
339
+ body: JSON.stringify({ message: message })
340
+ });
341
+
342
+ const data = await response.json();
343
+ this.hideTypingIndicator();
344
+
345
+ if (response.ok) {
346
+ const botMessage = data.response;
347
+ const hasContext = data.has_context;
348
+
349
+ // Add bot message without any prefix
350
+ this.addMessage(botMessage, 'bot');
351
+
352
+ // Only show context notification when document context is actually used
353
+ if (hasContext && botMessage.includes('document')) {
354
+ this.showNotification('Response based on uploaded documents', 'info');
355
+ }
356
+ } else {
357
+ throw new Error(data.error || 'Failed to get response');
358
+ }
359
+ } catch (error) {
360
+ this.hideTypingIndicator();
361
+ this.addMessage(`❌ Error: ${error.message}`, 'bot');
362
+ this.showNotification(`Error: ${error.message}`, 'error');
363
+ }
364
+ }
365
+
366
+ addMessage(content, sender) {
367
+ const messageDiv = document.createElement('div');
368
+ messageDiv.className = `message ${sender} fade-in`;
369
+
370
+ const timestamp = new Date().toLocaleTimeString([], {
371
+ hour: '2-digit',
372
+ minute: '2-digit'
373
+ });
374
+
375
+ // Create avatar HTML for both user and bot
376
+ let avatarHtml = '';
377
+ if (sender === 'user') {
378
+ // User avatar with 'U'
379
+ avatarHtml = `
380
+ <div class="message-avatar user-avatar">
381
+ <span class="avatar-text">U</span>
382
+ </div>
383
+ `;
384
+ } else {
385
+ // Bot avatar with image
386
+ avatarHtml = `
387
+ <div class="message-avatar bot-avatar">
388
+ <img src="juno avatar.jpg" alt="Bot Avatar">
389
+ </div>
390
+ `;
391
+ }
392
+
393
+ messageDiv.innerHTML = `
394
+ ${avatarHtml}
395
+ <div class="message-content">
396
+ <div class="message-bubble">
397
+ <div class="message-text">${this.formatMessage(content)}</div>
398
+ </div>
399
+ <div class="message-time">${timestamp}</div>
400
+ </div>
401
+ `;
402
+
403
+ this.messagesContainer.appendChild(messageDiv);
404
+ this.scrollToBottom();
405
+
406
+ // Hide welcome message after first user message
407
+ if (sender === 'user' && this.welcomeMessage) {
408
+ this.welcomeMessage.style.display = 'none';
409
+ }
410
+ }
411
+
412
+ formatMessage(text) {
413
+ return text
414
+ .replace(/\n/g, '<br>')
415
+ .replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
416
+ .replace(/\*(.*?)\*/g, '<em>$1</em>')
417
+ .replace(/`(.*?)`/g, '<code>$1</code>');
418
+ }
419
+
420
+ showTypingIndicator() {
421
+ this.isTyping = true;
422
+ this.sendBtn.disabled = true;
423
+
424
+ const typingDiv = document.createElement('div');
425
+ typingDiv.className = 'message bot typing fade-in';
426
+ typingDiv.innerHTML = `
427
+ <div class="message-avatar bot-avatar">
428
+ <img src="juno avatar.jpg" alt="Bot Avatar">
429
+ </div>
430
+ <div class="message-content">
431
+ <div class="typing-indicator">
432
+ <div class="typing-dot"></div>
433
+ <div class="typing-dot"></div>
434
+ <div class="typing-dot"></div>
435
+ </div>
436
+ </div>
437
+ `;
438
+
439
+ this.messagesContainer.appendChild(typingDiv);
440
+ this.scrollToBottom();
441
+ }
442
+
443
+ hideTypingIndicator() {
444
+ this.isTyping = false;
445
+ this.sendBtn.disabled = false;
446
+ const typingIndicator = this.messagesContainer.querySelector('.typing');
447
+ if (typingIndicator) {
448
+ typingIndicator.remove();
449
+ }
450
+ }
451
+
452
+ handleFileUpload(event) {
453
+ const file = event.target.files[0];
454
+ if (file) {
455
+ this.processFile(file);
456
+ }
457
+ }
458
+
459
+ async processFile(file) {
460
+ if (file.type !== 'application/pdf') {
461
+ this.showNotification('Please select a PDF file', 'error');
462
+ return;
463
+ }
464
+
465
+ const formData = new FormData();
466
+ formData.append('file', file);
467
+ this.showLoading();
468
+ this.showNotification(`Uploading ${file.name}...`, 'info');
469
+
470
+ try {
471
+ const response = await fetch(`${this.apiBase}/upload`, {
472
+ method: 'POST',
473
+ body: formData
474
+ });
475
+
476
+ const data = await response.json();
477
+ this.hideLoading();
478
+
479
+ if (response.ok) {
480
+ this.hasDocuments = true;
481
+ this.updateDocumentStatus(`Processed: ${data.filename}`, true);
482
+
483
+ // Hide welcome message when document is uploaded
484
+ if (this.welcomeMessage) {
485
+ this.welcomeMessage.style.display = 'none';
486
+ }
487
+
488
+ this.addMessage(
489
+ `📄 **Document Processed Successfully**\n\n**File:** ${data.filename}\n**Length:** ${data.text_length.toLocaleString()} characters\n\n**Summary:**\n${data.summary}`,
490
+ 'bot'
491
+ );
492
+ this.showNotification('Document processed successfully!', 'success');
493
+ } else {
494
+ throw new Error(data.error || 'Failed to upload file');
495
+ }
496
+
497
+ } catch (error) {
498
+ this.hideLoading();
499
+ this.showNotification(`Upload failed: ${error.message}`, 'error');
500
+ }
501
+
502
+ this.fileInput.value = '';
503
+ }
504
+
505
+ updateDocumentStatus(message, hasDocuments) {
506
+ const iconClass = hasDocuments ? 'fas fa-file-pdf' : 'fas fa-file-pdf';
507
+ const color = hasDocuments ? 'var(--success-color)' : 'var(--text-muted)';
508
+ this.documentStatus.innerHTML = `<i class="${iconClass}" style="color: ${color};"></i><span>${message}</span>`;
509
+ }
510
+
511
+ async showMemory() {
512
+ try {
513
+ const response = await fetch(`${this.apiBase}/memory`);
514
+ const data = await response.json();
515
+
516
+ let memoryHtml = `**Session ID:** ${data.session_id}
517
+ **Chat History:** ${data.chat_history_length} messages
518
+ **Documents Loaded:** ${data.has_vectorstore ? 'Yes' : 'No'}
519
+
520
+ **Memory Data:**
521
+ ${JSON.stringify(data.memory, null, 2)}`;
522
+
523
+ this.memoryContent.innerHTML = `<pre>${memoryHtml}</pre>`;
524
+ this.memoryModal.classList.add('active');
525
+ } catch (error) {
526
+ this.showNotification(`Failed to load memory: ${error.message}`, 'error');
527
+ }
528
+ }
529
+
530
+ hideMemory() {
531
+ this.memoryModal.classList.remove('active');
532
+ }
533
+
534
+ async clearSession() {
535
+ if (!confirm('Are you sure you want to clear the current session? This will remove all chat history and uploaded documents.')) {
536
+ return;
537
+ }
538
+
539
+ try {
540
+ const response = await fetch(`${this.apiBase}/clear`, {
541
+ method: 'POST'
542
+ });
543
+
544
+ const data = await response.json();
545
+
546
+ if (response.ok) {
547
+ this.messagesContainer.innerHTML = `
548
+ <div class="welcome-message" id="welcomeMessage">
549
+ <div class="welcome-icon">
550
+ <img src="juno logo.jpg" alt="Juno Logo" class="welcome-logo">
551
+ </div>
552
+ <h2>Welcome to AI Assistant</h2>
553
+ <p>Upload documents, ask questions, or start a conversation!</p>
554
+
555
+ <div class="feature-grid">
556
+ <div class="feature-item">
557
+ <i class="fas fa-file-upload"></i>
558
+ <span>Upload PDFs</span>
559
+ </div>
560
+ <div class="feature-item">
561
+ <i class="fas fa-brain"></i>
562
+ <span>Smart Memory</span>
563
+ </div>
564
+ <div class="feature-item">
565
+ <i class="fas fa-globe"></i>
566
+ <span>Web Scraping</span>
567
+ </div>
568
+ <div class="feature-item">
569
+ <i class="fas fa-microphone"></i>
570
+ <span>Voice Input</span>
571
+ </div>
572
+ </div>
573
+
574
+ <div class="quick-actions">
575
+ <button class="quick-btn" onclick="chatbot.fillInput('Summarize this document')">
576
+ <i class="fas fa-file-text"></i> Summarize Doc
577
+ </button>
578
+ <button class="quick-btn" onclick="chatbot.fillInput('Extract key insights from this content')">
579
+ <i class="fas fa-lightbulb"></i> Key Insights
580
+ </button>
581
+ <button class="quick-btn" onclick="chatbot.fillInput('What is the main topic of this document?')">
582
+ <i class="fas fa-bullseye"></i> Main Topic
583
+ </button>
584
+ <button class="quick-btn" onclick="chatbot.fillInput('List the important points mentioned')">
585
+ <i class="fas fa-list"></i> Key Points
586
+ </button>
587
+ </div>
588
+ </div>
589
+ `;
590
+
591
+ // Re-initialize welcome message reference
592
+ this.welcomeMessage = document.getElementById('welcomeMessage');
593
+
594
+ this.hasDocuments = false;
595
+ this.updateDocumentStatus('No documents loaded', false);
596
+ this.showNotification('Session cleared successfully!', 'success');
597
+ this.currentConversationId = null;
598
+ this.loadConversations();
599
+ } else {
600
+ throw new Error(data.error || 'Failed to clear session');
601
+ }
602
+
603
+ } catch (error) {
604
+ this.showNotification(`Clear failed: ${error.message}`, 'error');
605
+ }
606
+ }
607
+
608
+ async scrapeWebsite() {
609
+ const url = prompt('Enter the website URL to scrape:');
610
+ if (!url) return;
611
+
612
+ this.showLoading();
613
+ this.showNotification('Scraping website...', 'info');
614
+
615
+ try {
616
+ const response = await fetch(`${this.apiBase}/scrape`, {
617
+ method: 'POST',
618
+ headers: {
619
+ 'Content-Type': 'application/json'
620
+ },
621
+ body: JSON.stringify({ url: url })
622
+ });
623
+
624
+ const data = await response.json();
625
+ this.hideLoading();
626
+
627
+ if (response.ok) {
628
+ this.hasDocuments = true;
629
+ this.updateDocumentStatus(`Scraped: ${data.url}`, true);
630
+
631
+ // Hide welcome message when content is scraped
632
+ if (this.welcomeMessage) {
633
+ this.welcomeMessage.style.display = 'none';
634
+ }
635
+
636
+ this.addMessage(
637
+ `🌐 **Website Scraped Successfully**\n\n**URL:** ${data.url}\n**Content Length:** ${data.content_length.toLocaleString()} characters\n\n**Summary:**\n${data.summary}`,
638
+ 'bot'
639
+ );
640
+ this.showNotification('Website scraped successfully!', 'success');
641
+ } else {
642
+ throw new Error(data.error || 'Failed to scrape website');
643
+ }
644
+
645
+ } catch (error) {
646
+ this.hideLoading();
647
+ this.showNotification(`Scraping failed: ${error.message}`, 'error');
648
+ }
649
+ }
650
+
651
+ closeSidebar() {
652
+ this.sidebar.classList.remove('active');
653
+ }
654
+
655
+ autoResizeInput() {
656
+ this.messageInput.style.height = 'auto';
657
+ this.messageInput.style.height = Math.min(this.messageInput.scrollHeight, 120) + 'px';
658
+ }
659
+
660
+ updateSendButton() {
661
+ const hasText = this.messageInput.value.trim().length > 0;
662
+ this.sendBtn.disabled = !hasText || this.isTyping;
663
+ }
664
+
665
+ scrollToBottom() {
666
+ this.messagesContainer.scrollTop = this.messagesContainer.scrollHeight;
667
+ }
668
+
669
+ showNotification(message, type = 'info', duration = 4000) {
670
+ const notification = document.createElement('div');
671
+ notification.className = `notification ${type}`;
672
+ notification.textContent = message;
673
+
674
+ document.body.appendChild(notification);
675
+
676
+ // Auto-hide notifications except errors
677
+ if (type !== 'error') {
678
+ setTimeout(() => {
679
+ if (document.body.contains(notification)) {
680
+ notification.remove();
681
+ }
682
+ }, duration);
683
+ } else {
684
+ setTimeout(() => {
685
+ if (document.body.contains(notification)) {
686
+ notification.remove();
687
+ }
688
+ }, 7000); // Errors stay longer
689
+ }
690
+ }
691
+
692
+ applyInitialTheme() {
693
+ const savedTheme = localStorage.getItem('theme') || 'light';
694
+ document.body.setAttribute('data-theme', savedTheme);
695
+ this.updateThemeIcon(savedTheme);
696
+ }
697
+
698
+ toggleTheme() {
699
+ const currentTheme = document.body.getAttribute('data-theme');
700
+ const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
701
+
702
+ document.body.setAttribute('data-theme', newTheme);
703
+ localStorage.setItem('theme', newTheme);
704
+ this.updateThemeIcon(newTheme);
705
+ }
706
+
707
+ updateThemeIcon(theme) {
708
+ const icon = this.themeToggleBtn.querySelector('i');
709
+ icon.className = theme === 'dark' ? 'fas fa-sun' : 'fas fa-moon';
710
+ }
711
+
712
+ // Conversation management methods
713
+ async saveConversation() {
714
+ const title = prompt('Enter conversation title:') || `Chat ${new Date().toLocaleString()}`;
715
+
716
+ try {
717
+ const response = await fetch(`${this.apiBase}/conversations`, {
718
+ method: 'POST',
719
+ headers: {
720
+ 'Content-Type': 'application/json'
721
+ },
722
+ body: JSON.stringify({ title: title })
723
+ });
724
+
725
+ const data = await response.json();
726
+
727
+ if (response.ok) {
728
+ this.showNotification('Conversation saved successfully!', 'success');
729
+ this.loadConversations();
730
+ } else {
731
+ throw new Error(data.error || 'Failed to save conversation');
732
+ }
733
+
734
+ } catch (error) {
735
+ this.showNotification(`Save failed: ${error.message}`, 'error');
736
+ }
737
+ }
738
+
739
+ async loadConversation(conversationId) {
740
+ try {
741
+ const response = await fetch(`${this.apiBase}/conversations/${conversationId}`);
742
+ const data = await response.json();
743
+
744
+ if (response.ok) {
745
+ this.currentConversationId = conversationId;
746
+ this.messagesContainer.innerHTML = '';
747
+
748
+ // Hide welcome message when loading conversation
749
+ if (this.welcomeMessage) {
750
+ this.welcomeMessage.style.display = 'none';
751
+ }
752
+
753
+ // Load messages
754
+ data.conversation.messages.forEach(msg => {
755
+ this.addMessage(msg.user, 'user');
756
+ this.addMessage(msg.bot, 'bot');
757
+ });
758
+
759
+ this.showNotification('Conversation loaded successfully!', 'success');
760
+ this.updateConversationsList();
761
+ this.closeSidebar();
762
+ } else {
763
+ throw new Error(data.error || 'Failed to load conversation');
764
+ }
765
+
766
+ } catch (error) {
767
+ this.showNotification(`Load failed: ${error.message}`, 'error');
768
+ }
769
+ }
770
+
771
+ async deleteConversation(conversationId) {
772
+ if (!confirm('Are you sure you want to delete this conversation?')) {
773
+ return;
774
+ }
775
+
776
+ try {
777
+ const response = await fetch(`${this.apiBase}/conversations/${conversationId}`, {
778
+ method: 'DELETE'
779
+ });
780
+
781
+ const data = await response.json();
782
+
783
+ if (response.ok) {
784
+ this.showNotification('Conversation deleted successfully!', 'success');
785
+ this.loadConversations();
786
+
787
+ if (this.currentConversationId === conversationId) {
788
+ this.currentConversationId = null;
789
+ }
790
+ } else {
791
+ throw new Error(data.error || 'Failed to delete conversation');
792
+ }
793
+
794
+ } catch (error) {
795
+ this.showNotification(`Delete failed: ${error.message}`, 'error');
796
+ }
797
+ }
798
+
799
+ newConversation() {
800
+ this.currentConversationId = null;
801
+ this.messagesContainer.innerHTML = `
802
+ <div class="welcome-message" id="welcomeMessage">
803
+ <div class="welcome-icon">
804
+ <img src="juno logo.jpg" alt="Juno Logo" class="welcome-logo">
805
+ </div>
806
+ <h2>Welcome to AI Assistant</h2>
807
+ <p>Upload documents, ask questions, or start a conversation!</p>
808
+
809
+ <div class="feature-grid">
810
+ <div class="feature-item">
811
+ <i class="fas fa-file-upload"></i>
812
+ <span>Upload PDFs</span>
813
+ </div>
814
+ <div class="feature-item">
815
+ <i class="fas fa-brain"></i>
816
+ <span>Smart Memory</span>
817
+ </div>
818
+ <div class="feature-item">
819
+ <i class="fas fa-globe"></i>
820
+ <span>Web Scraping</span>
821
+ </div>
822
+ <div class="feature-item">
823
+ <i class="fas fa-microphone"></i>
824
+ <span>Voice Input</span>
825
+ </div>
826
+ </div>
827
+
828
+ <div class="quick-actions">
829
+ <button class="quick-btn" onclick="chatbot.fillInput('Summarize this document')">
830
+ <i class="fas fa-file-text"></i> Summarize Doc
831
+ </button>
832
+ <button class="quick-btn" onclick="chatbot.fillInput('Extract key insights from this content')">
833
+ <i class="fas fa-lightbulb"></i> Key Insights
834
+ </button>
835
+ <button class="quick-btn" onclick="chatbot.fillInput('What is the main topic of this document?')">
836
+ <i class="fas fa-bullseye"></i> Main Topic
837
+ </button>
838
+ <button class="quick-btn" onclick="chatbot.fillInput('List the important points mentioned')">
839
+ <i class="fas fa-list"></i> Key Points
840
+ </button>
841
+ </div>
842
+ </div>
843
+ `;
844
+
845
+ // Re-initialize welcome message reference
846
+ this.welcomeMessage = document.getElementById('welcomeMessage');
847
+
848
+ this.updateConversationsList();
849
+ this.closeSidebar();
850
+ this.showNotification('New conversation started!', 'success');
851
+ }
852
+ }
853
+
854
+ // Initialize the chatbot when the page loads
855
+ let chatbot;
856
+ document.addEventListener('DOMContentLoaded', () => {
857
+ chatbot = new ChatbotUI();
858
+ });
styles.css ADDED
@@ -0,0 +1,1179 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ :root {
2
+ --primary-color: #667eea;
3
+ --primary-dark: #5a67d8;
4
+ --secondary-color: #764ba2;
5
+ --success-color: #48bb78;
6
+ --warning-color: #ed8936;
7
+ --error-color: #f56565;
8
+ --text-primary: #2d3748;
9
+ --text-secondary: #4a5568;
10
+ --text-muted: #718096;
11
+ --bg-primary: #ffffff;
12
+ --bg-secondary: #f7fafc;
13
+ --bg-tertiary: #edf2f7;
14
+ --bg-overlay: rgba(255, 255, 255, 0.9);
15
+ --border-color: #e2e8f0;
16
+ --border-light: #f1f5f9;
17
+ --shadow-sm: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
18
+ --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
19
+ --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
20
+ --shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1);
21
+ --border-radius: 12px;
22
+ --radius-sm: 0.375rem;
23
+ --radius-md: 0.5rem;
24
+ --radius-lg: 0.75rem;
25
+ --radius-xl: 1rem;
26
+ --radius-full: 9999px;
27
+ --transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
28
+ }
29
+
30
+ body[data-theme="dark"] {
31
+ --success-color: #38a169;
32
+ --warning-color: #dd6b20;
33
+ --error-color: #e53e3e;
34
+ --text-primary: #edf2f7;
35
+ --text-secondary: #a0aec0;
36
+ --text-muted: #718096;
37
+ --bg-primary: #1a202c;
38
+ --bg-secondary: #2d3748;
39
+ --bg-tertiary: #4a5568;
40
+ --bg-overlay: rgba(26, 32, 44, 0.9);
41
+ --border-color: #4a5568;
42
+ --border-light: #2d3748;
43
+ --shadow-sm: 0 1px 3px 0 rgba(0, 0, 0, 0.3), 0 1px 2px 0 rgba(0, 0, 0, 0.2);
44
+ --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.3), 0 2px 4px -1px rgba(0, 0, 0, 0.2);
45
+ --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.3), 0 4px 6px -2px rgba(0, 0, 0, 0.2);
46
+ }
47
+
48
+ * {
49
+ margin: 0;
50
+ padding: 0;
51
+ box-sizing: border-box;
52
+ }
53
+
54
+ body {
55
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
56
+ background: var(--bg-secondary);
57
+ min-height: 100vh;
58
+ color: var(--text-primary);
59
+ line-height: 1.6;
60
+ transition: var(--transition);
61
+ }
62
+
63
+ .app-container {
64
+ display: flex;
65
+ flex-direction: column;
66
+ height: 100vh;
67
+ width: 100%;
68
+ margin: 0;
69
+ background: var(--bg-primary);
70
+ box-shadow: var(--shadow-lg);
71
+ border-radius: 0;
72
+ overflow: hidden;
73
+ }
74
+
75
+ /* Header Styles - RESTORED PREVIOUS GRADIENT BACKGROUND */
76
+ .header {
77
+ background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
78
+ color: white;
79
+ padding: 1rem 2rem;
80
+ box-shadow: var(--shadow-sm);
81
+ }
82
+
83
+ .header-content {
84
+ display: flex;
85
+ justify-content: space-between;
86
+ align-items: center;
87
+ }
88
+
89
+ .logo {
90
+ display: flex;
91
+ align-items: center;
92
+ gap: 0.75rem;
93
+ }
94
+
95
+ .logo-image {
96
+ width: 40px;
97
+ height: 40px;
98
+ border-radius: var(--radius-lg);
99
+ object-fit: cover;
100
+ box-shadow: var(--shadow-md);
101
+ }
102
+
103
+ .logo h1 {
104
+ font-size: 1.5rem;
105
+ font-weight: 600;
106
+ margin: 0;
107
+ }
108
+
109
+ .header-actions {
110
+ display: flex;
111
+ gap: 0.75rem;
112
+ }
113
+
114
+ /* Button Styles - RESTORED PREVIOUS STYLING */
115
+ .btn-secondary {
116
+ background: rgba(255, 255, 255, 0.2);
117
+ color: white;
118
+ border: 1px solid rgba(255, 255, 255, 0.3);
119
+ padding: 0.5rem 1rem;
120
+ border-radius: 8px;
121
+ font-size: 0.875rem;
122
+ font-weight: 500;
123
+ cursor: pointer;
124
+ transition: var(--transition);
125
+ display: flex;
126
+ align-items: center;
127
+ gap: 0.5rem;
128
+ }
129
+
130
+ #themeToggleBtn, #conversationsBtn {
131
+ padding: 0.5rem 0.8rem;
132
+ }
133
+
134
+ .btn-secondary:hover {
135
+ background: rgba(255, 255, 255, 0.3);
136
+ transform: translateY(-2px);
137
+ }
138
+
139
+ .btn-icon {
140
+ background: transparent;
141
+ border: none;
142
+ padding: 0.5rem;
143
+ border-radius: 8px;
144
+ cursor: pointer;
145
+ color: var(--text-muted);
146
+ transition: var(--transition);
147
+ display: flex;
148
+ align-items: center;
149
+ justify-content: center;
150
+ }
151
+
152
+ .btn-icon:hover {
153
+ background: var(--bg-tertiary);
154
+ color: var(--primary-color);
155
+ }
156
+
157
+ /* Input Area Styling - Fixed Alignment */
158
+ .input-area {
159
+ padding: 0.75rem 2rem;
160
+ background: var(--bg-overlay);
161
+ backdrop-filter: blur(12px);
162
+ border-top: 1px solid var(--border-color);
163
+ box-shadow: 0 -4px 6px -1px rgb(0 0 0 / 0.1);
164
+ }
165
+
166
+ .input-wrapper {
167
+ display: flex;
168
+ align-items: center;
169
+ gap: 0.75rem;
170
+ background: var(--bg-primary);
171
+ border: 2px solid var(--border-color);
172
+ border-radius: var(--radius-xl);
173
+ padding: 0.75rem;
174
+ box-shadow: var(--shadow-lg);
175
+ transition: var(--transition);
176
+ min-height: 56px;
177
+ }
178
+
179
+ .input-wrapper:focus-within {
180
+ border-color: var(--primary-color);
181
+ box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
182
+ }
183
+
184
+ .input-actions-left,
185
+ .input-actions-right {
186
+ display: flex;
187
+ align-items: center;
188
+ gap: 0.5rem;
189
+ }
190
+
191
+ .action-btn {
192
+ width: 40px;
193
+ height: 40px;
194
+ border: none;
195
+ border-radius: var(--radius-lg);
196
+ cursor: pointer;
197
+ display: flex;
198
+ align-items: center;
199
+ justify-content: center;
200
+ font-size: 1rem;
201
+ transition: var(--transition);
202
+ position: relative;
203
+ overflow: hidden;
204
+ flex-shrink: 0;
205
+ }
206
+
207
+ .upload-btn,
208
+ .scrape-btn,
209
+ .voice-btn {
210
+ background: var(--bg-secondary);
211
+ color: var(--text-secondary);
212
+ border: 1px solid var(--border-color);
213
+ }
214
+
215
+ .upload-btn:hover,
216
+ .scrape-btn:hover,
217
+ .voice-btn:hover {
218
+ background: var(--bg-tertiary);
219
+ color: var(--text-primary);
220
+ transform: scale(1.05);
221
+ }
222
+
223
+ .send-btn {
224
+ background: linear-gradient(135deg, var(--primary-color), var(--primary-dark));
225
+ color: white;
226
+ box-shadow: var(--shadow-md);
227
+ }
228
+
229
+ .send-btn:enabled:hover {
230
+ transform: scale(1.05);
231
+ box-shadow: var(--shadow-lg);
232
+ }
233
+
234
+ .send-btn:disabled {
235
+ opacity: 0.5;
236
+ cursor: not-allowed;
237
+ transform: none;
238
+ }
239
+
240
+ .input-field-wrapper {
241
+ flex: 1;
242
+ position: relative;
243
+ display: flex;
244
+ align-items: center;
245
+ }
246
+
247
+ #messageInput {
248
+ width: 100%;
249
+ border: none;
250
+ outline: none;
251
+ background: transparent;
252
+ font-family: inherit;
253
+ font-size: 1rem;
254
+ color: var(--text-primary);
255
+ resize: none;
256
+ min-height: 40px;
257
+ max-height: 120px;
258
+ padding: 0.5rem 0;
259
+ line-height: 1.5;
260
+ }
261
+
262
+ #messageInput::placeholder {
263
+ color: var(--text-muted);
264
+ }
265
+
266
+ /* Voice Recording State */
267
+ .voice-recording {
268
+ background: var(--error-color) !important;
269
+ color: white !important;
270
+ animation: voicePulse 2s infinite;
271
+ }
272
+
273
+ @keyframes voicePulse {
274
+ 0% { box-shadow: 0 0 0 0 rgba(245, 101, 101, 0.7); }
275
+ 70% { box-shadow: 0 0 0 10px rgba(245, 101, 101, 0); }
276
+ 100% { box-shadow: 0 0 0 0 rgba(245, 101, 101, 0); }
277
+ }
278
+
279
+ /* Main Content */
280
+ .main-content {
281
+ display: flex;
282
+ flex: 1;
283
+ overflow: hidden;
284
+ }
285
+
286
+ .chat-container {
287
+ flex: 1;
288
+ display: flex;
289
+ flex-direction: column;
290
+ background: var(--bg-primary);
291
+ }
292
+
293
+ .chat-header {
294
+ padding: 1rem 2rem;
295
+ border-bottom: 1px solid var(--border-color);
296
+ display: flex;
297
+ justify-content: space-between;
298
+ align-items: center;
299
+ background: var(--bg-secondary);
300
+ }
301
+
302
+ .status-indicator {
303
+ display: flex;
304
+ align-items: center;
305
+ gap: 0.5rem;
306
+ font-size: 0.875rem;
307
+ color: var(--text-secondary);
308
+ }
309
+
310
+ .status-dot {
311
+ width: 8px;
312
+ height: 8px;
313
+ border-radius: 50%;
314
+ background: var(--text-muted);
315
+ }
316
+
317
+ .status-dot.active {
318
+ background: var(--success-color);
319
+ animation: pulse 2s infinite;
320
+ }
321
+
322
+ @keyframes pulse {
323
+ 0% { opacity: 1; }
324
+ 50% { opacity: 0.5; }
325
+ 100% { opacity: 1; }
326
+ }
327
+
328
+ .document-status {
329
+ display: flex;
330
+ align-items: center;
331
+ gap: 0.5rem;
332
+ font-size: 0.875rem;
333
+ color: var(--text-secondary);
334
+ }
335
+
336
+ /* Messages */
337
+ .messages-container {
338
+ flex: 1;
339
+ overflow-y: auto;
340
+ padding: 2rem;
341
+ background: var(--bg-secondary);
342
+ }
343
+
344
+ /* RESTORED PREVIOUS WELCOME MESSAGE CONTAINER */
345
+ .welcome-message {
346
+ text-align: center;
347
+ max-width: 700px;
348
+ margin: 2rem auto;
349
+ padding: 2rem;
350
+ background: var(--bg-overlay);
351
+ backdrop-filter: blur(12px);
352
+ border-radius: var(--radius-xl);
353
+ box-shadow: var(--shadow-lg);
354
+ border: 1px solid var(--border-color);
355
+ }
356
+
357
+ .welcome-icon {
358
+ margin-bottom: 1rem;
359
+ }
360
+
361
+ .welcome-logo {
362
+ width: 64px;
363
+ height: 64px;
364
+ border-radius: var(--radius-xl);
365
+ object-fit: cover;
366
+ box-shadow: var(--shadow-xl);
367
+ }
368
+
369
+ .welcome-message h2 {
370
+ margin-bottom: 0.5rem;
371
+ color: var(--text-primary);
372
+ font-weight: 600;
373
+ font-size: 1.75rem;
374
+ }
375
+
376
+ .welcome-message p {
377
+ color: var(--text-secondary);
378
+ margin-bottom: 2rem;
379
+ font-size: 1rem;
380
+ }
381
+
382
+ .feature-grid {
383
+ display: grid;
384
+ grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
385
+ gap: 1rem;
386
+ margin-bottom: 2rem;
387
+ }
388
+
389
+ .feature-item {
390
+ display: flex;
391
+ flex-direction: column;
392
+ align-items: center;
393
+ gap: 0.5rem;
394
+ padding: 1rem;
395
+ background: var(--bg-secondary);
396
+ border-radius: var(--radius-lg);
397
+ color: var(--text-secondary);
398
+ font-size: 0.875rem;
399
+ transition: transform 0.25s cubic-bezier(.4,2,.35,1.3), box-shadow 0.18s;
400
+ border: 1px solid var(--border-color);
401
+ }
402
+
403
+ .feature-item:hover {
404
+ transform: scale(1.06) rotate(-1deg);
405
+ box-shadow: 0 2px 32px 2px rgba(126,116,244,0.12);
406
+ background: var(--bg-tertiary);
407
+ }
408
+
409
+ .feature-item i {
410
+ font-size: 1.5rem;
411
+ color: var(--primary-color);
412
+ }
413
+
414
+ /* Quick Actions */
415
+ .quick-actions {
416
+ display: flex;
417
+ justify-content: center;
418
+ gap: 0.75rem;
419
+ flex-wrap: wrap;
420
+ }
421
+
422
+ .quick-btn {
423
+ background: var(--bg-overlay);
424
+ backdrop-filter: blur(12px);
425
+ border: 1px solid var(--border-color);
426
+ border-radius: var(--radius-full);
427
+ padding: 0.6rem 1.25rem;
428
+ cursor: pointer;
429
+ display: flex;
430
+ align-items: center;
431
+ gap: 0.5rem;
432
+ color: var(--text-primary);
433
+ font-weight: 500;
434
+ font-size: 0.85rem;
435
+ transition: var(--transition);
436
+ box-shadow: var(--shadow-md);
437
+ }
438
+
439
+ .quick-btn:hover {
440
+ background: var(--primary-color);
441
+ color: white;
442
+ border-color: var(--primary-color);
443
+ transform: translateY(-2px);
444
+ }
445
+
446
+ .message {
447
+ margin-bottom: 1.5rem;
448
+ display: flex;
449
+ align-items: flex-start;
450
+ gap: 0.75rem;
451
+ animation: fadeInUp 0.3s ease-out;
452
+ }
453
+
454
+ @keyframes fadeInUp {
455
+ from {
456
+ opacity: 0;
457
+ transform: translateY(20px);
458
+ }
459
+ to {
460
+ opacity: 1;
461
+ transform: translateY(0);
462
+ }
463
+ }
464
+
465
+ .message.user {
466
+ flex-direction: row-reverse;
467
+ }
468
+
469
+ /* Modern Attractive Avatars - KEEPING THE NEW BEAUTIFUL DESIGN */
470
+ .message-avatar {
471
+ width: 40px;
472
+ height: 40px;
473
+ border-radius: 50%;
474
+ flex-shrink: 0;
475
+ display: flex;
476
+ align-items: center;
477
+ justify-content: center;
478
+ overflow: hidden;
479
+ position: relative;
480
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
481
+ box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4);
482
+ border: 3px solid rgba(255, 255, 255, 0.9);
483
+ }
484
+
485
+ /* Stylish User Avatar with Modern Design */
486
+ .user-avatar {
487
+ background: linear-gradient(135deg, var(--primary-color), var(--primary-dark));
488
+ box-shadow: 0 8px 25px rgba(102, 126, 234, 0.4);
489
+ position: relative;
490
+ }
491
+
492
+ .user-avatar::before {
493
+ content: '';
494
+ position: absolute;
495
+ top: -2px;
496
+ left: -2px;
497
+ right: -2px;
498
+ bottom: -2px;
499
+ background: linear-gradient(45deg, var(--primary-color), var(--primary-dark), #667eea, #5a67d8);
500
+ border-radius: 50%;
501
+ z-index: -1;
502
+ animation: avatarGlow 3s ease-in-out infinite alternate;
503
+ }
504
+
505
+
506
+ @keyframes avatarGlow {
507
+ 0% { opacity: 0.5; transform: scale(1); }
508
+ 100% { opacity: 0.8; transform: scale(1.05); }
509
+ }
510
+
511
+ .user-avatar .avatar-text {
512
+ font-size: 1.1rem;
513
+ font-weight: 700;
514
+ color: white;
515
+ text-shadow: 0 2px 4px rgba(0,0,0,0.3);
516
+ line-height: 1;
517
+ }
518
+
519
+ /* Stylish Bot Avatar with Modern Design */
520
+ .bot-avatar {
521
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
522
+ box-shadow: 0 8px 25px rgba(102, 126, 234, 0.4);
523
+ padding: 0;
524
+ border: 3px solid rgba(255, 255, 255, 0.9);
525
+ position: relative;
526
+ }
527
+
528
+ .bot-avatar::before {
529
+ content: '';
530
+ position: absolute;
531
+ top: -3px;
532
+ left: -3px;
533
+ right: -3px;
534
+ bottom: -3px;
535
+ background: linear-gradient(45deg, #667eea, #764ba2, #f093fb, #f5576c);
536
+ border-radius: 50%;
537
+ z-index: -1;
538
+ animation: botAvatarGlow 4s ease-in-out infinite alternate;
539
+ }
540
+
541
+ @keyframes botAvatarGlow {
542
+ 0% { opacity: 0.3; }
543
+ 100% { opacity: 0.7; }
544
+ }
545
+
546
+ .bot-avatar img {
547
+ width: 100%;
548
+ height: 100%;
549
+ object-fit: cover;
550
+ border-radius: 50%;
551
+ }
552
+
553
+ /* Hover effects for avatars */
554
+ .message-avatar:hover {
555
+ transform: scale(1.1);
556
+ transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
557
+ }
558
+
559
+ .message-content {
560
+ max-width: 70%;
561
+ display: flex;
562
+ flex-direction: column;
563
+ }
564
+
565
+ .message.user .message-content {
566
+ align-items: flex-end;
567
+ }
568
+
569
+ .message.bot .message-content {
570
+ align-items: flex-start;
571
+ }
572
+
573
+ .message-bubble {
574
+ padding: 1rem 1.25rem;
575
+ border-radius: var(--radius-xl);
576
+ box-shadow: var(--shadow-lg);
577
+ word-wrap: break-word;
578
+ display: inline-block;
579
+ max-width: 100%;
580
+ text-align: left;
581
+ position: relative;
582
+ }
583
+
584
+ .message.user .message-bubble {
585
+ background: linear-gradient(135deg, var(--primary-color), var(--primary-dark));
586
+ color: white;
587
+ border-bottom-right-radius: var(--radius-sm);
588
+ }
589
+
590
+ .message.bot .message-bubble {
591
+ background: var(--bg-overlay);
592
+ backdrop-filter: blur(12px);
593
+ color: var(--text-primary);
594
+ border: 1px solid var(--border-color);
595
+ border-bottom-left-radius: var(--radius-sm);
596
+ }
597
+
598
+ .message-time {
599
+ font-size: 0.75rem;
600
+ color: var(--text-muted);
601
+ margin-top: 0.5rem;
602
+ text-align: right;
603
+ }
604
+
605
+ .message.bot .message-time {
606
+ text-align: left;
607
+ }
608
+
609
+ /* Voice Modal */
610
+ .voice-modal {
611
+ position: fixed;
612
+ top: 0;
613
+ left: 0;
614
+ right: 0;
615
+ bottom: 0;
616
+ background: rgba(15, 23, 42, 0.9);
617
+ backdrop-filter: blur(12px);
618
+ display: none;
619
+ align-items: center;
620
+ justify-content: center;
621
+ z-index: 2000;
622
+ }
623
+
624
+ .voice-modal.active {
625
+ display: flex;
626
+ }
627
+
628
+ .voice-content {
629
+ text-align: center;
630
+ color: white;
631
+ padding: 3rem;
632
+ max-width: 400px;
633
+ }
634
+
635
+ .voice-animation {
636
+ display: flex;
637
+ justify-content: center;
638
+ align-items: center;
639
+ gap: 4px;
640
+ height: 60px;
641
+ margin-bottom: 2rem;
642
+ }
643
+
644
+ .voice-wave {
645
+ width: 4px;
646
+ background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
647
+ border-radius: var(--radius-full);
648
+ animation: voiceWave 1.2s ease-in-out infinite;
649
+ }
650
+
651
+ .voice-wave:nth-child(1) { animation-delay: 0s; }
652
+ .voice-wave:nth-child(2) { animation-delay: 0.1s; }
653
+ .voice-wave:nth-child(3) { animation-delay: 0.2s; }
654
+ .voice-wave:nth-child(4) { animation-delay: 0.3s; }
655
+ .voice-wave:nth-child(5) { animation-delay: 0.4s; }
656
+
657
+ @keyframes voiceWave {
658
+ 0%, 100% {
659
+ height: 20px;
660
+ opacity: 0.5;
661
+ }
662
+ 50% {
663
+ height: 60px;
664
+ opacity: 1;
665
+ }
666
+ }
667
+
668
+ .voice-title {
669
+ font-size: 1.5rem;
670
+ font-weight: 600;
671
+ margin-bottom: 0.5rem;
672
+ }
673
+
674
+ .voice-subtitle {
675
+ font-size: 1rem;
676
+ opacity: 0.8;
677
+ margin-bottom: 2rem;
678
+ }
679
+
680
+ .stop-voice-btn {
681
+ background: var(--error-color);
682
+ color: white;
683
+ border: none;
684
+ padding: 0.75rem 1.5rem;
685
+ border-radius: var(--radius-full);
686
+ cursor: pointer;
687
+ font-size: 1rem;
688
+ font-weight: 500;
689
+ transition: var(--transition);
690
+ display: flex;
691
+ align-items: center;
692
+ gap: 0.5rem;
693
+ margin: 0 auto;
694
+ }
695
+
696
+ .stop-voice-btn:hover {
697
+ background: #e53e3e;
698
+ transform: scale(1.05);
699
+ }
700
+
701
+ /* Advanced Loading Spinner */
702
+ .loading-overlay {
703
+ position: fixed;
704
+ top: 0;
705
+ left: 0;
706
+ right: 0;
707
+ bottom: 0;
708
+ background: rgba(15, 23, 42, 0.8);
709
+ backdrop-filter: blur(8px);
710
+ display: none;
711
+ align-items: center;
712
+ justify-content: center;
713
+ z-index: 1000;
714
+ }
715
+
716
+ .loading-overlay.active {
717
+ display: flex;
718
+ }
719
+
720
+ .loading-spinner {
721
+ position: relative;
722
+ width: 80px;
723
+ height: 80px;
724
+ margin-bottom: 2rem;
725
+ }
726
+
727
+ .spinner-ring {
728
+ position: absolute;
729
+ border: 3px solid transparent;
730
+ border-top: 3px solid var(--primary-color);
731
+ border-radius: 50%;
732
+ animation: spin 1s linear infinite;
733
+ }
734
+
735
+ .spinner-ring:nth-child(1) {
736
+ width: 80px;
737
+ height: 80px;
738
+ animation-delay: 0s;
739
+ }
740
+
741
+ .spinner-ring:nth-child(2) {
742
+ width: 60px;
743
+ height: 60px;
744
+ top: 10px;
745
+ left: 10px;
746
+ border-top-color: var(--secondary-color);
747
+ animation-delay: 0.1s;
748
+ }
749
+
750
+ .spinner-ring:nth-child(3) {
751
+ width: 40px;
752
+ height: 40px;
753
+ top: 20px;
754
+ left: 20px;
755
+ border-top-color: var(--success-color);
756
+ animation-delay: 0.2s;
757
+ }
758
+
759
+ .loading-text {
760
+ color: white;
761
+ font-size: 1.125rem;
762
+ font-weight: 500;
763
+ text-align: center;
764
+ }
765
+
766
+ @keyframes spin {
767
+ 0% { transform: rotate(0deg); }
768
+ 100% { transform: rotate(360deg); }
769
+ }
770
+
771
+ /* Sidebar */
772
+ .sidebar {
773
+ width: 350px;
774
+ background: var(--bg-primary);
775
+ border-left: 1px solid var(--border-color);
776
+ display: none;
777
+ flex-direction: column;
778
+ transition: var(--transition);
779
+ transform: translateX(100%);
780
+ }
781
+
782
+ .sidebar.active {
783
+ display: flex;
784
+ transform: translateX(0);
785
+ }
786
+
787
+ .sidebar-header {
788
+ padding: 1rem 2rem;
789
+ border-bottom: 1px solid var(--border-color);
790
+ display: flex;
791
+ justify-content: space-between;
792
+ align-items: center;
793
+ background: var(--bg-secondary);
794
+ }
795
+
796
+ .sidebar-header h3 {
797
+ font-size: 1.125rem;
798
+ font-weight: 600;
799
+ color: var(--text-primary);
800
+ }
801
+
802
+ .sidebar-content {
803
+ flex: 1;
804
+ padding: 2rem;
805
+ overflow-y: auto;
806
+ }
807
+
808
+ /* Modal */
809
+ .modal {
810
+ display: none;
811
+ position: fixed;
812
+ top: 0;
813
+ left: 0;
814
+ width: 100%;
815
+ height: 100%;
816
+ background: rgba(0, 0, 0, 0.5);
817
+ z-index: 1000;
818
+ align-items: center;
819
+ justify-content: center;
820
+ }
821
+
822
+ .modal.active {
823
+ display: flex;
824
+ }
825
+
826
+ .modal-content {
827
+ background: var(--bg-primary);
828
+ border-radius: var(--border-radius);
829
+ box-shadow: var(--shadow-lg);
830
+ max-width: 600px;
831
+ width: 90%;
832
+ max-height: 80vh;
833
+ overflow: hidden;
834
+ display: flex;
835
+ flex-direction: column;
836
+ }
837
+
838
+ .modal-header {
839
+ padding: 1.5rem 2rem;
840
+ border-bottom: 1px solid var(--border-color);
841
+ display: flex;
842
+ justify-content: space-between;
843
+ align-items: center;
844
+ background: var(--bg-secondary);
845
+ }
846
+
847
+ .modal-header h3 {
848
+ font-size: 1.25rem;
849
+ font-weight: 600;
850
+ color: var(--text-primary);
851
+ }
852
+
853
+ .modal-body {
854
+ flex: 1;
855
+ padding: 2rem;
856
+ overflow-y: auto;
857
+ }
858
+
859
+ /* Improved Memory Modal Font */
860
+ #memoryContent, .modal-body pre, .modal-body code {
861
+ font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Roboto Mono', 'Consolas', 'Courier New', monospace;
862
+ font-size: 0.875rem;
863
+ line-height: 1.6;
864
+ white-space: pre-wrap;
865
+ color: var(--text-primary);
866
+ background: var(--bg-tertiary);
867
+ padding: 1rem;
868
+ border-radius: var(--radius-md);
869
+ border: 1px solid var(--border-color);
870
+ }
871
+
872
+ /* Loading Animation */
873
+ .typing-indicator {
874
+ display: flex;
875
+ align-items: center;
876
+ gap: 0.25rem;
877
+ padding: 1rem 1.25rem;
878
+ background: var(--bg-overlay);
879
+ backdrop-filter: blur(12px);
880
+ border: 1px solid var(--border-color);
881
+ border-radius: var(--radius-xl);
882
+ border-bottom-left-radius: var(--radius-sm);
883
+ box-shadow: var(--shadow-lg);
884
+ }
885
+
886
+ .typing-dot {
887
+ width: 8px;
888
+ height: 8px;
889
+ border-radius: 50%;
890
+ background: var(--text-muted);
891
+ animation: typing 1.4s infinite ease-in-out;
892
+ }
893
+
894
+ .typing-dot:nth-child(1) { animation-delay: -0.32s; }
895
+ .typing-dot:nth-child(2) { animation-delay: -0.16s; }
896
+
897
+ @keyframes typing {
898
+ 0%, 80%, 100% {
899
+ transform: scale(0);
900
+ opacity: 0.5;
901
+ }
902
+ 40% {
903
+ transform: scale(1);
904
+ opacity: 1;
905
+ }
906
+ }
907
+
908
+ /* Conversation sidebar styles */
909
+ .conversations-list {
910
+ margin-top: 1rem;
911
+ }
912
+
913
+ .conversation-item {
914
+ display: flex;
915
+ align-items: center;
916
+ justify-content: space-between;
917
+ padding: 0.75rem;
918
+ margin-bottom: 0.5rem;
919
+ background: var(--bg-secondary);
920
+ border-radius: 8px;
921
+ border: 1px solid var(--border-color);
922
+ cursor: pointer;
923
+ transition: var(--transition);
924
+ }
925
+
926
+ .conversation-item:hover {
927
+ background: var(--bg-tertiary);
928
+ transform: translateY(-2px);
929
+ }
930
+
931
+ .conversation-item.active {
932
+ background: var(--primary-color);
933
+ color: white;
934
+ border-color: var(--primary-color);
935
+ }
936
+
937
+ .conversation-info {
938
+ flex: 1;
939
+ }
940
+
941
+ .conversation-title {
942
+ font-weight: 500;
943
+ margin-bottom: 0.25rem;
944
+ }
945
+
946
+ .conversation-meta {
947
+ font-size: 0.75rem;
948
+ opacity: 0.7;
949
+ }
950
+
951
+ .conversation-actions {
952
+ display: flex;
953
+ gap: 0.25rem;
954
+ }
955
+
956
+ .conversation-actions .btn-icon {
957
+ padding: 0.25rem;
958
+ font-size: 0.75rem;
959
+ }
960
+
961
+ .sidebar-actions {
962
+ display: flex;
963
+ flex-direction: column;
964
+ gap: 0.5rem;
965
+ padding-bottom: 1rem;
966
+ border-bottom: 1px solid var(--border-color);
967
+ }
968
+
969
+ .sidebar-actions .btn-secondary {
970
+ background: var(--bg-tertiary);
971
+ color: var(--text-secondary);
972
+ border: 1px solid var(--border-color);
973
+ justify-content: center;
974
+ width: 100%;
975
+ }
976
+
977
+ .sidebar-actions .btn-secondary:hover {
978
+ background: var(--border-color);
979
+ color: var(--text-primary);
980
+ }
981
+
982
+ body[data-theme="dark"] .sidebar-actions .btn-secondary {
983
+ background: var(--bg-secondary);
984
+ color: var(--text-primary);
985
+ border-color: var(--border-color);
986
+ }
987
+
988
+ /* Scrollbar Styling */
989
+ .messages-container::-webkit-scrollbar,
990
+ .sidebar-content::-webkit-scrollbar,
991
+ .modal-body::-webkit-scrollbar {
992
+ width: 6px;
993
+ }
994
+
995
+ .messages-container::-webkit-scrollbar-track,
996
+ .sidebar-content::-webkit-scrollbar-track,
997
+ .modal-body::-webkit-scrollbar-track {
998
+ background: var(--bg-tertiary);
999
+ border-radius: 3px;
1000
+ }
1001
+
1002
+ .messages-container::-webkit-scrollbar-thumb,
1003
+ .sidebar-content::-webkit-scrollbar-thumb,
1004
+ .modal-body::-webkit-scrollbar-thumb {
1005
+ background: var(--text-muted);
1006
+ border-radius: 3px;
1007
+ }
1008
+
1009
+ .messages-container::-webkit-scrollbar-thumb:hover,
1010
+ .sidebar-content::-webkit-scrollbar-thumb:hover,
1011
+ .modal-body::-webkit-scrollbar-thumb:hover {
1012
+ background: var(--text-secondary);
1013
+ }
1014
+
1015
+ /* Responsive Design */
1016
+ @media (max-width: 768px) {
1017
+ .header {
1018
+ padding: 1rem;
1019
+ }
1020
+
1021
+ .logo h1 {
1022
+ font-size: 1.25rem;
1023
+ }
1024
+
1025
+ .header-actions {
1026
+ gap: 0.5rem;
1027
+ }
1028
+
1029
+ .chat-header {
1030
+ padding: 1rem;
1031
+ flex-direction: column;
1032
+ gap: 0.5rem;
1033
+ align-items: flex-start;
1034
+ }
1035
+
1036
+ .messages-container {
1037
+ padding: 1rem;
1038
+ }
1039
+
1040
+ .input-area {
1041
+ padding: 1rem;
1042
+ }
1043
+
1044
+ .message-content {
1045
+ max-width: 85%;
1046
+ }
1047
+
1048
+ .feature-grid {
1049
+ grid-template-columns: repeat(2, 1fr);
1050
+ }
1051
+
1052
+ .quick-actions {
1053
+ flex-direction: column;
1054
+ align-items: center;
1055
+ }
1056
+
1057
+ .sidebar {
1058
+ position: fixed;
1059
+ top: 0;
1060
+ right: -350px;
1061
+ height: 100vh;
1062
+ z-index: 100;
1063
+ box-shadow: var(--shadow-lg);
1064
+ }
1065
+
1066
+ .sidebar.active {
1067
+ right: 0;
1068
+ }
1069
+
1070
+ .modal-content {
1071
+ margin: 1rem;
1072
+ width: calc(100% - 2rem);
1073
+ }
1074
+
1075
+ .voice-content {
1076
+ padding: 2rem;
1077
+ }
1078
+
1079
+ .voice-title {
1080
+ font-size: 1.25rem;
1081
+ }
1082
+
1083
+ .message-avatar {
1084
+ width: 36px;
1085
+ height: 36px;
1086
+ }
1087
+
1088
+ .user-avatar .avatar-text {
1089
+ font-size: 1rem;
1090
+ }
1091
+ }
1092
+
1093
+ @media (max-width: 480px) {
1094
+ .input-wrapper {
1095
+ gap: 0.5rem;
1096
+ padding: 0.5rem;
1097
+ }
1098
+
1099
+ .action-btn {
1100
+ width: 36px;
1101
+ height: 36px;
1102
+ font-size: 0.875rem;
1103
+ }
1104
+
1105
+ #messageInput {
1106
+ font-size: 16px; /* Prevent zoom on iOS */
1107
+ }
1108
+
1109
+ .message-avatar {
1110
+ width: 32px;
1111
+ height: 32px;
1112
+ }
1113
+
1114
+ .user-avatar .avatar-text {
1115
+ font-size: 0.9rem;
1116
+ }
1117
+ }
1118
+
1119
+ /* Notification styles */
1120
+ .notification {
1121
+ position: fixed;
1122
+ top: 1rem;
1123
+ right: 1rem;
1124
+ padding: 0.75rem 1rem;
1125
+ border-radius: 8px;
1126
+ color: white;
1127
+ font-weight: 500;
1128
+ z-index: 1000;
1129
+ animation: slideInRight 0.3s ease-out;
1130
+ }
1131
+
1132
+ .notification.success { background: var(--success-color); }
1133
+ .notification.error { background: var(--error-color); }
1134
+ .notification.info { background: var(--primary-color); }
1135
+
1136
+ @keyframes slideInRight {
1137
+ from { transform: translateX(100%); }
1138
+ to { transform: translateX(0); }
1139
+ }
1140
+
1141
+ /* Animation Classes */
1142
+ .fade-in {
1143
+ animation: fadeIn 0.3s ease-out;
1144
+ }
1145
+
1146
+ .slide-up {
1147
+ animation: slideUp 0.3s ease-out;
1148
+ }
1149
+
1150
+ @keyframes fadeIn {
1151
+ from {
1152
+ opacity: 0;
1153
+ transform: translateY(10px);
1154
+ }
1155
+ to {
1156
+ opacity: 1;
1157
+ transform: translateY(0);
1158
+ }
1159
+ }
1160
+
1161
+ @keyframes slideUp {
1162
+ from {
1163
+ opacity: 0;
1164
+ transform: translateY(20px);
1165
+ }
1166
+ to {
1167
+ opacity: 1;
1168
+ transform: translateY(0);
1169
+ }
1170
+ }
1171
+
1172
+ /* Utility Classes */
1173
+ .hidden {
1174
+ display: none !important;
1175
+ }
1176
+
1177
+ .visible {
1178
+ display: block !important;
1179
+ }