Spaces:
Sleeping
Sleeping
Merge branch 'main' into sohank-feature
Browse files- README.md +219 -2
- multi_llm_chatbot_backend/app/api/routes.py +20 -1
- multi_llm_chatbot_backend/app/core/orchestrator.py +19 -1
- multi_llm_chatbot_backend/app/llm/embedding_client.py +11 -0
- multi_llm_chatbot_backend/app/utils/chroma_client.py +25 -0
- phd-advisor-frontend/src/components/EnhancedChatInput.js +220 -0
- phd-advisor-frontend/src/components/FileUpload.js +199 -0
- phd-advisor-frontend/src/data/advisors.js +3 -3
- phd-advisor-frontend/src/pages/ChatPage.js +96 -37
- phd-advisor-frontend/src/styles/ChatPage.css +128 -80
- phd-advisor-frontend/src/styles/EnhancedChatInput.css +602 -0
- phd-advisor-frontend/src/styles/FileUpload.css +332 -0
- phd-advisor-frontend/src/styles/components.css +24 -9
README.md
CHANGED
|
@@ -1,3 +1,220 @@
|
|
| 1 |
-
#
|
| 2 |
|
| 3 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# PhD Advisory Panel
|
| 2 |
|
| 3 |
+
An AI-powered academic guidance system that provides personalized advice through specialized advisor personas. Get diverse perspectives on your PhD journey from multiple AI advisors, each bringing unique expertise in methodology, theory, and practical guidance.
|
| 4 |
+
|
| 5 |
+
## Features
|
| 6 |
+
|
| 7 |
+
- **Multiple AI Advisor Personas**: Chat with specialized advisors including Methodist (methodology expert), Theorist (conceptual frameworks), and Pragmatist (action-focused guidance)
|
| 8 |
+
- **Document Upload Support**: Upload PDFs, Word documents, and text files to provide context for your questions
|
| 9 |
+
- **Multi-LLM Backend**: Supports both Gemini API and Ollama models with seamless provider switching
|
| 10 |
+
- **Real-time Chat Interface**: Modern, responsive chat interface with advisor-specific styling
|
| 11 |
+
- **Context-Aware Responses**: Maintains conversation history and document context across the session
|
| 12 |
+
- **Sequential Advisor Responses**: Get input from all advisors in a structured sequence
|
| 13 |
+
- **Individual Advisor Chat**: Have focused conversations with specific advisors
|
| 14 |
+
|
| 15 |
+
## Architecture
|
| 16 |
+
|
| 17 |
+
### Frontend (React)
|
| 18 |
+
- **Technology**: React 18 with modern hooks and functional components
|
| 19 |
+
- **Styling**: CSS custom properties with dark/light theme support
|
| 20 |
+
- **Components**: Modular component architecture with reusable UI elements
|
| 21 |
+
- **State Management**: React hooks for local state management
|
| 22 |
+
- **Icons**: Lucide React for consistent iconography
|
| 23 |
+
|
| 24 |
+
### Backend (FastAPI)
|
| 25 |
+
- **Framework**: FastAPI with automatic API documentation
|
| 26 |
+
- **LLM Integration**: Support for multiple providers (Gemini, Ollama)
|
| 27 |
+
- **Document Processing**: PDF, DOCX, and text file extraction
|
| 28 |
+
- **Session Management**: Global session context with file upload tracking
|
| 29 |
+
- **CORS Support**: Configured for React development server
|
| 30 |
+
|
| 31 |
+
## Quick Start
|
| 32 |
+
|
| 33 |
+
### Prerequisites
|
| 34 |
+
- Node.js 16+ and npm
|
| 35 |
+
- Python 3.8+
|
| 36 |
+
- (Optional) Gemini API key for Google's models
|
| 37 |
+
- (Optional) Ollama installation for local models
|
| 38 |
+
|
| 39 |
+
### Backend Setup
|
| 40 |
+
|
| 41 |
+
1. **Clone and navigate to backend directory**
|
| 42 |
+
```bash
|
| 43 |
+
cd multi_llm_chatbot_backend
|
| 44 |
+
```
|
| 45 |
+
|
| 46 |
+
2. **Install dependencies**
|
| 47 |
+
```bash
|
| 48 |
+
pip install -r requirements.txt
|
| 49 |
+
```
|
| 50 |
+
|
| 51 |
+
3. **Set up environment variables**
|
| 52 |
+
Create a `.env` file:
|
| 53 |
+
```env
|
| 54 |
+
GEMINI_API_KEY=your_gemini_api_key_here
|
| 55 |
+
GEMINI_MODEL=gemini-2.0-flash
|
| 56 |
+
```
|
| 57 |
+
|
| 58 |
+
4. **Start the backend server**
|
| 59 |
+
```bash
|
| 60 |
+
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000
|
| 61 |
+
```
|
| 62 |
+
|
| 63 |
+
The API will be available at `http://localhost:8000` with interactive docs at `http://localhost:8000/docs`
|
| 64 |
+
|
| 65 |
+
### Frontend Setup
|
| 66 |
+
|
| 67 |
+
1. **Navigate to frontend directory**
|
| 68 |
+
```bash
|
| 69 |
+
cd phd-advisor-frontend
|
| 70 |
+
```
|
| 71 |
+
|
| 72 |
+
2. **Install dependencies**
|
| 73 |
+
```bash
|
| 74 |
+
npm install
|
| 75 |
+
```
|
| 76 |
+
|
| 77 |
+
3. **Start the development server**
|
| 78 |
+
```bash
|
| 79 |
+
npm start
|
| 80 |
+
```
|
| 81 |
+
|
| 82 |
+
The application will open at `http://localhost:3000`
|
| 83 |
+
|
| 84 |
+
## Advisor Personas
|
| 85 |
+
|
| 86 |
+
### Methodist - Research Methodology Expert
|
| 87 |
+
- **Focus**: Research design, validity, sampling, methodological rigor
|
| 88 |
+
- **Style**: Precise, analytical, methodology-focused
|
| 89 |
+
- **Best for**: Research design questions, data collection methods, validity concerns
|
| 90 |
+
|
| 91 |
+
### Theorist - Conceptual Frameworks Expert
|
| 92 |
+
- **Focus**: Theoretical positioning, epistemological assumptions, conceptual clarity
|
| 93 |
+
- **Style**: Thoughtful, intellectually rigorous, theory-oriented
|
| 94 |
+
- **Best for**: Literature review, theoretical frameworks, conceptual development
|
| 95 |
+
|
| 96 |
+
### Pragmatist - Action-Focused Advisor
|
| 97 |
+
- **Focus**: Practical next steps, immediate actions, progress over perfection
|
| 98 |
+
- **Style**: Warm, motivational, results-oriented
|
| 99 |
+
- **Best for**: Getting unstuck, prioritizing tasks, actionable advice
|
| 100 |
+
|
| 101 |
+
## API Endpoints
|
| 102 |
+
|
| 103 |
+
### Core Chat Endpoints
|
| 104 |
+
- `POST /chat-sequential` - Get responses from all advisors in sequence
|
| 105 |
+
- `POST /chat/{persona_id}` - Chat with a specific advisor
|
| 106 |
+
- `POST /reply-to-advisor` - Reply to a specific advisor's message
|
| 107 |
+
|
| 108 |
+
### Document Management
|
| 109 |
+
- `POST /upload-document` - Upload documents (PDF, DOCX, TXT)
|
| 110 |
+
- `GET /uploaded-files` - List uploaded filenames
|
| 111 |
+
- `GET /context` - View current session context
|
| 112 |
+
|
| 113 |
+
### Provider Management
|
| 114 |
+
- `GET /current-provider` - Get current LLM provider info
|
| 115 |
+
- `POST /switch-provider` - Switch between Gemini and Ollama
|
| 116 |
+
|
| 117 |
+
### System
|
| 118 |
+
- `GET /debug/personas` - Debug persona configurations
|
| 119 |
+
- `GET /` - API health check
|
| 120 |
+
|
| 121 |
+
## Configuration
|
| 122 |
+
|
| 123 |
+
### Supported LLM Providers
|
| 124 |
+
|
| 125 |
+
**Gemini (Default)**
|
| 126 |
+
- Requires GEMINI_API_KEY environment variable
|
| 127 |
+
- Uses gemini-2.0-flash model by default
|
| 128 |
+
- Cloud-based, requires internet connection
|
| 129 |
+
|
| 130 |
+
**Ollama (Local)**
|
| 131 |
+
- Requires Ollama installation
|
| 132 |
+
- Uses llama3.2:1b model by default
|
| 133 |
+
- Runs locally, no internet required
|
| 134 |
+
|
| 135 |
+
### File Upload Limits
|
| 136 |
+
- Maximum file size: 10MB per file
|
| 137 |
+
- Total session limit: 50MB
|
| 138 |
+
- Supported formats: PDF, DOCX, TXT
|
| 139 |
+
|
| 140 |
+
### Environment Variables
|
| 141 |
+
```env
|
| 142 |
+
# Required for Gemini
|
| 143 |
+
GEMINI_API_KEY=your_api_key
|
| 144 |
+
|
| 145 |
+
# Optional configurations
|
| 146 |
+
GEMINI_MODEL=gemini-2.0-flash
|
| 147 |
+
OLLAMA_BASE_URL=http://localhost:11434
|
| 148 |
+
```
|
| 149 |
+
|
| 150 |
+
## Development
|
| 151 |
+
|
| 152 |
+
### Project Structure
|
| 153 |
+
```
|
| 154 |
+
phd-advisor-frontend/
|
| 155 |
+
├── public/
|
| 156 |
+
├── src/
|
| 157 |
+
│ ├── components/ # Reusable UI components
|
| 158 |
+
│ ├── data/ # Advisor configurations
|
| 159 |
+
│ ├── pages/ # Main page components
|
| 160 |
+
│ ├── styles/ # CSS stylesheets
|
| 161 |
+
│ └── utils/ # Helper functions
|
| 162 |
+
|
| 163 |
+
multi_llm_chatbot_backend/
|
| 164 |
+
├── app/
|
| 165 |
+
│ ├── api/ # API routes
|
| 166 |
+
│ ├── core/ # Business logic
|
| 167 |
+
│ ├── llm/ # LLM client implementations
|
| 168 |
+
│ ├── models/ # Data models
|
| 169 |
+
│ ├── tests/ # Test files
|
| 170 |
+
│ └── utils/ # Utility functions
|
| 171 |
+
```
|
| 172 |
+
|
| 173 |
+
### Adding New Advisors
|
| 174 |
+
1. Create persona in `app/api/routes.py` with system prompt
|
| 175 |
+
2. Add advisor configuration in frontend `src/data/advisors.js`
|
| 176 |
+
3. Update advisor styling in `src/styles/`
|
| 177 |
+
|
| 178 |
+
### Testing Document Upload
|
| 179 |
+
Use the test script:
|
| 180 |
+
```bash
|
| 181 |
+
cd multi_llm_chatbot_backend/app/tests
|
| 182 |
+
python test_document_upload.py
|
| 183 |
+
```
|
| 184 |
+
|
| 185 |
+
## Usage Examples
|
| 186 |
+
|
| 187 |
+
### Starting a Conversation
|
| 188 |
+
1. Navigate to the home page
|
| 189 |
+
2. Click "Start Conversation"
|
| 190 |
+
3. Type your PhD-related question
|
| 191 |
+
4. Receive responses from all three advisors
|
| 192 |
+
|
| 193 |
+
### Uploading Documents
|
| 194 |
+
1. In the chat interface, click the upload button
|
| 195 |
+
2. Select your PDF, DOCX, or TXT file
|
| 196 |
+
3. The document content will be added to the conversation context
|
| 197 |
+
4. Ask questions about your uploaded documents
|
| 198 |
+
|
| 199 |
+
### Switching LLM Providers
|
| 200 |
+
```bash
|
| 201 |
+
curl -X POST "http://localhost:8000/switch-provider" \
|
| 202 |
+
-H "Content-Type: application/json" \
|
| 203 |
+
-d '{"provider": "ollama"}'
|
| 204 |
+
```
|
| 205 |
+
|
| 206 |
+
## Contributing
|
| 207 |
+
|
| 208 |
+
1. Fork the repository
|
| 209 |
+
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
|
| 210 |
+
3. Commit your changes (`git commit -m 'Add amazing feature'`)
|
| 211 |
+
4. Push to the branch (`git push origin feature/amazing-feature`)
|
| 212 |
+
5. Open a Pull Request
|
| 213 |
+
|
| 214 |
+
|
| 215 |
+
## Support
|
| 216 |
+
|
| 217 |
+
- Check the API documentation at `http://localhost:8000/docs`
|
| 218 |
+
- Review the debug endpoints for troubleshooting
|
| 219 |
+
- Ensure all environment variables are properly configured
|
| 220 |
+
- Verify that your LLM provider (Gemini/Ollama) is accessible
|
multi_llm_chatbot_backend/app/api/routes.py
CHANGED
|
@@ -13,6 +13,9 @@ from pydantic import BaseModel
|
|
| 13 |
from typing import Optional, List
|
| 14 |
from fastapi import UploadFile, File
|
| 15 |
from app.utils.document_extractor import extract_text_from_file
|
|
|
|
|
|
|
|
|
|
| 16 |
from app.utils.file_limits import is_within_upload_limit
|
| 17 |
|
| 18 |
router = APIRouter()
|
|
@@ -380,6 +383,22 @@ async def debug_personas():
|
|
| 380 |
"current_provider": current_provider
|
| 381 |
}
|
| 382 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 383 |
@router.get("/uploaded-files")
|
| 384 |
def get_uploaded_filenames():
|
| 385 |
-
return {"files": session_context.uploaded_files}
|
|
|
|
| 13 |
from typing import Optional, List
|
| 14 |
from fastapi import UploadFile, File
|
| 15 |
from app.utils.document_extractor import extract_text_from_file
|
| 16 |
+
from app.core.orchestrator import answer_with_persona_context
|
| 17 |
+
from app.utils.chroma_client import add_persona_doc
|
| 18 |
+
import hashlib
|
| 19 |
from app.utils.file_limits import is_within_upload_limit
|
| 20 |
|
| 21 |
router = APIRouter()
|
|
|
|
| 383 |
"current_provider": current_provider
|
| 384 |
}
|
| 385 |
|
| 386 |
+
class PersonaQuery(BaseModel):
|
| 387 |
+
question: str
|
| 388 |
+
persona: str
|
| 389 |
+
|
| 390 |
+
@router.post("/ask/")
|
| 391 |
+
async def ask_question(query: PersonaQuery):
|
| 392 |
+
response = await answer_with_persona_context(query.question, query.persona)
|
| 393 |
+
|
| 394 |
+
# Store Q&A in vector DB
|
| 395 |
+
combined_text = f"Q: {query.question}\nA: {response}"
|
| 396 |
+
doc_id = hashlib.md5(combined_text.encode()).hexdigest() # Create a unique doc ID
|
| 397 |
+
|
| 398 |
+
add_persona_doc(combined_text, query.persona, doc_id)
|
| 399 |
+
|
| 400 |
+
return {"response": response}
|
| 401 |
+
|
| 402 |
@router.get("/uploaded-files")
|
| 403 |
def get_uploaded_filenames():
|
| 404 |
+
return {"files": session_context.uploaded_files}
|
multi_llm_chatbot_backend/app/core/orchestrator.py
CHANGED
|
@@ -1,5 +1,8 @@
|
|
| 1 |
from app.models.persona import Persona
|
| 2 |
from typing import List
|
|
|
|
|
|
|
|
|
|
| 3 |
|
| 4 |
class ChatOrchestrator:
|
| 5 |
def __init__(self):
|
|
@@ -20,4 +23,19 @@ class ChatOrchestrator:
|
|
| 20 |
# I have created this function to be a placeholder for the actual logic of response sequencing
|
| 21 |
# This logic can be replaced with something smarter like a LLM deciding order based on chat context
|
| 22 |
|
| 23 |
-
return self.personas
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
from app.models.persona import Persona
|
| 2 |
from typing import List
|
| 3 |
+
from app.utils.chroma_client import query_persona_knowledge
|
| 4 |
+
from app.llm.gemini_client import GeminiClient
|
| 5 |
+
from app.llm.llm_client import LLMClient
|
| 6 |
|
| 7 |
class ChatOrchestrator:
|
| 8 |
def __init__(self):
|
|
|
|
| 23 |
# I have created this function to be a placeholder for the actual logic of response sequencing
|
| 24 |
# This logic can be replaced with something smarter like a LLM deciding order based on chat context
|
| 25 |
|
| 26 |
+
return self.personas
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
async def answer_with_persona_context(question: str, persona: str) -> str:
|
| 31 |
+
llm: LLMClient = GeminiClient()
|
| 32 |
+
context_chunks = query_persona_knowledge(question, persona)
|
| 33 |
+
context = "\n".join(context_chunks) if context_chunks else "No relevant info found."
|
| 34 |
+
|
| 35 |
+
system_prompt = f"You are a helpful assistant responding as a {persona}."
|
| 36 |
+
message_context = [
|
| 37 |
+
{"role": "system", "content": system_prompt},
|
| 38 |
+
{"role": "user", "content": f"Context:\n{context}\n\nQuestion:\n{question}"}
|
| 39 |
+
]
|
| 40 |
+
|
| 41 |
+
return await llm.generate(system_prompt, message_context)
|
multi_llm_chatbot_backend/app/llm/embedding_client.py
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from sentence_transformers import SentenceTransformer
|
| 2 |
+
import os
|
| 3 |
+
|
| 4 |
+
API_KEY = os.getenv("GEMINI_API_KEY")
|
| 5 |
+
|
| 6 |
+
# Using a compact, fast model good for semantic search
|
| 7 |
+
model = SentenceTransformer("all-MiniLM-L6-v2")
|
| 8 |
+
|
| 9 |
+
def get_embedding(text: str) -> list[float]:
|
| 10 |
+
embedding = model.encode(text, convert_to_numpy=True)
|
| 11 |
+
return embedding.tolist()
|
multi_llm_chatbot_backend/app/utils/chroma_client.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import chromadb
|
| 2 |
+
from chromadb.config import Settings
|
| 3 |
+
from app.llm.embedding_client import get_embedding
|
| 4 |
+
|
| 5 |
+
client = chromadb.PersistentClient(path="./chroma_storage")
|
| 6 |
+
|
| 7 |
+
collection = client.get_or_create_collection("persona_knowledge")
|
| 8 |
+
|
| 9 |
+
def add_persona_doc(text: str, persona: str, doc_id: str):
|
| 10 |
+
embedding = get_embedding(text)
|
| 11 |
+
collection.add(
|
| 12 |
+
documents=[text],
|
| 13 |
+
embeddings=[embedding],
|
| 14 |
+
metadatas=[{"persona": persona}],
|
| 15 |
+
ids=[doc_id]
|
| 16 |
+
)
|
| 17 |
+
|
| 18 |
+
def query_persona_knowledge(query: str, persona: str):
|
| 19 |
+
query_embedding = get_embedding(query)
|
| 20 |
+
results = collection.query(
|
| 21 |
+
query_embeddings=[query_embedding],
|
| 22 |
+
n_results=3,
|
| 23 |
+
where={"persona": persona}
|
| 24 |
+
)
|
| 25 |
+
return results['documents'][0] if results['documents'] else []
|
phd-advisor-frontend/src/components/EnhancedChatInput.js
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React, { useState, useRef, useEffect } from 'react';
|
| 2 |
+
import { Send, Paperclip, FileText, X, Trash2, Download } from 'lucide-react';
|
| 3 |
+
import FileUpload from './FileUpload';
|
| 4 |
+
|
| 5 |
+
const EnhancedChatInput = ({
|
| 6 |
+
onSendMessage,
|
| 7 |
+
onFileUploaded,
|
| 8 |
+
uploadedDocuments = [],
|
| 9 |
+
isLoading,
|
| 10 |
+
placeholder = "Ask your advisors anything about your PhD journey..."
|
| 11 |
+
}) => {
|
| 12 |
+
const [inputMessage, setInputMessage] = useState('');
|
| 13 |
+
const [showUpload, setShowUpload] = useState(false);
|
| 14 |
+
const [showDocuments, setShowDocuments] = useState(false);
|
| 15 |
+
const [isUploading, setIsUploading] = useState(false);
|
| 16 |
+
const textareaRef = useRef(null);
|
| 17 |
+
|
| 18 |
+
const handleSend = () => {
|
| 19 |
+
if (!inputMessage.trim() || isLoading || isUploading) return;
|
| 20 |
+
|
| 21 |
+
onSendMessage(inputMessage);
|
| 22 |
+
setInputMessage('');
|
| 23 |
+
if (textareaRef.current) {
|
| 24 |
+
textareaRef.current.style.height = 'auto';
|
| 25 |
+
}
|
| 26 |
+
};
|
| 27 |
+
|
| 28 |
+
const handleKeyPress = (e) => {
|
| 29 |
+
if (e.key === 'Enter' && !e.shiftKey) {
|
| 30 |
+
e.preventDefault();
|
| 31 |
+
handleSend();
|
| 32 |
+
}
|
| 33 |
+
};
|
| 34 |
+
|
| 35 |
+
const handleFileUploaded = (file, response) => {
|
| 36 |
+
setIsUploading(false);
|
| 37 |
+
setShowUpload(false);
|
| 38 |
+
|
| 39 |
+
if (onFileUploaded) {
|
| 40 |
+
onFileUploaded(file, response);
|
| 41 |
+
}
|
| 42 |
+
};
|
| 43 |
+
|
| 44 |
+
const handleUploadStart = () => {
|
| 45 |
+
setIsUploading(true);
|
| 46 |
+
};
|
| 47 |
+
|
| 48 |
+
const toggleUpload = () => {
|
| 49 |
+
if (!isUploading) {
|
| 50 |
+
setShowUpload(!showUpload);
|
| 51 |
+
setShowDocuments(false); // Close documents panel when opening upload
|
| 52 |
+
}
|
| 53 |
+
};
|
| 54 |
+
|
| 55 |
+
const toggleDocuments = () => {
|
| 56 |
+
setShowDocuments(!showDocuments);
|
| 57 |
+
setShowUpload(false); // Close upload panel when opening documents
|
| 58 |
+
};
|
| 59 |
+
|
| 60 |
+
// Auto-resize textarea
|
| 61 |
+
useEffect(() => {
|
| 62 |
+
if (textareaRef.current) {
|
| 63 |
+
textareaRef.current.style.height = 'auto';
|
| 64 |
+
textareaRef.current.style.height = textareaRef.current.scrollHeight + 'px';
|
| 65 |
+
}
|
| 66 |
+
}, [inputMessage]);
|
| 67 |
+
|
| 68 |
+
const formatFileSize = (bytes) => {
|
| 69 |
+
if (bytes === 0) return '0 Bytes';
|
| 70 |
+
const k = 1024;
|
| 71 |
+
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
| 72 |
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
| 73 |
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
| 74 |
+
};
|
| 75 |
+
|
| 76 |
+
const getFileIcon = (type) => {
|
| 77 |
+
if (type.includes('pdf')) return '📄';
|
| 78 |
+
if (type.includes('word') || type.includes('document')) return '📝';
|
| 79 |
+
if (type.includes('text')) return '📃';
|
| 80 |
+
return '📄';
|
| 81 |
+
};
|
| 82 |
+
|
| 83 |
+
const formatUploadTime = (date) => {
|
| 84 |
+
return new Date(date).toLocaleString([], {
|
| 85 |
+
month: 'short',
|
| 86 |
+
day: 'numeric',
|
| 87 |
+
hour: '2-digit',
|
| 88 |
+
minute: '2-digit'
|
| 89 |
+
});
|
| 90 |
+
};
|
| 91 |
+
|
| 92 |
+
const isDisabled = isLoading || isUploading;
|
| 93 |
+
const canSend = inputMessage.trim() && !isDisabled;
|
| 94 |
+
|
| 95 |
+
return (
|
| 96 |
+
<div className="enhanced-chat-input-container">
|
| 97 |
+
{/* File Upload Area */}
|
| 98 |
+
{showUpload && (
|
| 99 |
+
<div className="floating-upload-section">
|
| 100 |
+
<FileUpload
|
| 101 |
+
onFileUploaded={handleFileUploaded}
|
| 102 |
+
isUploading={isUploading}
|
| 103 |
+
onUploadStart={handleUploadStart}
|
| 104 |
+
/>
|
| 105 |
+
</div>
|
| 106 |
+
)}
|
| 107 |
+
|
| 108 |
+
{/* Documents Viewer Panel */}
|
| 109 |
+
{showDocuments && (
|
| 110 |
+
<div className="floating-documents-section">
|
| 111 |
+
<div className="documents-header">
|
| 112 |
+
<div className="documents-title">
|
| 113 |
+
<FileText size={16} />
|
| 114 |
+
<span>Uploaded Documents ({uploadedDocuments.length})</span>
|
| 115 |
+
</div>
|
| 116 |
+
<button
|
| 117 |
+
onClick={() => setShowDocuments(false)}
|
| 118 |
+
className="close-documents-btn"
|
| 119 |
+
>
|
| 120 |
+
<X size={16} />
|
| 121 |
+
</button>
|
| 122 |
+
</div>
|
| 123 |
+
|
| 124 |
+
<div className="documents-list">
|
| 125 |
+
{uploadedDocuments.length === 0 ? (
|
| 126 |
+
<div className="no-documents">
|
| 127 |
+
<FileText size={24} />
|
| 128 |
+
<p>No documents uploaded yet</p>
|
| 129 |
+
<span>Upload documents to reference them in your conversations</span>
|
| 130 |
+
</div>
|
| 131 |
+
) : (
|
| 132 |
+
uploadedDocuments.map((doc) => (
|
| 133 |
+
<div key={doc.id} className="document-item">
|
| 134 |
+
<div className="document-icon">
|
| 135 |
+
{getFileIcon(doc.type)}
|
| 136 |
+
</div>
|
| 137 |
+
<div className="document-info">
|
| 138 |
+
<div className="document-name">{doc.name}</div>
|
| 139 |
+
<div className="document-details">
|
| 140 |
+
{formatFileSize(doc.size)} • {formatUploadTime(doc.uploadTime)}
|
| 141 |
+
</div>
|
| 142 |
+
</div>
|
| 143 |
+
<div className="document-actions">
|
| 144 |
+
<button
|
| 145 |
+
className="document-action-btn"
|
| 146 |
+
title="Remove document"
|
| 147 |
+
onClick={() => {
|
| 148 |
+
// TODO: Implement remove functionality
|
| 149 |
+
console.log('Remove document:', doc.id);
|
| 150 |
+
}}
|
| 151 |
+
>
|
| 152 |
+
<Trash2 size={14} />
|
| 153 |
+
</button>
|
| 154 |
+
</div>
|
| 155 |
+
</div>
|
| 156 |
+
))
|
| 157 |
+
)}
|
| 158 |
+
</div>
|
| 159 |
+
</div>
|
| 160 |
+
)}
|
| 161 |
+
|
| 162 |
+
{/* Main Input Box */}
|
| 163 |
+
<div className="floating-input-box">
|
| 164 |
+
{/* Text Input Row */}
|
| 165 |
+
<div className="text-input-row">
|
| 166 |
+
<textarea
|
| 167 |
+
ref={textareaRef}
|
| 168 |
+
value={inputMessage}
|
| 169 |
+
onChange={(e) => setInputMessage(e.target.value)}
|
| 170 |
+
onKeyPress={handleKeyPress}
|
| 171 |
+
placeholder={placeholder}
|
| 172 |
+
className="main-textarea"
|
| 173 |
+
disabled={isDisabled}
|
| 174 |
+
rows={1}
|
| 175 |
+
/>
|
| 176 |
+
</div>
|
| 177 |
+
|
| 178 |
+
{/* Controls Row */}
|
| 179 |
+
<div className="controls-row">
|
| 180 |
+
{/* Left - File Controls */}
|
| 181 |
+
<div className="file-controls">
|
| 182 |
+
<button
|
| 183 |
+
onClick={toggleUpload}
|
| 184 |
+
className={`add-docs-btn ${showUpload ? 'active' : ''}`}
|
| 185 |
+
disabled={isUploading}
|
| 186 |
+
type="button"
|
| 187 |
+
>
|
| 188 |
+
<Paperclip size={16} />
|
| 189 |
+
<span>Add documents</span>
|
| 190 |
+
</button>
|
| 191 |
+
|
| 192 |
+
{uploadedDocuments.length > 0 && (
|
| 193 |
+
<button
|
| 194 |
+
onClick={toggleDocuments}
|
| 195 |
+
className={`view-docs-btn ${showDocuments ? 'active' : ''}`}
|
| 196 |
+
type="button"
|
| 197 |
+
title={`View ${uploadedDocuments.length} uploaded document${uploadedDocuments.length !== 1 ? 's' : ''}`}
|
| 198 |
+
>
|
| 199 |
+
<FileText size={16} />
|
| 200 |
+
<span className="docs-count">{uploadedDocuments.length}</span>
|
| 201 |
+
</button>
|
| 202 |
+
)}
|
| 203 |
+
</div>
|
| 204 |
+
|
| 205 |
+
{/* Right - Send Button */}
|
| 206 |
+
<button
|
| 207 |
+
onClick={handleSend}
|
| 208 |
+
disabled={!canSend}
|
| 209 |
+
className={`send-button ${canSend ? 'enabled' : 'disabled'}`}
|
| 210 |
+
type="button"
|
| 211 |
+
>
|
| 212 |
+
<Send size={16} />
|
| 213 |
+
</button>
|
| 214 |
+
</div>
|
| 215 |
+
</div>
|
| 216 |
+
</div>
|
| 217 |
+
);
|
| 218 |
+
};
|
| 219 |
+
|
| 220 |
+
export default EnhancedChatInput;
|
phd-advisor-frontend/src/components/FileUpload.js
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React, { useState, useRef } from 'react';
|
| 2 |
+
import { Upload, FileText, File, X, CheckCircle, AlertCircle } from 'lucide-react';
|
| 3 |
+
import { useTheme } from '../contexts/ThemeContext';
|
| 4 |
+
import '../styles/FileUpload.css'
|
| 5 |
+
|
| 6 |
+
const FileUpload = ({ onFileUploaded, isUploading, onUploadStart }) => {
|
| 7 |
+
const [dragActive, setDragActive] = useState(false);
|
| 8 |
+
const [uploadStatus, setUploadStatus] = useState(null); // 'success', 'error', null
|
| 9 |
+
const [uploadMessage, setUploadMessage] = useState('');
|
| 10 |
+
const [selectedFile, setSelectedFile] = useState(null);
|
| 11 |
+
const fileInputRef = useRef(null);
|
| 12 |
+
const { isDark } = useTheme();
|
| 13 |
+
|
| 14 |
+
const supportedTypes = {
|
| 15 |
+
'application/pdf': { ext: 'PDF', icon: FileText, color: '#EF4444' },
|
| 16 |
+
'application/vnd.openxmlformats-officedocument.wordprocessingml.document': { ext: 'DOCX', icon: File, color: '#3B82F6' },
|
| 17 |
+
'text/plain': { ext: 'TXT', icon: FileText, color: '#10B981' }
|
| 18 |
+
};
|
| 19 |
+
|
| 20 |
+
const validateFile = (file) => {
|
| 21 |
+
if (!supportedTypes[file.type]) {
|
| 22 |
+
return { valid: false, error: 'Only PDF, DOCX, and TXT files are supported.' };
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
if (file.size > 10 * 1024 * 1024) { // 10MB limit
|
| 26 |
+
return { valid: false, error: 'File size must be less than 10MB.' };
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
return { valid: true };
|
| 30 |
+
};
|
| 31 |
+
|
| 32 |
+
const uploadFile = async (file) => {
|
| 33 |
+
const validation = validateFile(file);
|
| 34 |
+
if (!validation.valid) {
|
| 35 |
+
setUploadStatus('error');
|
| 36 |
+
setUploadMessage(validation.error);
|
| 37 |
+
return;
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
setSelectedFile(file);
|
| 41 |
+
onUploadStart && onUploadStart();
|
| 42 |
+
|
| 43 |
+
const formData = new FormData();
|
| 44 |
+
formData.append('file', file);
|
| 45 |
+
|
| 46 |
+
try {
|
| 47 |
+
const response = await fetch('http://localhost:8000/upload-document', {
|
| 48 |
+
method: 'POST',
|
| 49 |
+
body: formData,
|
| 50 |
+
});
|
| 51 |
+
|
| 52 |
+
if (response.ok) {
|
| 53 |
+
const data = await response.json();
|
| 54 |
+
setUploadStatus('success');
|
| 55 |
+
setUploadMessage(`${file.name} uploaded successfully and added to context.`);
|
| 56 |
+
onFileUploaded && onFileUploaded(file, data);
|
| 57 |
+
|
| 58 |
+
// Auto-clear success message after 5 seconds
|
| 59 |
+
setTimeout(() => {
|
| 60 |
+
setUploadStatus(null);
|
| 61 |
+
setSelectedFile(null);
|
| 62 |
+
setUploadMessage('');
|
| 63 |
+
}, 5000);
|
| 64 |
+
} else {
|
| 65 |
+
const errorData = await response.json();
|
| 66 |
+
throw new Error(errorData.detail || 'Upload failed');
|
| 67 |
+
}
|
| 68 |
+
} catch (error) {
|
| 69 |
+
setUploadStatus('error');
|
| 70 |
+
setUploadMessage(`Upload failed: ${error.message}`);
|
| 71 |
+
console.error('Upload error:', error);
|
| 72 |
+
}
|
| 73 |
+
};
|
| 74 |
+
|
| 75 |
+
const handleDrag = (e) => {
|
| 76 |
+
e.preventDefault();
|
| 77 |
+
e.stopPropagation();
|
| 78 |
+
if (e.type === 'dragenter' || e.type === 'dragover') {
|
| 79 |
+
setDragActive(true);
|
| 80 |
+
} else if (e.type === 'dragleave') {
|
| 81 |
+
setDragActive(false);
|
| 82 |
+
}
|
| 83 |
+
};
|
| 84 |
+
|
| 85 |
+
const handleDrop = (e) => {
|
| 86 |
+
e.preventDefault();
|
| 87 |
+
e.stopPropagation();
|
| 88 |
+
setDragActive(false);
|
| 89 |
+
|
| 90 |
+
if (isUploading) return;
|
| 91 |
+
|
| 92 |
+
const files = Array.from(e.dataTransfer.files);
|
| 93 |
+
if (files.length > 0) {
|
| 94 |
+
uploadFile(files[0]);
|
| 95 |
+
}
|
| 96 |
+
};
|
| 97 |
+
|
| 98 |
+
const handleFileSelect = (e) => {
|
| 99 |
+
const files = Array.from(e.target.files);
|
| 100 |
+
if (files.length > 0) {
|
| 101 |
+
uploadFile(files[0]);
|
| 102 |
+
}
|
| 103 |
+
};
|
| 104 |
+
|
| 105 |
+
const openFileDialog = () => {
|
| 106 |
+
if (!isUploading) {
|
| 107 |
+
fileInputRef.current?.click();
|
| 108 |
+
}
|
| 109 |
+
};
|
| 110 |
+
|
| 111 |
+
const clearStatus = () => {
|
| 112 |
+
setUploadStatus(null);
|
| 113 |
+
setSelectedFile(null);
|
| 114 |
+
setUploadMessage('');
|
| 115 |
+
};
|
| 116 |
+
|
| 117 |
+
const getFileIcon = (file) => {
|
| 118 |
+
const fileInfo = supportedTypes[file.type];
|
| 119 |
+
if (fileInfo) {
|
| 120 |
+
const Icon = fileInfo.icon;
|
| 121 |
+
return <Icon size={16} style={{ color: fileInfo.color }} />;
|
| 122 |
+
}
|
| 123 |
+
return <File size={16} />;
|
| 124 |
+
};
|
| 125 |
+
|
| 126 |
+
return (
|
| 127 |
+
<div className="file-upload-container">
|
| 128 |
+
{/* Upload Status Banner */}
|
| 129 |
+
{uploadStatus && (
|
| 130 |
+
<div className={`upload-status ${uploadStatus}`}>
|
| 131 |
+
<div className="status-content">
|
| 132 |
+
{uploadStatus === 'success' ? (
|
| 133 |
+
<CheckCircle size={16} className="status-icon success" />
|
| 134 |
+
) : (
|
| 135 |
+
<AlertCircle size={16} className="status-icon error" />
|
| 136 |
+
)}
|
| 137 |
+
<span className="status-message">{uploadMessage}</span>
|
| 138 |
+
<button onClick={clearStatus} className="status-close">
|
| 139 |
+
<X size={14} />
|
| 140 |
+
</button>
|
| 141 |
+
</div>
|
| 142 |
+
</div>
|
| 143 |
+
)}
|
| 144 |
+
|
| 145 |
+
{/* Upload Area */}
|
| 146 |
+
<div
|
| 147 |
+
className={`file-upload-area ${dragActive ? 'drag-active' : ''} ${isUploading ? 'uploading' : ''}`}
|
| 148 |
+
onDragEnter={handleDrag}
|
| 149 |
+
onDragLeave={handleDrag}
|
| 150 |
+
onDragOver={handleDrag}
|
| 151 |
+
onDrop={handleDrop}
|
| 152 |
+
onClick={openFileDialog}
|
| 153 |
+
>
|
| 154 |
+
<input
|
| 155 |
+
ref={fileInputRef}
|
| 156 |
+
type="file"
|
| 157 |
+
onChange={handleFileSelect}
|
| 158 |
+
accept=".pdf,.docx,.txt"
|
| 159 |
+
style={{ display: 'none' }}
|
| 160 |
+
disabled={isUploading}
|
| 161 |
+
/>
|
| 162 |
+
|
| 163 |
+
<div className="upload-content">
|
| 164 |
+
{isUploading ? (
|
| 165 |
+
<>
|
| 166 |
+
<div className="upload-spinner">
|
| 167 |
+
<div className="spinner"></div>
|
| 168 |
+
</div>
|
| 169 |
+
<p className="upload-text">Uploading {selectedFile?.name}...</p>
|
| 170 |
+
</>
|
| 171 |
+
) : (
|
| 172 |
+
<>
|
| 173 |
+
<Upload size={24} className="upload-icon" />
|
| 174 |
+
<p className="upload-text">
|
| 175 |
+
<span className="upload-primary">Click to upload</span> or drag and drop
|
| 176 |
+
</p>
|
| 177 |
+
<p className="upload-secondary">PDF, DOCX, or TXT files only</p>
|
| 178 |
+
</>
|
| 179 |
+
)}
|
| 180 |
+
</div>
|
| 181 |
+
</div>
|
| 182 |
+
|
| 183 |
+
{/* Supported File Types */}
|
| 184 |
+
<div className="supported-types">
|
| 185 |
+
{Object.entries(supportedTypes).map(([mimeType, info]) => {
|
| 186 |
+
const Icon = info.icon;
|
| 187 |
+
return (
|
| 188 |
+
<div key={mimeType} className="file-type-chip">
|
| 189 |
+
<Icon size={12} style={{ color: info.color }} />
|
| 190 |
+
<span>{info.ext}</span>
|
| 191 |
+
</div>
|
| 192 |
+
);
|
| 193 |
+
})}
|
| 194 |
+
</div>
|
| 195 |
+
</div>
|
| 196 |
+
);
|
| 197 |
+
};
|
| 198 |
+
|
| 199 |
+
export default FileUpload;
|
phd-advisor-frontend/src/data/advisors.js
CHANGED
|
@@ -2,7 +2,7 @@ import { BookOpen, Target, Brain } from 'lucide-react';
|
|
| 2 |
|
| 3 |
export const advisors = {
|
| 4 |
methodist: {
|
| 5 |
-
name: '
|
| 6 |
role: 'Methodical Advisor',
|
| 7 |
// Light theme colors
|
| 8 |
color: '#3B82F6',
|
|
@@ -15,7 +15,7 @@ export const advisors = {
|
|
| 15 |
description: 'Structured & Planning-focused'
|
| 16 |
},
|
| 17 |
theorist: {
|
| 18 |
-
name: '
|
| 19 |
role: 'Theoretical Advisor',
|
| 20 |
// Light theme colors
|
| 21 |
color: '#8B5CF6',
|
|
@@ -28,7 +28,7 @@ export const advisors = {
|
|
| 28 |
description: 'Abstract & Conceptual'
|
| 29 |
},
|
| 30 |
pragmatist: {
|
| 31 |
-
name: '
|
| 32 |
role: 'Practical Advisor',
|
| 33 |
// Light theme colors
|
| 34 |
color: '#10B981',
|
|
|
|
| 2 |
|
| 3 |
export const advisors = {
|
| 4 |
methodist: {
|
| 5 |
+
name: 'Methodist',
|
| 6 |
role: 'Methodical Advisor',
|
| 7 |
// Light theme colors
|
| 8 |
color: '#3B82F6',
|
|
|
|
| 15 |
description: 'Structured & Planning-focused'
|
| 16 |
},
|
| 17 |
theorist: {
|
| 18 |
+
name: 'Theorist',
|
| 19 |
role: 'Theoretical Advisor',
|
| 20 |
// Light theme colors
|
| 21 |
color: '#8B5CF6',
|
|
|
|
| 28 |
description: 'Abstract & Conceptual'
|
| 29 |
},
|
| 30 |
pragmatist: {
|
| 31 |
+
name: 'Pragmatist',
|
| 32 |
role: 'Practical Advisor',
|
| 33 |
// Light theme colors
|
| 34 |
color: '#10B981',
|
phd-advisor-frontend/src/pages/ChatPage.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
// src/pages/ChatPage.js
|
| 2 |
import React, { useState, useEffect, useRef } from 'react';
|
| 3 |
-
import { Home, MessageCircle, Reply, X, Sparkles, Users, Settings2 } from 'lucide-react';
|
| 4 |
-
import
|
| 5 |
import MessageBubble from '../components/MessageBubble';
|
| 6 |
import ThinkingIndicator from '../components/ThinkingIndicator';
|
| 7 |
import SuggestionsPanel from '../components/SuggestionsPanel';
|
|
@@ -10,6 +10,7 @@ import ProviderDropdown from '../components/ProviderDropdown';
|
|
| 10 |
import { advisors, getAdvisorColors } from '../data/advisors';
|
| 11 |
import { useTheme } from '../contexts/ThemeContext';
|
| 12 |
import '../styles/ChatPage.css';
|
|
|
|
| 13 |
|
| 14 |
const ChatPage = ({ onNavigateToHome }) => {
|
| 15 |
const [messages, setMessages] = useState([]);
|
|
@@ -19,6 +20,7 @@ const ChatPage = ({ onNavigateToHome }) => {
|
|
| 19 |
const [replyingTo, setReplyingTo] = useState(null);
|
| 20 |
const [currentProvider, setCurrentProvider] = useState('gemini');
|
| 21 |
const [isProviderSwitching, setIsProviderSwitching] = useState(false);
|
|
|
|
| 22 |
const messagesEndRef = useRef(null);
|
| 23 |
const { isDark } = useTheme();
|
| 24 |
|
|
@@ -102,6 +104,39 @@ const ChatPage = ({ onNavigateToHome }) => {
|
|
| 102 |
return Date.now().toString() + Math.random().toString(36).substr(2, 9);
|
| 103 |
};
|
| 104 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 105 |
const handleSendMessage = async (inputMessage) => {
|
| 106 |
if (replyingTo) {
|
| 107 |
await handleReplyToAdvisor(inputMessage, replyingTo);
|
|
@@ -199,21 +234,17 @@ const ChatPage = ({ onNavigateToHome }) => {
|
|
| 199 |
};
|
| 200 |
|
| 201 |
const handleReplyToAdvisor = async (inputMessage, replyInfo) => {
|
| 202 |
-
const
|
| 203 |
id: generateMessageId(),
|
| 204 |
type: 'user',
|
| 205 |
content: inputMessage,
|
| 206 |
-
|
| 207 |
-
|
| 208 |
-
advisorName: replyInfo.advisorName,
|
| 209 |
-
messageId: replyInfo.messageId
|
| 210 |
-
},
|
| 211 |
-
timestamp: new Date()
|
| 212 |
};
|
| 213 |
-
setMessages(prev => [...prev,
|
| 214 |
-
|
| 215 |
-
setThinkingAdvisors([replyInfo.advisorId]);
|
| 216 |
setIsLoading(true);
|
|
|
|
| 217 |
|
| 218 |
try {
|
| 219 |
const response = await fetch('http://localhost:8000/reply-to-advisor', {
|
|
@@ -233,24 +264,34 @@ const ChatPage = ({ onNavigateToHome }) => {
|
|
| 233 |
}
|
| 234 |
|
| 235 |
const data = await response.json();
|
|
|
|
| 236 |
|
| 237 |
-
|
| 238 |
-
|
| 239 |
-
|
| 240 |
-
|
| 241 |
-
|
| 242 |
-
|
| 243 |
-
|
| 244 |
-
|
| 245 |
-
|
| 246 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 247 |
|
| 248 |
} catch (error) {
|
| 249 |
console.error('Error sending reply:', error);
|
| 250 |
const errorMessage = {
|
| 251 |
id: generateMessageId(),
|
| 252 |
type: 'error',
|
| 253 |
-
content: 'Sorry, I encountered an error
|
| 254 |
timestamp: new Date()
|
| 255 |
};
|
| 256 |
setMessages(prev => [...prev, errorMessage]);
|
|
@@ -322,6 +363,14 @@ const ChatPage = ({ onNavigateToHome }) => {
|
|
| 322 |
</div>
|
| 323 |
|
| 324 |
<div className="header-controls">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 325 |
<ProviderDropdown
|
| 326 |
currentProvider={currentProvider}
|
| 327 |
onProviderChange={handleProviderSwitch}
|
|
@@ -355,7 +404,7 @@ const ChatPage = ({ onNavigateToHome }) => {
|
|
| 355 |
</div>
|
| 356 |
<div className="orchestrator-content">
|
| 357 |
<div className="orchestrator-header">
|
| 358 |
-
<span className="orchestrator-name">
|
| 359 |
<span className="message-timestamp">
|
| 360 |
{message.timestamp.toLocaleTimeString([], {
|
| 361 |
hour: '2-digit',
|
|
@@ -378,6 +427,17 @@ const ChatPage = ({ onNavigateToHome }) => {
|
|
| 378 |
</div>
|
| 379 |
);
|
| 380 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 381 |
|
| 382 |
return (
|
| 383 |
<MessageBubble
|
|
@@ -396,7 +456,7 @@ const ChatPage = ({ onNavigateToHome }) => {
|
|
| 396 |
<MessageCircle size={20} />
|
| 397 |
</div>
|
| 398 |
<div className="thinking-content">
|
| 399 |
-
<span className="thinking-label">
|
| 400 |
<div className="thinking-animation">
|
| 401 |
<div className="dot"></div>
|
| 402 |
<div className="dot"></div>
|
|
@@ -417,7 +477,6 @@ const ChatPage = ({ onNavigateToHome }) => {
|
|
| 417 |
)}
|
| 418 |
</div>
|
| 419 |
|
| 420 |
-
{/* Floating Input Area */}
|
| 421 |
<div className="floating-input-area">
|
| 422 |
{replyingTo && (
|
| 423 |
<div className="reply-banner">
|
|
@@ -431,17 +490,17 @@ const ChatPage = ({ onNavigateToHome }) => {
|
|
| 431 |
</div>
|
| 432 |
)}
|
| 433 |
|
| 434 |
-
<
|
| 435 |
-
|
| 436 |
-
|
| 437 |
-
|
| 438 |
-
|
| 439 |
-
|
| 440 |
-
|
| 441 |
-
|
| 442 |
-
|
| 443 |
-
|
| 444 |
-
|
| 445 |
</div>
|
| 446 |
</div>
|
| 447 |
);
|
|
|
|
| 1 |
// src/pages/ChatPage.js
|
| 2 |
import React, { useState, useEffect, useRef } from 'react';
|
| 3 |
+
import { Home, MessageCircle, Reply, X, Sparkles, Users, Settings2, FileText } from 'lucide-react';
|
| 4 |
+
import EnhancedChatInput from '../components/EnhancedChatInput';
|
| 5 |
import MessageBubble from '../components/MessageBubble';
|
| 6 |
import ThinkingIndicator from '../components/ThinkingIndicator';
|
| 7 |
import SuggestionsPanel from '../components/SuggestionsPanel';
|
|
|
|
| 10 |
import { advisors, getAdvisorColors } from '../data/advisors';
|
| 11 |
import { useTheme } from '../contexts/ThemeContext';
|
| 12 |
import '../styles/ChatPage.css';
|
| 13 |
+
import '../styles/EnhancedChatInput.css';
|
| 14 |
|
| 15 |
const ChatPage = ({ onNavigateToHome }) => {
|
| 16 |
const [messages, setMessages] = useState([]);
|
|
|
|
| 20 |
const [replyingTo, setReplyingTo] = useState(null);
|
| 21 |
const [currentProvider, setCurrentProvider] = useState('gemini');
|
| 22 |
const [isProviderSwitching, setIsProviderSwitching] = useState(false);
|
| 23 |
+
const [uploadedDocuments, setUploadedDocuments] = useState([]);
|
| 24 |
const messagesEndRef = useRef(null);
|
| 25 |
const { isDark } = useTheme();
|
| 26 |
|
|
|
|
| 104 |
return Date.now().toString() + Math.random().toString(36).substr(2, 9);
|
| 105 |
};
|
| 106 |
|
| 107 |
+
const handleFileUploaded = (file, response) => {
|
| 108 |
+
// Add the uploaded document to our list
|
| 109 |
+
const docInfo = {
|
| 110 |
+
id: generateMessageId(),
|
| 111 |
+
name: file.name,
|
| 112 |
+
size: file.size,
|
| 113 |
+
type: file.type,
|
| 114 |
+
uploadTime: new Date()
|
| 115 |
+
};
|
| 116 |
+
setUploadedDocuments(prev => [...prev, docInfo]);
|
| 117 |
+
|
| 118 |
+
// Add a system message about the upload
|
| 119 |
+
const uploadMessage = {
|
| 120 |
+
id: generateMessageId(),
|
| 121 |
+
type: 'document_upload',
|
| 122 |
+
content: `Successfully uploaded "${file.name}". Your advisors can now reference this document in their responses.`,
|
| 123 |
+
timestamp: new Date(),
|
| 124 |
+
documentInfo: docInfo
|
| 125 |
+
};
|
| 126 |
+
|
| 127 |
+
// Force update messages state
|
| 128 |
+
setMessages(prev => {
|
| 129 |
+
const newMessages = [...prev, uploadMessage];
|
| 130 |
+
console.log('Added upload message:', uploadMessage); // Debug log
|
| 131 |
+
return newMessages;
|
| 132 |
+
});
|
| 133 |
+
|
| 134 |
+
// Scroll to bottom to show the new message
|
| 135 |
+
setTimeout(() => {
|
| 136 |
+
scrollToBottom();
|
| 137 |
+
}, 100);
|
| 138 |
+
};
|
| 139 |
+
|
| 140 |
const handleSendMessage = async (inputMessage) => {
|
| 141 |
if (replyingTo) {
|
| 142 |
await handleReplyToAdvisor(inputMessage, replyingTo);
|
|
|
|
| 234 |
};
|
| 235 |
|
| 236 |
const handleReplyToAdvisor = async (inputMessage, replyInfo) => {
|
| 237 |
+
const userMessage = {
|
| 238 |
id: generateMessageId(),
|
| 239 |
type: 'user',
|
| 240 |
content: inputMessage,
|
| 241 |
+
timestamp: new Date(),
|
| 242 |
+
replyingTo: replyInfo
|
|
|
|
|
|
|
|
|
|
|
|
|
| 243 |
};
|
| 244 |
+
setMessages(prev => [...prev, userMessage]);
|
| 245 |
+
|
|
|
|
| 246 |
setIsLoading(true);
|
| 247 |
+
setThinkingAdvisors([replyInfo.advisorId]);
|
| 248 |
|
| 249 |
try {
|
| 250 |
const response = await fetch('http://localhost:8000/reply-to-advisor', {
|
|
|
|
| 264 |
}
|
| 265 |
|
| 266 |
const data = await response.json();
|
| 267 |
+
setThinkingAdvisors([]);
|
| 268 |
|
| 269 |
+
if (data.type === 'advisor_reply') {
|
| 270 |
+
const replyMessage = {
|
| 271 |
+
id: generateMessageId(),
|
| 272 |
+
type: 'advisor',
|
| 273 |
+
advisorId: replyInfo.advisorId,
|
| 274 |
+
content: data.response,
|
| 275 |
+
timestamp: new Date(),
|
| 276 |
+
isReply: true
|
| 277 |
+
};
|
| 278 |
+
setMessages(prev => [...prev, replyMessage]);
|
| 279 |
+
} else if (data.type === 'error') {
|
| 280 |
+
const errorMessage = {
|
| 281 |
+
id: generateMessageId(),
|
| 282 |
+
type: 'error',
|
| 283 |
+
content: data.response,
|
| 284 |
+
timestamp: new Date()
|
| 285 |
+
};
|
| 286 |
+
setMessages(prev => [...prev, errorMessage]);
|
| 287 |
+
}
|
| 288 |
|
| 289 |
} catch (error) {
|
| 290 |
console.error('Error sending reply:', error);
|
| 291 |
const errorMessage = {
|
| 292 |
id: generateMessageId(),
|
| 293 |
type: 'error',
|
| 294 |
+
content: 'Sorry, I encountered an error. Please try again.',
|
| 295 |
timestamp: new Date()
|
| 296 |
};
|
| 297 |
setMessages(prev => [...prev, errorMessage]);
|
|
|
|
| 363 |
</div>
|
| 364 |
|
| 365 |
<div className="header-controls">
|
| 366 |
+
{/* Document Count Indicator */}
|
| 367 |
+
{uploadedDocuments.length > 0 && (
|
| 368 |
+
<div className="document-indicator" title={`${uploadedDocuments.length} document(s) uploaded`}>
|
| 369 |
+
<FileText size={16} />
|
| 370 |
+
<span className="doc-count">{uploadedDocuments.length}</span>
|
| 371 |
+
</div>
|
| 372 |
+
)}
|
| 373 |
+
|
| 374 |
<ProviderDropdown
|
| 375 |
currentProvider={currentProvider}
|
| 376 |
onProviderChange={handleProviderSwitch}
|
|
|
|
| 404 |
</div>
|
| 405 |
<div className="orchestrator-content">
|
| 406 |
<div className="orchestrator-header">
|
| 407 |
+
<span className="orchestrator-name">Orchestrator</span>
|
| 408 |
<span className="message-timestamp">
|
| 409 |
{message.timestamp.toLocaleTimeString([], {
|
| 410 |
hour: '2-digit',
|
|
|
|
| 427 |
</div>
|
| 428 |
);
|
| 429 |
}
|
| 430 |
+
|
| 431 |
+
if (message.type === 'document_upload') {
|
| 432 |
+
return (
|
| 433 |
+
<div key={message.id || index} className="document-upload-notification">
|
| 434 |
+
<div className="upload-notification-content">
|
| 435 |
+
<FileText size={16} className="upload-icon" />
|
| 436 |
+
<span>{message.content}</span>
|
| 437 |
+
</div>
|
| 438 |
+
</div>
|
| 439 |
+
);
|
| 440 |
+
}
|
| 441 |
|
| 442 |
return (
|
| 443 |
<MessageBubble
|
|
|
|
| 456 |
<MessageCircle size={20} />
|
| 457 |
</div>
|
| 458 |
<div className="thinking-content">
|
| 459 |
+
<span className="thinking-label">Orchestrator is thinking...</span>
|
| 460 |
<div className="thinking-animation">
|
| 461 |
<div className="dot"></div>
|
| 462 |
<div className="dot"></div>
|
|
|
|
| 477 |
)}
|
| 478 |
</div>
|
| 479 |
|
|
|
|
| 480 |
<div className="floating-input-area">
|
| 481 |
{replyingTo && (
|
| 482 |
<div className="reply-banner">
|
|
|
|
| 490 |
</div>
|
| 491 |
)}
|
| 492 |
|
| 493 |
+
<EnhancedChatInput
|
| 494 |
+
onSendMessage={handleSendMessage}
|
| 495 |
+
onFileUploaded={handleFileUploaded}
|
| 496 |
+
uploadedDocuments={uploadedDocuments}
|
| 497 |
+
isLoading={isLoading}
|
| 498 |
+
placeholder={
|
| 499 |
+
replyingTo
|
| 500 |
+
? `Reply to ${replyingTo.advisorName}...`
|
| 501 |
+
: "Ask your advisors anything about your PhD journey..."
|
| 502 |
+
}
|
| 503 |
+
/>
|
| 504 |
</div>
|
| 505 |
</div>
|
| 506 |
);
|
phd-advisor-frontend/src/styles/ChatPage.css
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
/* ChatPage.css -
|
| 2 |
|
| 3 |
/* Modern Chat Page Layout */
|
| 4 |
.modern-chat-page {
|
|
@@ -95,6 +95,12 @@
|
|
| 95 |
gap: 20px;
|
| 96 |
}
|
| 97 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 98 |
/* Advisor Pills */
|
| 99 |
.advisor-pills {
|
| 100 |
display: flex;
|
|
@@ -113,42 +119,30 @@
|
|
| 113 |
font-weight: 500;
|
| 114 |
transition: all 0.3s ease;
|
| 115 |
position: relative;
|
| 116 |
-
overflow: hidden;
|
| 117 |
}
|
| 118 |
|
| 119 |
.advisor-pill.thinking {
|
| 120 |
-
animation: pulse 2s infinite;
|
| 121 |
-
}
|
| 122 |
-
|
| 123 |
-
.advisor-pill span {
|
| 124 |
-
font-weight: 600;
|
| 125 |
}
|
| 126 |
|
| 127 |
.thinking-pulse {
|
| 128 |
position: absolute;
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
height: 6px;
|
| 134 |
border-radius: 50%;
|
| 135 |
-
animation: pulse-dot 1.5s infinite;
|
| 136 |
}
|
| 137 |
|
| 138 |
@keyframes pulse {
|
| 139 |
-
0%, 100% { transform: scale(1); }
|
| 140 |
-
50% { transform: scale(1.05); }
|
| 141 |
-
}
|
| 142 |
-
|
| 143 |
-
@keyframes pulse-dot {
|
| 144 |
0%, 100% { opacity: 1; }
|
| 145 |
-
50% { opacity: 0.
|
| 146 |
}
|
| 147 |
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
gap: 12px;
|
| 152 |
}
|
| 153 |
|
| 154 |
/* Main Content Area */
|
|
@@ -156,25 +150,24 @@
|
|
| 156 |
flex: 1;
|
| 157 |
display: flex;
|
| 158 |
flex-direction: column;
|
| 159 |
-
padding:
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
}
|
| 164 |
|
| 165 |
-
/* Welcome State
|
| 166 |
.welcome-state {
|
|
|
|
| 167 |
display: flex;
|
| 168 |
-
flex-direction: column;
|
| 169 |
align-items: center;
|
| 170 |
justify-content: center;
|
| 171 |
-
max-width: 900px;
|
| 172 |
-
margin: 0 auto;
|
| 173 |
min-height: 60vh;
|
| 174 |
}
|
| 175 |
|
| 176 |
.suggestions-container {
|
| 177 |
width: 100%;
|
|
|
|
| 178 |
}
|
| 179 |
|
| 180 |
/* Chat State */
|
|
@@ -186,42 +179,33 @@
|
|
| 186 |
|
| 187 |
.messages-area {
|
| 188 |
flex: 1;
|
| 189 |
-
background: var(--bg-primary);
|
| 190 |
-
border-radius: 24px;
|
| 191 |
-
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
|
| 192 |
-
border: 1px solid var(--border-primary);
|
| 193 |
overflow: hidden;
|
| 194 |
-
max-width: 900px;
|
| 195 |
-
margin: 0 auto;
|
| 196 |
-
width: 100%;
|
| 197 |
}
|
| 198 |
|
| 199 |
.messages-scroll {
|
| 200 |
height: 100%;
|
| 201 |
overflow-y: auto;
|
| 202 |
-
padding:
|
| 203 |
-
|
| 204 |
-
flex-direction: column;
|
| 205 |
-
gap: 24px;
|
| 206 |
-
scroll-behavior: smooth;
|
| 207 |
}
|
| 208 |
|
| 209 |
-
/* Message
|
| 210 |
.orchestrator-message {
|
| 211 |
display: flex;
|
| 212 |
gap: 16px;
|
|
|
|
| 213 |
align-items: flex-start;
|
| 214 |
}
|
| 215 |
|
| 216 |
.orchestrator-avatar {
|
| 217 |
-
width:
|
| 218 |
-
height:
|
| 219 |
-
|
| 220 |
-
|
| 221 |
display: flex;
|
| 222 |
align-items: center;
|
| 223 |
justify-content: center;
|
| 224 |
-
color:
|
| 225 |
flex-shrink: 0;
|
| 226 |
}
|
| 227 |
|
|
@@ -229,15 +213,16 @@
|
|
| 229 |
flex: 1;
|
| 230 |
background: var(--bg-secondary);
|
| 231 |
border-radius: 16px;
|
| 232 |
-
padding: 20px;
|
| 233 |
border: 1px solid var(--border-primary);
|
|
|
|
| 234 |
}
|
| 235 |
|
| 236 |
.orchestrator-header {
|
| 237 |
display: flex;
|
| 238 |
align-items: center;
|
| 239 |
justify-content: space-between;
|
| 240 |
-
margin-bottom:
|
| 241 |
}
|
| 242 |
|
| 243 |
.orchestrator-name {
|
|
@@ -282,6 +267,7 @@
|
|
| 282 |
gap: 16px;
|
| 283 |
align-items: center;
|
| 284 |
opacity: 0.8;
|
|
|
|
| 285 |
}
|
| 286 |
|
| 287 |
.thinking-content {
|
|
@@ -322,47 +308,57 @@
|
|
| 322 |
40% { transform: scale(1); }
|
| 323 |
}
|
| 324 |
|
| 325 |
-
/* Floating Input Area */
|
| 326 |
.floating-input-area {
|
| 327 |
position: fixed;
|
| 328 |
-
bottom:
|
| 329 |
left: 0;
|
| 330 |
right: 0;
|
| 331 |
z-index: 50;
|
| 332 |
-
background:
|
| 333 |
-
padding:
|
| 334 |
-
backdrop-filter: blur(20px);
|
| 335 |
-
-webkit-backdrop-filter: blur(20px);
|
| 336 |
-
}
|
| 337 |
-
|
| 338 |
-
.reply-banner {
|
| 339 |
display: flex;
|
|
|
|
| 340 |
align-items: center;
|
| 341 |
-
|
| 342 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 343 |
border: 1px solid var(--border-primary);
|
| 344 |
border-radius: 12px;
|
| 345 |
padding: 12px 16px;
|
| 346 |
-
margin
|
| 347 |
-
|
| 348 |
-
|
| 349 |
-
|
| 350 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 351 |
}
|
| 352 |
|
| 353 |
-
|
|
|
|
| 354 |
display: flex;
|
| 355 |
align-items: center;
|
| 356 |
gap: 8px;
|
| 357 |
-
font-size: 14px;
|
| 358 |
color: var(--text-secondary);
|
|
|
|
| 359 |
}
|
| 360 |
|
| 361 |
-
.reply-info strong {
|
| 362 |
color: var(--accent-primary);
|
| 363 |
}
|
| 364 |
|
| 365 |
-
|
|
|
|
| 366 |
background: none;
|
| 367 |
border: none;
|
| 368 |
color: var(--text-tertiary);
|
|
@@ -372,17 +368,50 @@
|
|
| 372 |
transition: all 0.2s ease;
|
| 373 |
}
|
| 374 |
|
| 375 |
-
.cancel-reply:hover {
|
| 376 |
-
background: var(--bg-
|
| 377 |
color: var(--text-secondary);
|
| 378 |
}
|
| 379 |
|
| 380 |
-
|
| 381 |
-
|
| 382 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 383 |
}
|
| 384 |
|
| 385 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 386 |
.provider-dropdown {
|
| 387 |
position: relative;
|
| 388 |
display: inline-block;
|
|
@@ -486,6 +515,10 @@
|
|
| 486 |
-webkit-backdrop-filter: blur(20px);
|
| 487 |
}
|
| 488 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 489 |
.provider-option {
|
| 490 |
width: 100%;
|
| 491 |
display: flex;
|
|
@@ -579,7 +612,7 @@
|
|
| 579 |
}
|
| 580 |
|
| 581 |
.main-content {
|
| 582 |
-
padding: 24px
|
| 583 |
}
|
| 584 |
|
| 585 |
.welcome-state {
|
|
@@ -587,10 +620,25 @@
|
|
| 587 |
}
|
| 588 |
|
| 589 |
.messages-scroll {
|
| 590 |
-
padding:
|
|
|
|
| 591 |
}
|
| 592 |
|
| 593 |
.floating-input-area {
|
| 594 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 595 |
}
|
| 596 |
}
|
|
|
|
| 1 |
+
/* ChatPage.css - Complete Fixed File */
|
| 2 |
|
| 3 |
/* Modern Chat Page Layout */
|
| 4 |
.modern-chat-page {
|
|
|
|
| 95 |
gap: 20px;
|
| 96 |
}
|
| 97 |
|
| 98 |
+
.header-controls {
|
| 99 |
+
display: flex;
|
| 100 |
+
align-items: center;
|
| 101 |
+
gap: 12px;
|
| 102 |
+
}
|
| 103 |
+
|
| 104 |
/* Advisor Pills */
|
| 105 |
.advisor-pills {
|
| 106 |
display: flex;
|
|
|
|
| 119 |
font-weight: 500;
|
| 120 |
transition: all 0.3s ease;
|
| 121 |
position: relative;
|
|
|
|
| 122 |
}
|
| 123 |
|
| 124 |
.advisor-pill.thinking {
|
| 125 |
+
animation: pulse 2s ease-in-out infinite;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 126 |
}
|
| 127 |
|
| 128 |
.thinking-pulse {
|
| 129 |
position: absolute;
|
| 130 |
+
top: -2px;
|
| 131 |
+
right: -2px;
|
| 132 |
+
width: 8px;
|
| 133 |
+
height: 8px;
|
|
|
|
| 134 |
border-radius: 50%;
|
| 135 |
+
animation: pulse-dot 1.5s ease-in-out infinite;
|
| 136 |
}
|
| 137 |
|
| 138 |
@keyframes pulse {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 139 |
0%, 100% { opacity: 1; }
|
| 140 |
+
50% { opacity: 0.7; }
|
| 141 |
}
|
| 142 |
|
| 143 |
+
@keyframes pulse-dot {
|
| 144 |
+
0%, 100% { transform: scale(1); opacity: 1; }
|
| 145 |
+
50% { transform: scale(1.2); opacity: 0.8; }
|
|
|
|
| 146 |
}
|
| 147 |
|
| 148 |
/* Main Content Area */
|
|
|
|
| 150 |
flex: 1;
|
| 151 |
display: flex;
|
| 152 |
flex-direction: column;
|
| 153 |
+
padding-top: 24px;
|
| 154 |
+
padding-bottom: 140px; /* Space for floating input */
|
| 155 |
+
padding-left: 16px;
|
| 156 |
+
padding-right: 16px;
|
| 157 |
}
|
| 158 |
|
| 159 |
+
/* Welcome State */
|
| 160 |
.welcome-state {
|
| 161 |
+
flex: 1;
|
| 162 |
display: flex;
|
|
|
|
| 163 |
align-items: center;
|
| 164 |
justify-content: center;
|
|
|
|
|
|
|
| 165 |
min-height: 60vh;
|
| 166 |
}
|
| 167 |
|
| 168 |
.suggestions-container {
|
| 169 |
width: 100%;
|
| 170 |
+
max-width: 1200px;
|
| 171 |
}
|
| 172 |
|
| 173 |
/* Chat State */
|
|
|
|
| 179 |
|
| 180 |
.messages-area {
|
| 181 |
flex: 1;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 182 |
overflow: hidden;
|
|
|
|
|
|
|
|
|
|
| 183 |
}
|
| 184 |
|
| 185 |
.messages-scroll {
|
| 186 |
height: 100%;
|
| 187 |
overflow-y: auto;
|
| 188 |
+
padding: 20px;
|
| 189 |
+
padding-bottom: 40px; /* Extra space before floating input */
|
|
|
|
|
|
|
|
|
|
| 190 |
}
|
| 191 |
|
| 192 |
+
/* Message Components */
|
| 193 |
.orchestrator-message {
|
| 194 |
display: flex;
|
| 195 |
gap: 16px;
|
| 196 |
+
margin-bottom: 24px;
|
| 197 |
align-items: flex-start;
|
| 198 |
}
|
| 199 |
|
| 200 |
.orchestrator-avatar {
|
| 201 |
+
width: 40px;
|
| 202 |
+
height: 40px;
|
| 203 |
+
border-radius: 20px;
|
| 204 |
+
background: var(--accent-gradient);
|
| 205 |
display: flex;
|
| 206 |
align-items: center;
|
| 207 |
justify-content: center;
|
| 208 |
+
color: white;
|
| 209 |
flex-shrink: 0;
|
| 210 |
}
|
| 211 |
|
|
|
|
| 213 |
flex: 1;
|
| 214 |
background: var(--bg-secondary);
|
| 215 |
border-radius: 16px;
|
| 216 |
+
padding: 16px 20px;
|
| 217 |
border: 1px solid var(--border-primary);
|
| 218 |
+
max-width: 600px;
|
| 219 |
}
|
| 220 |
|
| 221 |
.orchestrator-header {
|
| 222 |
display: flex;
|
| 223 |
align-items: center;
|
| 224 |
justify-content: space-between;
|
| 225 |
+
margin-bottom: 8px;
|
| 226 |
}
|
| 227 |
|
| 228 |
.orchestrator-name {
|
|
|
|
| 267 |
gap: 16px;
|
| 268 |
align-items: center;
|
| 269 |
opacity: 0.8;
|
| 270 |
+
margin-bottom: 24px;
|
| 271 |
}
|
| 272 |
|
| 273 |
.thinking-content {
|
|
|
|
| 308 |
40% { transform: scale(1); }
|
| 309 |
}
|
| 310 |
|
| 311 |
+
/* Floating Input Area - FIXED VERSION */
|
| 312 |
.floating-input-area {
|
| 313 |
position: fixed;
|
| 314 |
+
bottom: 20px;
|
| 315 |
left: 0;
|
| 316 |
right: 0;
|
| 317 |
z-index: 50;
|
| 318 |
+
background: transparent;
|
| 319 |
+
padding: 0;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 320 |
display: flex;
|
| 321 |
+
flex-direction: column;
|
| 322 |
align-items: center;
|
| 323 |
+
max-width: 800px;
|
| 324 |
+
margin: 0 auto;
|
| 325 |
+
}
|
| 326 |
+
|
| 327 |
+
/* Reply Banner in ChatPage - matches input width and fixes dark mode */
|
| 328 |
+
.floating-input-area .reply-banner {
|
| 329 |
+
background: var(--bg-primary);
|
| 330 |
border: 1px solid var(--border-primary);
|
| 331 |
border-radius: 12px;
|
| 332 |
padding: 12px 16px;
|
| 333 |
+
margin: 0 16px 12px 16px;
|
| 334 |
+
display: flex;
|
| 335 |
+
align-items: center;
|
| 336 |
+
justify-content: space-between;
|
| 337 |
+
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
|
| 338 |
+
width: 100%;
|
| 339 |
+
max-width: 768px;
|
| 340 |
+
box-sizing: border-box;
|
| 341 |
+
}
|
| 342 |
+
|
| 343 |
+
[data-theme="dark"] .floating-input-area .reply-banner {
|
| 344 |
+
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3);
|
| 345 |
}
|
| 346 |
|
| 347 |
+
/* Reply Info - Fix dark mode text color */
|
| 348 |
+
.floating-input-area .reply-info {
|
| 349 |
display: flex;
|
| 350 |
align-items: center;
|
| 351 |
gap: 8px;
|
|
|
|
| 352 |
color: var(--text-secondary);
|
| 353 |
+
font-size: 14px;
|
| 354 |
}
|
| 355 |
|
| 356 |
+
.floating-input-area .reply-info strong {
|
| 357 |
color: var(--accent-primary);
|
| 358 |
}
|
| 359 |
|
| 360 |
+
/* Cancel Reply Button */
|
| 361 |
+
.floating-input-area .cancel-reply {
|
| 362 |
background: none;
|
| 363 |
border: none;
|
| 364 |
color: var(--text-tertiary);
|
|
|
|
| 368 |
transition: all 0.2s ease;
|
| 369 |
}
|
| 370 |
|
| 371 |
+
.floating-input-area .cancel-reply:hover {
|
| 372 |
+
background: var(--bg-secondary);
|
| 373 |
color: var(--text-secondary);
|
| 374 |
}
|
| 375 |
|
| 376 |
+
/* Document Indicator Styles */
|
| 377 |
+
.document-indicator {
|
| 378 |
+
display: flex;
|
| 379 |
+
align-items: center;
|
| 380 |
+
gap: 6px;
|
| 381 |
+
padding: 6px 12px;
|
| 382 |
+
background: var(--feature-bg); /* Use theme variable */
|
| 383 |
+
border: 1px solid var(--accent-primary); /* Use theme variable */
|
| 384 |
+
border-radius: 20px;
|
| 385 |
+
color: var(--accent-primary); /* Use theme variable */
|
| 386 |
+
font-size: 13px;
|
| 387 |
+
font-weight: 600;
|
| 388 |
+
cursor: default;
|
| 389 |
+
transition: all 0.2s ease;
|
| 390 |
}
|
| 391 |
|
| 392 |
+
.document-indicator:hover {
|
| 393 |
+
background: var(--accent-primary);
|
| 394 |
+
color: white;
|
| 395 |
+
}
|
| 396 |
+
|
| 397 |
+
.doc-count {
|
| 398 |
+
background: var(--accent-primary);
|
| 399 |
+
color: white;
|
| 400 |
+
border-radius: 10px;
|
| 401 |
+
padding: 2px 6px;
|
| 402 |
+
font-size: 11px;
|
| 403 |
+
font-weight: 700;
|
| 404 |
+
min-width: 16px;
|
| 405 |
+
text-align: center;
|
| 406 |
+
line-height: 1.2;
|
| 407 |
+
}
|
| 408 |
+
|
| 409 |
+
.document-indicator:hover .doc-count {
|
| 410 |
+
background: white;
|
| 411 |
+
color: var(--accent-primary);
|
| 412 |
+
}
|
| 413 |
+
|
| 414 |
+
/* Provider Dropdown Styles - FIXED */
|
| 415 |
.provider-dropdown {
|
| 416 |
position: relative;
|
| 417 |
display: inline-block;
|
|
|
|
| 515 |
-webkit-backdrop-filter: blur(20px);
|
| 516 |
}
|
| 517 |
|
| 518 |
+
[data-theme="dark"] .provider-dropdown-menu {
|
| 519 |
+
box-shadow: 0 12px 32px rgba(0, 0, 0, 0.4);
|
| 520 |
+
}
|
| 521 |
+
|
| 522 |
.provider-option {
|
| 523 |
width: 100%;
|
| 524 |
display: flex;
|
|
|
|
| 612 |
}
|
| 613 |
|
| 614 |
.main-content {
|
| 615 |
+
padding: 24px 12px 120px 12px; /* Adjusted for mobile */
|
| 616 |
}
|
| 617 |
|
| 618 |
.welcome-state {
|
|
|
|
| 620 |
}
|
| 621 |
|
| 622 |
.messages-scroll {
|
| 623 |
+
padding: 16px;
|
| 624 |
+
padding-bottom: 30px;
|
| 625 |
}
|
| 626 |
|
| 627 |
.floating-input-area {
|
| 628 |
+
bottom: 10px;
|
| 629 |
+
}
|
| 630 |
+
|
| 631 |
+
.floating-input-area .reply-banner {
|
| 632 |
+
margin: 0 12px 12px 12px;
|
| 633 |
+
}
|
| 634 |
+
|
| 635 |
+
.orchestrator-content {
|
| 636 |
+
max-width: none;
|
| 637 |
+
}
|
| 638 |
+
|
| 639 |
+
.system-content {
|
| 640 |
+
max-width: 90%;
|
| 641 |
+
padding: 10px 16px;
|
| 642 |
+
font-size: 13px;
|
| 643 |
}
|
| 644 |
}
|
phd-advisor-frontend/src/styles/EnhancedChatInput.css
ADDED
|
@@ -0,0 +1,602 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/* Enhanced Chat Input Styles */
|
| 2 |
+
.enhanced-chat-input-container {
|
| 3 |
+
position: relative;
|
| 4 |
+
width: 100%;
|
| 5 |
+
max-width: 800px;
|
| 6 |
+
margin: 0 auto;
|
| 7 |
+
padding: 0 16px;
|
| 8 |
+
}
|
| 9 |
+
|
| 10 |
+
/* Reply Banner */
|
| 11 |
+
.reply-banner {
|
| 12 |
+
background: var(--bg-secondary);
|
| 13 |
+
border: 1px solid var(--border-primary);
|
| 14 |
+
border-radius: 12px 12px 0 0;
|
| 15 |
+
padding: 12px 16px;
|
| 16 |
+
margin: 0 16px;
|
| 17 |
+
display: flex;
|
| 18 |
+
align-items: center;
|
| 19 |
+
justify-content: space-between;
|
| 20 |
+
font-size: 14px;
|
| 21 |
+
color: var(--text-secondary);
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
.reply-content {
|
| 25 |
+
display: flex;
|
| 26 |
+
align-items: center;
|
| 27 |
+
gap: 8px;
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
.cancel-reply {
|
| 31 |
+
background: none;
|
| 32 |
+
border: none;
|
| 33 |
+
color: var(--text-tertiary);
|
| 34 |
+
cursor: pointer;
|
| 35 |
+
padding: 4px;
|
| 36 |
+
border-radius: 6px;
|
| 37 |
+
transition: all 0.2s ease;
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
.cancel-reply:hover {
|
| 41 |
+
background: var(--bg-secondary);
|
| 42 |
+
color: var(--text-secondary);
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
/* Floating Upload Section */
|
| 46 |
+
.floating-upload-section {
|
| 47 |
+
background: var(--bg-primary);
|
| 48 |
+
border: 1px solid var(--border-primary);
|
| 49 |
+
border-radius: 16px;
|
| 50 |
+
margin-bottom: 12px;
|
| 51 |
+
padding: 20px;
|
| 52 |
+
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
|
| 53 |
+
animation: slideUpFade 0.2s ease-out;
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
[data-theme="dark"] .floating-upload-section {
|
| 57 |
+
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
/* Floating Documents Section */
|
| 61 |
+
.floating-documents-section {
|
| 62 |
+
background: var(--bg-primary);
|
| 63 |
+
border: 1px solid var(--border-primary);
|
| 64 |
+
border-radius: 16px;
|
| 65 |
+
margin-bottom: 12px;
|
| 66 |
+
max-height: 400px;
|
| 67 |
+
overflow: hidden;
|
| 68 |
+
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
|
| 69 |
+
animation: slideUpFade 0.2s ease-out;
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
[data-theme="dark"] .floating-documents-section {
|
| 73 |
+
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
.documents-header {
|
| 77 |
+
display: flex;
|
| 78 |
+
align-items: center;
|
| 79 |
+
justify-content: space-between;
|
| 80 |
+
padding: 16px 20px;
|
| 81 |
+
border-bottom: 1px solid var(--border-primary);
|
| 82 |
+
background: var(--bg-secondary);
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
.documents-title {
|
| 86 |
+
display: flex;
|
| 87 |
+
align-items: center;
|
| 88 |
+
gap: 8px;
|
| 89 |
+
font-weight: 500;
|
| 90 |
+
color: var(--text-primary);
|
| 91 |
+
font-size: 14px;
|
| 92 |
+
}
|
| 93 |
+
|
| 94 |
+
.close-documents-btn {
|
| 95 |
+
background: none;
|
| 96 |
+
border: none;
|
| 97 |
+
color: var(--text-tertiary);
|
| 98 |
+
cursor: pointer;
|
| 99 |
+
padding: 6px;
|
| 100 |
+
border-radius: 6px;
|
| 101 |
+
transition: all 0.2s ease;
|
| 102 |
+
}
|
| 103 |
+
|
| 104 |
+
.close-documents-btn:hover {
|
| 105 |
+
background: var(--bg-tertiary);
|
| 106 |
+
color: var(--text-secondary);
|
| 107 |
+
}
|
| 108 |
+
|
| 109 |
+
.documents-list {
|
| 110 |
+
max-height: 300px;
|
| 111 |
+
overflow-y: auto;
|
| 112 |
+
padding: 8px;
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
+
.no-documents {
|
| 116 |
+
display: flex;
|
| 117 |
+
flex-direction: column;
|
| 118 |
+
align-items: center;
|
| 119 |
+
justify-content: center;
|
| 120 |
+
padding: 40px 20px;
|
| 121 |
+
text-align: center;
|
| 122 |
+
color: var(--text-tertiary);
|
| 123 |
+
}
|
| 124 |
+
|
| 125 |
+
.no-documents svg {
|
| 126 |
+
margin-bottom: 12px;
|
| 127 |
+
opacity: 0.5;
|
| 128 |
+
}
|
| 129 |
+
|
| 130 |
+
.no-documents p {
|
| 131 |
+
margin: 8px 0;
|
| 132 |
+
font-weight: 500;
|
| 133 |
+
color: var(--text-primary);
|
| 134 |
+
}
|
| 135 |
+
|
| 136 |
+
.no-documents span {
|
| 137 |
+
font-size: 13px;
|
| 138 |
+
color: var(--text-tertiary);
|
| 139 |
+
}
|
| 140 |
+
|
| 141 |
+
.document-item {
|
| 142 |
+
display: flex;
|
| 143 |
+
align-items: center;
|
| 144 |
+
gap: 12px;
|
| 145 |
+
padding: 12px;
|
| 146 |
+
border-radius: 8px;
|
| 147 |
+
transition: background-color 0.2s ease;
|
| 148 |
+
}
|
| 149 |
+
|
| 150 |
+
.document-item:hover {
|
| 151 |
+
background: var(--bg-secondary);
|
| 152 |
+
}
|
| 153 |
+
|
| 154 |
+
.document-icon {
|
| 155 |
+
font-size: 20px;
|
| 156 |
+
flex-shrink: 0;
|
| 157 |
+
}
|
| 158 |
+
|
| 159 |
+
.document-info {
|
| 160 |
+
flex: 1;
|
| 161 |
+
min-width: 0;
|
| 162 |
+
}
|
| 163 |
+
|
| 164 |
+
.document-name {
|
| 165 |
+
font-weight: 500;
|
| 166 |
+
color: var(--text-primary);
|
| 167 |
+
font-size: 14px;
|
| 168 |
+
white-space: nowrap;
|
| 169 |
+
overflow: hidden;
|
| 170 |
+
text-overflow: ellipsis;
|
| 171 |
+
}
|
| 172 |
+
|
| 173 |
+
.document-details {
|
| 174 |
+
font-size: 12px;
|
| 175 |
+
color: var(--text-secondary);
|
| 176 |
+
margin-top: 2px;
|
| 177 |
+
}
|
| 178 |
+
|
| 179 |
+
.document-actions {
|
| 180 |
+
display: flex;
|
| 181 |
+
gap: 4px;
|
| 182 |
+
}
|
| 183 |
+
|
| 184 |
+
.document-action-btn {
|
| 185 |
+
background: none;
|
| 186 |
+
border: none;
|
| 187 |
+
color: var(--text-tertiary);
|
| 188 |
+
cursor: pointer;
|
| 189 |
+
padding: 6px;
|
| 190 |
+
border-radius: 4px;
|
| 191 |
+
transition: all 0.2s ease;
|
| 192 |
+
}
|
| 193 |
+
|
| 194 |
+
.document-action-btn:hover {
|
| 195 |
+
background: var(--bg-tertiary);
|
| 196 |
+
color: var(--text-secondary);
|
| 197 |
+
}
|
| 198 |
+
|
| 199 |
+
.document-action-btn:hover:has(svg[data-icon="trash-2"]) {
|
| 200 |
+
background: #FEF2F2;
|
| 201 |
+
color: #DC2626;
|
| 202 |
+
}
|
| 203 |
+
|
| 204 |
+
[data-theme="dark"] .document-action-btn:hover:has(svg[data-icon="trash-2"]) {
|
| 205 |
+
background: #7F1D1D;
|
| 206 |
+
color: #FCA5A5;
|
| 207 |
+
}
|
| 208 |
+
|
| 209 |
+
/* Main Input Box */
|
| 210 |
+
.floating-input-box {
|
| 211 |
+
background: var(--bg-primary);
|
| 212 |
+
border: 1px solid var(--border-primary);
|
| 213 |
+
border-radius: 16px;
|
| 214 |
+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12);
|
| 215 |
+
backdrop-filter: blur(20px);
|
| 216 |
+
-webkit-backdrop-filter: blur(20px);
|
| 217 |
+
overflow: hidden;
|
| 218 |
+
transition: all 0.2s ease;
|
| 219 |
+
}
|
| 220 |
+
|
| 221 |
+
[data-theme="dark"] .floating-input-box {
|
| 222 |
+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
|
| 223 |
+
}
|
| 224 |
+
|
| 225 |
+
.floating-input-box:focus-within {
|
| 226 |
+
border-color: var(--accent-primary);
|
| 227 |
+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.16), 0 0 0 1px var(--accent-primary);
|
| 228 |
+
}
|
| 229 |
+
|
| 230 |
+
[data-theme="dark"] .floating-input-box:focus-within {
|
| 231 |
+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5), 0 0 0 1px var(--accent-primary);
|
| 232 |
+
}
|
| 233 |
+
|
| 234 |
+
/* Text Input Row */
|
| 235 |
+
.text-input-row {
|
| 236 |
+
padding: 16px 20px 4px 20px;
|
| 237 |
+
}
|
| 238 |
+
|
| 239 |
+
.main-textarea {
|
| 240 |
+
width: 100%;
|
| 241 |
+
min-height: 24px;
|
| 242 |
+
max-height: 200px;
|
| 243 |
+
border: none;
|
| 244 |
+
outline: none;
|
| 245 |
+
resize: none;
|
| 246 |
+
background: transparent;
|
| 247 |
+
color: var(--text-primary);
|
| 248 |
+
font-size: 16px;
|
| 249 |
+
font-family: inherit;
|
| 250 |
+
line-height: 1.5;
|
| 251 |
+
overflow-y: auto;
|
| 252 |
+
}
|
| 253 |
+
|
| 254 |
+
.main-textarea::placeholder {
|
| 255 |
+
color: var(--text-tertiary);
|
| 256 |
+
}
|
| 257 |
+
|
| 258 |
+
/* Controls Row */
|
| 259 |
+
.controls-row {
|
| 260 |
+
display: flex;
|
| 261 |
+
align-items: center;
|
| 262 |
+
justify-content: space-between;
|
| 263 |
+
padding: 8px 20px 16px 20px;
|
| 264 |
+
}
|
| 265 |
+
|
| 266 |
+
/* File Controls - ensure buttons stay side by side */
|
| 267 |
+
.file-controls {
|
| 268 |
+
display: flex;
|
| 269 |
+
align-items: center;
|
| 270 |
+
gap: 8px;
|
| 271 |
+
flex-wrap: nowrap;
|
| 272 |
+
}
|
| 273 |
+
|
| 274 |
+
/* Add Documents Button */
|
| 275 |
+
.add-docs-btn {
|
| 276 |
+
display: flex;
|
| 277 |
+
align-items: center;
|
| 278 |
+
gap: 6px;
|
| 279 |
+
background: var(--bg-secondary);
|
| 280 |
+
border: 1px solid var(--border-secondary);
|
| 281 |
+
border-radius: 8px;
|
| 282 |
+
padding: 8px 12px;
|
| 283 |
+
font-size: 14px;
|
| 284 |
+
font-weight: 500;
|
| 285 |
+
color: var(--text-secondary);
|
| 286 |
+
cursor: pointer;
|
| 287 |
+
transition: all 0.2s ease;
|
| 288 |
+
}
|
| 289 |
+
|
| 290 |
+
.add-docs-btn:hover:not(:disabled) {
|
| 291 |
+
background: var(--bg-tertiary);
|
| 292 |
+
border-color: var(--border-primary);
|
| 293 |
+
color: var(--text-primary);
|
| 294 |
+
}
|
| 295 |
+
|
| 296 |
+
.add-docs-btn:disabled {
|
| 297 |
+
opacity: 0.5;
|
| 298 |
+
cursor: not-allowed;
|
| 299 |
+
}
|
| 300 |
+
|
| 301 |
+
.add-docs-btn.active {
|
| 302 |
+
background: var(--accent-primary);
|
| 303 |
+
border-color: var(--accent-primary);
|
| 304 |
+
color: white;
|
| 305 |
+
}
|
| 306 |
+
|
| 307 |
+
/* View Documents Button */
|
| 308 |
+
.view-docs-btn {
|
| 309 |
+
display: flex;
|
| 310 |
+
align-items: center;
|
| 311 |
+
gap: 6px;
|
| 312 |
+
background: var(--bg-secondary);
|
| 313 |
+
border: 1px solid var(--border-secondary);
|
| 314 |
+
border-radius: 8px;
|
| 315 |
+
padding: 8px 10px;
|
| 316 |
+
color: var(--text-secondary);
|
| 317 |
+
cursor: pointer;
|
| 318 |
+
transition: all 0.2s ease;
|
| 319 |
+
position: relative;
|
| 320 |
+
}
|
| 321 |
+
|
| 322 |
+
.view-docs-btn:hover {
|
| 323 |
+
background: var(--bg-tertiary);
|
| 324 |
+
border-color: var(--border-primary);
|
| 325 |
+
color: var(--text-primary);
|
| 326 |
+
}
|
| 327 |
+
|
| 328 |
+
.view-docs-btn.active {
|
| 329 |
+
background: var(--accent-primary);
|
| 330 |
+
border-color: var(--accent-primary);
|
| 331 |
+
color: white;
|
| 332 |
+
}
|
| 333 |
+
|
| 334 |
+
.docs-count {
|
| 335 |
+
background: var(--accent-primary);
|
| 336 |
+
color: white;
|
| 337 |
+
border-radius: 10px;
|
| 338 |
+
padding: 2px 6px;
|
| 339 |
+
font-size: 10px;
|
| 340 |
+
font-weight: 700;
|
| 341 |
+
min-width: 16px;
|
| 342 |
+
text-align: center;
|
| 343 |
+
line-height: 1.2;
|
| 344 |
+
}
|
| 345 |
+
|
| 346 |
+
.view-docs-btn:hover .docs-count,
|
| 347 |
+
.view-docs-btn.active .docs-count {
|
| 348 |
+
background: white;
|
| 349 |
+
color: var(--accent-primary);
|
| 350 |
+
}
|
| 351 |
+
|
| 352 |
+
/* Send Button - FIXED VERSION */
|
| 353 |
+
.send-button {
|
| 354 |
+
display: flex;
|
| 355 |
+
align-items: center;
|
| 356 |
+
justify-content: center;
|
| 357 |
+
width: 36px;
|
| 358 |
+
height: 36px;
|
| 359 |
+
border-radius: 8px;
|
| 360 |
+
border: none;
|
| 361 |
+
cursor: pointer;
|
| 362 |
+
transition: all 0.2s ease;
|
| 363 |
+
position: relative;
|
| 364 |
+
flex-shrink: 0;
|
| 365 |
+
}
|
| 366 |
+
|
| 367 |
+
/* Enabled state - clean approach */
|
| 368 |
+
.send-button.enabled {
|
| 369 |
+
background: #3B82F6;
|
| 370 |
+
color: #FFFFFF;
|
| 371 |
+
box-shadow: 0 2px 8px rgba(59, 130, 246, 0.3);
|
| 372 |
+
}
|
| 373 |
+
|
| 374 |
+
/* Icon styling - use currentColor for better inheritance */
|
| 375 |
+
.send-button svg {
|
| 376 |
+
width: 16px;
|
| 377 |
+
height: 16px;
|
| 378 |
+
color: currentColor;
|
| 379 |
+
}
|
| 380 |
+
|
| 381 |
+
.send-button.enabled:hover {
|
| 382 |
+
background: #2563EB;
|
| 383 |
+
transform: translateY(-1px);
|
| 384 |
+
box-shadow: 0 4px 12px rgba(37, 99, 235, 0.4);
|
| 385 |
+
}
|
| 386 |
+
|
| 387 |
+
.send-button.enabled:active {
|
| 388 |
+
transform: translateY(0);
|
| 389 |
+
box-shadow: 0 2px 8px rgba(59, 130, 246, 0.3);
|
| 390 |
+
}
|
| 391 |
+
|
| 392 |
+
/* Disabled state */
|
| 393 |
+
.send-button.disabled {
|
| 394 |
+
background: #E5E7EB;
|
| 395 |
+
color: #9CA3AF;
|
| 396 |
+
cursor: not-allowed;
|
| 397 |
+
box-shadow: none;
|
| 398 |
+
}
|
| 399 |
+
|
| 400 |
+
[data-theme="dark"] .send-button.disabled {
|
| 401 |
+
background: #374151;
|
| 402 |
+
color: #6B7280;
|
| 403 |
+
}
|
| 404 |
+
|
| 405 |
+
/* Document Upload Notifications */
|
| 406 |
+
.document-upload-notification {
|
| 407 |
+
display: flex;
|
| 408 |
+
justify-content: center;
|
| 409 |
+
margin: 16px 0;
|
| 410 |
+
animation: slideUpFade 0.3s ease-out;
|
| 411 |
+
background: transparent;
|
| 412 |
+
box-shadow: none;
|
| 413 |
+
border: none;
|
| 414 |
+
padding: 0;
|
| 415 |
+
}
|
| 416 |
+
|
| 417 |
+
/* [data-theme="dark"] .document-upload-notification {
|
| 418 |
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
| 419 |
+
} */
|
| 420 |
+
|
| 421 |
+
.upload-notification-content {
|
| 422 |
+
display: flex;
|
| 423 |
+
align-items: center;
|
| 424 |
+
gap: 8px;
|
| 425 |
+
padding: 12px 16px;
|
| 426 |
+
background: var(--feature-bg); /* Change from fixed color */
|
| 427 |
+
border: 1px solid var(--accent-primary); /* Change from fixed color */
|
| 428 |
+
border-radius: 12px;
|
| 429 |
+
color: var(--accent-primary); /* Change from fixed color */
|
| 430 |
+
font-size: 14px;
|
| 431 |
+
font-weight: 500;
|
| 432 |
+
max-width: 80%;
|
| 433 |
+
}
|
| 434 |
+
|
| 435 |
+
.upload-notification-icon {
|
| 436 |
+
flex-shrink: 0;
|
| 437 |
+
}
|
| 438 |
+
|
| 439 |
+
.upload-notification-icon.success {
|
| 440 |
+
color: #10B981;
|
| 441 |
+
}
|
| 442 |
+
|
| 443 |
+
.upload-notification-icon.error {
|
| 444 |
+
color: #EF4444;
|
| 445 |
+
}
|
| 446 |
+
|
| 447 |
+
.upload-notification-text {
|
| 448 |
+
flex: 1;
|
| 449 |
+
}
|
| 450 |
+
|
| 451 |
+
.upload-notification-title {
|
| 452 |
+
font-weight: 500;
|
| 453 |
+
color: var(--text-primary);
|
| 454 |
+
margin: 0 0 2px 0;
|
| 455 |
+
font-size: 14px;
|
| 456 |
+
}
|
| 457 |
+
|
| 458 |
+
.upload-notification-message {
|
| 459 |
+
color: var(--text-secondary);
|
| 460 |
+
margin: 0;
|
| 461 |
+
font-size: 13px;
|
| 462 |
+
}
|
| 463 |
+
|
| 464 |
+
/* [data-theme="dark"] .upload-notification-content {
|
| 465 |
+
box-shadow: 0 4px 16px rgba(99, 102, 241, 0.25);
|
| 466 |
+
} */
|
| 467 |
+
|
| 468 |
+
.notification-close-btn {
|
| 469 |
+
background: none;
|
| 470 |
+
border: none;
|
| 471 |
+
color: var(--text-tertiary);
|
| 472 |
+
cursor: pointer;
|
| 473 |
+
padding: 4px;
|
| 474 |
+
border-radius: 6px;
|
| 475 |
+
transition: all 0.2s ease;
|
| 476 |
+
}
|
| 477 |
+
|
| 478 |
+
.notification-close-btn:hover {
|
| 479 |
+
background: var(--bg-secondary);
|
| 480 |
+
color: var(--text-secondary);
|
| 481 |
+
}
|
| 482 |
+
|
| 483 |
+
/* Animations */
|
| 484 |
+
@keyframes slideUpFade {
|
| 485 |
+
from {
|
| 486 |
+
opacity: 0;
|
| 487 |
+
transform: translateY(10px);
|
| 488 |
+
}
|
| 489 |
+
to {
|
| 490 |
+
opacity: 1;
|
| 491 |
+
transform: translateY(0);
|
| 492 |
+
}
|
| 493 |
+
}
|
| 494 |
+
|
| 495 |
+
/* Mobile Responsive */
|
| 496 |
+
@media (max-width: 768px) {
|
| 497 |
+
.enhanced-chat-input-container {
|
| 498 |
+
padding: 0 12px;
|
| 499 |
+
}
|
| 500 |
+
|
| 501 |
+
.text-input-row {
|
| 502 |
+
padding: 14px 16px 4px 16px;
|
| 503 |
+
}
|
| 504 |
+
|
| 505 |
+
.controls-row {
|
| 506 |
+
padding: 8px 16px 14px 16px;
|
| 507 |
+
}
|
| 508 |
+
|
| 509 |
+
.main-textarea {
|
| 510 |
+
font-size: 16px; /* Prevents zoom on iOS */
|
| 511 |
+
}
|
| 512 |
+
|
| 513 |
+
.add-docs-btn {
|
| 514 |
+
padding: 6px 10px;
|
| 515 |
+
font-size: 13px;
|
| 516 |
+
}
|
| 517 |
+
|
| 518 |
+
.add-docs-btn span {
|
| 519 |
+
display: none; /* Hide text on mobile, show only icon */
|
| 520 |
+
}
|
| 521 |
+
|
| 522 |
+
.view-docs-btn {
|
| 523 |
+
padding: 6px 8px;
|
| 524 |
+
}
|
| 525 |
+
|
| 526 |
+
.send-button {
|
| 527 |
+
width: 32px;
|
| 528 |
+
height: 32px;
|
| 529 |
+
}
|
| 530 |
+
|
| 531 |
+
.floating-upload-section,
|
| 532 |
+
.floating-documents-section {
|
| 533 |
+
padding: 16px;
|
| 534 |
+
}
|
| 535 |
+
|
| 536 |
+
.documents-header {
|
| 537 |
+
padding: 12px 16px;
|
| 538 |
+
}
|
| 539 |
+
|
| 540 |
+
.documents-list {
|
| 541 |
+
max-height: 250px;
|
| 542 |
+
}
|
| 543 |
+
|
| 544 |
+
.upload-notification-content {
|
| 545 |
+
max-width: 95%;
|
| 546 |
+
padding: 10px 14px;
|
| 547 |
+
font-size: 13px;
|
| 548 |
+
}
|
| 549 |
+
|
| 550 |
+
.reply-banner {
|
| 551 |
+
margin: 0 12px 12px 12px;
|
| 552 |
+
}
|
| 553 |
+
}
|
| 554 |
+
|
| 555 |
+
/* Focus and accessibility */
|
| 556 |
+
.add-docs-btn:focus-visible,
|
| 557 |
+
.view-docs-btn:focus-visible,
|
| 558 |
+
.send-button:focus-visible {
|
| 559 |
+
outline: 2px solid var(--accent-primary);
|
| 560 |
+
outline-offset: 2px;
|
| 561 |
+
}
|
| 562 |
+
|
| 563 |
+
.main-textarea:focus {
|
| 564 |
+
outline: none; /* Handled by parent container */
|
| 565 |
+
}
|
| 566 |
+
|
| 567 |
+
/* Loading states */
|
| 568 |
+
.floating-input-box.loading {
|
| 569 |
+
opacity: 0.7;
|
| 570 |
+
pointer-events: none;
|
| 571 |
+
}
|
| 572 |
+
|
| 573 |
+
.main-textarea.loading {
|
| 574 |
+
cursor: wait;
|
| 575 |
+
}
|
| 576 |
+
|
| 577 |
+
/* High contrast mode support */
|
| 578 |
+
@media (prefers-contrast: high) {
|
| 579 |
+
.floating-input-box {
|
| 580 |
+
border-width: 2px;
|
| 581 |
+
}
|
| 582 |
+
|
| 583 |
+
.send-button.enabled {
|
| 584 |
+
border: 2px solid #3B82F6;
|
| 585 |
+
}
|
| 586 |
+
}
|
| 587 |
+
|
| 588 |
+
/* Reduced motion support */
|
| 589 |
+
@media (prefers-reduced-motion: reduce) {
|
| 590 |
+
.floating-input-box,
|
| 591 |
+
.add-docs-btn,
|
| 592 |
+
.view-docs-btn,
|
| 593 |
+
.send-button,
|
| 594 |
+
.floating-upload-section,
|
| 595 |
+
.floating-documents-section {
|
| 596 |
+
transition: none;
|
| 597 |
+
}
|
| 598 |
+
|
| 599 |
+
.slideUpFade {
|
| 600 |
+
animation: none;
|
| 601 |
+
}
|
| 602 |
+
}
|
phd-advisor-frontend/src/styles/FileUpload.css
ADDED
|
@@ -0,0 +1,332 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/* File Upload Container */
|
| 2 |
+
.file-upload-container {
|
| 3 |
+
width: 100%;
|
| 4 |
+
margin-bottom: 12px;
|
| 5 |
+
}
|
| 6 |
+
|
| 7 |
+
/* Upload Status Banner */
|
| 8 |
+
.upload-status {
|
| 9 |
+
padding: 8px 12px;
|
| 10 |
+
border-radius: 8px;
|
| 11 |
+
margin-bottom: 8px;
|
| 12 |
+
border: 1px solid;
|
| 13 |
+
animation: slideDown 0.3s ease-out;
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
+
.upload-status.success {
|
| 17 |
+
background-color: #DCFCE7;
|
| 18 |
+
border-color: #22C55E;
|
| 19 |
+
color: #15803D;
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
[data-theme="dark"] .upload-status.success {
|
| 23 |
+
background-color: #14532D;
|
| 24 |
+
border-color: #22C55E;
|
| 25 |
+
color: #22C55E;
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
+
.upload-status.error {
|
| 29 |
+
background-color: #FEF2F2;
|
| 30 |
+
border-color: #EF4444;
|
| 31 |
+
color: #DC2626;
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
[data-theme="dark"] .upload-status.error {
|
| 35 |
+
background-color: #7F1D1D;
|
| 36 |
+
border-color: #EF4444;
|
| 37 |
+
color: #FCA5A5;
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
.status-content {
|
| 41 |
+
display: flex;
|
| 42 |
+
align-items: center;
|
| 43 |
+
gap: 8px;
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
.status-icon {
|
| 47 |
+
flex-shrink: 0;
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
.status-message {
|
| 51 |
+
flex: 1;
|
| 52 |
+
font-size: 14px;
|
| 53 |
+
font-weight: 500;
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
.status-close {
|
| 57 |
+
background: none;
|
| 58 |
+
border: none;
|
| 59 |
+
padding: 2px;
|
| 60 |
+
cursor: pointer;
|
| 61 |
+
border-radius: 4px;
|
| 62 |
+
color: inherit;
|
| 63 |
+
opacity: 0.7;
|
| 64 |
+
transition: opacity 0.2s ease;
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
.status-close:hover {
|
| 68 |
+
opacity: 1;
|
| 69 |
+
}
|
| 70 |
+
|
| 71 |
+
/* Upload Area */
|
| 72 |
+
.file-upload-area {
|
| 73 |
+
border: 2px dashed #D1D5DB;
|
| 74 |
+
border-radius: 12px;
|
| 75 |
+
padding: 24px;
|
| 76 |
+
text-align: center;
|
| 77 |
+
cursor: pointer;
|
| 78 |
+
transition: all 0.3s ease;
|
| 79 |
+
background-color: #F9FAFB;
|
| 80 |
+
position: relative;
|
| 81 |
+
overflow: hidden;
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
[data-theme="dark"] .file-upload-area {
|
| 85 |
+
border-color: #374151;
|
| 86 |
+
background-color: #1F2937;
|
| 87 |
+
}
|
| 88 |
+
|
| 89 |
+
.file-upload-area:hover:not(.uploading) {
|
| 90 |
+
border-color: #3B82F6;
|
| 91 |
+
background-color: #EFF6FF;
|
| 92 |
+
transform: translateY(-1px);
|
| 93 |
+
}
|
| 94 |
+
|
| 95 |
+
[data-theme="dark"] .file-upload-area:hover:not(.uploading) {
|
| 96 |
+
border-color: #60A5FA;
|
| 97 |
+
background-color: #1E3A8A;
|
| 98 |
+
}
|
| 99 |
+
|
| 100 |
+
.file-upload-area.drag-active {
|
| 101 |
+
border-color: #3B82F6;
|
| 102 |
+
background-color: #EFF6FF;
|
| 103 |
+
transform: scale(1.02);
|
| 104 |
+
}
|
| 105 |
+
|
| 106 |
+
[data-theme="dark"] .file-upload-area.drag-active {
|
| 107 |
+
border-color: #60A5FA;
|
| 108 |
+
background-color: #1E3A8A;
|
| 109 |
+
}
|
| 110 |
+
|
| 111 |
+
.file-upload-area.uploading {
|
| 112 |
+
cursor: not-allowed;
|
| 113 |
+
opacity: 0.7;
|
| 114 |
+
}
|
| 115 |
+
|
| 116 |
+
.upload-content {
|
| 117 |
+
display: flex;
|
| 118 |
+
flex-direction: column;
|
| 119 |
+
align-items: center;
|
| 120 |
+
gap: 8px;
|
| 121 |
+
}
|
| 122 |
+
|
| 123 |
+
.upload-icon {
|
| 124 |
+
color: #6B7280;
|
| 125 |
+
margin-bottom: 4px;
|
| 126 |
+
}
|
| 127 |
+
|
| 128 |
+
[data-theme="dark"] .upload-icon {
|
| 129 |
+
color: #9CA3AF;
|
| 130 |
+
}
|
| 131 |
+
|
| 132 |
+
.file-upload-area:hover:not(.uploading) .upload-icon {
|
| 133 |
+
color: #3B82F6;
|
| 134 |
+
}
|
| 135 |
+
|
| 136 |
+
[data-theme="dark"] .file-upload-area:hover:not(.uploading) .upload-icon {
|
| 137 |
+
color: #60A5FA;
|
| 138 |
+
}
|
| 139 |
+
|
| 140 |
+
.upload-text {
|
| 141 |
+
margin: 0;
|
| 142 |
+
font-size: 14px;
|
| 143 |
+
color: #374151;
|
| 144 |
+
}
|
| 145 |
+
|
| 146 |
+
[data-theme="dark"] .upload-text {
|
| 147 |
+
color: #D1D5DB;
|
| 148 |
+
}
|
| 149 |
+
|
| 150 |
+
.upload-primary {
|
| 151 |
+
font-weight: 600;
|
| 152 |
+
color: #3B82F6;
|
| 153 |
+
}
|
| 154 |
+
|
| 155 |
+
[data-theme="dark"] .upload-primary {
|
| 156 |
+
color: #60A5FA;
|
| 157 |
+
}
|
| 158 |
+
|
| 159 |
+
.upload-secondary {
|
| 160 |
+
margin: 0;
|
| 161 |
+
font-size: 12px;
|
| 162 |
+
color: #6B7280;
|
| 163 |
+
margin-top: 4px;
|
| 164 |
+
}
|
| 165 |
+
|
| 166 |
+
[data-theme="dark"] .upload-secondary {
|
| 167 |
+
color: #9CA3AF;
|
| 168 |
+
}
|
| 169 |
+
|
| 170 |
+
/* Upload Spinner */
|
| 171 |
+
.upload-spinner {
|
| 172 |
+
display: flex;
|
| 173 |
+
justify-content: center;
|
| 174 |
+
align-items: center;
|
| 175 |
+
}
|
| 176 |
+
|
| 177 |
+
.spinner {
|
| 178 |
+
width: 24px;
|
| 179 |
+
height: 24px;
|
| 180 |
+
border: 2px solid #E5E7EB;
|
| 181 |
+
border-top: 2px solid #3B82F6;
|
| 182 |
+
border-radius: 50%;
|
| 183 |
+
animation: spin 1s linear infinite;
|
| 184 |
+
}
|
| 185 |
+
|
| 186 |
+
[data-theme="dark"] .spinner {
|
| 187 |
+
border-color: #374151;
|
| 188 |
+
border-top-color: #60A5FA;
|
| 189 |
+
}
|
| 190 |
+
|
| 191 |
+
/* Supported File Types */
|
| 192 |
+
.supported-types {
|
| 193 |
+
display: flex;
|
| 194 |
+
justify-content: center;
|
| 195 |
+
gap: 8px;
|
| 196 |
+
margin-top: 8px;
|
| 197 |
+
flex-wrap: wrap;
|
| 198 |
+
}
|
| 199 |
+
|
| 200 |
+
.file-type-chip {
|
| 201 |
+
display: flex;
|
| 202 |
+
align-items: center;
|
| 203 |
+
gap: 4px;
|
| 204 |
+
padding: 4px 8px;
|
| 205 |
+
background-color: #F3F4F6;
|
| 206 |
+
border-radius: 16px;
|
| 207 |
+
font-size: 11px;
|
| 208 |
+
font-weight: 500;
|
| 209 |
+
color: #6B7280;
|
| 210 |
+
border: 1px solid #E5E7EB;
|
| 211 |
+
}
|
| 212 |
+
|
| 213 |
+
[data-theme="dark"] .file-type-chip {
|
| 214 |
+
background-color: #374151;
|
| 215 |
+
border-color: #4B5563;
|
| 216 |
+
color: #9CA3AF;
|
| 217 |
+
}
|
| 218 |
+
|
| 219 |
+
/* Enhanced Chat Input with Upload */
|
| 220 |
+
.enhanced-chat-input {
|
| 221 |
+
display: flex;
|
| 222 |
+
flex-direction: column;
|
| 223 |
+
gap: 8px;
|
| 224 |
+
width: 100%;
|
| 225 |
+
}
|
| 226 |
+
|
| 227 |
+
.input-with-upload {
|
| 228 |
+
display: flex;
|
| 229 |
+
align-items: flex-end;
|
| 230 |
+
gap: 8px;
|
| 231 |
+
width: 100%;
|
| 232 |
+
}
|
| 233 |
+
|
| 234 |
+
.upload-toggle-btn {
|
| 235 |
+
background: #F3F4F6;
|
| 236 |
+
border: 1px solid #D1D5DB;
|
| 237 |
+
border-radius: 8px;
|
| 238 |
+
padding: 8px;
|
| 239 |
+
cursor: pointer;
|
| 240 |
+
transition: all 0.2s ease;
|
| 241 |
+
display: flex;
|
| 242 |
+
align-items: center;
|
| 243 |
+
justify-content: center;
|
| 244 |
+
min-width: 40px;
|
| 245 |
+
height: 40px;
|
| 246 |
+
}
|
| 247 |
+
|
| 248 |
+
[data-theme="dark"] .upload-toggle-btn {
|
| 249 |
+
background-color: #374151;
|
| 250 |
+
border-color: #4B5563;
|
| 251 |
+
}
|
| 252 |
+
|
| 253 |
+
.upload-toggle-btn:hover {
|
| 254 |
+
background-color: #E5E7EB;
|
| 255 |
+
border-color: #3B82F6;
|
| 256 |
+
}
|
| 257 |
+
|
| 258 |
+
[data-theme="dark"] .upload-toggle-btn:hover {
|
| 259 |
+
background-color: #4B5563;
|
| 260 |
+
border-color: #60A5FA;
|
| 261 |
+
}
|
| 262 |
+
|
| 263 |
+
.upload-toggle-btn.active {
|
| 264 |
+
background-color: #3B82F6;
|
| 265 |
+
border-color: #3B82F6;
|
| 266 |
+
color: white;
|
| 267 |
+
}
|
| 268 |
+
|
| 269 |
+
.upload-toggle-btn svg {
|
| 270 |
+
color: #6B7280;
|
| 271 |
+
}
|
| 272 |
+
|
| 273 |
+
[data-theme="dark"] .upload-toggle-btn svg {
|
| 274 |
+
color: #9CA3AF;
|
| 275 |
+
}
|
| 276 |
+
|
| 277 |
+
.upload-toggle-btn:hover svg,
|
| 278 |
+
.upload-toggle-btn.active svg {
|
| 279 |
+
color: currentColor;
|
| 280 |
+
}
|
| 281 |
+
|
| 282 |
+
/* Animations */
|
| 283 |
+
@keyframes slideDown {
|
| 284 |
+
from {
|
| 285 |
+
opacity: 0;
|
| 286 |
+
transform: translateY(-10px);
|
| 287 |
+
}
|
| 288 |
+
to {
|
| 289 |
+
opacity: 1;
|
| 290 |
+
transform: translateY(0);
|
| 291 |
+
}
|
| 292 |
+
}
|
| 293 |
+
|
| 294 |
+
@keyframes spin {
|
| 295 |
+
0% { transform: rotate(0deg); }
|
| 296 |
+
100% { transform: rotate(360deg); }
|
| 297 |
+
}
|
| 298 |
+
|
| 299 |
+
/* Compact upload area for chat input */
|
| 300 |
+
.file-upload-container.compact .file-upload-area {
|
| 301 |
+
padding: 16px;
|
| 302 |
+
font-size: 13px;
|
| 303 |
+
}
|
| 304 |
+
|
| 305 |
+
.file-upload-container.compact .upload-content {
|
| 306 |
+
gap: 6px;
|
| 307 |
+
}
|
| 308 |
+
|
| 309 |
+
.file-upload-container.compact .upload-icon {
|
| 310 |
+
width: 20px;
|
| 311 |
+
height: 20px;
|
| 312 |
+
}
|
| 313 |
+
|
| 314 |
+
.file-upload-container.compact .supported-types {
|
| 315 |
+
margin-top: 6px;
|
| 316 |
+
}
|
| 317 |
+
|
| 318 |
+
/* Responsive adjustments */
|
| 319 |
+
@media (max-width: 640px) {
|
| 320 |
+
.file-upload-area {
|
| 321 |
+
padding: 16px;
|
| 322 |
+
}
|
| 323 |
+
|
| 324 |
+
.supported-types {
|
| 325 |
+
gap: 6px;
|
| 326 |
+
}
|
| 327 |
+
|
| 328 |
+
.file-type-chip {
|
| 329 |
+
font-size: 10px;
|
| 330 |
+
padding: 3px 6px;
|
| 331 |
+
}
|
| 332 |
+
}
|
phd-advisor-frontend/src/styles/components.css
CHANGED
|
@@ -1,5 +1,3 @@
|
|
| 1 |
-
/* components.css - General Component Styles */
|
| 2 |
-
|
| 3 |
/* CSS Custom Properties for Theming */
|
| 4 |
:root[data-theme="light"] {
|
| 5 |
/* Background Colors */
|
|
@@ -215,6 +213,7 @@
|
|
| 215 |
line-height: 1.6;
|
| 216 |
}
|
| 217 |
|
|
|
|
| 218 |
.cta-button {
|
| 219 |
display: inline-flex;
|
| 220 |
align-items: center;
|
|
@@ -241,6 +240,11 @@
|
|
| 241 |
height: 24px;
|
| 242 |
}
|
| 243 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 244 |
/* Advisors Grid */
|
| 245 |
.advisors-grid {
|
| 246 |
display: grid;
|
|
@@ -623,6 +627,15 @@
|
|
| 623 |
margin: 0;
|
| 624 |
}
|
| 625 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 626 |
.thinking-dots {
|
| 627 |
display: flex;
|
| 628 |
gap: 4px;
|
|
@@ -641,7 +654,7 @@
|
|
| 641 |
margin-bottom: 0;
|
| 642 |
}
|
| 643 |
|
| 644 |
-
/* Chat Input */
|
| 645 |
.input-area {
|
| 646 |
border-top: 1px solid var(--border-primary);
|
| 647 |
padding: 16px;
|
|
@@ -675,7 +688,8 @@
|
|
| 675 |
box-shadow: var(--input-focus-shadow);
|
| 676 |
}
|
| 677 |
|
| 678 |
-
|
|
|
|
| 679 |
background: var(--accent-gradient);
|
| 680 |
color: #FFFFFF;
|
| 681 |
padding: 12px 24px;
|
|
@@ -689,23 +703,23 @@
|
|
| 689 |
gap: 8px;
|
| 690 |
}
|
| 691 |
|
| 692 |
-
.send-button:hover:not(:disabled) {
|
| 693 |
transform: translateY(-1px);
|
| 694 |
box-shadow: var(--shadow-md);
|
| 695 |
}
|
| 696 |
|
| 697 |
-
.send-button:disabled {
|
| 698 |
opacity: 0.5;
|
| 699 |
cursor: not-allowed;
|
| 700 |
transform: none;
|
| 701 |
}
|
| 702 |
|
| 703 |
-
.send-icon {
|
| 704 |
width: 20px;
|
| 705 |
height: 20px;
|
| 706 |
}
|
| 707 |
|
| 708 |
-
.send-text {
|
| 709 |
display: none;
|
| 710 |
}
|
| 711 |
|
|
@@ -755,7 +769,8 @@
|
|
| 755 |
font-size: 13px;
|
| 756 |
}
|
| 757 |
|
| 758 |
-
|
|
|
|
| 759 |
display: inline;
|
| 760 |
}
|
| 761 |
|
|
|
|
|
|
|
|
|
|
| 1 |
/* CSS Custom Properties for Theming */
|
| 2 |
:root[data-theme="light"] {
|
| 3 |
/* Background Colors */
|
|
|
|
| 213 |
line-height: 1.6;
|
| 214 |
}
|
| 215 |
|
| 216 |
+
/* CTA Button */
|
| 217 |
.cta-button {
|
| 218 |
display: inline-flex;
|
| 219 |
align-items: center;
|
|
|
|
| 240 |
height: 24px;
|
| 241 |
}
|
| 242 |
|
| 243 |
+
.cta-arrow {
|
| 244 |
+
width: 20px;
|
| 245 |
+
height: 20px;
|
| 246 |
+
}
|
| 247 |
+
|
| 248 |
/* Advisors Grid */
|
| 249 |
.advisors-grid {
|
| 250 |
display: grid;
|
|
|
|
| 627 |
margin: 0;
|
| 628 |
}
|
| 629 |
|
| 630 |
+
.thinking-indicator {
|
| 631 |
+
display: flex;
|
| 632 |
+
align-items: center;
|
| 633 |
+
gap: 8px;
|
| 634 |
+
color: var(--text-secondary);
|
| 635 |
+
font-style: italic;
|
| 636 |
+
margin-bottom: 8px;
|
| 637 |
+
}
|
| 638 |
+
|
| 639 |
.thinking-dots {
|
| 640 |
display: flex;
|
| 641 |
gap: 4px;
|
|
|
|
| 654 |
margin-bottom: 0;
|
| 655 |
}
|
| 656 |
|
| 657 |
+
/* Basic Chat Input - SCOPED TO AVOID CONFLICTS */
|
| 658 |
.input-area {
|
| 659 |
border-top: 1px solid var(--border-primary);
|
| 660 |
padding: 16px;
|
|
|
|
| 688 |
box-shadow: var(--input-focus-shadow);
|
| 689 |
}
|
| 690 |
|
| 691 |
+
/* IMPORTANT: Make send button styles specific to basic ChatInput only */
|
| 692 |
+
.input-container .send-button {
|
| 693 |
background: var(--accent-gradient);
|
| 694 |
color: #FFFFFF;
|
| 695 |
padding: 12px 24px;
|
|
|
|
| 703 |
gap: 8px;
|
| 704 |
}
|
| 705 |
|
| 706 |
+
.input-container .send-button:hover:not(:disabled) {
|
| 707 |
transform: translateY(-1px);
|
| 708 |
box-shadow: var(--shadow-md);
|
| 709 |
}
|
| 710 |
|
| 711 |
+
.input-container .send-button:disabled {
|
| 712 |
opacity: 0.5;
|
| 713 |
cursor: not-allowed;
|
| 714 |
transform: none;
|
| 715 |
}
|
| 716 |
|
| 717 |
+
.input-container .send-icon {
|
| 718 |
width: 20px;
|
| 719 |
height: 20px;
|
| 720 |
}
|
| 721 |
|
| 722 |
+
.input-container .send-text {
|
| 723 |
display: none;
|
| 724 |
}
|
| 725 |
|
|
|
|
| 769 |
font-size: 13px;
|
| 770 |
}
|
| 771 |
|
| 772 |
+
/* Basic ChatInput mobile styles */
|
| 773 |
+
.input-container .send-text {
|
| 774 |
display: inline;
|
| 775 |
}
|
| 776 |
|