Co-authored-by: Cursor <[email protected]>
This commit is contained in:
lejay
2026-05-28 07:20:38 +02:00
co-authored by Cursor
parent 6923c319a1
commit 6c0d7e47b8
81 changed files with 21780 additions and 2986 deletions
+89 -18
View File
@@ -64,40 +64,107 @@ export async function getDesignTip(config) {
/* -------------------------------------------------------------------------- */
const MATERIAL_REC_PROMPT = `Tu es un conseiller en matériaux et couleurs pour armoires sur mesure suisses haut de gamme.
À partir de la configuration actuelle et de la liste de matériaux disponibles, recommande 3 à 5 matériaux qui iraient parfaitement ensemble.
À partir de la configuration actuelle, des PRIORITÉS UTILISATEUR (échelle 1–5) et de la liste de matériaux disponibles, recommande 3 à 5 finitions.
Critères de recommandation :
- Harmonie des couleurs avec les matériaux déjà sélectionnés
- Cohérence avec le style d'armoire (dimensions, configuration)
- Tendances actuelles en design d'intérieur suisse
- Si un contexte de pièce est fourni, s'accorder avec le style de la pièce
Règles :
- Les priorités indiquent ce qui compte le PLUS pour le client (5 = très important, 1 = peu important). Tu dois PONDÉRER tes choix en conséquence (ex. : priorité « prix » élevée → privilégier des décors à bon rapport qualité-prix et l'expliquer ; priorité « qualité » élevée → insister sur la noblesse de la finition, la précision du relief, etc.).
- Si un prix indicatif [CHF] est fourni pour un matériau, utilise-le honnêtement pour le critère prix.
- Pour CHAQUE recommandation, explique CLAIREMENT POURQUOI tu proposes CE matériau : lien explicite avec les priorités du client (pas seulement « joli »).
- Remplis les champs criteria avec une phrase courte CHACUN (en français) : ce que ce décor apporte sur ce critère, au regard des priorités.
Réponds UNIQUEMENT en JSON :
Réponds UNIQUEMENT en JSON valide :
{
"recommendations": [
{ "finishId": <number>, "reason": "…raison courte en français…" }
{
"finishId": <number>,
"summary": "2 phrases max : pourquoi ce choix au regard des priorités",
"criteria": {
"price": "phrase sur le rapport prix / gamme / accessibilité",
"quality": "phrase sur la qualité de finition, rendu, sensation",
"durability": "phrase sur tenue dans le temps, entretien, résistance",
"aesthetics": "phrase sur l'harmonie couleur/style avec le projet"
},
"reason": "une ligne très courte pour infobulle (optionnel, peut résumer summary)"
}
]
}`;
function clampPriority(n, fallback = 3) {
const x = Number(n);
if (!Number.isFinite(x)) return fallback;
return Math.min(5, Math.max(1, Math.round(x)));
}
/**
* @param {object} [p] - { price, quality, durability, aesthetics } each 1–5
*/
export function normalizeRecommendationPriorities(p) {
const d = { price: 3, quality: 3, durability: 3, aesthetics: 3 };
if (!p || typeof p !== 'object') return { ...d };
return {
price: clampPriority(p.price, d.price),
quality: clampPriority(p.quality, d.quality),
durability: clampPriority(p.durability, d.durability),
aesthetics: clampPriority(p.aesthetics, d.aesthetics)
};
}
function normalizeMaterialRecommendation(rec) {
if (!rec || rec.finishId == null) return null;
const criteria = rec.criteria && typeof rec.criteria === 'object' ? rec.criteria : {};
const summary = (rec.summary || rec.reason || '').trim();
const reason = (rec.reason || summary).trim();
return {
finishId: Number(rec.finishId),
summary,
reason,
criteria: {
price: String(criteria.price || '').trim(),
quality: String(criteria.quality || '').trim(),
durability: String(criteria.durability || '').trim(),
aesthetics: String(criteria.aesthetics || '').trim()
}
};
}
function buildPrioritiesBlock(priorities) {
const p = normalizeRecommendationPriorities(priorities);
return `PRIORITÉS UTILISATEUR (1 = peu important, 5 = très important — ajuste tes recommandations et tes explications en conséquence) :
- Prix / rapport qualité-prix : ${p.price}
- Qualité perçue & finition : ${p.quality}
- Durabilité & entretien : ${p.durability}
- Esthétique & harmonie avec le projet : ${p.aesthetics}`;
}
/**
* Recommend materials that complement the current configuration
* @param {object} config - Current wardrobe configuration
* @param {Array} availableFinishes - [{ id, name, collection, code }]
* @param {Array} availableFinishes - [{ id, name, collection, code, price_chf? }]
* @param {object} [roomAnalysis] - Optional room photo analysis
* @returns {{ recommendations: Array<{ finishId: number, reason: string }> }}
* @param {object} [priorities] - { price, quality, durability, aesthetics } each 1–5
* @returns {{ recommendations: Array<{ finishId, summary, reason, criteria }> }}
*/
export async function recommendMaterials(config, availableFinishes, roomAnalysis) {
export async function recommendMaterials(config, availableFinishes, roomAnalysis, priorities) {
const client = getOpenAIClient();
const configSummary = buildConfigSummary(config);
const priBlock = buildPrioritiesBlock(priorities);
// Compact finish list (only names + ids to save tokens)
// Compact finish list (names + ids + optional price for fair "prix" explanations)
const finishList = availableFinishes
.slice(0, 80) // limit to avoid token overflow
.map(f => `${f.id}: ${f.name || f.label} (${f.collection || '?'})`)
.map((f) => {
const name = f.name || f.label || '?';
const col = f.collection || '?';
const price =
f.price_chf != null && Number.isFinite(Number(f.price_chf))
? ` — indicatif ${Number(f.price_chf).toFixed(2)} CHF`
: '';
return `${f.id}: ${name} (${col})${price}`;
})
.join('\n');
let userMessage = `CONFIGURATION ACTUELLE:\n${configSummary}\n\nMATÉRIAUX DISPONIBLES:\n${finishList}`;
let userMessage = `${priBlock}\n\nCONFIGURATION ACTUELLE:\n${configSummary}\n\nMATÉRIAUX DISPONIBLES:\n${finishList}`;
if (roomAnalysis) {
userMessage += `\n\nCONTEXTE PIÈCE:\n${JSON.stringify(roomAnalysis, null, 1)}`;
@@ -105,8 +172,8 @@ export async function recommendMaterials(config, availableFinishes, roomAnalysis
const response = await client.chat.completions.create({
model: 'gpt-4o',
temperature: 0.7,
max_tokens: 400,
temperature: 0.65,
max_tokens: 1400,
messages: [
{ role: 'system', content: MATERIAL_REC_PROMPT },
{ role: 'user', content: userMessage }
@@ -117,9 +184,13 @@ export async function recommendMaterials(config, availableFinishes, roomAnalysis
const raw = response.choices[0]?.message?.content;
try {
const parsed = JSON.parse(raw);
return { recommendations: (parsed.recommendations || []).slice(0, 5) };
const rawList = (parsed.recommendations || []).slice(0, 5);
const recommendations = rawList
.map(normalizeMaterialRecommendation)
.filter(Boolean);
return { recommendations, priorities: normalizeRecommendationPriorities(priorities) };
} catch {
return { recommendations: [] };
return { recommendations: [], priorities: normalizeRecommendationPriorities(priorities) };
}
}