import React, { useState, useEffect } from 'react'; import { motion, AnimatePresence } from 'motion/react'; import { Search, Droplets, Wind, Flower2, Sparkles, Loader2, ArrowRight, RefreshCw, Bookmark, Trash2, LayoutGrid, History, X, Filter, ChevronDown, ChevronUp, Share2, Check, ChevronLeft, ChevronRight, Download, FileText } from 'lucide-react'; import { getScentProfile, getScentImage, getScentVideo, ScentProfile } from './services/gemini'; interface SavedScent extends ScentProfile { id: number; imageUrl: string; videoUrl?: string; category?: string; createdAt: string; } const CATEGORIES = [ 'Floreale', 'Legnoso', 'Orientale', 'Fresco', 'Agrumato', 'Speziato', 'Muschiato', 'Acquatico' ]; const ITEMS_PER_PAGE = 20; export default function App() { const [input, setInput] = useState(''); const [loading, setLoading] = useState(false); const [regenerating, setRegenerating] = useState(false); const [profile, setProfile] = useState(null); const [imageUrl, setImageUrl] = useState(null); const [videoUrl, setVideoUrl] = useState(null); const [showVideo, setShowVideo] = useState(false); const [isAnimating, setIsAnimating] = useState(false); const [error, setError] = useState(null); const [collection, setCollection] = useState([]); const [view, setView] = useState<'design' | 'collection'>('design'); const [isSaving, setIsSaving] = useState(false); const [saveSuccess, setSaveSuccess] = useState(false); const [shareSuccess, setShareSuccess] = useState(false); const [filterNote, setFilterNote] = useState(null); const [filterCategory, setFilterCategory] = useState(null); const [selectedCategory, setSelectedCategory] = useState('Fresco'); const [expandedId, setExpandedId] = useState(null); const [playingVideoId, setPlayingVideoId] = useState(null); const [currentPage, setCurrentPage] = useState(1); useEffect(() => { fetchCollection(); const params = new URLSearchParams(window.location.search); const sharedId = params.get('share'); if (sharedId) fetchSharedScent(sharedId); }, []); const fetchSharedScent = async (id: string) => { setLoading(true); try { const res = await fetch(`/api/scents/${id}`); if (res.ok) { const data = await res.json(); setProfile(data); setImageUrl(data.imageUrl); setView('design'); } } catch (err) { console.error('Failed to fetch shared scent', err); } finally { setLoading(false); } }; const fetchCollection = async () => { try { const res = await fetch('/api/scents'); if (res.ok) { const data = await res.json(); setCollection(data); } } catch (err) { console.error('Failed to fetch collection', err); } }; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (!input.trim()) return; setLoading(true); setError(null); setProfile(null); setImageUrl(null); setView('design'); try { const scentProfile = await getScentProfile(input); setProfile(scentProfile); setVideoUrl(null); const img = await getScentImage(scentProfile.visualPrompt); setImageUrl(img); } catch (err: any) { setError(err.message || 'Errore nella creazione. Riprova.'); } finally { setLoading(false); } }; const handleDownloadInfo = (scent: ScentProfile & { category?: string }) => { const content = ` ECCENCE DESIGN - SCHEDA OLFATTIVA -------------------------- NOME: ${scent.name} CATEGORIA: ${scent.category || 'N/A'} NOTE DI TESTA: ${scent.topNotes.join(', ')} NOTE DI CUORE: ${scent.heartNotes.join(', ')} NOTE DI FONDO: ${scent.baseNotes.join(', ')} ISPIRAZIONE: ${scent.description} -------------------------- creato da eccence design app by Soumiya F. `.trim(); const blob = new Blob([content], { type: 'text/plain' }); const url = window.URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.download = `${scent.name.replace(/\s+/g, '_').toLowerCase()}_scheda.txt`; link.click(); }; // ... (Altre funzioni di gestione: handleSave, handleDelete, handleAnimate, ecc.) return (
{/* Navigazione */}

Essence Designer

{view === 'design' ? (
setInput(e.target.value)} placeholder="Descrivi un'emozione o un ricordo..." className="w-full bg-white border border-neutral-200 rounded-full py-5 px-8 pr-16 text-lg focus:outline-none focus:ring-2 focus:ring-bordeaux/20 shadow-sm" />
{/* Visualizzazione Risultati con Animazione Hover */} {profile && (
{/* ... (Overlay e Info) */}
{/* ... (Piramide Olfattiva) */}
)}
) : ( /* Vista Collezione */
{/* ... (Griglia della collezione) */}
)}

ECCENCE DESIGN app by Soumiya F.

); } import express from "express"; import { createServer as createViteServer } from "vite"; import Database from "better-sqlite3"; import path from "path"; import fs from "fs"; const db = new Database("scents.db"); // Inizializzazione Database db.exec(` CREATE TABLE IF NOT EXISTS saved_scents ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, description TEXT, topNotes TEXT, heartNotes TEXT, baseNotes TEXT, imageUrl TEXT, videoUrl TEXT, category TEXT, createdAt DATETIME DEFAULT CURRENT_TIMESTAMP ) `); async function startServer() { const app = express(); app.use(express.json({ limit: '10mb' })); // API: Recupera Collezione app.get("/api/scents", (req, res) => { const scents = db.prepare("SELECT * FROM saved_scents ORDER BY createdAt DESC").all(); res.json(scents.map(s => ({ ...s, topNotes: JSON.parse(s.topNotes), heartNotes: JSON.parse(s.heartNotes), baseNotes: JSON.parse(s.baseNotes) }))); }); // API: Salva Profumo app.post("/api/scents", (req, res) => { const { name, description, topNotes, heartNotes, baseNotes, imageUrl, videoUrl, category } = req.body; const info = db.prepare(` INSERT INTO saved_scents (name, description, topNotes, heartNotes, baseNotes, imageUrl, videoUrl, category) VALUES (?, ?, ?, ?, ?, ?, ?, ?) `).run(name, description, JSON.stringify(topNotes), JSON.stringify(heartNotes), JSON.stringify(baseNotes), imageUrl, videoUrl, category); res.json({ id: info.lastInsertRowid }); }); // ... (Altre rotte API e Middleware Vite) app.listen(3000, "0.0.0.0", () => { console.log(`Server running on http://localhost:3000`); }); } startServer(); import { GoogleGenAI, Type } from "@google/genai"; const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY || "" }); export async function getScentProfile(prompt: string): Promise { const response = await ai.models.generateContent({ model: "gemini-3.1-pro-preview", contents: `Crea un profilo olfattivo professionale per: "${prompt}"...`, config: { responseMimeType: "application/json", responseSchema: { /* Schema dei dati */ }, }, }); return JSON.parse(response.text || "{}"); } export async function getScentImage(visualPrompt: string): Promise { const response = await ai.models.generateContent({ model: "gemini-2.5-flash-image", contents: { parts: [{ text: `Design di lusso: ${visualPrompt}` }] }, }); // Estrazione dati base64... }