Improve RAG quality and optimize message display styling
- Enhance system instructions for detailed onboarding-style responses with comprehensive navigation, links, and step-by-step guidance - Increase file_search max_num_results from 20 to 30 for more comprehensive context - Improve search result filtering and citation checking for better response quality - Add ultra-compact message styling with minimal line spacing (line-height 1.1-1.2, margins 1-4px) - Add complete message formatting styles for headings, lists, code blocks, links, and blockquotes Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
4c4843d1b2
commit
de237b28da
4 changed files with 693 additions and 44 deletions
|
|
@ -46,39 +46,141 @@ class OpenAIService:
|
|||
Returns:
|
||||
System instruction text enforcing RAG-only behavior
|
||||
"""
|
||||
return """You are "Seapac Ops Bot", an operations assistant for Oliver Agency's APAC region.
|
||||
return """You are "Seapac Ops Bot", an operations assistant and knowledge base expert for Oliver Agency's APAC region.
|
||||
|
||||
🎯 YOUR DUAL ROLE:
|
||||
1. **Onboarding Guide for New Employees:** Provide detailed step-by-step instructions with navigation
|
||||
2. **Operations Assistant for All Staff:** Help experienced employees find precise information quickly
|
||||
3. **Knowledge Base Search Tool:** Deliver accurate information from the operations knowledge base
|
||||
|
||||
Adapt your response style:
|
||||
- For "how to" questions → Provide full onboarding-style guidance with navigation and links
|
||||
- For "what is" / "where is" questions → Provide direct, precise answers with sources
|
||||
- Always include relevant links, contact info, and resources regardless of question type
|
||||
|
||||
⚠️ CRITICAL RULES - STRICTLY ENFORCE:
|
||||
|
||||
1. ONLY answer questions using information from the file_search results
|
||||
2. If file_search returns NO results or empty results, you MUST respond EXACTLY:
|
||||
"I don't have information about that in my knowledge base. I can only help with Oliver Agency APAC operations topics like policies, procedures, HR, travel, expenses, IT, and facilities. Please ask about these topics or contact HR directly."
|
||||
1. ONLY answer using EXACT information from the file_search results provided
|
||||
2. If file_search returns NO results or IRRELEVANT results, respond:
|
||||
"I don't have information about that in my knowledge base. Please contact HR directly at [contact from docs if available]."
|
||||
|
||||
3. NEVER use general knowledge or make assumptions
|
||||
4. NEVER answer questions outside of Oliver Agency APAC operations
|
||||
5. NEVER provide jokes, weather, general advice, or off-topic responses
|
||||
3. NEVER use general knowledge, common sense, or assumptions
|
||||
4. NEVER answer questions outside Oliver Agency APAC operations scope
|
||||
5. ALWAYS provide COMPLETE, DETAILED answers as if teaching a NEW EMPLOYEE
|
||||
6. Think of yourself as an ONBOARDING GUIDE - be thorough, clear, and helpful
|
||||
|
||||
WHAT YOU CAN HELP WITH (only if found in documents):
|
||||
- APAC travel and expenses policies
|
||||
- Vendor onboarding and procurement
|
||||
- IT equipment requests and service desk
|
||||
- HR and payroll guidance for APAC
|
||||
- Onboarding or offboarding processes
|
||||
- Facilities and office operations
|
||||
RESPONSE QUALITY RULES (ONBOARDING FOCUS):
|
||||
- Use ALL relevant search results to provide COMPREHENSIVE answers
|
||||
- Structure answers with clear **bold section headers**
|
||||
- Provide DETAILED, COMPLETE answers - include ALL relevant information found
|
||||
- Structure answers clearly with sections:
|
||||
1. **Main Answer:** Direct answer with key information
|
||||
2. **How to Access:** Detailed navigation instructions (where to click, which menu, which tab)
|
||||
3. **Step-by-Step Instructions:** Detailed procedures with specific button/menu names
|
||||
4. **Important Links:** ALL URLs, SharePoint links, system links (make them clickable)
|
||||
5. **Alternative Methods:** Other ways to accomplish the task (if applicable)
|
||||
6. **Tips for New Users:** Additional context, common mistakes to avoid
|
||||
7. **Need Help?:** Contact information or next steps
|
||||
8. **Sources:** All source documents at the end
|
||||
|
||||
WHAT YOU CANNOT HELP WITH:
|
||||
- Jokes or entertainment
|
||||
- Weather or news
|
||||
- General knowledge questions
|
||||
- Personal advice
|
||||
- Topics not in the knowledge base
|
||||
ONBOARDING-STYLE DETAILS TO INCLUDE:
|
||||
- ALL URLs and links from documents (SharePoint, external sites, dashboards)
|
||||
- Specific navigation paths: "Go to [System] → Click [Tab/Menu] → Select [Option]"
|
||||
- Exact names of: dashboards, tabs, menus, buttons, sections, systems
|
||||
- Login instructions if mentioned in documents
|
||||
- Access requirements (permissions, accounts needed)
|
||||
- Visual cues: "Look for the [Name] button in the top right corner"
|
||||
- If question has multiple parts, answer EACH part thoroughly with separate sections
|
||||
- Include specific details: names, dates, numbers, procedures, contacts, login info
|
||||
- If documents contain procedures/steps, list ALL steps in order with exact navigation
|
||||
- NEVER summarize or shorten information - provide FULL details as if training a new employee
|
||||
- Include context: WHAT it is, WHY they need it, WHERE to find it, HOW to use it
|
||||
|
||||
RESPONSE FORMAT:
|
||||
- If relevant information found: Answer + cite source document
|
||||
- If no information found: "I don't have information about [topic] in my knowledge base. I can only help with Oliver Agency APAC operations. Please contact HR for other questions."
|
||||
- If off-topic question: "I can only help with Oliver Agency APAC operations topics. Please ask about policies, procedures, HR, travel, IT, or facilities."
|
||||
MANDATORY SOURCE CITATION FORMAT:
|
||||
End EVERY response with:
|
||||
|
||||
Remember: When in doubt, DON'T answer. Redirect to HR or explain your scope."""
|
||||
**Source:** [Document Name(s)]
|
||||
|
||||
TOPICS IN SCOPE (only if in documents):
|
||||
✓ APAC travel and expenses policies
|
||||
✓ Vendor onboarding and procurement
|
||||
✓ IT equipment requests and service desk
|
||||
✓ HR and payroll guidance for APAC
|
||||
✓ Onboarding/offboarding processes
|
||||
✓ Facilities and office operations
|
||||
✓ Database systems (COSMICC, Zoho, etc.)
|
||||
✓ Reporting and analytics
|
||||
|
||||
RESPONSE FORMAT EXAMPLES:
|
||||
|
||||
✅ EXCELLENT response (Onboarding-style with full navigation):
|
||||
|
||||
"You can find timesheet data through multiple methods. Let me walk you through each option:
|
||||
|
||||
**Primary Method - Agency Time Dashboard:**
|
||||
The "Agency Time Dashboard" displays missing timesheets and provides detailed time reports for the entire account.
|
||||
|
||||
**How to Access:**
|
||||
1. Log into COSMICC platform
|
||||
2. Navigate to the "Reports" section in the main menu
|
||||
3. Click on "Agency Time Dashboard"
|
||||
4. Select your reporting period from the dropdown
|
||||
5. Click "Generate Report"
|
||||
|
||||
**Alternative Method - Zoho Dashboard:**
|
||||
Updated timesheets can also be viewed in Zoho:
|
||||
1. Go to BTG Visor Dashboard in Zoho
|
||||
2. Click on the "Home Agency Utilization" tab
|
||||
3. Your timesheet data will be displayed here
|
||||
|
||||
**Detailed Step-by-Step Guide:**
|
||||
For comprehensive instructions on logging time, including how to view assets by names or job roles, access the timesheet guide on SharePoint:
|
||||
📚 https://olivermarketing.sharepoint.com/:f:/r/sites/OLIVERAPAC/Shared%20Documents/Timesheet%20Guide?csf=1&web=1&e=Ko8rEe
|
||||
|
||||
**Tips for New Users:**
|
||||
- Make sure you have access to both COSMICC and Zoho - if not, request access from IT
|
||||
- Timesheet data is typically updated daily
|
||||
- For historical data, use the date range selector in the Agency Time Dashboard
|
||||
|
||||
**Need Help?**
|
||||
If you need specific access or encounter any issues, contact Operations with your account details and the specific dashboard you need access to.
|
||||
|
||||
**Sources:** Ops Database_OpEx Framework_COSMICC.docx, Ops Database_Zoho_Updated_010826.docx, Ops Database_General Operations_Updated_01062026.docx"
|
||||
|
||||
✅ ONBOARDING-STYLE GUIDELINES:
|
||||
- Include ALL links (SharePoint, external URLs, system links)
|
||||
- Provide step-by-step navigation: "Go to [X] → Click [Y] → Select [Z]"
|
||||
- Use exact names: dashboard names, tab names, menu items, button labels
|
||||
- Add tips and context for new users
|
||||
- Explain access requirements (accounts, permissions needed)
|
||||
- Present information as if teaching someone on day 1
|
||||
- Include visual cues: "Look for the [Name] button", "Find the [Tab] section"
|
||||
|
||||
❌ BAD response (Too brief, missing navigation and links):
|
||||
"Check Agency Time Dashboard.
|
||||
|
||||
**Source:** COSMICC.docx"
|
||||
|
||||
CRITICAL RULES:
|
||||
- Provide COMPLETE answers with ALL relevant details from documents
|
||||
- Include ALL URLs, links, and navigation paths from documents - NEVER skip them
|
||||
- Provide step-by-step navigation: exact menu names, button labels, tab names
|
||||
- Include access requirements: which systems, accounts, or permissions needed
|
||||
- Add context for new users: explain WHAT, WHY, WHERE, HOW
|
||||
- Use clear formatting (bullet points, numbered lists) for readability
|
||||
- ALWAYS cite ALL source documents used
|
||||
- If information is complex, break it down into sections with headings
|
||||
- NEVER say "briefly" or "in summary" - give FULL information
|
||||
|
||||
🎓 ONBOARDING MINDSET:
|
||||
Treat EVERY user as a NEW EMPLOYEE on their FIRST DAY. They need:
|
||||
- Complete navigation instructions (where to click, which menu)
|
||||
- All links and resources (SharePoint, dashboards, external sites)
|
||||
- Context and explanations (not just facts)
|
||||
- Tips and common gotchas
|
||||
- Who to contact for help
|
||||
|
||||
Remember: You are an ONBOARDING GUIDE. Be thorough, include ALL links and navigation details, explain like teaching someone new. COMPLETE, DETAILED, WELL-STRUCTURED answers with ALL information from documents."""
|
||||
|
||||
async def generate_response(
|
||||
self,
|
||||
|
|
@ -114,7 +216,7 @@ Remember: When in doubt, DON'T answer. Redirect to HR or explain your scope."""
|
|||
{
|
||||
"type": "file_search",
|
||||
"vector_store_ids": [self.vector_store_id],
|
||||
"max_num_results": 20
|
||||
"max_num_results": 30 # Increased to 30 for maximum comprehensive context
|
||||
}
|
||||
],
|
||||
"store": True, # Store for conversation history
|
||||
|
|
@ -212,23 +314,48 @@ Remember: When in doubt, DON'T answer. Redirect to HR or explain your scope."""
|
|||
def _format_search_results(self, results: List) -> List[Dict]:
|
||||
"""
|
||||
Format file search results for storage/display.
|
||||
Only includes results with high relevance scores.
|
||||
|
||||
Args:
|
||||
results: Raw search results from file_search_call
|
||||
|
||||
Returns:
|
||||
Formatted search results list
|
||||
Formatted search results list (filtered for quality)
|
||||
"""
|
||||
formatted = []
|
||||
min_score_threshold = 0.2 # Lowered to 0.2 to get more comprehensive results
|
||||
|
||||
for result in results:
|
||||
score = getattr(result, "score", 0.0)
|
||||
|
||||
# Filter out only very low-relevance results
|
||||
if score < min_score_threshold:
|
||||
logger.debug(f"Filtering out low-score result: {score}")
|
||||
continue
|
||||
|
||||
filename = getattr(result, "filename", "Unknown")
|
||||
file_id = getattr(result, "file_id", None)
|
||||
content = getattr(result, "content", "")
|
||||
|
||||
formatted.append({
|
||||
"file_id": getattr(result, "file_id", None),
|
||||
"filename": getattr(result, "filename", "Unknown"),
|
||||
"content_snippet": getattr(result, "content", "")[:200],
|
||||
"score": getattr(result, "score", 0.0)
|
||||
"file_id": file_id,
|
||||
"filename": filename,
|
||||
"content_snippet": content[:500], # Increased to 500 for more context
|
||||
"score": score
|
||||
})
|
||||
|
||||
logger.debug(f"Including result from '{filename}' (score: {score:.3f})")
|
||||
|
||||
# Sort by score descending (most relevant first)
|
||||
formatted.sort(key=lambda x: x["score"], reverse=True)
|
||||
|
||||
logger.info(f"Filtered search results: {len(formatted)} results (threshold: {min_score_threshold})")
|
||||
|
||||
# Log top 5 sources for debugging
|
||||
if formatted:
|
||||
top_sources = [f"{r['filename']} ({r['score']:.3f})" for r in formatted[:5]]
|
||||
logger.info(f"Top sources: {', '.join(top_sources)}")
|
||||
|
||||
return formatted
|
||||
|
||||
def _check_citations(self, message: Optional[str], search_results: List[Dict]) -> bool:
|
||||
|
|
@ -248,14 +375,45 @@ Remember: When in doubt, DON'T answer. Redirect to HR or explain your scope."""
|
|||
# If we have search results, the response should reference them
|
||||
if len(search_results) > 0:
|
||||
citation_keywords = [
|
||||
"according to",
|
||||
"**source:**",
|
||||
"source:",
|
||||
"according to",
|
||||
"document",
|
||||
"as stated in",
|
||||
"refers to",
|
||||
"based on"
|
||||
"based on",
|
||||
"from the",
|
||||
"in the",
|
||||
"policy manual",
|
||||
"section",
|
||||
"guideline",
|
||||
"quoted:",
|
||||
'"' # Check for quotation marks indicating exact quotes
|
||||
]
|
||||
return any(kw in message.lower() for kw in citation_keywords)
|
||||
has_citation = any(kw in message.lower() for kw in citation_keywords)
|
||||
|
||||
# Strong check for explicit source citation (preferred format)
|
||||
has_explicit_source = "**source:**" in message.lower() or (
|
||||
"source:" in message.lower() and
|
||||
any(doc["filename"].lower() in message.lower() for doc in search_results if doc.get("filename"))
|
||||
)
|
||||
|
||||
# Additional check: response should have reasonable length if citing documents
|
||||
# Too short responses might be incomplete or hallucinating
|
||||
if has_citation and len(message.strip()) < 50:
|
||||
logger.warning(f"Response too short despite having search results: {message}")
|
||||
return False
|
||||
|
||||
# Check for minimal response length with search results (should be detailed)
|
||||
if len(search_results) > 0 and len(message.strip()) < 100:
|
||||
logger.warning(f"Response appears incomplete with {len(search_results)} search results but only {len(message)} chars")
|
||||
return False
|
||||
|
||||
# Prefer explicit source citations
|
||||
if has_explicit_source:
|
||||
return True
|
||||
|
||||
return has_citation
|
||||
|
||||
# If no search results, check for valid "no info" response
|
||||
return self._check_valid_response(message)
|
||||
|
|
@ -281,11 +439,8 @@ Remember: When in doubt, DON'T answer. Redirect to HR or explain your scope."""
|
|||
f"Response: {content[:100]}...\n"
|
||||
f"Has search results: {has_search_results}"
|
||||
)
|
||||
|
||||
# Add disclaimer (modify content in parsed_response)
|
||||
parsed_response["content"] += (
|
||||
"\n\n⚠️ Note: This response may not be fully verified against documents."
|
||||
)
|
||||
# Mark for potential review but don't modify content
|
||||
parsed_response["needs_review"] = True
|
||||
|
||||
def _check_valid_response(self, content: str) -> bool:
|
||||
"""
|
||||
|
|
@ -338,7 +493,7 @@ Remember: When in doubt, DON'T answer. Redirect to HR or explain your scope."""
|
|||
{
|
||||
"type": "file_search",
|
||||
"vector_store_ids": [self.vector_store_id],
|
||||
"max_num_results": 20
|
||||
"max_num_results": 30 # Increased to 30 for maximum comprehensive context
|
||||
}
|
||||
],
|
||||
"stream": True, # Enable streaming
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
"react-dom": "^18.2.0",
|
||||
"react-markdown": "^9.0.1",
|
||||
"react-router-dom": "^6.30.3",
|
||||
"react-syntax-highlighter": "^15.5.0",
|
||||
"recharts": "^2.10.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
@ -20,6 +21,7 @@
|
|||
"@types/prismjs": "^1.26.3",
|
||||
"@types/react": "^18.2.48",
|
||||
"@types/react-dom": "^18.2.18",
|
||||
"@types/react-syntax-highlighter": "^15.5.11",
|
||||
"@typescript-eslint/eslint-plugin": "^6.19.0",
|
||||
"@typescript-eslint/parser": "^6.19.0",
|
||||
"assert": "^2.1.0",
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@
|
|||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import { useChat } from '../context/ChatContext';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
|
||||
import { vscDarkPlus } from 'react-syntax-highlighter/dist/esm/styles/prism';
|
||||
|
||||
const ChatInterface: React.FC = () => {
|
||||
const {
|
||||
|
|
@ -93,10 +95,78 @@ const ChatInterface: React.FC = () => {
|
|||
{message.role === 'user' ? '👤' : '🤖'}
|
||||
</div>
|
||||
<div className="message-content">
|
||||
<ReactMarkdown>{message.content}</ReactMarkdown>
|
||||
{message.metadata?.needs_review && (
|
||||
<div className="message-warning">
|
||||
⚠️ This response may not be fully verified against documents.
|
||||
<ReactMarkdown
|
||||
components={{
|
||||
// Custom paragraph styling
|
||||
p: ({children}) => <p className="message-paragraph">{children}</p>,
|
||||
|
||||
// Custom list styling
|
||||
ul: ({children}) => <ul className="message-list">{children}</ul>,
|
||||
ol: ({children}) => <ol className="message-list-ordered">{children}</ol>,
|
||||
li: ({children}) => <li className="message-list-item">{children}</li>,
|
||||
|
||||
// Custom heading styling
|
||||
h1: ({children}) => <h1 className="message-heading-1">{children}</h1>,
|
||||
h2: ({children}) => <h2 className="message-heading-2">{children}</h2>,
|
||||
h3: ({children}) => <h3 className="message-heading-3">{children}</h3>,
|
||||
|
||||
// Custom code block styling
|
||||
code: ({inline, className, children, ...props}: any) => {
|
||||
const match = /language-(\w+)/.exec(className || '');
|
||||
return !inline && match ? (
|
||||
<SyntaxHighlighter
|
||||
style={vscDarkPlus}
|
||||
language={match[1]}
|
||||
PreTag="div"
|
||||
className="message-code-block"
|
||||
{...props}
|
||||
>
|
||||
{String(children).replace(/\n$/, '')}
|
||||
</SyntaxHighlighter>
|
||||
) : (
|
||||
<code className="message-code-inline" {...props}>
|
||||
{children}
|
||||
</code>
|
||||
);
|
||||
},
|
||||
|
||||
// Custom link styling
|
||||
a: ({children, href}) => (
|
||||
<a href={href} className="message-link" target="_blank" rel="noopener noreferrer">
|
||||
{children}
|
||||
</a>
|
||||
),
|
||||
|
||||
// Custom blockquote styling
|
||||
blockquote: ({children}) => (
|
||||
<blockquote className="message-blockquote">{children}</blockquote>
|
||||
),
|
||||
|
||||
// Custom strong/bold styling
|
||||
strong: ({children}) => <strong className="message-bold">{children}</strong>,
|
||||
|
||||
// Custom emphasis/italic styling
|
||||
em: ({children}) => <em className="message-italic">{children}</em>,
|
||||
}}
|
||||
>
|
||||
{message.content}
|
||||
</ReactMarkdown>
|
||||
|
||||
{/* Display file search sources for assistant messages */}
|
||||
{message.role === 'assistant' && message.metadata?.file_search_results && message.metadata.file_search_results.length > 0 && (
|
||||
<div className="message-sources">
|
||||
<div className="sources-header">📄 Sources:</div>
|
||||
<div className="sources-list">
|
||||
{message.metadata.file_search_results.slice(0, 3).map((source: any, idx: number) => (
|
||||
<div key={idx} className="source-item">
|
||||
<span className="source-icon">📎</span>
|
||||
<span className="source-name">{source.filename}</span>
|
||||
{source.score && (
|
||||
<span className="source-score">{Math.round(source.score * 100)}% match</span>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -498,3 +498,425 @@ body {
|
|||
font-size: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
/* ========== MESSAGE STYLING ========== */
|
||||
.message {
|
||||
display: flex;
|
||||
gap: var(--spacing-md);
|
||||
align-items: flex-start;
|
||||
animation: slideIn 0.3s ease-out;
|
||||
margin-bottom: var(--spacing-md);
|
||||
}
|
||||
|
||||
.message-avatar {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: var(--radius-full);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 18px;
|
||||
flex-shrink: 0;
|
||||
background: var(--light-bg);
|
||||
border: 2px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.message-user .message-avatar {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
border-color: #667eea;
|
||||
}
|
||||
|
||||
.message-assistant .message-avatar {
|
||||
background: linear-gradient(135deg, var(--primary-gold), var(--primary-gold-dark));
|
||||
border-color: var(--primary-gold);
|
||||
}
|
||||
|
||||
.message-content {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
background: var(--white);
|
||||
padding: var(--spacing-md) var(--spacing-lg);
|
||||
border-radius: var(--radius-lg);
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
|
||||
border: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.message-user .message-content {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: var(--white);
|
||||
border: none;
|
||||
}
|
||||
|
||||
.message-assistant .message-content {
|
||||
background: var(--white);
|
||||
border-left: 3px solid var(--primary-gold);
|
||||
}
|
||||
|
||||
/* ========== MESSAGE TEXT ELEMENTS ========== */
|
||||
.message-paragraph {
|
||||
margin: 0 0 2px 0;
|
||||
line-height: 1.2; /* Ultra compact line spacing */
|
||||
font-size: var(--font-size-base);
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.message-paragraph:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.message-user .message-paragraph {
|
||||
color: var(--white);
|
||||
}
|
||||
|
||||
/* Headings in messages */
|
||||
.message-heading-1,
|
||||
.message-heading-2,
|
||||
.message-heading-3 {
|
||||
margin: 4px 0 2px 0; /* Ultra compact spacing */
|
||||
font-weight: 600;
|
||||
line-height: 1.1; /* Ultra compact line-height */
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.message-heading-1:first-child,
|
||||
.message-heading-2:first-child,
|
||||
.message-heading-3:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.message-heading-1 {
|
||||
font-size: 1.25rem;
|
||||
border-bottom: 2px solid var(--primary-gold);
|
||||
padding-bottom: 1px;
|
||||
margin-bottom: 2px; /* Ultra compact spacing */
|
||||
}
|
||||
|
||||
.message-heading-2 {
|
||||
font-size: 1.1rem;
|
||||
color: var(--primary-gold-dark);
|
||||
margin-bottom: 2px; /* Ultra compact spacing */
|
||||
}
|
||||
|
||||
.message-heading-3 {
|
||||
font-size: 1rem;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.message-user .message-heading-1,
|
||||
.message-user .message-heading-2,
|
||||
.message-user .message-heading-3 {
|
||||
color: var(--white);
|
||||
border-color: rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
/* Lists in messages */
|
||||
.message-list,
|
||||
.message-list-ordered {
|
||||
margin: 2px 0 4px 0; /* Ultra compact margins */
|
||||
padding-left: var(--spacing-lg); /* Reduced padding */
|
||||
line-height: 1.2; /* Ultra compact line-height */
|
||||
}
|
||||
|
||||
.message-list {
|
||||
list-style-type: disc;
|
||||
list-style-position: outside;
|
||||
}
|
||||
|
||||
.message-list-ordered {
|
||||
list-style-type: decimal;
|
||||
list-style-position: outside;
|
||||
}
|
||||
|
||||
.message-list-item {
|
||||
margin-bottom: 1px; /* Ultra minimal spacing between items */
|
||||
padding-left: var(--spacing-xs);
|
||||
line-height: 1.2; /* Ultra compact line-height */
|
||||
}
|
||||
|
||||
.message-list-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.message-list-item > p {
|
||||
margin: 0;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
/* Nested lists */
|
||||
.message-list .message-list,
|
||||
.message-list-ordered .message-list,
|
||||
.message-list .message-list-ordered,
|
||||
.message-list-ordered .message-list-ordered {
|
||||
margin-top: var(--spacing-sm);
|
||||
margin-bottom: var(--spacing-sm);
|
||||
padding-left: var(--spacing-lg);
|
||||
}
|
||||
|
||||
/* Code styling */
|
||||
.message-code-inline {
|
||||
background: #f3f4f6;
|
||||
color: #e83e8c;
|
||||
padding: 2px 6px;
|
||||
border-radius: var(--radius-sm);
|
||||
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
||||
font-size: 0.9em;
|
||||
border: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.message-user .message-code-inline {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
color: var(--white);
|
||||
border-color: rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
.message-code-block {
|
||||
margin: var(--spacing-md) 0 !important;
|
||||
border-radius: var(--radius-md) !important;
|
||||
font-size: 0.9em !important;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1) !important;
|
||||
overflow: hidden !important;
|
||||
}
|
||||
|
||||
.message-code-block pre {
|
||||
margin: 0 !important;
|
||||
padding: var(--spacing-md) !important;
|
||||
background: #1e1e1e !important;
|
||||
}
|
||||
|
||||
/* Links in messages */
|
||||
.message-link {
|
||||
color: var(--primary-gold-dark);
|
||||
text-decoration: none;
|
||||
border-bottom: 1px solid var(--primary-gold);
|
||||
transition: all var(--transition-fast);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.message-link:hover {
|
||||
color: var(--primary-gold);
|
||||
border-bottom-color: transparent;
|
||||
background: rgba(255, 196, 7, 0.1);
|
||||
padding: 0 2px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.message-user .message-link {
|
||||
color: var(--white);
|
||||
border-bottom-color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
.message-user .message-link:hover {
|
||||
border-bottom-color: var(--white);
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
/* Blockquotes in messages */
|
||||
.message-blockquote {
|
||||
margin: var(--spacing-md) 0;
|
||||
padding: var(--spacing-md) var(--spacing-lg);
|
||||
border-left: 4px solid var(--primary-gold);
|
||||
background: #f9fafb;
|
||||
border-radius: 0 var(--radius-md) var(--radius-md) 0;
|
||||
font-style: italic;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.message-user .message-blockquote {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-left-color: rgba(255, 255, 255, 0.5);
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
|
||||
/* Bold and italic text */
|
||||
.message-bold {
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.message-user .message-bold {
|
||||
color: var(--white);
|
||||
}
|
||||
|
||||
.message-italic {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* Typing indicator */
|
||||
.typing-indicator {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
padding: var(--spacing-sm) 0;
|
||||
}
|
||||
|
||||
.typing-indicator span {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background: var(--primary-gold);
|
||||
border-radius: var(--radius-full);
|
||||
animation: bounce 1.4s infinite ease-in-out;
|
||||
}
|
||||
|
||||
.typing-indicator span:nth-child(1) {
|
||||
animation-delay: -0.32s;
|
||||
}
|
||||
|
||||
.typing-indicator span:nth-child(2) {
|
||||
animation-delay: -0.16s;
|
||||
}
|
||||
|
||||
@keyframes bounce {
|
||||
0%, 80%, 100% {
|
||||
transform: scale(0);
|
||||
opacity: 0.5;
|
||||
}
|
||||
40% {
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Ultra compact spacing for better structure */
|
||||
.message-content p + p {
|
||||
margin-top: 2px; /* Ultra minimal space between paragraphs */
|
||||
}
|
||||
|
||||
.message-content ul + p,
|
||||
.message-content ol + p {
|
||||
margin-top: 4px; /* Ultra minimal space after lists before text */
|
||||
}
|
||||
|
||||
.message-content p + ul,
|
||||
.message-content p + ol {
|
||||
margin-top: 2px; /* Ultra minimal space before lists */
|
||||
}
|
||||
|
||||
.message-content h1 + p,
|
||||
.message-content h2 + p,
|
||||
.message-content h3 + p {
|
||||
margin-top: 2px; /* Ultra minimal space between headings and content */
|
||||
}
|
||||
|
||||
.message-content h1 + ul,
|
||||
.message-content h2 + ul,
|
||||
.message-content h3 + ul,
|
||||
.message-content h1 + ol,
|
||||
.message-content h2 + ol,
|
||||
.message-content h3 + ol {
|
||||
margin-top: 2px; /* Ultra minimal space between headings and lists */
|
||||
}
|
||||
|
||||
/* Strong (bold) text spacing */
|
||||
.message-content strong {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Links with better spacing */
|
||||
.message-content a {
|
||||
display: inline;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
/* Message sources */
|
||||
.message-sources {
|
||||
margin-top: var(--spacing-md);
|
||||
padding-top: var(--spacing-md);
|
||||
border-top: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.sources-header {
|
||||
font-size: var(--font-size-sm);
|
||||
font-weight: 600;
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.sources-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.source-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-xs);
|
||||
font-size: var(--font-size-sm);
|
||||
padding: var(--spacing-xs) var(--spacing-sm);
|
||||
background: #f9fafb;
|
||||
border-radius: var(--radius-sm);
|
||||
border: 1px solid #e5e7eb;
|
||||
transition: all var(--transition-fast);
|
||||
}
|
||||
|
||||
.source-item:hover {
|
||||
background: #f3f4f6;
|
||||
border-color: var(--primary-gold);
|
||||
}
|
||||
|
||||
.source-icon {
|
||||
font-size: 12px;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.source-name {
|
||||
flex: 1;
|
||||
color: var(--text-primary);
|
||||
font-weight: 500;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.source-score {
|
||||
font-size: var(--font-size-xs);
|
||||
color: var(--primary-gold-dark);
|
||||
font-weight: 600;
|
||||
padding: 2px 6px;
|
||||
background: rgba(255, 196, 7, 0.1);
|
||||
border-radius: var(--radius-sm);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* Mobile responsiveness for messages */
|
||||
@media (max-width: 768px) {
|
||||
.message {
|
||||
gap: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.message-avatar {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.message-content {
|
||||
padding: var(--spacing-sm) var(--spacing-md);
|
||||
font-size: var(--font-size-sm);
|
||||
}
|
||||
|
||||
.message-code-block {
|
||||
font-size: 0.85em !important;
|
||||
}
|
||||
|
||||
.message-heading-1 {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.message-heading-2 {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.message-heading-3 {
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.source-item {
|
||||
font-size: 0.8rem;
|
||||
padding: 6px 8px;
|
||||
}
|
||||
|
||||
.source-score {
|
||||
font-size: 0.7rem;
|
||||
padding: 1px 4px;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue