588 lines
33 KiB
TypeScript
588 lines
33 KiB
TypeScript
import { openai } from '@ai-sdk/openai';
|
|
import { streamText, UIMessage, convertToModelMessages, tool } from 'ai';
|
|
import { z } from 'zod';
|
|
import { prisma } from '@/lib/prisma';
|
|
|
|
export const maxDuration = 30;
|
|
|
|
// ─── PHYSICS CONSTANTS (NOT from DB — these are engineering benchmarks) ──────
|
|
// These stay hardcoded because they are physical/scientific constants,
|
|
// not business data that changes with CMS updates.
|
|
|
|
const ENERGY_DATA: Record<string, {
|
|
traditionalKwhPerKg: number;
|
|
rfKwhPerKg: number;
|
|
traditionalMethod: string;
|
|
co2FactorKgPerKwh: number;
|
|
typicalPaybackMonths: number;
|
|
}> = {
|
|
textile: { traditionalKwhPerKg: 1.8, rfKwhPerKg: 0.85, traditionalMethod: 'Hot Air Stenter', co2FactorKgPerKwh: 0.4, typicalPaybackMonths: 18 },
|
|
food: { traditionalKwhPerKg: 2.2, rfKwhPerKg: 0.95, traditionalMethod: 'Convection Oven', co2FactorKgPerKwh: 0.4, typicalPaybackMonths: 14 },
|
|
rubber: { traditionalKwhPerKg: 3.0, rfKwhPerKg: 1.2, traditionalMethod: 'Steam Autoclave', co2FactorKgPerKwh: 0.4, typicalPaybackMonths: 12 },
|
|
pharma: { traditionalKwhPerKg: 2.5, rfKwhPerKg: 1.0, traditionalMethod: 'Vacuum Tray Dryer', co2FactorKgPerKwh: 0.4, typicalPaybackMonths: 16 },
|
|
wood: { traditionalKwhPerKg: 2.0, rfKwhPerKg: 0.9, traditionalMethod: 'Kiln Drying', co2FactorKgPerKwh: 0.4, typicalPaybackMonths: 20 },
|
|
default: { traditionalKwhPerKg: 2.0, rfKwhPerKg: 0.9, traditionalMethod: 'Conventional Heating', co2FactorKgPerKwh: 0.4, typicalPaybackMonths: 18 },
|
|
};
|
|
|
|
const COMPARISON_DATA: Record<string, { rf: number; traditional: number; unit: string }> = {
|
|
efficiency: { rf: 95, traditional: 45, unit: '%' },
|
|
uniformity: { rf: 98, traditional: 70, unit: '%' },
|
|
speed: { rf: 85, traditional: 40, unit: 'score' },
|
|
maintenance: { rf: 90, traditional: 55, unit: 'score' },
|
|
lifespan: { rf: 20, traditional: 8, unit: 'years' },
|
|
footprint: { rf: 30, traditional: 100, unit: 'relative' },
|
|
};
|
|
|
|
// ─── DYNAMIC SYSTEM PROMPT BUILDER ──────────────────────────────
|
|
// Injects real-time database context so the AI knows what exists
|
|
|
|
async function buildSystemPrompt(): Promise<string> {
|
|
// Query real data from Prisma
|
|
const [activeApps, installationCount, eventCount, partsCount] = await Promise.all([
|
|
prisma.application.findMany({
|
|
where: { isActive: true },
|
|
select: { slug: true, title: true, shortDescription: true, category: true },
|
|
orderBy: { title: 'asc' },
|
|
}),
|
|
prisma.globalNode.count({ where: { nodeType: 'installation', isActive: true } }),
|
|
prisma.globalNode.count({ where: { nodeType: 'event', isActive: true } }),
|
|
prisma.sparePart.count({ where: { isActive: true } }),
|
|
]);
|
|
|
|
const appList = activeApps.map((a: any) => ` - ${a.title} (slug: "${a.slug}", category: ${a.category})`).join('\n');
|
|
|
|
return `You are "FluxAI", the intelligent engineering advisor and sales specialist for FLUX Srl — a world leader in solid-state Radio Frequency (RF), Microwave, and Infrared industrial equipment. Founded by Patrizio Grando with 40+ years of legacy. Headquarters: Romano d'Ezzelino, Vicenza, Italy.
|
|
|
|
PERSONALITY:
|
|
- Senior RF engineer who also understands business ROI.
|
|
- Concise, authoritative, no filler — every sentence delivers value.
|
|
- Apple-level design thinking meets German engineering precision.
|
|
- NO emojis. NO exclamation marks. Professional warmth only.
|
|
|
|
═══════════════════════════════════════════
|
|
LIVE DATABASE STATE (auto-updated from CMS):
|
|
- ${installationCount} active installations worldwide
|
|
- ${eventCount} upcoming/recent events
|
|
- ${partsCount} spare parts in catalog
|
|
- Applications available:
|
|
${appList}
|
|
═══════════════════════════════════════════
|
|
|
|
CORE KNOWLEDGE:
|
|
- FLUX solid-state RF operates at 27.12 MHz with 95%+ power transfer efficiency
|
|
- Volumetric heating (not surface): energy goes directly into the product mass
|
|
- Applications: Drying, Vulcanization, Curing, Baking, Defrosting, Sanitization
|
|
- Industries: Textile, Food Processing, Rubber/Latex, Pharma, Wood, Ceramics
|
|
- Key advantage vs vacuum tubes: no consumable parts, 20+ year lifespan, digital control
|
|
- Key advantage vs microwave (2450 MHz): deeper penetration, more uniform field, better for bulk materials
|
|
|
|
MULTI-STEP AUTONOMY (CRITICAL):
|
|
You are an autonomous agent. You MUST chain tools automatically without waiting for the user.
|
|
|
|
Example of a perfect autonomous flow:
|
|
1. User: "Will RF save me money in textile drying?"
|
|
2. You call 'search_installations' to find real textile drying installations.
|
|
3. You read the results and see 2 installations with -47% and -53% savings.
|
|
4. You call 'energy_savings_calculator' with the user's context.
|
|
5. You call 'show_case_study' with the most relevant installation's nodeId.
|
|
6. You output your final text referencing real data, the case study card, and gently offer a consultation.
|
|
|
|
═══════════════════════════════════════════
|
|
SALES METHODOLOGY — SPIN FRAMEWORK:
|
|
═══════════════════════════════════════════
|
|
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.
|
|
|
|
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)
|
|
|
|
═══════════════════════════════════════════
|
|
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.
|
|
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.
|
|
|
|
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?")
|
|
|
|
LANGUAGE: Respond in the exact same language the user writes in.`;
|
|
}
|
|
|
|
// ─── HELPER: Parse JSON safely ──────────────────────────────────
|
|
|
|
function safeParseJson<T>(json: string | null | undefined, fallback: T): T {
|
|
if (!json) return fallback;
|
|
try { return JSON.parse(json); } catch { return fallback; }
|
|
}
|
|
|
|
// ─── HELPER: Extract industry from application slug ─────────────
|
|
|
|
function industryFromSlug(slug: string): string {
|
|
if (slug.includes('textile')) return 'textile';
|
|
if (slug.includes('food') || slug.includes('defrost') || slug.includes('bak') || slug.includes('pasteuriz')) return 'food';
|
|
if (slug.includes('rubber') || slug.includes('vulcaniz') || slug.includes('foam')) return 'rubber';
|
|
if (slug.includes('pharma') || slug.includes('cannabis') || slug.includes('lab')) return 'pharma';
|
|
if (slug.includes('wood')) return 'wood';
|
|
return 'other';
|
|
}
|
|
|
|
// ─── ROUTE HANDLER ──────────────────────────────────────────────
|
|
|
|
export async function POST(req: Request) {
|
|
const { messages, context }: {
|
|
messages: UIMessage[];
|
|
context?: { section?: string; activeTab?: string };
|
|
} = await req.json();
|
|
|
|
const contextNote = context?.section
|
|
? `\n\n[CONTEXT: User is currently viewing the "${context.section}" section${context.activeTab ? `, tab: "${context.activeTab}"` : ''}.`
|
|
: '';
|
|
|
|
// Build system prompt with live database context
|
|
const systemPrompt = await buildSystemPrompt();
|
|
|
|
const coreMessages = await convertToModelMessages(messages);
|
|
|
|
const result = streamText({
|
|
model: openai('gpt-4o'),
|
|
system: systemPrompt + contextNote,
|
|
messages: coreMessages,
|
|
// maxSteps has been temporarily removed to ensure compatibility with the installed AI SDK version
|
|
tools: {
|
|
|
|
// ══════════════════════════════════════════════════════════════
|
|
// DATA TOOLS (have execute, return data for AI to reason about)
|
|
// ══════════════════════════════════════════════════════════════
|
|
|
|
// ── 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.`,
|
|
inputSchema: z.object({
|
|
application: z.string().optional()
|
|
.describe('Application slug to filter by, e.g. "textile-drying", "food-defrosting". Leave empty for all.'),
|
|
keyword: z.string().optional()
|
|
.describe('Keyword to search in title or location, e.g. "Japan", "latex"'),
|
|
limit: z.number().default(5)
|
|
.describe('Max results to return (default 5)'),
|
|
}),
|
|
execute: async ({ application, keyword, limit }) => {
|
|
const where: any = {
|
|
nodeType: 'installation',
|
|
isActive: true,
|
|
};
|
|
if (application) where.application = application;
|
|
|
|
let nodes = await prisma.globalNode.findMany({
|
|
where,
|
|
select: {
|
|
id: true,
|
|
title: true,
|
|
location: true,
|
|
application: true,
|
|
stats: true,
|
|
energySavings: true,
|
|
projectOverview: true,
|
|
specificDatasheetJson: true,
|
|
},
|
|
orderBy: { createdAt: 'desc' },
|
|
take: limit,
|
|
});
|
|
|
|
// Optional keyword filter (Prisma SQLite doesn't support full-text, so manual)
|
|
if (keyword) {
|
|
const kw = keyword.toLowerCase();
|
|
nodes = nodes.filter((n: any) =>
|
|
n.title.toLowerCase().includes(kw) ||
|
|
n.location.toLowerCase().includes(kw) ||
|
|
(n.projectOverview?.toLowerCase().includes(kw) ?? false)
|
|
);
|
|
}
|
|
|
|
if (nodes.length === 0) {
|
|
return {
|
|
found: 0,
|
|
message: `No installations found${application ? ` for application "${application}"` : ''}${keyword ? ` matching "${keyword}"` : ''}. Try broadening your search or check available applications.`,
|
|
installations: [],
|
|
};
|
|
}
|
|
|
|
return {
|
|
found: nodes.length,
|
|
installations: nodes.map((n: any) => ({
|
|
nodeId: n.id,
|
|
title: n.title,
|
|
location: n.location,
|
|
application: n.application,
|
|
stats: n.stats,
|
|
energySavings: n.energySavings || 'Data pending',
|
|
hasProjectOverview: !!n.projectOverview,
|
|
hasDatasheet: n.specificDatasheetJson !== '[]' && !!n.specificDatasheetJson,
|
|
// Include a brief excerpt for AI reasoning (first 200 chars)
|
|
overviewExcerpt: n.projectOverview?.slice(0, 200) || null,
|
|
})),
|
|
};
|
|
},
|
|
}),
|
|
|
|
// ── TOOL 2: Get Application Knowledge (DATA — queries Prisma) ──
|
|
get_application_knowledge: tool({
|
|
description: `Retrieve deep technical knowledge about a FLUX application from our knowledge base. Returns the scientific theory (heroDescription), technical sections, competitive advantages, and general datasheet. Use when you need to explain WHY RF is suitable for a specific application, or when the user asks technical questions about a process.`,
|
|
inputSchema: z.object({
|
|
slug: z.string()
|
|
.describe('Application slug, e.g. "textile-drying", "food-defrosting", "rubber-vulcanization"'),
|
|
}),
|
|
execute: async ({ slug }) => {
|
|
const app = await prisma.application.findUnique({
|
|
where: { slug },
|
|
select: {
|
|
title: true,
|
|
subtitle: true,
|
|
category: true,
|
|
shortDescription: true,
|
|
heroDescription: true,
|
|
sectionsJson: true,
|
|
advantagesJson: true,
|
|
datasheetJson: true,
|
|
dashboardMetricsJson: true,
|
|
},
|
|
});
|
|
|
|
if (!app) {
|
|
return { found: false, message: `Application "${slug}" not found. Check available applications in your database context.` };
|
|
}
|
|
|
|
return {
|
|
found: true,
|
|
title: app.title,
|
|
subtitle: app.subtitle,
|
|
category: app.category,
|
|
shortDescription: app.shortDescription,
|
|
// Truncate heroDescription for AI context (keep first 1500 chars — enough for reasoning)
|
|
theoryExcerpt: app.heroDescription.slice(0, 1500),
|
|
sections: safeParseJson(app.sectionsJson, []),
|
|
advantages: safeParseJson(app.advantagesJson, []),
|
|
datasheet: safeParseJson(app.datasheetJson, []),
|
|
metrics: safeParseJson(app.dashboardMetricsJson, []),
|
|
};
|
|
},
|
|
}),
|
|
|
|
// ══════════════════════════════════════════════════════════════
|
|
// UI + DATA TOOLS (have execute, return data for component rendering)
|
|
// ══════════════════════════════════════════════════════════════
|
|
|
|
// ── TOOL 3: Show Case Study (UI — fetches full GlobalNode for card) ──
|
|
show_case_study: tool({
|
|
description: `Display a rich, interactive case study card for a specific FLUX installation. Shows cover image, metrics, project overview, gallery, and equipment datasheet. Use AFTER calling 'search_installations' to get the nodeId, or provide an application slug to auto-select the best match. The card renders inline in the chat with CTAs to view the full case study modal and locate on the 3D globe.`,
|
|
inputSchema: z.object({
|
|
nodeId: z.string().optional()
|
|
.describe('Specific GlobalNode ID to display. Preferred — get this from search_installations results.'),
|
|
application: z.string().optional()
|
|
.describe('Fallback: application slug to auto-pick the best installation if nodeId is not available.'),
|
|
relevanceNote: z.string()
|
|
.describe('1-2 sentence AI explanation of WHY this case is relevant to the current conversation. Reference the user\'s industry, process, or concerns.'),
|
|
}),
|
|
execute: async ({ nodeId, application, relevanceNote }) => {
|
|
let node;
|
|
|
|
if (nodeId) {
|
|
node = await prisma.globalNode.findUnique({ where: { id: nodeId } });
|
|
}
|
|
|
|
if (!node && application) {
|
|
node = await prisma.globalNode.findFirst({
|
|
where: { application, nodeType: 'installation', isActive: true, projectOverview: { not: null } },
|
|
orderBy: { createdAt: 'desc' },
|
|
});
|
|
}
|
|
|
|
if (!node) {
|
|
return { found: false, message: 'No matching installation found.' };
|
|
}
|
|
|
|
return {
|
|
found: true,
|
|
id: node.id,
|
|
title: node.title,
|
|
location: node.location,
|
|
application: node.application,
|
|
industry: industryFromSlug(node.application),
|
|
stats: node.stats,
|
|
energySavings: node.energySavings,
|
|
projectOverview: node.projectOverview,
|
|
mediaFileName: node.mediaFileName,
|
|
gallery: safeParseJson(node.galleryJson, []),
|
|
datasheet: safeParseJson(node.specificDatasheetJson, []),
|
|
videos: safeParseJson(node.videosJson, []),
|
|
relevanceNote,
|
|
};
|
|
},
|
|
}),
|
|
|
|
// ── TOOL 4: Show Equipment Specs (UI — real machine datasheet) ──
|
|
show_equipment_specs: tool({
|
|
description: `Display a detailed equipment specification card based on a REAL installed FLUX machine. Shows the actual datasheet (specificDatasheetJson) from a proven installation, along with AI-generated sizing guidance. Use when the user asks about equipment, models, specs, sizing, "which machine", or wants to know what FLUX system fits their needs. The card shows real specs from a reference installation — more credible than generic catalog data.`,
|
|
inputSchema: z.object({
|
|
nodeId: z.string().optional()
|
|
.describe('Specific GlobalNode ID of the reference installation. Get from search_installations.'),
|
|
application: z.string().optional()
|
|
.describe('Fallback: application slug to auto-pick an installation with datasheet data.'),
|
|
whyThisModel: z.string()
|
|
.describe('2-3 sentence AI explanation of why this equipment configuration fits the user\'s needs.'),
|
|
sizingNote: z.string()
|
|
.describe('Specific sizing guidance for the user. Reference their production volume if known.'),
|
|
alternativeNote: z.string().nullable()
|
|
.describe('Note about scaling options or alternatives. Null if not applicable.'),
|
|
}),
|
|
execute: async ({ nodeId, application, whyThisModel, sizingNote, alternativeNote }) => {
|
|
let node;
|
|
|
|
if (nodeId) {
|
|
node = await prisma.globalNode.findUnique({ where: { id: nodeId } });
|
|
}
|
|
|
|
if (!node && application) {
|
|
// Find an installation WITH datasheet data
|
|
node = await prisma.globalNode.findFirst({
|
|
where: {
|
|
application,
|
|
nodeType: 'installation',
|
|
isActive: true,
|
|
specificDatasheetJson: { not: '[]' },
|
|
},
|
|
orderBy: { createdAt: 'desc' },
|
|
});
|
|
}
|
|
|
|
if (!node) {
|
|
return { found: false, message: 'No installation with equipment datasheet found for this application.' };
|
|
}
|
|
|
|
const datasheet = safeParseJson<{ label: string; value: string }[]>(node.specificDatasheetJson, []);
|
|
|
|
return {
|
|
found: true,
|
|
id: node.id,
|
|
title: node.title,
|
|
location: node.location,
|
|
application: node.application,
|
|
industry: industryFromSlug(node.application),
|
|
stats: node.stats,
|
|
mediaFileName: node.mediaFileName,
|
|
model3DPath: node.model3DPath,
|
|
dimensions: safeParseJson(node.model3DDimsJson, null),
|
|
datasheet,
|
|
whyThisModel,
|
|
sizingNote,
|
|
alternativeNote,
|
|
};
|
|
},
|
|
}),
|
|
|
|
// ── TOOL 5: Energy Savings Calculator (standalone — physics constants) ──
|
|
energy_savings_calculator: tool({
|
|
description: `Calculate and display an interactive energy savings comparison between FLUX solid-state RF technology and traditional industrial heating/drying methods. Use when the user asks about savings, ROI, energy costs, or efficiency comparisons. If they haven't specified volume, use defaults and state your assumptions.`,
|
|
inputSchema: z.object({
|
|
industry: z.enum(['textile', 'food', 'rubber', 'pharma', 'wood', 'other']),
|
|
process: z.string(),
|
|
productionVolumeKgPerHour: z.number().default(500),
|
|
operatingHoursPerDay: z.number().default(16),
|
|
currentMethod: z.string().optional(),
|
|
}),
|
|
execute: async ({ industry, process, productionVolumeKgPerHour, operatingHoursPerDay, currentMethod }) => {
|
|
const data = ENERGY_DATA[industry] || ENERGY_DATA.default;
|
|
const annualKg = productionVolumeKgPerHour * operatingHoursPerDay * 300;
|
|
const annualKwhTraditional = annualKg * data.traditionalKwhPerKg;
|
|
const annualKwhRF = annualKg * data.rfKwhPerKg;
|
|
const annualSavingsKwh = annualKwhTraditional - annualKwhRF;
|
|
const savingsPercent = Math.round((annualSavingsKwh / annualKwhTraditional) * 100);
|
|
|
|
return {
|
|
industry, process, productionVolumeKgPerHour, operatingHoursPerDay,
|
|
traditionalMethod: currentMethod || data.traditionalMethod,
|
|
traditionalKwhPerKg: data.traditionalKwhPerKg,
|
|
rfKwhPerKg: data.rfKwhPerKg,
|
|
savingsPercent,
|
|
annualKwhTraditional: Math.round(annualKwhTraditional),
|
|
annualKwhRF: Math.round(annualKwhRF),
|
|
annualSavingsKwh: Math.round(annualSavingsKwh),
|
|
annualCO2SavedTonnes: Math.round(annualSavingsKwh * data.co2FactorKgPerKwh / 1000),
|
|
annualCostSavingsEur: Math.round(annualSavingsKwh * 0.15),
|
|
paybackMonths: data.typicalPaybackMonths,
|
|
rfEfficiency: 95,
|
|
};
|
|
},
|
|
}),
|
|
|
|
// ── TOOL 6: Navigate to Section (client-side — NO execute) ──
|
|
navigate_to_section: tool({
|
|
description: `Maps the user to a specific section of the FLUX website. Use when the user says "show me", "take me to", "where is", or asks about a specific page section. Available sections: "hero", "applications-dashboard", "applications-deep", "global" (globe), "timeline", "heritage", "news", "parts-catalog", "contact".`,
|
|
inputSchema: z.object({
|
|
section: z.string().describe('Target section ID'),
|
|
subAction: z.enum(['none', 'activate-tab', 'highlight-node']).default('none'),
|
|
tabId: z.string().optional().describe('Application slug to activate'),
|
|
nodeId: z.string().optional().describe('Globe node ID to highlight'),
|
|
}),
|
|
}),
|
|
|
|
// ── TOOL 7: Process Comparison Table (standalone — physics data) ──
|
|
process_comparison_table: tool({
|
|
description: `Display a detailed visual comparison table between FLUX solid-state RF technology and a traditional/competing industrial heating method. Use when the user explicitly asks to compare technologies.`,
|
|
inputSchema: z.object({
|
|
competitorMethod: z.enum(['Hot Air', 'Steam', 'Microwave (2450 MHz)', 'Infrared', 'Vacuum Tubes', 'Convection Oven', 'Kiln Drying']),
|
|
applicationContext: z.string(),
|
|
}),
|
|
execute: async ({ competitorMethod, applicationContext }) => {
|
|
const compContext = `${applicationContext} application`;
|
|
const categories = [
|
|
{ label: 'Power Transfer Efficiency', rf: COMPARISON_DATA.efficiency.rf, competitor: COMPARISON_DATA.efficiency.traditional, unit: '%', note: 'FLUX solid-state delivers 95%+ of generated power directly into the product.' },
|
|
{ label: 'Heating Uniformity', rf: COMPARISON_DATA.uniformity.rf, competitor: COMPARISON_DATA.uniformity.traditional, unit: '%', note: 'Volumetric RF heats the entire mass simultaneously — no hot spots or cold cores.' },
|
|
{ label: 'Processing Speed', rf: COMPARISON_DATA.speed.rf, competitor: COMPARISON_DATA.speed.traditional, unit: ' (score)', note: 'RF reduces processing time by 50-80% depending on material and moisture content.' },
|
|
{ label: 'Maintenance Cost', rf: COMPARISON_DATA.maintenance.rf, competitor: COMPARISON_DATA.maintenance.traditional, unit: ' (score)', note: 'Solid-state = zero consumable parts. No magnetrons, no vacuum tubes to replace.' },
|
|
{ label: 'Equipment Lifespan', rf: COMPARISON_DATA.lifespan.rf, competitor: COMPARISON_DATA.lifespan.traditional, unit: ' years', note: 'FLUX solid-state generators are rated for 20+ years of continuous industrial operation.' },
|
|
{ label: 'Carbon Footprint', rf: COMPARISON_DATA.footprint.rf, competitor: COMPARISON_DATA.footprint.traditional, unit: ' (relative)', note: 'Less energy consumed = proportionally lower CO2 emissions.' },
|
|
];
|
|
return { fluxMethod: 'Solid-State RF (27.12 MHz)', competitorMethod, context: compContext, categories };
|
|
},
|
|
}),
|
|
|
|
// ── TOOL 8: RF Technology Explainer (standalone — physics) ──
|
|
rf_technology_explainer: tool({
|
|
description: `Display an interactive animated visualization explaining how Radio Frequency (RF) heating works at 27.12 MHz. Use for physics/mechanism questions.`,
|
|
inputSchema: z.object({
|
|
material: z.enum(['textile', 'food', 'rubber', 'pharma', 'wood', 'other']),
|
|
comparisonMethod: z.string().default('Hot Air'),
|
|
}),
|
|
execute: async ({ material, comparisonMethod }) => {
|
|
const materialData: Record<string, {
|
|
label: string; waterContent: number; dielectric: number;
|
|
penetration: string; timeReduction: number; advantages: string[]; physics: string;
|
|
}> = {
|
|
textile: {
|
|
label: 'Textile & Fabrics', waterContent: 60, dielectric: 80, penetration: 'Full web width',
|
|
timeReduction: 55,
|
|
advantages: [
|
|
'Uniform drying across full fabric width — eliminates edge-to-center moisture variation',
|
|
'No thermal damage to fibers — RF energy targets water, not the material',
|
|
'Replaces 40+ meter stenters with compact 10-12m RF modules',
|
|
'Self-regulating: wetter areas absorb more energy automatically',
|
|
],
|
|
physics: 'At 27.12 MHz, water molecules in the textile rotate 27 million times per second. This molecular friction generates heat precisely where moisture exists, leaving dry fibers unaffected.',
|
|
},
|
|
food: {
|
|
label: 'Food Products', waterContent: 70, dielectric: 75, penetration: 'Full depth',
|
|
timeReduction: 60,
|
|
advantages: [
|
|
'Defrost from -18°C to -2°C in minutes with zero temperature differential',
|
|
'No surface cooking or bacterial growth window',
|
|
'Uniform pasteurization through the entire product mass',
|
|
'Preserves texture, color, and nutritional value',
|
|
],
|
|
physics: 'RF dielectric heating at 27.12 MHz penetrates frozen food blocks completely. Unlike microwave (2450 MHz), the longer wavelength provides far more uniform energy distribution — no hot spots.',
|
|
},
|
|
rubber: {
|
|
label: 'Rubber & Latex', waterContent: 40, dielectric: 50, penetration: 'Full thickness (up to 25cm)',
|
|
timeReduction: 65,
|
|
advantages: [
|
|
'Simultaneous vulcanization through entire foam thickness',
|
|
'Eliminates thermal gradient: no over-cured surface / under-cured core',
|
|
'Cycle times reduced from 45+ minutes to 10-15 minutes',
|
|
'Uniform crosslink density = consistent product quality',
|
|
],
|
|
physics: 'Latex foam contains water and polar molecules that respond to the 27.12 MHz field. The electromagnetic energy converts to heat uniformly throughout the block, achieving the vulcanization temperature simultaneously at every point.',
|
|
},
|
|
pharma: {
|
|
label: 'Pharma & Botanicals', waterContent: 55, dielectric: 65, penetration: 'Full depth',
|
|
timeReduction: 50,
|
|
advantages: [
|
|
'Precise temperature control protects active pharmaceutical ingredients',
|
|
'Cannabis decontamination without terpene degradation',
|
|
'Uniform sanitization of herbs and spices',
|
|
'Gentle drying preserves molecular integrity',
|
|
],
|
|
physics: 'RF dielectric heating at 27.12 MHz provides gentle, volumetric energy delivery that can pasteurize or sanitize without exceeding critical temperatures that would degrade sensitive compounds.',
|
|
},
|
|
wood: {
|
|
label: 'Wood & Composites', waterContent: 30, dielectric: 20, penetration: 'Full depth',
|
|
timeReduction: 65,
|
|
advantages: [
|
|
'No case-hardening — moisture escapes uniformly',
|
|
'Eliminates internal stresses and checking/cracking',
|
|
'Phytosanitary treatment (ISPM-15) in minutes, not days',
|
|
'Adhesive curing in laminated products',
|
|
],
|
|
physics: 'Wood is a poor thermal conductor. Conventional kiln drying takes days because heat must slowly penetrate inward. RF bypasses this limitation — the electromagnetic field heats all water molecules simultaneously regardless of depth.',
|
|
},
|
|
other: {
|
|
label: 'Industrial Materials', waterContent: 25, dielectric: 40, penetration: 'Full depth',
|
|
timeReduction: 50,
|
|
advantages: [
|
|
'Volumetric heating eliminates processing bottlenecks',
|
|
'Digital control for precise, repeatable results',
|
|
'No combustion byproducts — clean process',
|
|
'Compact footprint vs conventional thermal equipment',
|
|
],
|
|
physics: 'At 27.12 MHz (ISM band), the RF electromagnetic field induces rapid dipole rotation and ionic conduction within the material, converting electrical energy to thermal energy with 95%+ efficiency directly inside the product.',
|
|
},
|
|
};
|
|
|
|
const d = materialData[material] || materialData.other;
|
|
return {
|
|
material, materialLabel: d.label, comparisonMethod, rfFrequency: '27.12 MHz',
|
|
penetrationDepth: d.penetration, heatingMechanism: 'Dielectric loss (dipole rotation + ionic conduction)',
|
|
waterContentPercent: d.waterContent, dielectricConstant: d.dielectric,
|
|
processingTimeReduction: d.timeReduction, keyAdvantages: d.advantages, physicsNote: d.physics,
|
|
};
|
|
},
|
|
}),
|
|
|
|
// ── TOOL 9: Schedule Consultation (server-side, pre-fills form) ──
|
|
schedule_consultation: tool({
|
|
description: `Display an intelligent consultation booking form pre-filled with context from the current conversation. This is the PRIMARY conversion tool — the goal of every FLUX conversation. Use when the user shows buying intent: schedule, contact, quote, proposal, "talk to someone", "I'm interested".`,
|
|
inputSchema: z.object({
|
|
industry: z.enum(['textile', 'food', 'rubber', 'pharma', 'wood', 'other']),
|
|
process: z.string(),
|
|
conversationInsights: z.array(z.string())
|
|
.describe('3-5 key points from the conversation with numbers, comparisons, technical details.'),
|
|
estimatedSavingsPercent: z.number().nullable(),
|
|
productionVolume: z.string().nullable(),
|
|
suggestedTopics: z.array(z.string())
|
|
.describe('2-4 topics the FLUX engineer should prepare for.'),
|
|
}),
|
|
execute: async ({ industry, process, conversationInsights, estimatedSavingsPercent, productionVolume, suggestedTopics }) => {
|
|
const industryLabels: Record<string, string> = {
|
|
textile: 'Textile', food: 'Food Processing', rubber: 'Rubber & Latex',
|
|
pharma: 'Pharma & Cosmetics', wood: 'Wood Treatment', other: 'Industrial',
|
|
};
|
|
return {
|
|
industry, industryLabel: industryLabels[industry] || industry,
|
|
process, conversationInsights, estimatedSavingsPercent, productionVolume, suggestedTopics,
|
|
};
|
|
},
|
|
}),
|
|
},
|
|
});
|
|
|
|
return result.toUIMessageStreamResponse();
|
|
} |