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:
SamoilenkoVadym 2026-02-03 16:20:07 +00:00
parent 4c4843d1b2
commit de237b28da
4 changed files with 693 additions and 44 deletions

View file

@ -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

View file

@ -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",

View file

@ -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>

View file

@ -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;
}
}