feat: website analysis pipeline, voice agent, configurator improvements
All checks were successful
Build & Push / build-and-push (push) Successful in 6m2s

- Site analysis: cheerio HTML parsing, inline tech stack detection (~20 CMS/framework/analytics signatures), Google PageSpeed API integration
- Gemini Live voice agent: WebSocket-based real-time voice mode with live transcript, selection chips, and mid-conversation website analysis
- Type/Talk mode toggle with silent capability detection
- Stepped progress animation during brief generation (4 animated steps)
- URL + thoughts fields in Step 2, phone + contact preference in Step 3
- AI prompt improvements: dedicated website analysis section, 30-min call, concrete benefits, industry depth
- Email redesign: branded templates with logo, proper markdown rendering for both client and admin
- French locale support for AI-generated briefs
- Smaller checkmark, compact booking CTA, expanded brief area

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-28 13:41:35 +01:00
parent 16cd2a74ee
commit bab45b981e
19 changed files with 2923 additions and 119 deletions

164
src/lib/gemini-live.ts Normal file
View File

@@ -0,0 +1,164 @@
import { GoogleGenAI, Type } from '@google/genai';
// ─── Constants ────────────────────────────────────────────────────────────────
export const GEMINI_LIVE_MODEL = 'gemini-3.1-flash-live-preview';
// ─── Agent Tools ─────────────────────────────────────────────────────────────
export const AGENT_TOOLS = [
{
name: 'update_selections',
description:
'Emit structured project data as it is confirmed during conversation. Call incrementally as each detail is captured.',
parameters: {
type: Type.OBJECT,
properties: {
services: {
type: Type.ARRAY,
items: { type: Type.STRING },
description: 'Selected services: web, systems, infrastructure',
},
aiEnabled: {
type: Type.BOOLEAN,
description: 'Whether AI integration is requested',
},
aiTypes: {
type: Type.ARRAY,
items: { type: Type.STRING },
description: 'AI types: teammate, customer-facing, data-intelligence, notsure',
},
industry: { type: Type.STRING, description: 'Industry sector' },
timeline: { type: Type.STRING, description: 'Timeline preference' },
currentSiteUrl: { type: Type.STRING, description: 'Current website URL' },
scope: { type: Type.STRING, description: 'Project goals/scope summary' },
},
},
},
{
name: 'analyze_website',
description:
'Analyze a website URL to understand its current technology, performance, and structure. Call when the user provides their current website URL.',
parameters: {
type: Type.OBJECT,
properties: {
url: { type: Type.STRING, description: 'The website URL to analyze' },
},
required: ['url'],
},
},
{
name: 'complete_brief',
description:
'Generate and send the project brief. Call once all information is collected and the user has confirmed their name and email.',
parameters: {
type: Type.OBJECT,
properties: {
name: { type: Type.STRING },
email: { type: Type.STRING },
company: { type: Type.STRING },
phone: { type: Type.STRING },
contactPreference: { type: Type.STRING },
services: { type: Type.ARRAY, items: { type: Type.STRING } },
aiEnabled: { type: Type.BOOLEAN },
aiTypes: { type: Type.ARRAY, items: { type: Type.STRING } },
industry: { type: Type.STRING },
timeline: { type: Type.STRING },
currentSiteUrl: { type: Type.STRING },
currentSiteThoughts: { type: Type.STRING },
scope: { type: Type.STRING },
},
required: ['name', 'email', 'services'],
},
},
];
// ─── System Prompt ────────────────────────────────────────────────────────────
export function buildSystemPrompt(locale: string): string {
const isFr = locale === 'fr';
if (isFr) {
return `Tu es l'assistant de projets LetsBe, un consultant amical et compétent pour LetsBe Solutions. Tu mènes toute cette conversation en français.
Présente-toi ainsi : "Bonjour, je suis l'assistant de projets LetsBe. Parlez-moi de votre projet et je préparerai un brief personnalisé pour vous."
Ton rôle est de guider naturellement la conversation à travers les sujets suivants :
1. Quels services ils recherchent (web, logiciels sur mesure, infrastructure privée)
2. S'ils souhaitent une intégration IA — et si oui, quel type (assistant interne, IA pour les clients, intelligence de données, ou pas encore sûr)
3. Leur secteur d'activité
4. Leur calendrier préféré
5. S'ils ont un site web actuel (propose de l'analyser si c'est le cas)
6. Leurs objectifs et la portée du projet
7. Enfin, leur prénom, nom et adresse e-mail pour envoyer le brief
Instructions :
- Appelle update_selections chaque fois qu'un point est confirmé dans la conversation.
- Appelle analyze_website dès que l'utilisateur fournit une URL — puis intègre naturellement les résultats dans la discussion.
- Appelle complete_brief une fois que le nom et l'e-mail sont confirmés.
- Garde tes réponses concises : 2 à 3 phrases maximum par tour.
- Sois chaleureux, direct et professionnel — jamais générique.
Faits clés sur LetsBe à mentionner si pertinent :
- Tout est développé sur mesure — aucun template, aucun constructeur de pages
- Infrastructure privée : le client possède et contrôle entièrement ses données et serveurs
- Petite équipe expérimentée avec des décennies d'expérience combinée
- Intégration IA profonde dans tous types de systèmes
- Souveraineté numérique et protection des données comme priorité`;
}
return `You are the LetsBe project assistant, a friendly and knowledgeable project consultant for LetsBe Solutions.
Introduce yourself: "Hi, I'm the LetsBe project assistant. Tell me about your project and I'll put together a personalized brief for you."
Your role is to walk through the following topics naturally in conversation:
1. What services they need (web, custom software, private infrastructure)
2. Whether they want AI integration — and if so, what kind (internal teammate, customer-facing, data intelligence, or not sure yet)
3. Their industry
4. Their timeline preference
5. Whether they have a current website (offer to analyze it if they do)
6. Their goals and project scope
7. Finally, their name and email to send the brief
Instructions:
- Call update_selections each time a data point is confirmed during the conversation.
- Call analyze_website as soon as the user provides a URL — then discuss the findings naturally.
- Call complete_brief once name and email are confirmed.
- Keep responses concise: 23 sentences maximum per turn.
- Be warm, direct, and professional — never generic.
Key facts about LetsBe to reference when relevant:
- Everything is custom-built from scratch — no templates, no page builders
- Private infrastructure: the client fully owns and controls their data and servers
- Small, experienced team with decades of combined expertise in design and engineering
- Deep AI integration into any type of system they build
- Data sovereignty and digital privacy as a core focus`;
}
// ─── Live Config ──────────────────────────────────────────────────────────────
export function buildLiveConfig(locale: string) {
return {
responseModalities: ['AUDIO'],
systemInstruction: buildSystemPrompt(locale),
tools: [{ functionDeclarations: AGENT_TOOLS }],
speechConfig: {
voiceConfig: {
prebuiltVoiceConfig: { voiceName: 'Aoede' },
},
},
};
}
// ─── Ephemeral Token ──────────────────────────────────────────────────────────
export async function generateEphemeralToken(locale: string) {
// GoogleGenAI is instantiated here to validate the API key at request time.
// The SDK does not yet expose an ephemeral token API; in production, replace
// this with ai.auth.tokens.create() or equivalent when available to avoid
// exposing the API key to the client.
new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY! });
const config = buildLiveConfig(locale);
return { config, model: GEMINI_LIVE_MODEL };
}