feat: refine Scan & Taste UI, fix desktop scrolling, and resolve production login fetch error

- Reverted theme from gold to amber and restored legacy typography.
- Refactored ScanAndTasteFlow and TastingEditor for robust desktop scrolling.
- Hotfixed sw.js to completely bypass Supabase Auth/API requests to fix 'Failed to fetch' in production.
- Integrated full tasting note persistence (tags, buddies, sessions).
This commit is contained in:
2025-12-21 22:29:16 +01:00
parent 4e8af60488
commit b57f5dc2ad
12 changed files with 1482 additions and 120 deletions

179
.aiideas
View File

@@ -1,123 +1,110 @@
Hier ist der Code, um Pixtral Large (das europäische Flaggschiff-Modell von Mistral) direkt in deine Next.js App zu integrieren.
Damit kannst du einen direkten "A/B-Test" gegen Gemini 3 Flash fahren.
1. Vorbereitung
Rolle: Du bist ein Senior Frontend Engineer und UX - Experte mit Spezialisierung auf "Mobile-First" - Webanwendungen.Dein Fokus liegt auf High - End Ästhetik(Dark Mode), flüssigen Animationen und reibungsloser User Experience.
Du brauchst das Mistral SDK und einen API Key von console.mistral.ai.
Bash
Aufgabe: Wir bauen den Core - Flow einer bestehenden Whisky - Tasting - App um.Implementiere den neuen "Scan & Taste" Flow.Ziel ist eine Single - Page - App - Experience(SPA) ohne Reloads, die sich nativ anfühlt.
npm install @mistralai/mistralai
Tech Stack(Anpassen falls nötig):
Füge deinen Key in die .env ein (nicht NEXT_PUBLIC_!): MISTRAL_API_KEY=dein_key_hier
2. Der Code (Server Action)
Framework: Next.js(Bitte nutze den bestehenden Stack der App)
Erstelle eine neue Datei, z.B. app/actions/scan-mistral.ts.
TypeScript
Styling: Tailwind CSS
'use server'
Icons: Lucide - React oder HeroIcons
import { Mistral } from '@mistralai/mistralai';
Charts: Recharts oder Chart.js(für das Radar Chart)
const client = new Mistral({ apiKey: process.env.MISTRAL_API_KEY });
Animation: Framer Motion(für Transitions)
export async function scanWithPixtral(base64Image: string, mimeType: string) {
// Pixtral braucht das Bild als Data-URL
const dataUrl = `data:${mimeType};base64,${base64Image}`;
1. Design System & Vibe
try {
const chatResponse = await client.chat.complete({
model: 'pixtral-large-latest', // Das beste Modell (Stand Dez 2025)
messages: [
{
role: 'user',
content: [
{
type: 'text',
text: `Du bist ein Whisky-Experte und OCR-Spezialist.
Analysiere dieses Etikett präzise.
Antworte AUSSCHLIESSLICH mit gültigem JSON (kein Markdown, kein Text davor/danach):
{
"distillery": "Name der Brennerei (z.B. Lagavulin)",
"name": "Exakter Name/Edition (z.B. 16 Year Old)",
"vintage": "Jahrgang oder null",
"age": "Alter oder null (z.B. 16)",
"abv": "Alkoholgehalt (z.B. 43%)",
"search_query": "site:whiskybase.com [Brennerei] [Name] [Alter]"
}`
},
{
type: 'image_url',
imageUrl: dataUrl
}
]
}
],
responseFormat: { type: 'json_object' }, // Erzwingt JSON (wichtig!)
temperature: 0.1 // Niedrig = präziser, weniger Halluzinationen
});
Theme: Strict Dark Mode.
const rawContent = chatResponse.choices?.[0].message.content;
if (!rawContent) throw new Error("Keine Antwort von Pixtral");
Background: #0F1014(Deep Anthracite / Black)
// JSON parsen
return JSON.parse(rawContent as string);
Surface / Cards: #1A1B20(Lighter Anthracite)
} catch (error) {
console.error("Pixtral Error:", error);
return null; // Fallback auslösen
}
}
Primary Accent: #C89D46(Whisky Gold / Amber)
3. Der "A/B-Switcher" (So nutzt du beides)
Text: Sans - Serif(Inter) für UI, Serif(Playfair Display) für Überschriften / Namen.
In deiner Haupt-Logik (app/actions/scan.ts) kannst du jetzt einfach umschalten oder Pixtral als Fallback nutzen, wenn Gemini zickt (oder andersrum).
TypeScript
Stil: "Premium & Warm".Runde Ecken(rounded - 2xl), Glassmorphism für Overlays(backdrop - blur - md, bg - white / 5), feine Borders(border - white / 10).
'use server'
import { scanWithGemini } from './scan-gemini'; // Deine bestehende Funktion
import { scanWithPixtral } from './scan-mistral';
2. Der Flow(Schritt für Schritt Implementierung)
export async function scanBottle(formData: FormData) {
// ... Bild zu Base64 konvertieren ...
const base64 = "...";
const mime = "image/jpeg";
Bitte implementiere folgende Views / Components als zusammenhängenden Flow:
A.Der Entry Point(Floating Action Button)
// STRATEGIE A: Der "Qualitäts-Check"
// Wir nutzen standardmäßig Gemini, aber Pixtral als EU-Option
let result;
const useEuModel = process.env.USE_EU_MODEL === 'true'; // Schalter in .env
Erstelle einen prominenten, schwebenden Button(unten mittig, fixed), der über dem Dashboard liegt.
if (useEuModel) {
console.log("🇪🇺 Nutze Pixtral (Mistral AI)...");
result = await scanWithPixtral(base64, mime);
} else {
console.log("🇺🇸 Nutze Gemini 3 Flash...");
result = await scanWithGemini(base64, mime);
}
Icon: Kamera - Symbol.
// Wenn das erste Modell versagt (null zurückgibt), versuche das andere
if (!result) {
console.log("⚠️ Erster Versuch fehlgeschlagen, starte Fallback...");
result = useEuModel
? await scanWithGemini(base64, mime)
: await scanWithPixtral(base64, mime);
}
Interaction: Beim Klick simulieren wir einen Kamera - Scan(nutze vorerst ein Mock - Timeout von 2s mit einer Lade - Animation "Analysiere Etikett..."), danach Transition zu View B.
// ... weiter mit Supabase Caching & Brave Search ...
return result;
}
B.Der Tasting Editor(Main Component)
Pixtral vs. Gemini 3 Flash (Dein Check)
Dies ist der wichtigste Screen.Layout - Struktur:
Achte beim Testen auf diese Feinheiten:
Top Bar(Sticky):
Helle Schrift auf dunklem Grund: Hier ist Gemini oft aggressiver und liest besser. Pixtral ist manchmal vorsichtiger.
Zeige einen "Context Indicator".
Schreibschrift (Signatory Vintage Abfüllungen): Pixtral Large ist hier erstaunlich gut, fast besser als Gemini, da es Handschrift extrem gut kann.
Logik: Zeige Text "Trinkst du in Gesellschaft? + Session wählen".
JSON-Struktur: Dank responseFormat: { type: 'json_object' } sollten beide Modelle sehr sauberen Code liefern.
Interaction: Klick öffnet ein Bottom Sheet(siehe C).Wenn eine Session gewählt wurde, zeige: "Session: [Name]".
Hero Section:
Zeige das(gemockte) Foto der Flasche links.
Rechts daneben: Name(Serif, Gold), Alter, ABV. (Mock Data: "Lagavulin 16, Islay, 43%").
Form Section(Scrollable):
Slider: Erstelle eine Custom - Komponente für "Nose", "Taste", "Finish".Nutze keine Zahlen - Inputs, sondern Range - Slider(0 - 100).
Smart Tags(Wichtig!): Implementiere eine Chip - Auswahl.
Design: "Ghost Button" Style(transparenter BG, feiner Border).
Active State: Füllt sich mit #C89D46(Gold), Text wird dunkel.
Data: Mocke AI - Vorschläge wie["Rauch", "Torf", "Jod", "Vanille"].
Sticky Footer:
Ein Button "Save Tasting"(Full Width), der immer sichtbar unten schwebt(z - index: 50).
C.Das Session Bottom Sheet(Overlay)
Wenn man in View B auf die Top Bar klickt, fährt von unten ein Sheet hoch(Höhe: 50vh).
Inhalt: Input Feld für "Neue Session" und Liste "Aktuelle Sessions".
Beim Auswählen schließt sich das Sheet und aktualisiert den State in View B(Context Bar).
D.Die Result Card(The Reward)
Nach dem Speichern(Transition: Fade out Editor -> Fade in Card):
Zeige eine "Trading Card" im 9: 16 Verhältnis, zentriert.
Inhalt:
Großes Foto der Flasche mit Vignette.
Ein Radar Chart(Spider Web) für die 5 Geschmacksprofile(Nose, Taste, Finish, Balance, Complexity).
Ein "Badge" oben rechts mit dem Score(z.B. 8.5).
Action: Ein Button "Share Image" unter der Karte. (Logik: Bereite navigator.share vor).
3. Technische Anforderungen & State
Nutze einen lokalen State(oder Context), um die Daten zwischen Editor und Result zu halten.
Mocke die "AI Response"(Flaschenerkennung) mit einem festen Datensatz(JSON), damit wir das UI testen können.
Achte auf Mobile - Viewport - Height(dvh), damit Safari - Bars nichts verdecken.
Wenn du etwas schon hast pass es an und integriere es in den neuen Flow
Wenn Pixtral Large für dich ähnlich gut funktioniert wie Gemini 3 Flash, hast du den großen Vorteil: Daten bleiben in Europa (Server in Frankreich/EU). Das ist ein starkes Marketing-Argument ("We love Whisky & Privacy").