206 lines
7.5 KiB
Python
206 lines
7.5 KiB
Python
|
|
#!/usr/bin/env python3
|
||
|
|
"""
|
||
|
|
quiXzoom Academy — Direkt Gemini Integration
|
||
|
|
Använder Gemini API direkt (via AAMOS nyckel) för att generera bildbeskrivningar
|
||
|
|
"""
|
||
|
|
|
||
|
|
import requests
|
||
|
|
import json
|
||
|
|
import os
|
||
|
|
from datetime import datetime
|
||
|
|
from pathlib import Path
|
||
|
|
|
||
|
|
# Gemini API-konfiguration (från AAMOS)
|
||
|
|
GEMINI_API_KEY = "AIzaSyANo7g3-EZTuH_VAw1ZRzW7n-A39ewIhqo"
|
||
|
|
GEMINI_ENDPOINT = "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash-exp:generateContent"
|
||
|
|
|
||
|
|
# Lektionsbilder som ska genereras
|
||
|
|
LESSON_IMAGES = {
|
||
|
|
"m1l1": {
|
||
|
|
"title": "Välkommen till quiXzoom",
|
||
|
|
"prompt": "Beskriv en pedagogisk illustration: En glad person med smartphone fotograferar en bro i solnedgång. Modern, ren illustration med blå och gröna färger. 16:9 format."
|
||
|
|
},
|
||
|
|
"m2l1": {
|
||
|
|
"title": "AI-granskning av bilder",
|
||
|
|
"prompt": "Beskriv en teknisk illustration: En bild delad i två delar. Vänster: Originalbild på en bro. Höger: Samma bild med AI-analys-overlay som markerar skärpa, ljus, komposition med färgkodade indikatorer. Futuristisk men pedagogisk stil."
|
||
|
|
},
|
||
|
|
"m2l2": {
|
||
|
|
"title": "Ljus och exponering",
|
||
|
|
"prompt": "Beskriv en pedagogisk jämförelse: Tre bilder i rad som visar samma bro. 1. Underexponerad (för mörk), 2. Perfekt exponerad (gyllene timmen), 3. Överexponerad (för ljus). Fotorealistisk stil."
|
||
|
|
},
|
||
|
|
"m2l3": {
|
||
|
|
"title": "Fokus och skärpa",
|
||
|
|
"prompt": "Beskriv en pedagogisk bild: Två bilder som visar en vägskylt. Vänster: Suddig, oskarp. Höger: Knivskarp med förstoringsglas-effekt som visar detaljer. Fotorealistisk med grafiska element."
|
||
|
|
},
|
||
|
|
"m2l4": {
|
||
|
|
"title": "Framing och komposition",
|
||
|
|
"prompt": "Beskriv en pedagogisk diagram-bild: En bro fotograferad med korrekt komposition. Overlay med rule of thirds grid, pil som pekar på huvudobjektet, grön markering för bra komposition."
|
||
|
|
},
|
||
|
|
"m3l1": {
|
||
|
|
"title": "GPS-accuracy",
|
||
|
|
"prompt": "Beskriv en teknisk illustration: En karta över en stad med en smartphone i mitten. 4 GPS-satelliter ovanför skickar signaler till telefonen. Färgkodade accuracy-cirklar: grön (noggrann), gul (medel), röd (låg)."
|
||
|
|
},
|
||
|
|
"m4l1": {
|
||
|
|
"title": "Personlig säkerhet",
|
||
|
|
"prompt": "Beskriv en illustration: En person med reflexväst och smartphone fotograferar en bro från säkert avstånd. Trafikkegel, varselväst, säkerhetsavstånd markerat. Modern, vänlig stil."
|
||
|
|
},
|
||
|
|
"m5l1": {
|
||
|
|
"title": "Batch-fotografering",
|
||
|
|
"prompt": "Beskriv en karta-baserad illustration: En stiliserad karta med fotopunkter (A, B, C, D, E) kopplade med en optimerad grön rutt. En person med smartphone följer rutten effektivt."
|
||
|
|
},
|
||
|
|
"m6l1": {
|
||
|
|
"title": "Utbetalningsflöde",
|
||
|
|
"prompt": "Beskriv ett flödesdiagram: Horisontellt flöde som visar 1. Godkänd fotografi → 2. AI-granskning → 3. Godkännande → 4. Utbetalning → 5. Pengar på kontot. Modern, ren illustration med ikoner."
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
def generate_with_gemini(prompt: str) -> str:
|
||
|
|
"""Generera text med Gemini API"""
|
||
|
|
|
||
|
|
url = f"{GEMINI_ENDPOINT}?key={GEMINI_API_KEY}"
|
||
|
|
|
||
|
|
payload = {
|
||
|
|
"contents": [{
|
||
|
|
"parts": [{
|
||
|
|
"text": prompt
|
||
|
|
}]
|
||
|
|
}],
|
||
|
|
"generationConfig": {
|
||
|
|
"temperature": 0.7,
|
||
|
|
"maxOutputTokens": 1000
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
try:
|
||
|
|
response = requests.post(url, json=payload, timeout=30)
|
||
|
|
|
||
|
|
if response.status_code == 200:
|
||
|
|
data = response.json()
|
||
|
|
|
||
|
|
# Extrahera text från response
|
||
|
|
if 'candidates' in data and len(data['candidates']) > 0:
|
||
|
|
candidate = data['candidates'][0]
|
||
|
|
if 'content' in candidate and 'parts' in candidate['content']:
|
||
|
|
parts = candidate['content']['parts']
|
||
|
|
if len(parts) > 0 and 'text' in parts[0]:
|
||
|
|
return parts[0]['text']
|
||
|
|
|
||
|
|
return None
|
||
|
|
else:
|
||
|
|
print(f" ⚠️ Gemini API fel: {response.status_code}")
|
||
|
|
print(f" {response.text[:200]}")
|
||
|
|
return None
|
||
|
|
|
||
|
|
except Exception as e:
|
||
|
|
print(f" ⚠️ Fel: {e}")
|
||
|
|
return None
|
||
|
|
|
||
|
|
def generate_image_description(lesson_id: str) -> dict:
|
||
|
|
"""Generera bildbeskrivning för en lektion"""
|
||
|
|
|
||
|
|
lesson = LESSON_IMAGES.get(lesson_id)
|
||
|
|
if not lesson:
|
||
|
|
return None
|
||
|
|
|
||
|
|
print(f"🎨 {lesson['title']}")
|
||
|
|
|
||
|
|
# Generera detaljerad bildbeskrivning
|
||
|
|
prompt = f"""Du är en expert på pedagogiska illustrationer för online-utbildningar.
|
||
|
|
|
||
|
|
Beskriv i detalj en illustration för följande lektion:
|
||
|
|
Titel: {lesson['title']}
|
||
|
|
Beskrivning: {lesson['prompt']}
|
||
|
|
|
||
|
|
Ge en detaljerad beskrivning som kan användas för att:
|
||
|
|
1. Generera en bild med AI
|
||
|
|
2. Förstå vad som ska visas i lektionen
|
||
|
|
|
||
|
|
Beskrivningen ska vara på svenska och inkludera:
|
||
|
|
- Huvudobjekt och deras placering
|
||
|
|
- Färger och stil
|
||
|
|
- Viktiga detaljer som måste synas
|
||
|
|
- Pedagogiska element"""
|
||
|
|
|
||
|
|
description = generate_with_gemini(prompt)
|
||
|
|
|
||
|
|
if description:
|
||
|
|
return {
|
||
|
|
"lesson_id": lesson_id,
|
||
|
|
"title": lesson['title'],
|
||
|
|
"description": description,
|
||
|
|
"generated_at": datetime.utcnow().isoformat(),
|
||
|
|
"status": "ready"
|
||
|
|
}
|
||
|
|
else:
|
||
|
|
return None
|
||
|
|
|
||
|
|
def generate_all_images():
|
||
|
|
"""Generera alla bildbeskrivningar"""
|
||
|
|
|
||
|
|
print("🚀 quiXzoom Academy — Gemini Bildgenerering")
|
||
|
|
print("=" * 60)
|
||
|
|
print(f"Använder Gemini API med nyckel: {GEMINI_API_KEY[:15]}...")
|
||
|
|
print("")
|
||
|
|
|
||
|
|
results = []
|
||
|
|
output_dir = Path("academy-generated")
|
||
|
|
output_dir.mkdir(exist_ok=True)
|
||
|
|
|
||
|
|
for lesson_id, lesson in LESSON_IMAGES.items():
|
||
|
|
result = generate_image_description(lesson_id)
|
||
|
|
|
||
|
|
if result:
|
||
|
|
results.append(result)
|
||
|
|
|
||
|
|
# Spara till fil
|
||
|
|
with open(output_dir / f"{lesson_id}.json", "w", encoding="utf-8") as f:
|
||
|
|
json.dump(result, f, indent=2, ensure_ascii=False)
|
||
|
|
|
||
|
|
print(f" ✅ Sparad till academy-generated/{lesson_id}.json")
|
||
|
|
else:
|
||
|
|
print(f" ❌ Kunde inte generera")
|
||
|
|
|
||
|
|
print("")
|
||
|
|
|
||
|
|
# Spara sammanfattning
|
||
|
|
summary = {
|
||
|
|
"generated_at": datetime.utcnow().isoformat(),
|
||
|
|
"total_images": len(LESSON_IMAGES),
|
||
|
|
"successful": len(results),
|
||
|
|
"failed": len(LESSON_IMAGES) - len(results),
|
||
|
|
"images": results
|
||
|
|
}
|
||
|
|
|
||
|
|
with open(output_dir / "summary.json", "w", encoding="utf-8") as f:
|
||
|
|
json.dump(summary, f, indent=2, ensure_ascii=False)
|
||
|
|
|
||
|
|
print("=" * 60)
|
||
|
|
print(f"✅ {len(results)}/{len(LESSON_IMAGES)} bildbeskrivningar genererade")
|
||
|
|
print(f"📄 Sparade i academy-generated/")
|
||
|
|
|
||
|
|
return summary
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
import sys
|
||
|
|
|
||
|
|
if len(sys.argv) > 1 and sys.argv[1] == "--generate":
|
||
|
|
generate_all_images()
|
||
|
|
else:
|
||
|
|
print("Användning:")
|
||
|
|
print(" python academy-gemini-direct.py --generate")
|
||
|
|
print("")
|
||
|
|
print("Detta kommer att:")
|
||
|
|
print(" 1. Ansluta till Gemini API")
|
||
|
|
print(" 2. Generera bildbeskrivningar för alla lektioner")
|
||
|
|
print(" 3. Spara resultat i academy-generated/")
|
||
|
|
print("")
|
||
|
|
print("Testar anslutning...")
|
||
|
|
|
||
|
|
# Testa anslutning
|
||
|
|
test = generate_with_gemini("Svara bara: Gemini fungerar!")
|
||
|
|
if test:
|
||
|
|
print(f"✅ Gemini är tillgänglig!")
|
||
|
|
print(f" Svar: {test[:100]}")
|
||
|
|
else:
|
||
|
|
print("❌ Kunde inte ansluta till Gemini")
|