fix(assistant): add FormattedMessage markdown parser and handle '¿Que puedo preguntar?' with comprehensive guide
This commit is contained in:
@@ -1,7 +1,105 @@
|
||||
"use client";
|
||||
|
||||
import React, { useState, useRef, useEffect } from 'react';
|
||||
import { Bot, Send, Sparkles, TrendingUp, Target, Filter, DollarSign, BookOpen } from 'lucide-react';
|
||||
import { Bot, Send, Sparkles, TrendingUp, Target, Filter, DollarSign, BookOpen, HelpCircle } from 'lucide-react';
|
||||
|
||||
function FormattedMessage({ content }: { content: string }) {
|
||||
const lines = content.split('\n');
|
||||
|
||||
const renderInline = (text: string) => {
|
||||
const parts: React.ReactNode[] = [];
|
||||
const regex = /(\*\*[^*]+\*\*|\*[^*]+\*|`[^`]+`)/g;
|
||||
let lastIndex = 0;
|
||||
let match: RegExpExecArray | null;
|
||||
|
||||
while ((match = regex.exec(text)) !== null) {
|
||||
if (match.index > lastIndex) {
|
||||
parts.push(text.substring(lastIndex, match.index));
|
||||
}
|
||||
const matchStr = match[0];
|
||||
if (matchStr.startsWith('**') && matchStr.endsWith('**')) {
|
||||
parts.push(
|
||||
<strong key={match.index} className="font-bold text-white tracking-wide">
|
||||
{matchStr.slice(2, -2)}
|
||||
</strong>
|
||||
);
|
||||
} else if (matchStr.startsWith('*') && matchStr.endsWith('*')) {
|
||||
parts.push(
|
||||
<em key={match.index} className="italic text-gray-300">
|
||||
{matchStr.slice(1, -1)}
|
||||
</em>
|
||||
);
|
||||
} else if (matchStr.startsWith('`') && matchStr.endsWith('`')) {
|
||||
parts.push(
|
||||
<code key={match.index} className="bg-gray-900/80 text-brand-400 font-mono text-[11px] px-1.5 py-0.5 rounded border border-gray-700/60">
|
||||
{matchStr.slice(1, -1)}
|
||||
</code>
|
||||
);
|
||||
}
|
||||
lastIndex = regex.lastIndex;
|
||||
}
|
||||
|
||||
if (lastIndex < text.length) {
|
||||
parts.push(text.substring(lastIndex));
|
||||
}
|
||||
|
||||
return parts;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-1.5 text-sm leading-relaxed">
|
||||
{lines.map((line, lineIdx) => {
|
||||
const trimmed = line.trim();
|
||||
if (!trimmed) {
|
||||
return <div key={lineIdx} className="h-1.5" />;
|
||||
}
|
||||
|
||||
const isBullet = trimmed.startsWith('- ') || trimmed.startsWith('• ');
|
||||
const isNumbered = /^\d+\.\s/.test(trimmed);
|
||||
const isQuote = trimmed.startsWith('> ');
|
||||
|
||||
let displayText = trimmed;
|
||||
if (isBullet) displayText = trimmed.slice(2);
|
||||
else if (isNumbered) displayText = trimmed.replace(/^\d+\.\s/, '');
|
||||
else if (isQuote) displayText = trimmed.slice(2);
|
||||
|
||||
if (isBullet) {
|
||||
return (
|
||||
<div key={lineIdx} className="flex items-start gap-2 pl-2">
|
||||
<span className="text-brand-400 select-none mt-1 leading-none text-xs">◆</span>
|
||||
<div className="flex-1">{renderInline(displayText)}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (isNumbered) {
|
||||
const numberMatch = trimmed.match(/^(\d+)\.\s/);
|
||||
const num = numberMatch ? numberMatch[1] : '•';
|
||||
return (
|
||||
<div key={lineIdx} className="flex items-start gap-2 pl-2">
|
||||
<span className="text-brand-400 font-bold font-mono text-xs mt-0.5">{num}.</span>
|
||||
<div className="flex-1">{renderInline(displayText)}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (isQuote) {
|
||||
return (
|
||||
<blockquote key={lineIdx} className="border-l-2 border-brand-500/80 pl-3 italic text-gray-300 my-1 bg-gray-850/40 py-1 rounded-r">
|
||||
{renderInline(displayText)}
|
||||
</blockquote>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<p key={lineIdx}>
|
||||
{renderInline(trimmed)}
|
||||
</p>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function AsistentePage() {
|
||||
const [input, setInput] = useState('');
|
||||
@@ -88,22 +186,22 @@ export default function AsistentePage() {
|
||||
className={`flex ${m.role === 'user' ? 'justify-end' : 'justify-start'}`}
|
||||
>
|
||||
<div
|
||||
className={`max-w-2xl px-4 py-3 rounded-xl text-sm leading-relaxed whitespace-pre-wrap ${
|
||||
className={`max-w-2xl px-5 py-3.5 rounded-2xl ${
|
||||
m.role === 'user'
|
||||
? 'bg-brand-500 text-white font-medium shadow-md shadow-brand-500/10'
|
||||
: 'bg-[#1F2937] text-gray-200 border border-gray-700/60 shadow-md shadow-black/20'
|
||||
? 'bg-gradient-to-r from-brand-500 to-amber-600 text-white font-medium shadow-md shadow-brand-500/10'
|
||||
: 'bg-[#1a2333] text-gray-200 border border-gray-800/80 shadow-lg shadow-black/20'
|
||||
}`}
|
||||
>
|
||||
{m.content}
|
||||
<FormattedMessage content={m.content} />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
{loading && (
|
||||
<div className="flex justify-start">
|
||||
<div className="bg-[#1F2937] text-gray-300 px-4 py-3 rounded-xl text-sm border border-gray-700/60 flex items-center gap-2">
|
||||
<div className="bg-[#1a2333] text-gray-300 px-5 py-3 rounded-2xl text-sm border border-gray-800/80 flex items-center gap-2">
|
||||
<Sparkles className="w-4 h-4 animate-spin text-brand-500" />
|
||||
Calculando proyecciones y consultando base de datos...
|
||||
Calculando métricas sin alucinaciones...
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@@ -112,6 +210,13 @@ export default function AsistentePage() {
|
||||
|
||||
{/* Quick Prompts Chips */}
|
||||
<div className="px-6 py-2.5 bg-[#0D121F]/80 border-t border-gray-800/80 flex gap-2 overflow-x-auto text-xs text-gray-300">
|
||||
<button
|
||||
onClick={() => sendMessage('¿Qué puedo preguntar?')}
|
||||
className="px-3 py-1.5 rounded-full bg-brand-500/20 text-brand-400 hover:bg-brand-500/30 whitespace-nowrap transition-colors flex items-center gap-1.5 cursor-pointer border border-brand-500/40"
|
||||
>
|
||||
<HelpCircle className="w-3.5 h-3.5" />
|
||||
❓ ¿Qué puedo preguntar?
|
||||
</button>
|
||||
<button
|
||||
onClick={() => sendMessage('¿Qué pasa si invierto 1.000 € en Meta?')}
|
||||
className="px-3 py-1.5 rounded-full bg-gray-800 hover:bg-gray-700 hover:text-white whitespace-nowrap transition-colors flex items-center gap-1.5 cursor-pointer border border-gray-700/50"
|
||||
@@ -127,7 +232,7 @@ export default function AsistentePage() {
|
||||
🎯 Objetivos de este mes
|
||||
</button>
|
||||
<button
|
||||
onClick={() => sendMessage('Muéstrame el embudo de conversión y tasa de cierre de los últimos 30 días')}
|
||||
onClick={() => sendMessage('Muéstrame el embudo comercial')}
|
||||
className="px-3 py-1.5 rounded-full bg-gray-800 hover:bg-gray-700 hover:text-white whitespace-nowrap transition-colors flex items-center gap-1.5 cursor-pointer border border-gray-700/50"
|
||||
>
|
||||
<Filter className="w-3.5 h-3.5 text-blue-400" />
|
||||
@@ -162,7 +267,7 @@ export default function AsistentePage() {
|
||||
type="text"
|
||||
value={input}
|
||||
onChange={(e) => setInput(e.target.value)}
|
||||
placeholder="Escribe una pregunta sobre inversión, metas, CAC, o escribe «Hola»..."
|
||||
placeholder="Pregunta sobre inversión, metas, CAC, o escribe «¿Qué puedo preguntar?»..."
|
||||
className="flex-1 bg-[#111827] border border-gray-700 rounded-xl px-4 py-2.5 text-sm text-white placeholder-gray-500 focus:outline-none focus:border-brand-500 transition-all"
|
||||
/>
|
||||
<button
|
||||
|
||||
Reference in New Issue
Block a user