diff --git a/backend/app/services/llm_agent.py b/backend/app/services/llm_agent.py
index 9999a30..7e12ff5 100644
--- a/backend/app/services/llm_agent.py
+++ b/backend/app/services/llm_agent.py
@@ -63,23 +63,26 @@ async def _get_user_tier(db: AsyncSession, user: User) -> str:
def _build_system_prompt(path: str) -> str:
tools = tool_catalog_for_prompt(path)
return (
- "You are the Pounce Hunter Companion, an expert domain trading assistant. Always respond in English.\n"
- "You help users with: domain analysis, auction hunting, portfolio management, and trading decisions.\n\n"
- "RESPONSE FORMAT (CRITICAL):\n"
- "- Write in plain text. NO markdown asterisks, NO ** or *, NO code blocks.\n"
- "- Use simple dashes (-) for bullet points.\n"
- "- Keep responses concise: 2-4 sentences intro, then bullets if needed.\n"
- "- Never show tool outputs, JSON, or internal data to the user.\n\n"
- "BEHAVIOR:\n"
- "- Be helpful, direct, and conversational like a knowledgeable colleague.\n"
- "- For domain questions: give a clear BUY / CONSIDER / SKIP recommendation with 3-5 reasons.\n"
- "- Do NOT invent user preferences, keywords, or data. Ask if unclear.\n"
- "- For greetings: respond naturally and ask how you can help.\n\n"
+ "You are the Pounce Hunter Companion, a domain trading expert. Always respond in English.\n\n"
+ "CRITICAL RULES:\n"
+ "1. NEVER invent or hallucinate data. You do NOT have access to SEMrush, Estibot, GoDaddy sales, or external databases.\n"
+ "2. If you don't have data, say so honestly. Only use data from tools you actually called.\n"
+ "3. Keep responses SHORT: 2-3 sentences max, then bullets if needed.\n"
+ "4. NO markdown: no ** or *, no code blocks, no headers with #.\n"
+ "5. Use dashes (-) for bullet points.\n\n"
+ "WHAT YOU CAN DO:\n"
+ "- Analyze domains using the analyze_domain tool (gives Pounce Score, risk, value estimate)\n"
+ "- Show user's watchlist, portfolio, listings, inbox, yield data\n"
+ "- Search auctions and drops\n"
+ "- Generate brandable names\n\n"
+ "WHAT YOU CANNOT DO:\n"
+ "- Access external sales databases or SEO tools\n"
+ "- Look up real-time WHOIS or DNS (unless via tool)\n"
+ "- Make up sales history or traffic stats\n\n"
"TOOL USAGE:\n"
- "- Use tools when user asks about their data (watchlist, portfolio, listings, inbox, yield) or a specific domain.\n"
- "- To call tools, respond with ONLY: {\"tool_calls\":[{\"name\":\"...\",\"args\":{...}}]}\n"
- "- After receiving tool results, answer naturally without mentioning tools.\n\n"
- f"AVAILABLE TOOLS:\n{json.dumps(tools, ensure_ascii=False)}\n"
+ "- To call a tool, respond with ONLY: {\"tool_calls\":[{\"name\":\"...\",\"args\":{...}}]}\n"
+ "- After tool results, summarize briefly without mentioning tools.\n\n"
+ f"TOOLS:\n{json.dumps(tools, ensure_ascii=False)}\n"
)
@@ -143,13 +146,8 @@ async def run_agent(
{
"role": "assistant",
"content": (
- "Hey! How can I help you today?\n\n"
- "I can help with:\n"
- "- Analyzing a specific domain\n"
- "- Finding auction deals or drops\n"
- "- Reviewing your portfolio or watchlist\n"
- "- Checking your listings and leads\n\n"
- "Just tell me what you need."
+ "Hey! What can I help you with?\n\n"
+ "Give me a domain to analyze, or ask about your watchlist, portfolio, or current auctions."
),
}
)
@@ -206,11 +204,11 @@ async def stream_final_answer(convo: list[dict[str, Any]], *, model: Optional[st
{
"role": "system",
"content": (
- "Final step: respond to the user in plain text.\n"
- "- NO markdown: no ** or * for bold/italic, no code blocks.\n"
- "- Use dashes (-) for bullets.\n"
- "- Do NOT output JSON or mention tools.\n"
- "- Be concise and helpful."
+ "Respond now. Rules:\n"
+ "- NEVER invent data. Only use data from tools you called.\n"
+ "- Keep it SHORT: 2-3 sentences, then bullet points if needed.\n"
+ "- NO markdown (no ** or *), just plain text with dashes for bullets.\n"
+ "- Do NOT mention tools or JSON."
),
}
],
diff --git a/frontend/src/components/chat/HunterCompanion.tsx b/frontend/src/components/chat/HunterCompanion.tsx
index f47b410..976ceed 100644
--- a/frontend/src/components/chat/HunterCompanion.tsx
+++ b/frontend/src/components/chat/HunterCompanion.tsx
@@ -98,27 +98,52 @@ function getTier(subscription: any): 'scout' | 'trader' | 'tycoon' {
return 'scout'
}
-// Simple markdown-like formatting to clean HTML
+// Format message text to clean HTML with proper spacing
function formatMessage(text: string): string {
if (!text) return ''
+
+ // Escape HTML first
let html = text
- // Escape HTML
.replace(/&/g, '&')
.replace(//g, '>')
- // Bold: **text** or __text__
- .replace(/\*\*(.+?)\*\*/g, '$1')
- .replace(/__(.+?)__/g, '$1')
- // Italic: *text* or _text_ (but not inside words)
- .replace(/(?$1')
- .replace(/(?$1')
- // Inline code: `code`
- .replace(/`([^`]+)`/g, '$1')
- // Line breaks
- .replace(/\n/g, '
')
- // Bullet points: - item or • item
- .replace(/
[-•]\s+/g, '
• ')
- return html
+
+ // Remove markdown formatting (** and * for bold/italic)
+ html = html
+ .replace(/\*\*(.+?)\*\*/g, '$1')
+ .replace(/\*([^*]+)\*/g, '$1')
+ .replace(/__(.+?)__/g, '$1')
+ .replace(/_([^_]+)_/g, '$1')
+
+ // Split into paragraphs (double newline = paragraph break)
+ const paragraphs = html.split(/\n\n+/)
+
+ const formatted = paragraphs.map(para => {
+ // Check if this paragraph is a list (starts with - or number.)
+ const lines = para.split('\n')
+ const isList = lines.every(line => {
+ const trimmed = line.trim()
+ return trimmed === '' || trimmed.startsWith('-') || trimmed.startsWith('•') || /^\d+\./.test(trimmed)
+ })
+
+ if (isList) {
+ // Format as list
+ const items = lines
+ .map(line => line.trim())
+ .filter(line => line)
+ .map(line => {
+ // Remove leading dash, bullet, or number
+ const content = line.replace(/^[-•]\s*/, '').replace(/^\d+\.\s*/, '')
+ return `
${para.replace(/\n/g, '
')}