import { GoogleGenAI, Type, Schema } from "@google/genai"; import { WizardState, GeneratedContent } from "../types"; import { getSystemPrompt } from "../_EDITABLE_CONFIG/ai_prompts"; // Updated path // SCHEMAT ODPOWIEDZI JSON const responseSchema: Schema = { type: Type.OBJECT, properties: { caption: { type: Type.STRING, description: "Gotowy opis pod post na Instagram/YouTube/Strava.", }, slides: { type: Type.ARRAY, description: "Lista elementów wizualnych. Dla Instagrama są to slajdy karuzeli. Dla Stravy są to sugestie zdjęć do galerii (Social Proof, Data, Reward).", items: { type: Type.OBJECT, properties: { overlay_text: { type: Type.STRING, description: "Tekst na grafikę (Instagram) LUB Etykieta typu zdjęcia (Strava: np. 'DANE', 'TWARZ').", }, image_prompt: { type: Type.STRING, description: "Sugestia dla użytkownika w języku polskim, jakie zdjęcie wykonać lub wybrać z galerii (np. 'Zbliżenie na zegarek z tętnem', 'Selfie z błotem na twarzy').", }, notes: { type: Type.STRING, description: "Uzasadnienie, dlaczego to zdjęcie buduje zasięg/historię.", } }, required: ["overlay_text", "image_prompt"], }, }, }, required: ["caption", "slides"], }; export interface RefinementOptions { slideCount: number; feedback: string; } export const generateStoryContent = async ( data: WizardState, apiKey: string, refinement?: RefinementOptions ): Promise => { if (!apiKey) { throw new Error("API Key is missing."); } const ai = new GoogleGenAI({ apiKey }); // 1. POBIERZ PROMPT SYSTEMOWY (TERAZ SKLEJANY Z PUZLI) const BASE_SYSTEM_PROMPT = getSystemPrompt(data); // 2. PRZYGOTUJ DANE (STATYSTYKI I WAYPOINTS) ZAMIAST SUROWEGO GPX // Wykorzystujemy bogaty raport z parsera GPX jeśli jest dostępny const statsInfo = data.gpxSummary ? data.gpxSummary : ` DYSTANS: ${data.stats.distance} CZAS TRWANIA: ${data.stats.duration} PRZEWYŻSZENIA: ${data.stats.elevation} `; const waypointsInfo = data.waypoints.length > 0 ? data.waypoints.map((wp, i) => ` - Moment ${i+1}: "${wp.header}" (Kontekst: ${wp.context})`).join('\n') : "Brak zdefiniowanych kluczowych punktów."; // 2b. PRZYGOTUJ DANE Z WYCIECZKI (JEŚLI SĄ) let tripInfo = ""; if (data.eventType === 'trip' && data.tripData) { const stopsList = data.tripData.stops .filter(s => s.place.trim() !== '') .map((s, i) => ` ${i+1}. PRZYSTANEK: ${s.place} - Opis: ${s.description}`) .join('\n'); tripInfo = ` === SZCZEGÓŁY WYCIECZKI (TRIP ITINERARY) === PUNKT STARTOWY: ${data.tripData.startPoint.place} (Opis: ${data.tripData.startPoint.description}) PUNKT KOŃCOWY: ${data.tripData.endPoint.place} (Opis: ${data.tripData.endPoint.description}) PLAN TRASY: ${stopsList} INSTRUKCJA DODATKOWA: Wykorzystaj te konkretne miejsca w narracji i sugestiach zdjęć. `; } // 3. OBSŁUGA POPRAWEK (REFINEMENT) let refinementInstruction = ""; if (refinement) { refinementInstruction = ` !!! WAŻNA AKTUALIZACJA OD UŻYTKOWNIKA (PRIORYTET) !!! Użytkownik prosi o poprawki do poprzedniej wersji. Zignoruj poprzednie wytyczne dotyczące liczby slajdów, jeśli są inne. 1. WYMAGANA LICZBA SLAJDÓW: Dokładnie ${refinement.slideCount}. Dostosuj tempo historii, aby idealnie wypełnić tę liczbę. 2. UWAGI MERYTORYCZNE: "${refinement.feedback}" Wprowadź te zmiany do narracji. `; } // 4. SKLEJANIE PEŁNEJ INSTRUKCJI const FULL_SYSTEM_INSTRUCTION = ` ${BASE_SYSTEM_PROMPT} === DANE WYDARZENIA (META DANE) === TYTUŁ: "${data.title}" OPIS UŻYTKOWNIKA: "${data.description}" === STATYSTYKI I PRZEBIEG AKTYWNOŚCI (GPX) === ${statsInfo} === KLUCZOWE MOMENTY (WAYPOINTS) === ${waypointsInfo} ${tripInfo} ${refinementInstruction} Instrukcja: Wykorzystaj powyższe statystyki, konfigurację i opis użytkownika do stworzenia narracji. `; // Budowanie zapytania z tekstem i plikami (tylko obrazy/PDF, bez surowego GPX) const promptParts: any[] = []; // Dodaj pełną instrukcję promptParts.push({ text: FULL_SYSTEM_INSTRUCTION }); // Dodaj pliki (Tylko obrazy i PDFy. GPX jest już przetworzony wyżej jako tekst) data.files.forEach(file => { // Ignorujemy treść pliku GPX w promptcie if (!file.file.name.toLowerCase().endsWith('.gpx')) { promptParts.push({ inlineData: { mimeType: file.mimeType, data: file.content as string } }); } }); try { const response = await ai.models.generateContent({ model: "gemini-3-flash-preview", contents: { parts: promptParts }, config: { responseMimeType: "application/json", responseSchema: responseSchema, }, }); if (response.text) { return JSON.parse(response.text) as GeneratedContent; } else { throw new Error("Nie udało się wygenerować treści."); } } catch (error) { console.error("Gemini API Error:", error); throw error; } };