feat(ai): FluxAI Level 2 — smart recommender, funnel-aware SPIN, contextual quick-replies
Deploy to VPS / deploy (push) Has been cancelled

Three improvements to the FluxAI sales intelligence:

1. New `recommend_application` tool: analyzes prospect's industry,
   problem, and process keywords against the database to rank-match
   the best FLUX products with confidence scores. Bridges the gap
   between "I have a problem" and "here's our solution."

2. Funnel-aware system prompt: replaces flat SPIN with 4-stage
   pipeline (Qualify → Recommend+Educate → Quantify+Prove →
   Specify+Convert) with clear rules for when to ask vs. act.

3. Contextual quick-reply buttons: after each AI response, dynamic
   suggestions appear based on which tools were used — guiding the
   prospect through the natural next step in the funnel.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-07 08:36:30 -05:00
parent 95132476ae
commit 8941d1a2c3
2 changed files with 199 additions and 28 deletions
+58
View File
@@ -187,6 +187,11 @@ export default function SilentObserver() {
const DataToolWorking = ({ label }: { label: string }) => (<div key={key} className="self-start flex items-center gap-2 py-1"><Database size={10} className="text-[#0066CC]/40 dark:text-[#4DA6FF]/40 animate-pulse" /><span className="text-[10px] text-[#86868B]/60 dark:text-[#A1A1A6]/40 italic">{label}</span></div>);
const ToolError = ({ msg }: { msg?: string }) => (<div key={key} className="text-[11px] text-red-400 dark:text-red-300/80 self-start py-1">Analysis unavailable. {msg}</div>);
if (part.type === "tool-recommend_application") {
if (part.state === "input-streaming" || part.state === "input-available") return <DataToolWorking key={key} label="Analyzing your needs..." />;
if (part.state === "output-available") return null;
if (part.state === "output-error") return <ToolError key={key} msg={(part as any).errorText} />;
}
if (part.type === "tool-search_installations") {
if (part.state === "input-streaming" || part.state === "input-available") return <DataToolWorking key={key} label="Searching installation database..." />;
if (part.state === "output-available") return null;
@@ -237,6 +242,41 @@ export default function SilentObserver() {
return null;
}
// ═══ Contextual Quick-Replies based on last assistant message ═══
function getContextualSuggestions(): string[] {
if (isLoading || messages.length === 0) return [];
const lastAssistant = [...messages].reverse().find(m => m.role === "assistant");
if (!lastAssistant?.parts) return [];
const toolTypes = new Set(
lastAssistant.parts
.filter((p: any) => p.type?.startsWith("tool-") && p.state === "output-available")
.map((p: any) => p.type)
);
// Priority order: suggest the next logical funnel step
if (toolTypes.has("tool-schedule_consultation")) return []; // End of funnel
if (toolTypes.has("tool-show_equipment_specs"))
return ["Schedule a consultation", "Compare with traditional methods"];
if (toolTypes.has("tool-show_case_study"))
return ["Show me equipment specs", "Calculate savings for my operation", "Schedule a consultation"];
if (toolTypes.has("tool-energy_savings_calculator"))
return ["Show me a real installation", "See equipment specs", "How does RF heating work?"];
if (toolTypes.has("tool-process_comparison_table"))
return ["Calculate savings for my operation", "Show me proven installations"];
if (toolTypes.has("tool-rf_technology_explainer"))
return ["What would I save in energy costs?", "Show me real installations"];
if (toolTypes.has("tool-recommend_application") || toolTypes.has("tool-get_application_knowledge"))
return ["Calculate energy savings", "Show me case studies", "Compare RF vs my current method"];
if (toolTypes.has("tool-navigate_to_section"))
return ["Tell me more about this", "How much energy can I save?"];
// Default: if assistant responded with text only (Stage 1 qualification)
return [];
}
const suggestions = getContextualSuggestions();
return (
<>
<AnimatePresence>
@@ -323,6 +363,24 @@ export default function SilentObserver() {
{[0, 1, 2].map((i) => (<motion.div key={i} className="w-1.5 h-1.5 rounded-full bg-[#0066CC] dark:bg-[#4DA6FF]" animate={{ opacity: [0.2, 1, 0.2], scale: [0.8, 1, 0.8] }} transition={{ duration: 1, repeat: Infinity, delay: i * 0.2 }} />))}
</div>
)}
{suggestions.length > 0 && !isLoading && (
<motion.div
initial={{ opacity: 0, y: 8 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3, delay: 0.2 }}
className="flex flex-wrap gap-1.5 pt-1 pb-2"
>
{suggestions.map((s) => (
<button
key={s}
onClick={() => { sendMessage({ text: s }); }}
className="px-3 py-1.5 rounded-full text-[11px] font-medium bg-[#0066CC]/[0.06] dark:bg-[#4DA6FF]/[0.08] border border-[#0066CC]/10 dark:border-[#4DA6FF]/10 text-[#0066CC] dark:text-[#4DA6FF] hover:bg-[#0066CC]/10 dark:hover:bg-[#4DA6FF]/15 hover:border-[#0066CC]/20 dark:hover:border-[#4DA6FF]/20 active:scale-95 transition-all duration-200"
>
{s}
</button>
))}
</motion.div>
)}
</div>
<form onSubmit={onSubmit} className="relative z-10 px-4 py-3 pb-[max(0.75rem,env(safe-area-inset-bottom))] md:pb-3 border-t border-black/[0.04] dark:border-white/[0.06] bg-white/40 dark:bg-white/[0.02] flex items-center gap-2.5 transition-colors duration-300">