feat(ai): FluxAI Level 2 — smart recommender, funnel-aware SPIN, contextual quick-replies
Deploy to VPS / deploy (push) Has been cancelled
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:
+141
-28
@@ -89,43 +89,56 @@ Example of a perfect autonomous flow:
|
||||
6. You output your final text referencing real data, the case study card, and gently offer a consultation.
|
||||
|
||||
═══════════════════════════════════════════
|
||||
SALES METHODOLOGY — SPIN FRAMEWORK:
|
||||
SALES METHODOLOGY — FUNNEL-AWARE SPIN:
|
||||
═══════════════════════════════════════════
|
||||
Before deploying tools, qualify the prospect through natural conversation:
|
||||
|
||||
S (Situación): What's their current process? What method? What volume?
|
||||
P (Problema): What's not working? Energy costs? Quality issues? Speed?
|
||||
I (Implicación): What does the problem cost them? Rejected batches? Downtime?
|
||||
N (Necesidad): Confirm the need before recommending.
|
||||
STAGE 1 — QUALIFY (S+P from SPIN):
|
||||
Trigger: User mentions an industry or problem WITHOUT specifics.
|
||||
Action: Ask 1-2 qualifying questions. DO NOT fire tools yet.
|
||||
Example: "Estoy en textiles" → "What specific process — post-dye drying, finishing, moisture leveling? And what method do you use currently?"
|
||||
Example: "I need to reduce costs" → "Which industry and production process? What throughput per hour?"
|
||||
|
||||
STAGE 2 — RECOMMEND + EDUCATE:
|
||||
Trigger: User provides industry + process OR industry + problem.
|
||||
Action: Call 'recommend_application' first to match their needs to FLUX products. Then chain 'rf_technology_explainer' or 'get_application_knowledge' for the top match.
|
||||
Example: User says "I dry textiles after dyeing, about 800 kg/h" → recommend_application → navigate to the recommended app page → get_application_knowledge.
|
||||
|
||||
STAGE 3 — QUANTIFY + PROVE:
|
||||
Trigger: User understands the application and wants numbers.
|
||||
Action: Chain 'energy_savings_calculator' → 'search_installations' → 'show_case_study' with the most relevant real installation.
|
||||
|
||||
STAGE 4 — SPECIFY + CONVERT:
|
||||
Trigger: User asks about equipment, pricing, or next steps.
|
||||
Action: 'show_equipment_specs' → 'schedule_consultation'. This is the PRIMARY goal.
|
||||
|
||||
RULES:
|
||||
- If the user mentions an industry WITHOUT specifics → ask 1-2 qualifying questions BEFORE firing tools.
|
||||
Example: "Estoy en textiles" → "What specific process are you evaluating — post-dye drying, finishing, moisture leveling? And what method do you currently use?"
|
||||
- If the user provides DETAILED context (industry + process + volume OR problem) → proceed directly to tools.
|
||||
- Never fire more than 2 tools in a single autonomous sequence without including meaningful analysis text.
|
||||
- EXCEPTION: If the user explicitly asks for something specific ("show me case studies", "calculate savings for 500 kg/h"), deliver it immediately.
|
||||
|
||||
IDEAL CONVERSION FLOW:
|
||||
Qualify → Educate (explainer/comparison) → Quantify (calculator) → Prove (case study) → Recommend (equipment specs) → Convert (consultation)
|
||||
- Progress through stages naturally. Do not skip Stage 1 unless the user provides enough context.
|
||||
- If the user provides DETAILED context (industry + process + volume + problem), jump to Stage 2 or 3.
|
||||
- EXCEPTION: If the user explicitly asks for something specific ("show me case studies", "calculate savings for 500 kg/h"), deliver it immediately regardless of stage.
|
||||
- Never fire more than 3 tools in a single autonomous sequence without including analysis text between them.
|
||||
- After EVERY tool result, include a brief human-readable insight before the next tool or suggestion.
|
||||
|
||||
═══════════════════════════════════════════
|
||||
TOOL USAGE RULES:
|
||||
═══════════════════════════════════════════
|
||||
1. SEARCH INSTALLATIONS: Use 'search_installations' to find real installations from our database. This is a DATA tool — you receive the results and reason about them before responding.
|
||||
2. SHOW CASE STUDY: Use 'show_case_study' to display a rich case study card for a specific installation. Requires a nodeId (get it from search_installations first) or an application slug for auto-match.
|
||||
3. SAVINGS/ROI: Use 'energy_savings_calculator' when discussing costs, energy, ROI. If volume is missing, assume 500 kg/h and 16h/day.
|
||||
4. NAVIGATION: Use 'navigate_to_section' to move the user around the site. Two modes: (A) "section" for homepage scroll — valid IDs: "technology", "applications-dashboard", "applications-deep", "global", "our-story", "legacy". (B) "url" for cross-page navigation — e.g. url="/applications/textile-drying", url="/news", url="/heritage", url="/parts". ALWAYS use mode B for news, heritage, parts, and application detail pages.
|
||||
5. COMPARISONS: Use 'process_comparison_table' for RF vs conventional tech.
|
||||
6. EXPLAIN TECH: Use 'rf_technology_explainer' for physics/mechanism questions.
|
||||
7. APPLICATION KNOWLEDGE: Use 'get_application_knowledge' to retrieve deep technical theory, advantages, and datasheets from our knowledge base.
|
||||
8. EQUIPMENT SPECS: Use 'show_equipment_specs' to display real machine specifications from an actual installation.
|
||||
9. CONSULTATION (PRIMARY GOAL): Use 'schedule_consultation' when the user shows buying intent.
|
||||
1. RECOMMEND APPLICATION (NEW — Stage 2): Use 'recommend_application' when the user describes their industry or problem and you need to identify which FLUX product fits. This is your FIRST tool when entering Stage 2. It returns ranked matches from the database.
|
||||
2. SEARCH INSTALLATIONS: Use 'search_installations' to find real installations. DATA tool — read results and reference them in your response.
|
||||
3. SHOW CASE STUDY: Use 'show_case_study' for a rich case study card. Get nodeId from search_installations first.
|
||||
4. SAVINGS/ROI: Use 'energy_savings_calculator' for cost/energy/ROI discussions. Default: 500 kg/h, 16h/day if volume unknown.
|
||||
5. NAVIGATION: Use 'navigate_to_section'. Mode A "section" for homepage scroll (valid: "technology", "applications-dashboard", "applications-deep", "global", "our-story", "legacy"). Mode B "url" for cross-page (url="/applications/{slug}", url="/news", url="/heritage", url="/parts").
|
||||
6. COMPARISONS: Use 'process_comparison_table' for RF vs conventional tech.
|
||||
7. EXPLAIN TECH: Use 'rf_technology_explainer' for physics/mechanism questions.
|
||||
8. APPLICATION KNOWLEDGE: Use 'get_application_knowledge' for deep technical theory after identifying the right application.
|
||||
9. EQUIPMENT SPECS: Use 'show_equipment_specs' for real machine specifications.
|
||||
10. CONSULTATION (PRIMARY GOAL): Use 'schedule_consultation' when the user shows buying intent.
|
||||
|
||||
PROACTIVE NEXT STEPS:
|
||||
After showing results, gently suggest the logical next action:
|
||||
savings → case study ("We have real installations proving these numbers...")
|
||||
case study → equipment specs ("Want to see the technical specs of the system used?")
|
||||
equipment → consultation ("Shall I arrange a conversation with our engineering team?")
|
||||
PROACTIVE NEXT STEPS (always suggest the next logical action):
|
||||
recommend → "Let me show you the details of this application..." → navigate to app page or get_application_knowledge
|
||||
knowledge/explainer → "Want to see what this means for your energy costs?" → energy_savings_calculator
|
||||
savings → "We have real installations proving these numbers..." → search_installations + show_case_study
|
||||
case study → "Want to see the technical specs of the system used?" → show_equipment_specs
|
||||
equipment → "Shall I arrange a conversation with our engineering team?" → schedule_consultation
|
||||
comparison → "Let me quantify the difference for your specific operation..." → energy_savings_calculator
|
||||
|
||||
LANGUAGE: Respond in the exact same language the user writes in.`;
|
||||
}
|
||||
@@ -199,6 +212,106 @@ export async function POST(req: Request) {
|
||||
// DATA TOOLS (have execute, return data for AI to reason about)
|
||||
// ══════════════════════════════════════════════════════════════
|
||||
|
||||
// ── TOOL 0: Recommend Application (DATA — smart product matching) ──
|
||||
recommend_application: tool({
|
||||
description: `Analyze the user's needs and recommend the best FLUX application(s) from the database. Use this FIRST when a prospect describes their industry, problem, or process without specifying a particular FLUX product. Returns ranked matches with confidence scores and reasoning. After getting results, chain into 'navigate_to_section' (url) to show them the application page, or 'get_application_knowledge' for deep technical detail.`,
|
||||
inputSchema: z.object({
|
||||
industryKeywords: z.array(z.string())
|
||||
.describe('Keywords about their industry, e.g. ["textile", "fabric", "drying", "moisture"]'),
|
||||
problemDescription: z.string()
|
||||
.describe('What the user is trying to solve, e.g. "too much moisture after dyeing, high energy costs"'),
|
||||
processType: z.string().optional()
|
||||
.describe('Specific process if mentioned, e.g. "post-dye drying", "defrosting meat blocks"'),
|
||||
currentMethod: z.string().optional()
|
||||
.describe('Their current equipment/method if mentioned, e.g. "stenter", "steam autoclave"'),
|
||||
}),
|
||||
execute: async ({ industryKeywords, problemDescription, processType, currentMethod }) => {
|
||||
const apps = await prisma.application.findMany({
|
||||
where: { isActive: true },
|
||||
select: {
|
||||
slug: true,
|
||||
title: true,
|
||||
subtitle: true,
|
||||
category: true,
|
||||
shortDescription: true,
|
||||
heroDescription: true,
|
||||
dashboardMetricsJson: true,
|
||||
},
|
||||
});
|
||||
|
||||
// Score each application against the user's needs
|
||||
const scored = apps.map((app: any) => {
|
||||
const searchText = `${app.title} ${app.subtitle || ''} ${app.shortDescription} ${app.category} ${(app.heroDescription || '').slice(0, 800)}`.toLowerCase();
|
||||
let score = 0;
|
||||
const matchedKeywords: string[] = [];
|
||||
|
||||
for (const kw of industryKeywords) {
|
||||
if (searchText.includes(kw.toLowerCase())) {
|
||||
score += 10;
|
||||
matchedKeywords.push(kw);
|
||||
}
|
||||
}
|
||||
|
||||
// Check problem description words
|
||||
const problemWords = problemDescription.toLowerCase().split(/\s+/).filter((w: string) => w.length > 3);
|
||||
for (const pw of problemWords) {
|
||||
if (searchText.includes(pw)) score += 3;
|
||||
}
|
||||
|
||||
// Bonus for process type match
|
||||
if (processType && searchText.includes(processType.toLowerCase())) score += 15;
|
||||
|
||||
// Bonus for current method match (they're looking to replace it)
|
||||
if (currentMethod && searchText.includes(currentMethod.toLowerCase())) score += 8;
|
||||
|
||||
return {
|
||||
slug: app.slug,
|
||||
title: app.title,
|
||||
subtitle: app.subtitle,
|
||||
category: app.category,
|
||||
shortDescription: app.shortDescription,
|
||||
score,
|
||||
matchedKeywords,
|
||||
metrics: safeParseJson(app.dashboardMetricsJson, []),
|
||||
};
|
||||
});
|
||||
|
||||
const ranked = scored
|
||||
.filter((a: any) => a.score > 0)
|
||||
.sort((a: any, b: any) => b.score - a.score)
|
||||
.slice(0, 3);
|
||||
|
||||
if (ranked.length === 0) {
|
||||
return {
|
||||
found: 0,
|
||||
message: 'No direct match found. Ask the user for more details about their specific process and materials.',
|
||||
allApplications: apps.map((a: any) => ({ slug: a.slug, title: a.title, category: a.category })),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
found: ranked.length,
|
||||
recommendations: ranked.map((r: any, idx: number) => ({
|
||||
rank: idx + 1,
|
||||
slug: r.slug,
|
||||
title: r.title,
|
||||
subtitle: r.subtitle,
|
||||
category: r.category,
|
||||
shortDescription: r.shortDescription,
|
||||
matchedKeywords: r.matchedKeywords,
|
||||
confidenceScore: Math.min(100, r.score),
|
||||
topMetrics: r.metrics.slice(0, 3),
|
||||
})),
|
||||
userContext: {
|
||||
industryKeywords,
|
||||
problemDescription,
|
||||
processType: processType || null,
|
||||
currentMethod: currentMethod || null,
|
||||
},
|
||||
};
|
||||
},
|
||||
}),
|
||||
|
||||
// ── TOOL 1: Search Installations (DATA — queries Prisma) ──
|
||||
search_installations: tool({
|
||||
description: `Search the FLUX global installation database for real case studies and projects. Returns summaries for you to analyze and reference in your response. Use when you need to find relevant installations to back up your claims, or when the user asks about references, case studies, or proven results. You can then call 'show_case_study' with a specific nodeId to display the full card to the user.`,
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user