fix(assistant): add FormattedMessage markdown parser and handle '¿Que puedo preguntar?' with comprehensive guide
This commit is contained in:
@@ -23,7 +23,14 @@ export async function POST(req: NextRequest) {
|
|||||||
console.error('Error parsing chat body:', e);
|
console.error('Error parsing chat body:', e);
|
||||||
}
|
}
|
||||||
|
|
||||||
const query = userMessage.toLowerCase().trim();
|
// Normalizar consulta eliminando tildes, signos de interrogación y puntuación
|
||||||
|
const normQuery = userMessage
|
||||||
|
.toLowerCase()
|
||||||
|
.normalize('NFD')
|
||||||
|
.replace(/[\u0300-\u036f]/g, '')
|
||||||
|
.replace(/[¿?¡!.,;:_()]/g, ' ')
|
||||||
|
.replace(/\s+/g, ' ')
|
||||||
|
.trim();
|
||||||
|
|
||||||
// 2. Obtener tenant_id dinámico del usuario autenticado
|
// 2. Obtener tenant_id dinámico del usuario autenticado
|
||||||
let tenantId = '0b3c5fc0-6901-4caf-b26a-b6a0201d8541';
|
let tenantId = '0b3c5fc0-6901-4caf-b26a-b6a0201d8541';
|
||||||
@@ -51,39 +58,64 @@ export async function POST(req: NextRequest) {
|
|||||||
return NextResponse.json([{ text }]);
|
return NextResponse.json([{ text }]);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 3. Intención: Saludos y Presentación
|
// 3. Intención: Saludos, Capacidades y "¿Qué puedo preguntar?"
|
||||||
if (
|
const isHelpQuery =
|
||||||
query === '' ||
|
normQuery === '' ||
|
||||||
query === 'hola' ||
|
normQuery === 'hola' ||
|
||||||
query === 'buenas' ||
|
normQuery === 'buenas' ||
|
||||||
query === 'buenos dias' ||
|
normQuery === 'buenos dias' ||
|
||||||
query === 'buenas tardes' ||
|
normQuery === 'buenas tardes' ||
|
||||||
query === 'hey' ||
|
normQuery === 'hey' ||
|
||||||
query.includes('que puedes hacer') ||
|
normQuery.includes('que puedo preguntar') ||
|
||||||
query.includes('quien eres') ||
|
normQuery.includes('que te puedo preguntar') ||
|
||||||
query.includes('ayuda')
|
normQuery.includes('que preguntar') ||
|
||||||
) {
|
normQuery.includes('que sabes') ||
|
||||||
|
normQuery.includes('que puedes hacer') ||
|
||||||
|
normQuery.includes('quien eres') ||
|
||||||
|
normQuery.includes('ayuda') ||
|
||||||
|
normQuery.includes('ejemplos') ||
|
||||||
|
normQuery.includes('comandos') ||
|
||||||
|
normQuery.includes('opciones') ||
|
||||||
|
normQuery.includes('como funciona el asistente') ||
|
||||||
|
normQuery.includes('instrucciones');
|
||||||
|
|
||||||
|
if (isHelpQuery) {
|
||||||
return reply(
|
return reply(
|
||||||
`👋 **¡Hola! Soy el Asistente Financiero y de Crecimiento de 4Ventures** para **${companyName}**.\n\n` +
|
`👋 **¡Hola! Soy tu Asistente Financiero & de Crecimiento de 4Ventures** para **${companyName}**.\n\n` +
|
||||||
`Estoy conectado directamente a tu base de datos analítica y ejecuto cálculos matemáticos deterministas (temperatura 0.0, sin alucinaciones):\n\n` +
|
`Estoy conectado directamente a tu base de datos analítica y ejecuto cálculos matemáticos deterministas (temperatura 0.0, sin alucinaciones). Aquí tienes todo lo que puedes preguntarme:\n\n` +
|
||||||
`💡 **¿Qué puedes consultarme?**\n` +
|
`1. **🔮 Simulaciones de Inversión ("¿Qué pasa si...?"):**\n` +
|
||||||
`1. **Simulaciones presupuestarias:** *«¿Qué pasa si invierto 1.500 € en Meta?»* o *«Simular 2.000 € en Google»*.\n` +
|
` - *«¿Qué pasa si invierto 1.000 € en Meta?»*\n` +
|
||||||
`2. **Cumplimiento de Objetivos:** *«¿Cómo vamos con los objetivos de este mes?»*.\n` +
|
` - *«Simular 2.500 € en Google Ads»*\n` +
|
||||||
`3. **Embudo y Conversión:** *«Muéstrame el embudo comercial y tasa de cierre»*.\n` +
|
` - *«Proyección con 5.000 € de presupuesto»*\n` +
|
||||||
`4. **Métricas de CAC y ROAS:** *«¿Cuál es el CAC y ROAS de los últimos 30 días?»*.\n` +
|
` *(Calcula retornos p10 conservador, p50 esperado y p90 optimista según tu histórico real).* \n\n` +
|
||||||
`5. **Ranking de Anuncios:** *«¿Cuáles son las mejores campañas activas?»*.\n` +
|
`2. **🎯 Objetivos y Cumplimiento Mensual:**\n` +
|
||||||
`6. **Biblioteca de Activos:** *«Busca copys de Meta Ads o informes de auditoría»*.\n\n` +
|
` - *«¿Cómo van los objetivos de este mes?»*\n` +
|
||||||
`¿Qué métrica o escenario te gustaría analizar hoy?`
|
` - *«¿Cuánto llevamos facturado vs la meta?»*\n` +
|
||||||
|
` - *«Progreso de captación de leads»*\n\n` +
|
||||||
|
`3. **🔄 Embudo Comercial y Conversión:**\n` +
|
||||||
|
` - *«Muéstrame el embudo comercial»*\n` +
|
||||||
|
` - *«¿Cuál es nuestra tasa de cierre lead a venta?»*\n` +
|
||||||
|
` - *«Ticket medio de los clientes ganados»*\n\n` +
|
||||||
|
`4. **📊 Métricas CAC y ROAS por Canal:**\n` +
|
||||||
|
` - *«¿Cuál es el CAC y ROAS de los últimos 30 días?»*\n` +
|
||||||
|
` - *«Rendimiento por canal publicitario»*\n\n` +
|
||||||
|
`5. **🏆 Ranking de Campañas:**\n` +
|
||||||
|
` - *«¿Cuáles son las mejores campañas activas?»*\n` +
|
||||||
|
` - *«¿Dónde estamos invirtiendo más?»*\n\n` +
|
||||||
|
`6. **📖 Metodología y Glosario 4Ventures:**\n` +
|
||||||
|
` - *«¿Qué diferencia hay entre CAC de cohorte y CAC de caja?»*\n` +
|
||||||
|
` - *«¿Cómo funciona la atribución universal a 90 días?»*\n\n` +
|
||||||
|
`💡 *Puedes hacer clic en cualquiera de los botones rápidos de abajo o escribir directamente tu consulta.*`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. Intención: Simulación Determinista "¿Qué pasa si...?"
|
// 4. Intención: Simulación Determinista "¿Qué pasa si...?"
|
||||||
if (
|
if (
|
||||||
query.includes('simul') ||
|
normQuery.includes('simul') ||
|
||||||
query.includes('invierto') ||
|
normQuery.includes('invierto') ||
|
||||||
query.includes('pasa si') ||
|
normQuery.includes('pasa si') ||
|
||||||
query.includes('proyec') ||
|
normQuery.includes('proyec') ||
|
||||||
query.includes('presupuesto')
|
normQuery.includes('presupuesto')
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
const matchNumber = query.match(/(\d+[\d\.,]*)/);
|
const matchNumber = query.match(/(\d+[\d\.,]*)/);
|
||||||
@@ -93,10 +125,10 @@ export async function POST(req: NextRequest) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let channel: string | null = null;
|
let channel: string | null = null;
|
||||||
if (query.includes('meta') || query.includes('facebook') || query.includes('instagram')) channel = 'meta';
|
if (normQuery.includes('meta') || normQuery.includes('facebook') || normQuery.includes('instagram')) channel = 'meta';
|
||||||
else if (query.includes('google')) channel = 'google';
|
else if (normQuery.includes('google')) channel = 'google';
|
||||||
else if (query.includes('tiktok')) channel = 'tiktok';
|
else if (normQuery.includes('tiktok')) channel = 'tiktok';
|
||||||
else if (query.includes('linkedin')) channel = 'linkedin';
|
else if (normQuery.includes('linkedin')) channel = 'linkedin';
|
||||||
|
|
||||||
const res = await fetch(`${supabaseUrl}/rest/v1/rpc/simulate_budget`, {
|
const res = await fetch(`${supabaseUrl}/rest/v1/rpc/simulate_budget`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -137,11 +169,11 @@ export async function POST(req: NextRequest) {
|
|||||||
|
|
||||||
// 5. Intención: Objetivos vs Real / Estrategia
|
// 5. Intención: Objetivos vs Real / Estrategia
|
||||||
if (
|
if (
|
||||||
query.includes('objetivo') ||
|
normQuery.includes('objetivo') ||
|
||||||
query.includes('meta') ||
|
normQuery.includes('meta') ||
|
||||||
query.includes('factura') ||
|
normQuery.includes('factura') ||
|
||||||
query.includes('target') ||
|
normQuery.includes('target') ||
|
||||||
query.includes('cumplimiento')
|
normQuery.includes('cumplimiento')
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
@@ -176,12 +208,12 @@ export async function POST(req: NextRequest) {
|
|||||||
|
|
||||||
// 6. Intención: Embudo / Funnel / Conversión
|
// 6. Intención: Embudo / Funnel / Conversión
|
||||||
if (
|
if (
|
||||||
query.includes('funnel') ||
|
normQuery.includes('funnel') ||
|
||||||
query.includes('embudo') ||
|
normQuery.includes('embudo') ||
|
||||||
query.includes('tasa') ||
|
normQuery.includes('tasa') ||
|
||||||
query.includes('ticket') ||
|
normQuery.includes('ticket') ||
|
||||||
query.includes('conversion') ||
|
normQuery.includes('conversion') ||
|
||||||
query.includes('cierre')
|
normQuery.includes('cierre')
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`${supabaseUrl}/rest/v1/rpc/lead_funnel`, {
|
const res = await fetch(`${supabaseUrl}/rest/v1/rpc/lead_funnel`, {
|
||||||
@@ -213,11 +245,11 @@ export async function POST(req: NextRequest) {
|
|||||||
|
|
||||||
// 7. Intención: Ranking de Campañas / Anuncios
|
// 7. Intención: Ranking de Campañas / Anuncios
|
||||||
if (
|
if (
|
||||||
query.includes('campa') ||
|
normQuery.includes('campa') ||
|
||||||
query.includes('anuncio') ||
|
normQuery.includes('anuncio') ||
|
||||||
query.includes('ranking') ||
|
normQuery.includes('ranking') ||
|
||||||
query.includes('mejor') ||
|
normQuery.includes('mejor') ||
|
||||||
query.includes('ads')
|
normQuery.includes('ads')
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`${supabaseUrl}/rest/v1/rpc/rank_campaigns`, {
|
const res = await fetch(`${supabaseUrl}/rest/v1/rpc/rank_campaigns`, {
|
||||||
@@ -245,12 +277,12 @@ export async function POST(req: NextRequest) {
|
|||||||
|
|
||||||
// 8. Intención: RAG Documental Híbrido (Biblioteca de Activos)
|
// 8. Intención: RAG Documental Híbrido (Biblioteca de Activos)
|
||||||
if (
|
if (
|
||||||
query.includes('document') ||
|
normQuery.includes('document') ||
|
||||||
query.includes('copy') ||
|
normQuery.includes('copy') ||
|
||||||
query.includes('informe') ||
|
normQuery.includes('informe') ||
|
||||||
query.includes('brief') ||
|
normQuery.includes('brief') ||
|
||||||
query.includes('activo') ||
|
normQuery.includes('activo') ||
|
||||||
query.includes('creativo')
|
normQuery.includes('creativo')
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`${supabaseUrl}/rest/v1/rpc/match_documents`, {
|
const res = await fetch(`${supabaseUrl}/rest/v1/rpc/match_documents`, {
|
||||||
@@ -290,12 +322,12 @@ export async function POST(req: NextRequest) {
|
|||||||
|
|
||||||
// 9. Intención: Metodología y Glosario 4Ventures
|
// 9. Intención: Metodología y Glosario 4Ventures
|
||||||
if (
|
if (
|
||||||
query.includes('que es cac') ||
|
normQuery.includes('que es cac') ||
|
||||||
query.includes('cohorte') ||
|
normQuery.includes('cohorte') ||
|
||||||
query.includes('caja') ||
|
normQuery.includes('caja') ||
|
||||||
query.includes('atribucion') ||
|
normQuery.includes('atribucion') ||
|
||||||
query.includes('90 dias') ||
|
normQuery.includes('90 dias') ||
|
||||||
query.includes('metodologia')
|
normQuery.includes('metodologia')
|
||||||
) {
|
) {
|
||||||
return reply(
|
return reply(
|
||||||
`📖 **Metodología Oficial de Crecimiento — 4Ventures:**\n\n` +
|
`📖 **Metodología Oficial de Crecimiento — 4Ventures:**\n\n` +
|
||||||
|
|||||||
@@ -1,7 +1,105 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import React, { useState, useRef, useEffect } from 'react';
|
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() {
|
export default function AsistentePage() {
|
||||||
const [input, setInput] = useState('');
|
const [input, setInput] = useState('');
|
||||||
@@ -88,22 +186,22 @@ export default function AsistentePage() {
|
|||||||
className={`flex ${m.role === 'user' ? 'justify-end' : 'justify-start'}`}
|
className={`flex ${m.role === 'user' ? 'justify-end' : 'justify-start'}`}
|
||||||
>
|
>
|
||||||
<div
|
<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'
|
m.role === 'user'
|
||||||
? 'bg-brand-500 text-white font-medium shadow-md shadow-brand-500/10'
|
? 'bg-gradient-to-r from-brand-500 to-amber-600 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-[#1a2333] text-gray-200 border border-gray-800/80 shadow-lg shadow-black/20'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{m.content}
|
<FormattedMessage content={m.content} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
{loading && (
|
{loading && (
|
||||||
<div className="flex justify-start">
|
<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" />
|
<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>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -112,6 +210,13 @@ export default function AsistentePage() {
|
|||||||
|
|
||||||
{/* Quick Prompts Chips */}
|
{/* 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">
|
<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
|
<button
|
||||||
onClick={() => sendMessage('¿Qué pasa si invierto 1.000 € en Meta?')}
|
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"
|
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
|
🎯 Objetivos de este mes
|
||||||
</button>
|
</button>
|
||||||
<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"
|
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" />
|
<Filter className="w-3.5 h-3.5 text-blue-400" />
|
||||||
@@ -162,7 +267,7 @@ export default function AsistentePage() {
|
|||||||
type="text"
|
type="text"
|
||||||
value={input}
|
value={input}
|
||||||
onChange={(e) => setInput(e.target.value)}
|
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"
|
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
|
<button
|
||||||
|
|||||||
Reference in New Issue
Block a user