173 lines
8.5 KiB
JavaScript
173 lines
8.5 KiB
JavaScript
// Version: 01.10.2025 11:45 (FINAL FIX)
|
|
const API_URL = 'api.php';
|
|
|
|
// --- Hilfsfunktion für Fehlerbehandlung ---
|
|
async function handleResponse(response) {
|
|
if (!response.ok) {
|
|
const errorData = await response.json().catch(() => ({ error: 'Unbekannter API-Fehler' }));
|
|
throw new Error(errorData.error || `HTTP-Fehler! Status: ${response.status}`);
|
|
}
|
|
return response.json();
|
|
}
|
|
|
|
// --- Nutzer-Funktionen ---
|
|
export async function getUserSettings() {
|
|
const response = await fetch(`${API_URL}?action=get_user_settings`);
|
|
return handleResponse(response);
|
|
}
|
|
|
|
export async function updateUserSettings(settings) {
|
|
const response = await fetch(API_URL, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ request_type: 'update_user_settings', settings: settings }),
|
|
});
|
|
return handleResponse(response);
|
|
}
|
|
|
|
// --- Lexikon-Funktionen ---
|
|
export async function getLexiconEntries() {
|
|
const response = await fetch(`${API_URL}?action=get_lexicon_entries`);
|
|
return handleResponse(response);
|
|
}
|
|
|
|
export async function getLexiconEntryDetails(latinName) {
|
|
const response = await fetch(`${API_URL}?action=get_lexicon_entry_details&latin_name=${encodeURIComponent(latinName)}`);
|
|
return handleResponse(response);
|
|
}
|
|
|
|
|
|
// --- Pflanzen-Funktionen ---
|
|
export async function getPlants() {
|
|
const response = await fetch(`${API_URL}?action=get_plants`);
|
|
return handleResponse(response);
|
|
}
|
|
|
|
export async function savePlant(formData) {
|
|
const response = await fetch(API_URL, { method: 'POST', body: formData });
|
|
return handleResponse(response);
|
|
}
|
|
|
|
export async function deletePlant(plantId) {
|
|
const response = await fetch(`${API_URL}?id=${plantId}`, { method: 'DELETE' });
|
|
return handleResponse(response);
|
|
}
|
|
|
|
export async function recordCareAction(plantId, action) {
|
|
const response = await fetch(API_URL, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ request_type: 'care_action', id: plantId, action }),
|
|
});
|
|
return handleResponse(response);
|
|
}
|
|
|
|
export async function setProfilePicture(plantId, photoPath) {
|
|
const response = await fetch(API_URL, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ request_type: 'set_profile_picture', plant_id: plantId, photo_path: photoPath }),
|
|
});
|
|
return handleResponse(response);
|
|
}
|
|
|
|
// --- Tagebuch-Funktionen ---
|
|
export async function getDiaryEntries(plantId) {
|
|
const response = await fetch(`${API_URL}?action=get_diary&plant_id=${plantId}`);
|
|
return handleResponse(response);
|
|
}
|
|
|
|
export async function addDiaryEntry(formData) {
|
|
formData.append('action', 'add_diary_entry');
|
|
const response = await fetch(API_URL, { method: 'POST', body: formData });
|
|
return handleResponse(response);
|
|
}
|
|
|
|
export async function deleteDiaryEntry(entryId) {
|
|
const response = await fetch(`${API_URL}?diary_entry_id=${entryId}`, { method: 'DELETE' });
|
|
return handleResponse(response);
|
|
}
|
|
|
|
// --- KI-Funktionen ---
|
|
|
|
// Schritt 1: Identifikation & Foto-Analyse
|
|
export async function performInitialAnalysis(base64Image, excludedLatinNames = []) {
|
|
let prompt = `
|
|
Du bist ein Botaniker und Foto-Analyst. Führe ZWEI Aufgaben aus und gib das Ergebnis als EINEN einzigen, sauberen JSON-String zurück. Das JSON-Objekt muss zwei Hauptschlüssel haben: "identification" und "photo_analysis".
|
|
|
|
1. **Im "identification"-Objekt:** Identifiziere die Pflanze.
|
|
- "name": Der gebräuchlichste Name, so wie er im deutschen Einzelhandel genutzt wird.
|
|
- "latin_name": Der korrekte, zweiteilige botanische Name.
|
|
- "confidence_score": Bewerte deine Sicherheit KRITISCH (0-100).
|
|
- "identification_notes": Kurze Notiz zur Identifizierung.
|
|
- REGEL: Falls dein confidence_score < 75 ist, setze alle Werte auf "Unbekannt" oder 0.
|
|
|
|
2. **Im "photo_analysis"-Objekt:** Analysiere die Fotoqualität.
|
|
- "photo_quality_score" (integer): Bewerte die Qualität KRITISCH (0-100).
|
|
- "is_high_quality" (boolean): true, wenn photo_quality_score > 70 ist.
|
|
- "rejection_reason" (string): Kurzer Grund, falls is_high_quality false ist, sonst null.
|
|
- "photo_type" (string): 'whole_plant', 'leaf_detail', 'flower_detail', oder 'other'.
|
|
- "focal_point" (object): Ein Objekt mit "x"- und "y"-Koordinaten (Prozent von 0-100) des visuellen Mittelpunkts.
|
|
- "dominant_color_hex" (string): Der dominante Farbton des Hauptmotivs als HEX-Code.
|
|
`;
|
|
if (excludedLatinNames.length > 0) {
|
|
prompt += ` Ignoriere dabei diese falschen Vorschläge: ${excludedLatinNames.join(', ')}.`;
|
|
}
|
|
const payload = {
|
|
request_type: 'ai_identification',
|
|
payload: { "contents": [{ "parts": [ { "text": prompt }, { "inline_data": { "mime_type": "image/jpeg", "data": base64Image } } ] }] }
|
|
};
|
|
const response = await fetch(API_URL, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(payload)
|
|
});
|
|
const result = await handleResponse(response);
|
|
const resultText = result.candidates[0].content.parts[0].text;
|
|
return JSON.parse(resultText.replace(/```json|```/g, '').trim());
|
|
}
|
|
|
|
// Schritt 2: Detaillierte Anreicherung
|
|
export async function aiEnrich(latinName) {
|
|
const prompt = `
|
|
Du bist ein Botanik-Experte. Gib detaillierte Pflegedaten für "${latinName}" als sauberen JSON-String zurück.
|
|
|
|
ANWEISUNGEN FÜR DEN INHALT:
|
|
- Für "description_full": Erstelle einen ausführlichen, enzyklopädischen Text. **Verwende KEINE einleitenden Grußformeln, Willkommenssätze oder persönlichen Anreden wie "Du"**. Gliedere den Text in Abschnitte mit Titeln (z.B. 'Standort & Licht:') und spreche den Leser neutral an (z.B. "Diese Pflanze benötigt..."). Die Titel dürfen KEIN Markdown enthalten.
|
|
- Für "care_tips": Erstelle genau drei kurze, prägnante Tipps aus der Ich-Perspektive der Pflanze.
|
|
- Für "tags": Gib 3-5 passende Schlagwörter als kommaseparierten String an (z.B. "Anfängerfreundlich, Haustier-sicher, Buntblatt").
|
|
|
|
Das JSON-Objekt MUSS die folgenden Schlüssel enthalten, auch wenn der Wert null ist: "plant_family", "plant_genus", "description_full", "watering_interval_days", "fertilizing_interval_days", "care_tips", "light_requirement", "water_requirement", "humidity_requirement", "care_difficulty", "growth_habit", "mature_size", "toxicity", "edibility", "temperature_preference", "flowering_period", "substrate_recommendation", "native_to", "tags".
|
|
|
|
WICHTIGE REGELN FÜR KATEGORIEN:
|
|
- Für "substrate_recommendation": Wähle einen oder MEHRERE passende Werte aus der Liste und gib sie als kommaseparierten String zurück: 'Universal-Erde', 'Sukkulenten/Kakteen-Erde', 'Anzucht-Erde', 'Kräuter-Erde', 'Saurer Boden (Moorbeet-Erde)', 'Orchideen-Substrat', 'Hydrokultur/Pon', 'Erde-frei (Aroid-Mix)'.
|
|
- Für die folgenden Felder, wähle EXAKT EINEN der angegebenen Werte in Kleinbuchstaben:
|
|
- "light_requirement": 'wenig', 'mittel', 'viel'.
|
|
- "water_requirement": 'niedrig', 'mittel', 'hoch'.
|
|
- "humidity_requirement": 'normal', 'erhöht'.
|
|
- "care_difficulty": 'einfach', 'mittel', 'schwer'.
|
|
- "growth_habit": 'kletternd', 'hängend', 'aufrecht', 'buschig', 'rosette'.
|
|
- "mature_size": 'klein', 'mittel', 'groß'.
|
|
- "toxicity": 'ungiftig', 'leicht giftig', 'giftig'.
|
|
- "edibility": 'essbar', 'nicht essbar', 'zierfrüchte'.
|
|
- "temperature_preference": 'zimmerwarm', 'kühl', 'winterhart'.
|
|
- "flowering_period": 'frühling', 'sommer', 'herbst', 'winter', 'ganzjährig', 'selten', oder null.
|
|
|
|
FORMATIERUNGSREGELN für "care_tips":
|
|
1. Jeder Tipp MUSS mit einem passenden Emoji und einem Titel beginnen (z.B. "☀️ Licht:").
|
|
2. Zwischen den Tipps MUSS sich ein doppelter Zeilenumbruch (\\n\\n) befinden.`;
|
|
|
|
const payload = {
|
|
request_type: 'ai_enrichment',
|
|
payload: { "contents": [{ "parts": [ { "text": prompt } ] }] }
|
|
};
|
|
const response = await fetch(API_URL, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(payload)
|
|
});
|
|
const result = await handleResponse(response);
|
|
const resultText = result.candidates[0].content.parts[0].text;
|
|
return JSON.parse(resultText.replace(/```json|```/g, '').trim());
|
|
}
|
|
|