Spaces:
Running
Running
Commit
Β·
a6ff259
1
Parent(s):
da2ab72
Seperate ui
Browse files- README.md +3 -9
- api/main.py +0 -3
- web_ui/index.html +0 -54
- web_ui/script.js +0 -185
- web_ui/style.css +0 -139
README.md
CHANGED
|
@@ -21,7 +21,6 @@ app_port: 7860
|
|
| 21 |
- Extracts text from PDFs.
|
| 22 |
- Improves retrieval speed and accuracy with semantic chunking.
|
| 23 |
- Uses `Alibaba-NLP/gte-multilingual-base` for robust multilingual support.
|
| 24 |
-
- **Interactive Web UI**: A modern, clean chat interface to interact with the assistant.
|
| 25 |
- **Chat History Management**: Maintains context across the conversation session (Reset supported).
|
| 26 |
- **FastAPI Backend**: A high-performance API to serve requests.
|
| 27 |
- **Docker Support**: Containerized for easy deployment.
|
|
@@ -29,7 +28,6 @@ app_port: 7860
|
|
| 29 |
## π οΈ Tech Stack
|
| 30 |
|
| 31 |
- **Language**: Python 3.10+
|
| 32 |
-
- **Frontend**: HTML5, CSS3, Vanilla JS
|
| 33 |
- **Backend**: FastAPI, Uvicorn
|
| 34 |
- **AI/ML**: LangChain, HuggingFace, ChromaDB, Groq
|
| 35 |
- **Tools**: `pypdf`, `sentence-transformers`, Docker
|
|
@@ -51,10 +49,6 @@ CorpGuideAI-HR-Policy-Assistant/
|
|
| 51 |
β βββ document_processor.py
|
| 52 |
β βββ vector_store.py
|
| 53 |
β βββ llm_client.py
|
| 54 |
-
βββ web_ui/ # Frontend Application
|
| 55 |
-
β βββ index.html
|
| 56 |
-
β βββ script.js
|
| 57 |
-
β βββ style.css
|
| 58 |
βββ ingest.py # Document ingestion script
|
| 59 |
βββ Dockerfile # Docker container configuration
|
| 60 |
βββ requirements.txt # Dependencies
|
|
@@ -106,7 +100,7 @@ Launch the FastAPI server:
|
|
| 106 |
uvicorn api.main:app --reload
|
| 107 |
```
|
| 108 |
|
| 109 |
-
|
| 110 |
|
| 111 |
### Option 2: Docker
|
| 112 |
|
|
@@ -120,11 +114,11 @@ uvicorn api.main:app --reload
|
|
| 120 |
```bash
|
| 121 |
docker run -p 7860:7860 --env-file .env corpguide-ai
|
| 122 |
```
|
| 123 |
-
|
| 124 |
|
| 125 |
## π API Endpoints
|
| 126 |
|
| 127 |
-
- `GET /`:
|
| 128 |
- `POST /chat`: Chat endpoint.
|
| 129 |
- Body: `{ "question": "..." }` (History managed internally)
|
| 130 |
|
|
|
|
| 21 |
- Extracts text from PDFs.
|
| 22 |
- Improves retrieval speed and accuracy with semantic chunking.
|
| 23 |
- Uses `Alibaba-NLP/gte-multilingual-base` for robust multilingual support.
|
|
|
|
| 24 |
- **Chat History Management**: Maintains context across the conversation session (Reset supported).
|
| 25 |
- **FastAPI Backend**: A high-performance API to serve requests.
|
| 26 |
- **Docker Support**: Containerized for easy deployment.
|
|
|
|
| 28 |
## π οΈ Tech Stack
|
| 29 |
|
| 30 |
- **Language**: Python 3.10+
|
|
|
|
| 31 |
- **Backend**: FastAPI, Uvicorn
|
| 32 |
- **AI/ML**: LangChain, HuggingFace, ChromaDB, Groq
|
| 33 |
- **Tools**: `pypdf`, `sentence-transformers`, Docker
|
|
|
|
| 49 |
β βββ document_processor.py
|
| 50 |
β βββ vector_store.py
|
| 51 |
β βββ llm_client.py
|
|
|
|
|
|
|
|
|
|
|
|
|
| 52 |
βββ ingest.py # Document ingestion script
|
| 53 |
βββ Dockerfile # Docker container configuration
|
| 54 |
βββ requirements.txt # Dependencies
|
|
|
|
| 100 |
uvicorn api.main:app --reload
|
| 101 |
```
|
| 102 |
|
| 103 |
+
The API will be accessible at: `http://localhost:8000`
|
| 104 |
|
| 105 |
### Option 2: Docker
|
| 106 |
|
|
|
|
| 114 |
```bash
|
| 115 |
docker run -p 7860:7860 --env-file .env corpguide-ai
|
| 116 |
```
|
| 117 |
+
The API will be accessible at: `http://localhost:7860`
|
| 118 |
|
| 119 |
## π API Endpoints
|
| 120 |
|
| 121 |
+
- `GET /`: Health check / Root.
|
| 122 |
- `POST /chat`: Chat endpoint.
|
| 123 |
- Body: `{ "question": "..." }` (History managed internally)
|
| 124 |
|
api/main.py
CHANGED
|
@@ -2,7 +2,6 @@ import logging
|
|
| 2 |
from fastapi import FastAPI, HTTPException
|
| 3 |
from contextlib import asynccontextmanager
|
| 4 |
from fastapi.middleware.cors import CORSMiddleware
|
| 5 |
-
from fastapi.staticfiles import StaticFiles
|
| 6 |
from fastapi.responses import FileResponse
|
| 7 |
from api.schemas import ChatRequest, ChatResponse, UploadResponse
|
| 8 |
from core.rag_pipeline import RagPipeline
|
|
@@ -43,8 +42,6 @@ app.add_middleware(
|
|
| 43 |
allow_headers=["*"],
|
| 44 |
)
|
| 45 |
|
| 46 |
-
app.mount("/static", StaticFiles(directory="web_ui"), name="static")
|
| 47 |
-
|
| 48 |
@app.get("/")
|
| 49 |
async def read_index():
|
| 50 |
return FileResponse("web_ui/index.html")
|
|
|
|
| 2 |
from fastapi import FastAPI, HTTPException
|
| 3 |
from contextlib import asynccontextmanager
|
| 4 |
from fastapi.middleware.cors import CORSMiddleware
|
|
|
|
| 5 |
from fastapi.responses import FileResponse
|
| 6 |
from api.schemas import ChatRequest, ChatResponse, UploadResponse
|
| 7 |
from core.rag_pipeline import RagPipeline
|
|
|
|
| 42 |
allow_headers=["*"],
|
| 43 |
)
|
| 44 |
|
|
|
|
|
|
|
| 45 |
@app.get("/")
|
| 46 |
async def read_index():
|
| 47 |
return FileResponse("web_ui/index.html")
|
web_ui/index.html
DELETED
|
@@ -1,54 +0,0 @@
|
|
| 1 |
-
<!DOCTYPE html>
|
| 2 |
-
<html lang="en" dir="ltr">
|
| 3 |
-
<head>
|
| 4 |
-
<meta charset="UTF-8" />
|
| 5 |
-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
| 6 |
-
<title>CorpGuide AI Assistant</title>
|
| 7 |
-
<link rel="stylesheet" href="/static/style.css" />
|
| 8 |
-
<link
|
| 9 |
-
rel="stylesheet"
|
| 10 |
-
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css"
|
| 11 |
-
/>
|
| 12 |
-
</head>
|
| 13 |
-
<body>
|
| 14 |
-
<div class="chat-container">
|
| 15 |
-
<div class="chat-header">
|
| 16 |
-
<div class="logo">
|
| 17 |
-
<i class="fa-solid fa-robot"></i>
|
| 18 |
-
<span>CorpGuide AI</span>
|
| 19 |
-
</div>
|
| 20 |
-
<button
|
| 21 |
-
onclick="startNewChat()"
|
| 22 |
-
class="new-chat-btn"
|
| 23 |
-
title="Start New Chat"
|
| 24 |
-
>
|
| 25 |
-
<i class="fa-solid fa-arrows-rotate"></i>
|
| 26 |
-
</button>
|
| 27 |
-
<div class="status-dot"></div>
|
| 28 |
-
</div>
|
| 29 |
-
|
| 30 |
-
<div class="chat-box" id="chat-box">
|
| 31 |
-
<div class="message bot-message">
|
| 32 |
-
<div class="msg-content">
|
| 33 |
-
Hi, I am CorpGuide AI. Ask me any question about your company's
|
| 34 |
-
policies.
|
| 35 |
-
</div>
|
| 36 |
-
</div>
|
| 37 |
-
</div>
|
| 38 |
-
|
| 39 |
-
<div class="input-area">
|
| 40 |
-
<input
|
| 41 |
-
type="text"
|
| 42 |
-
id="user-input"
|
| 43 |
-
placeholder="Ask me any question about your company's policies."
|
| 44 |
-
autocomplete="off"
|
| 45 |
-
/>
|
| 46 |
-
<button onclick="sendMessage()" id="send-btn">
|
| 47 |
-
<i class="fa-solid fa-paper-plane"></i>
|
| 48 |
-
</button>
|
| 49 |
-
</div>
|
| 50 |
-
</div>
|
| 51 |
-
|
| 52 |
-
<script src="/static/script.js"></script>
|
| 53 |
-
</body>
|
| 54 |
-
</html>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
web_ui/script.js
DELETED
|
@@ -1,185 +0,0 @@
|
|
| 1 |
-
// ==========================================
|
| 2 |
-
// Connection and Memory Settings
|
| 3 |
-
// ==========================================
|
| 4 |
-
|
| 5 |
-
// Relative URL to work both locally and on server
|
| 6 |
-
const API_URL = "/chat";
|
| 7 |
-
const RESET_URL = "/reset";
|
| 8 |
-
|
| 9 |
-
// Array to store chat history (so the model remembers context)
|
| 10 |
-
let chatHistory = [];
|
| 11 |
-
|
| 12 |
-
// Define page elements
|
| 13 |
-
const chatBox = document.getElementById('chat-box');
|
| 14 |
-
const userInput = document.getElementById('user-input');
|
| 15 |
-
const sendBtn = document.getElementById('send-btn');
|
| 16 |
-
|
| 17 |
-
// ==========================================
|
| 18 |
-
// Core Functions
|
| 19 |
-
// ==========================================
|
| 20 |
-
|
| 21 |
-
/**
|
| 22 |
-
* Function to send message and handle API interaction
|
| 23 |
-
*/
|
| 24 |
-
async function sendMessage() {
|
| 25 |
-
const question = userInput.value.trim();
|
| 26 |
-
|
| 27 |
-
// If no text, do nothing
|
| 28 |
-
if (!question) return;
|
| 29 |
-
|
| 30 |
-
// 1. Display user message in chat immediately
|
| 31 |
-
appendMessage(question, 'user');
|
| 32 |
-
userInput.value = ''; // Clear input
|
| 33 |
-
sendBtn.disabled = true; // Disable button to prevent double sending
|
| 34 |
-
|
| 35 |
-
// 2. Display "typing..." indicator
|
| 36 |
-
const loadingId = appendLoading();
|
| 37 |
-
|
| 38 |
-
try {
|
| 39 |
-
// 3. Prepare payload (question + old history)
|
| 40 |
-
const payload = {
|
| 41 |
-
question: question,
|
| 42 |
-
chat_history: chatHistory
|
| 43 |
-
};
|
| 44 |
-
|
| 45 |
-
// 4. Connect to server
|
| 46 |
-
const response = await fetch(API_URL, {
|
| 47 |
-
method: 'POST',
|
| 48 |
-
headers: { 'Content-Type': 'application/json' },
|
| 49 |
-
body: JSON.stringify(payload)
|
| 50 |
-
});
|
| 51 |
-
|
| 52 |
-
if (!response.ok) {
|
| 53 |
-
throw new Error(`Server Error: ${response.status}`);
|
| 54 |
-
}
|
| 55 |
-
|
| 56 |
-
const data = await response.json();
|
| 57 |
-
|
| 58 |
-
// 5. Remove loading indicator and show AI response
|
| 59 |
-
removeLoading(loadingId);
|
| 60 |
-
appendMessage(data.answer, 'bot', data.sources);
|
| 61 |
-
|
| 62 |
-
// 6. Update memory (for next question)
|
| 63 |
-
// Add question and answer to the list
|
| 64 |
-
chatHistory.push(["human", question]);
|
| 65 |
-
chatHistory.push(["ai", data.answer]);
|
| 66 |
-
|
| 67 |
-
} catch (error) {
|
| 68 |
-
console.error("Error:", error);
|
| 69 |
-
removeLoading(loadingId);
|
| 70 |
-
appendMessage("Sorry, an error occurred connecting to the server. Please ensure the backend is running. π", 'bot');
|
| 71 |
-
} finally {
|
| 72 |
-
// Re-enable button and focus input
|
| 73 |
-
sendBtn.disabled = false;
|
| 74 |
-
userInput.focus();
|
| 75 |
-
}
|
| 76 |
-
}
|
| 77 |
-
|
| 78 |
-
/**
|
| 79 |
-
* Function to start a new chat (Reset)
|
| 80 |
-
*/
|
| 81 |
-
async function startNewChat() {
|
| 82 |
-
// 1. Reset browser memory
|
| 83 |
-
chatHistory = [];
|
| 84 |
-
|
| 85 |
-
// 2. Clear screen (return to initial state)
|
| 86 |
-
chatBox.innerHTML = `
|
| 87 |
-
<div class="message bot-message">
|
| 88 |
-
<div class="msg-content">
|
| 89 |
-
Welcome back! π<br>Memory cleared, you can start a new topic.
|
| 90 |
-
</div>
|
| 91 |
-
</div>
|
| 92 |
-
`;
|
| 93 |
-
|
| 94 |
-
// 3. Notify server to clear memory (optional, for double confirmation)
|
| 95 |
-
// Server is stateless now, so no need to call /reset
|
| 96 |
-
console.log("Client history cleared.");
|
| 97 |
-
}
|
| 98 |
-
|
| 99 |
-
// ==========================================
|
| 100 |
-
// UI Helpers
|
| 101 |
-
// ==========================================
|
| 102 |
-
|
| 103 |
-
/**
|
| 104 |
-
* Add message to screen
|
| 105 |
-
* @param {string} text - Message text
|
| 106 |
-
* @param {string} sender - Sender ('user' or 'bot')
|
| 107 |
-
* @param {Array} sources - List of sources (optional)
|
| 108 |
-
*/
|
| 109 |
-
function appendMessage(text, sender, sources = []) {
|
| 110 |
-
const msgDiv = document.createElement('div');
|
| 111 |
-
msgDiv.classList.add('message', sender === 'user' ? 'user-message' : 'bot-message');
|
| 112 |
-
|
| 113 |
-
// Convert newlines to <br> for proper formatting
|
| 114 |
-
// Could use a library like 'marked' for full Markdown, but this is simpler
|
| 115 |
-
let formattedText = text.replace(/\n/g, '<br>');
|
| 116 |
-
|
| 117 |
-
// Convert text between ** ** to bold tags (simple implementation)
|
| 118 |
-
formattedText = formattedText.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
|
| 119 |
-
|
| 120 |
-
let html = `<div class="msg-content">${formattedText}</div>`;
|
| 121 |
-
|
| 122 |
-
/*
|
| 123 |
-
// If there are sources, add them in a small box below - DISABLED BY REQUEST
|
| 124 |
-
if (sources && sources.length > 0) {
|
| 125 |
-
// Remove duplicates from filenames
|
| 126 |
-
const uniqueSources = [...new Set(sources)];
|
| 127 |
-
html += `<div class="sources-box">π Sources: ${uniqueSources.join(', ')}</div>`;
|
| 128 |
-
}
|
| 129 |
-
*/
|
| 130 |
-
|
| 131 |
-
msgDiv.innerHTML = html;
|
| 132 |
-
chatBox.appendChild(msgDiv);
|
| 133 |
-
scrollToBottom();
|
| 134 |
-
}
|
| 135 |
-
|
| 136 |
-
/**
|
| 137 |
-
* Add loading indicator (3 moving dots)
|
| 138 |
-
*/
|
| 139 |
-
function appendLoading() {
|
| 140 |
-
const id = 'loading-' + Date.now();
|
| 141 |
-
const msgDiv = document.createElement('div');
|
| 142 |
-
msgDiv.classList.add('message', 'bot-message');
|
| 143 |
-
msgDiv.id = id;
|
| 144 |
-
msgDiv.innerHTML = `
|
| 145 |
-
<div class="msg-content">
|
| 146 |
-
<div class="typing-indicator">
|
| 147 |
-
<span></span><span></span><span></span>
|
| 148 |
-
</div>
|
| 149 |
-
</div>`;
|
| 150 |
-
chatBox.appendChild(msgDiv);
|
| 151 |
-
scrollToBottom();
|
| 152 |
-
return id;
|
| 153 |
-
}
|
| 154 |
-
|
| 155 |
-
/**
|
| 156 |
-
* Remove loading indicator
|
| 157 |
-
*/
|
| 158 |
-
function removeLoading(id) {
|
| 159 |
-
const element = document.getElementById(id);
|
| 160 |
-
if (element) element.remove();
|
| 161 |
-
}
|
| 162 |
-
|
| 163 |
-
/**
|
| 164 |
-
* Scroll to bottom of chat automatically
|
| 165 |
-
*/
|
| 166 |
-
function scrollToBottom() {
|
| 167 |
-
chatBox.scrollTop = chatBox.scrollHeight;
|
| 168 |
-
}
|
| 169 |
-
|
| 170 |
-
// ==========================================
|
| 171 |
-
// Event Listeners
|
| 172 |
-
// ==========================================
|
| 173 |
-
|
| 174 |
-
// When send button is clicked
|
| 175 |
-
sendBtn.addEventListener('click', sendMessage);
|
| 176 |
-
|
| 177 |
-
// When Enter is pressed in input box
|
| 178 |
-
userInput.addEventListener('keypress', (e) => {
|
| 179 |
-
if (e.key === 'Enter') {
|
| 180 |
-
sendMessage();
|
| 181 |
-
}
|
| 182 |
-
});
|
| 183 |
-
|
| 184 |
-
// Focus on input box when page loads
|
| 185 |
-
window.onload = () => userInput.focus();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
web_ui/style.css
DELETED
|
@@ -1,139 +0,0 @@
|
|
| 1 |
-
body {
|
| 2 |
-
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
| 3 |
-
background-color: #f0f2f5;
|
| 4 |
-
margin: 0;
|
| 5 |
-
display: flex;
|
| 6 |
-
justify-content: center;
|
| 7 |
-
align-items: center;
|
| 8 |
-
height: 100vh;
|
| 9 |
-
}
|
| 10 |
-
|
| 11 |
-
.chat-container {
|
| 12 |
-
width: 100%;
|
| 13 |
-
max-width: 500px; /* Suitable size for mobile and desktop */
|
| 14 |
-
background: white;
|
| 15 |
-
height: 90vh;
|
| 16 |
-
border-radius: 15px;
|
| 17 |
-
box-shadow: 0 10px 25px rgba(0,0,0,0.1);
|
| 18 |
-
display: flex;
|
| 19 |
-
flex-direction: column;
|
| 20 |
-
overflow: hidden;
|
| 21 |
-
}
|
| 22 |
-
|
| 23 |
-
.chat-header {
|
| 24 |
-
background: #2c3e50;
|
| 25 |
-
color: white;
|
| 26 |
-
padding: 15px;
|
| 27 |
-
display: flex;
|
| 28 |
-
align-items: center;
|
| 29 |
-
justify-content: space-between;
|
| 30 |
-
}
|
| 31 |
-
|
| 32 |
-
.new-chat-btn {
|
| 33 |
-
background: transparent;
|
| 34 |
-
border: 1px solid rgba(255, 255, 255, 0.3);
|
| 35 |
-
color: white;
|
| 36 |
-
width: 35px;
|
| 37 |
-
height: 35px;
|
| 38 |
-
border-radius: 50%;
|
| 39 |
-
cursor: pointer;
|
| 40 |
-
display: flex;
|
| 41 |
-
align-items: center;
|
| 42 |
-
justify-content: center;
|
| 43 |
-
transition: all 0.3s ease;
|
| 44 |
-
}
|
| 45 |
-
|
| 46 |
-
.new-chat-btn:hover {
|
| 47 |
-
background: rgba(255, 255, 255, 0.2);
|
| 48 |
-
transform: rotate(180deg); /* Cool rotation effect on hover */
|
| 49 |
-
}
|
| 50 |
-
|
| 51 |
-
.logo { font-size: 1.2rem; font-weight: bold; }
|
| 52 |
-
.logo i { margin-left: 10px; color: #3498db; }
|
| 53 |
-
.status-dot { width: 10px; height: 10px; background: #2ecc71; border-radius: 50%; }
|
| 54 |
-
|
| 55 |
-
.chat-box {
|
| 56 |
-
flex: 1;
|
| 57 |
-
padding: 20px;
|
| 58 |
-
overflow-y: auto;
|
| 59 |
-
background: #f9f9f9;
|
| 60 |
-
display: flex;
|
| 61 |
-
flex-direction: column;
|
| 62 |
-
gap: 15px;
|
| 63 |
-
}
|
| 64 |
-
|
| 65 |
-
.message { display: flex; flex-direction: column; max-width: 80%; }
|
| 66 |
-
.bot-message { align-self: flex-start; }
|
| 67 |
-
.user-message { align-self: flex-end; }
|
| 68 |
-
|
| 69 |
-
.msg-content {
|
| 70 |
-
padding: 12px 16px;
|
| 71 |
-
border-radius: 15px;
|
| 72 |
-
font-size: 0.95rem;
|
| 73 |
-
line-height: 1.5;
|
| 74 |
-
position: relative;
|
| 75 |
-
}
|
| 76 |
-
|
| 77 |
-
.bot-message .msg-content {
|
| 78 |
-
background: #e9ecef;
|
| 79 |
-
color: #333;
|
| 80 |
-
border-bottom-right-radius: 2px;
|
| 81 |
-
}
|
| 82 |
-
|
| 83 |
-
.user-message .msg-content {
|
| 84 |
-
background: #3498db;
|
| 85 |
-
color: white;
|
| 86 |
-
border-bottom-left-radius: 2px;
|
| 87 |
-
}
|
| 88 |
-
|
| 89 |
-
.sources-box {
|
| 90 |
-
font-size: 0.8rem;
|
| 91 |
-
color: #666;
|
| 92 |
-
margin-top: 5px;
|
| 93 |
-
background: #fff;
|
| 94 |
-
padding: 5px 10px;
|
| 95 |
-
border-radius: 5px;
|
| 96 |
-
border: 1px solid #ddd;
|
| 97 |
-
}
|
| 98 |
-
|
| 99 |
-
.input-area {
|
| 100 |
-
padding: 15px;
|
| 101 |
-
background: white;
|
| 102 |
-
border-top: 1px solid #eee;
|
| 103 |
-
display: flex;
|
| 104 |
-
gap: 10px;
|
| 105 |
-
}
|
| 106 |
-
|
| 107 |
-
input {
|
| 108 |
-
flex: 1;
|
| 109 |
-
padding: 12px;
|
| 110 |
-
border: 1px solid #ddd;
|
| 111 |
-
border-radius: 25px;
|
| 112 |
-
outline: none;
|
| 113 |
-
font-family: inherit;
|
| 114 |
-
}
|
| 115 |
-
|
| 116 |
-
button {
|
| 117 |
-
background: #2c3e50;
|
| 118 |
-
color: white;
|
| 119 |
-
border: none;
|
| 120 |
-
width: 45px;
|
| 121 |
-
height: 45px;
|
| 122 |
-
border-radius: 50%;
|
| 123 |
-
cursor: pointer;
|
| 124 |
-
transition: 0.2s;
|
| 125 |
-
}
|
| 126 |
-
|
| 127 |
-
button:hover { background: #34495e; }
|
| 128 |
-
button:disabled { background: #ccc; cursor: not-allowed; }
|
| 129 |
-
|
| 130 |
-
/* Loading Animation */
|
| 131 |
-
.typing-indicator span {
|
| 132 |
-
display: inline-block;
|
| 133 |
-
width: 6px; height: 6px;
|
| 134 |
-
background-color: #555;
|
| 135 |
-
border-radius: 50%;
|
| 136 |
-
animation: typing 1s infinite;
|
| 137 |
-
margin: 0 2px;
|
| 138 |
-
}
|
| 139 |
-
@keyframes typing { 0% {opacity: 0.3} 50% {opacity: 1} 100% {opacity: 0.3} }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|