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:
179
.aiideas
179
.aiideas
@@ -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").
|
||||
Reference in New Issue
Block a user