first commit
|
|
@ -0,0 +1,305 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
AC Control System untuk AC Panasonic
|
||||
Mengontrol AC berdasarkan deteksi kehadiran (AUTO) atau manual via MQTT
|
||||
"""
|
||||
|
||||
import paho.mqtt.client as mqtt
|
||||
from paho.mqtt import client as mqtt_client
|
||||
import subprocess
|
||||
import os
|
||||
import json
|
||||
import time
|
||||
import signal
|
||||
import sys
|
||||
import threading
|
||||
from datetime import datetime
|
||||
|
||||
# ====================== KONFIGURASI ======================
|
||||
MQTT_BROKER = "localhost"
|
||||
MQTT_PORT = 1883
|
||||
CONTROL_TOPIC = "classroom/ac/control"
|
||||
STATUS_TOPIC = "classroom/ac/status"
|
||||
PRESENCE_TOPIC = "classroom/presence"
|
||||
|
||||
# GPIO untuk IR
|
||||
TRANSMITTER_GPIO = 18
|
||||
|
||||
# File konfigurasi
|
||||
IRRP_SCRIPT = os.path.join(os.path.dirname(os.path.abspath(__file__)), "irrp.py")
|
||||
JSON_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "ac_codes.json")
|
||||
LOG_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "ac_control.log")
|
||||
|
||||
# State variables
|
||||
AUTO_MODE = True
|
||||
CURRENT_AC_STATE = "off"
|
||||
|
||||
# Timer Delay
|
||||
DELAY_TIMER_MINUTES = 5
|
||||
DELAY_TIMER_ACTIVE = False
|
||||
DELAY_TIMER_THREAD = None
|
||||
|
||||
# ====================== LOGGING ======================
|
||||
def log_message(message, level="INFO"):
|
||||
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
log_entry = f"[{timestamp}] [{level}] {message}"
|
||||
print(log_entry)
|
||||
try:
|
||||
with open(LOG_FILE, "a") as f:
|
||||
f.write(log_entry + "\n")
|
||||
except:
|
||||
pass
|
||||
|
||||
# ====================== FUNGSI KIRIM STATUS ======================
|
||||
def publish_status(client, reason=""):
|
||||
"""Kirim status AC ke MQTT"""
|
||||
status_message = {
|
||||
"ac_state": CURRENT_AC_STATE,
|
||||
"auto_mode": AUTO_MODE,
|
||||
"delay_active": DELAY_TIMER_ACTIVE,
|
||||
"delay_remaining": DELAY_TIMER_MINUTES * 60 if DELAY_TIMER_ACTIVE else 0,
|
||||
"reason": reason,
|
||||
"timestamp": datetime.now().isoformat()
|
||||
}
|
||||
client.publish(STATUS_TOPIC, json.dumps(status_message))
|
||||
log_message(f"📤 Status dikirim: {CURRENT_AC_STATE} (reason: {reason})")
|
||||
|
||||
# ====================== IR CONTROL ======================
|
||||
def send_ir(key):
|
||||
log_message(f"Mengirim IR: {key}")
|
||||
|
||||
if not os.path.exists(JSON_FILE):
|
||||
log_message(f"File {JSON_FILE} tidak ditemukan!", "ERROR")
|
||||
return False
|
||||
|
||||
try:
|
||||
with open(JSON_FILE, 'r') as f:
|
||||
codes = json.load(f)
|
||||
if key not in codes:
|
||||
log_message(f"Key '{key}' tidak ditemukan di JSON", "ERROR")
|
||||
return False
|
||||
except Exception as e:
|
||||
log_message(f"Gagal baca JSON: {e}", "ERROR")
|
||||
return False
|
||||
|
||||
cmd = ["python3", IRRP_SCRIPT, "-p", "-g", str(TRANSMITTER_GPIO), "-f", JSON_FILE, key]
|
||||
|
||||
try:
|
||||
subprocess.run(cmd, check=True, capture_output=True, text=True, timeout=10)
|
||||
log_message(f"IR berhasil dikirim: {key}")
|
||||
return True
|
||||
except Exception as e:
|
||||
log_message(f"Gagal kirim IR: {e}", "ERROR")
|
||||
return False
|
||||
|
||||
def set_ac_state(state, client=None, reason=""):
|
||||
global CURRENT_AC_STATE
|
||||
|
||||
if state == "on" and CURRENT_AC_STATE == "off":
|
||||
log_message("Menyalakan AC...")
|
||||
if send_ir("POWER_ON"):
|
||||
CURRENT_AC_STATE = "on"
|
||||
if client:
|
||||
publish_status(client, reason or "power_on")
|
||||
return True
|
||||
return False
|
||||
|
||||
elif state == "off" and CURRENT_AC_STATE == "on":
|
||||
log_message("Mematikan AC...")
|
||||
if send_ir("POWER_OFF"):
|
||||
CURRENT_AC_STATE = "off"
|
||||
if client:
|
||||
publish_status(client, reason or "power_off")
|
||||
return True
|
||||
return False
|
||||
|
||||
return False
|
||||
|
||||
# ====================== DELAY TIMER ======================
|
||||
def start_delay_timer(client):
|
||||
global DELAY_TIMER_ACTIVE, DELAY_TIMER_THREAD
|
||||
|
||||
if DELAY_TIMER_ACTIVE:
|
||||
log_message("Delay timer sudah aktif")
|
||||
return
|
||||
|
||||
DELAY_TIMER_ACTIVE = True
|
||||
log_message(f"⏳ Delay timer dimulai: {DELAY_TIMER_MINUTES} menit")
|
||||
|
||||
# Kirim status timer aktif
|
||||
publish_status(client, "delay_started")
|
||||
|
||||
def timer_worker():
|
||||
global DELAY_TIMER_ACTIVE, CURRENT_AC_STATE
|
||||
total_seconds = DELAY_TIMER_MINUTES * 60
|
||||
|
||||
for remaining in range(total_seconds, -1, -1):
|
||||
if not DELAY_TIMER_ACTIVE:
|
||||
log_message("Delay timer dibatalkan")
|
||||
return
|
||||
|
||||
if remaining > 0:
|
||||
# Update setiap 10 detik
|
||||
if remaining % 10 == 0 or remaining <= 5:
|
||||
status_msg = {
|
||||
"ac_state": CURRENT_AC_STATE,
|
||||
"auto_mode": AUTO_MODE,
|
||||
"delay_active": True,
|
||||
"delay_remaining": remaining
|
||||
}
|
||||
client.publish(STATUS_TOPIC, json.dumps(status_msg))
|
||||
|
||||
minutes = remaining // 60
|
||||
seconds = remaining % 60
|
||||
log_message(f"Sisa waktu: {minutes:02d}:{seconds:02d}")
|
||||
|
||||
time.sleep(1)
|
||||
else:
|
||||
# Timer habis
|
||||
if DELAY_TIMER_ACTIVE and CURRENT_AC_STATE == "on":
|
||||
log_message("⏰ TIMER HABIS - Mematikan AC")
|
||||
if send_ir("POWER_OFF"):
|
||||
CURRENT_AC_STATE = "off"
|
||||
publish_status(client, "delay_timer_expired")
|
||||
DELAY_TIMER_ACTIVE = False
|
||||
return
|
||||
|
||||
DELAY_TIMER_THREAD = threading.Thread(target=timer_worker, daemon=True)
|
||||
DELAY_TIMER_THREAD.start()
|
||||
|
||||
def cancel_delay_timer(client):
|
||||
global DELAY_TIMER_ACTIVE
|
||||
if DELAY_TIMER_ACTIVE:
|
||||
DELAY_TIMER_ACTIVE = False
|
||||
log_message("Delay timer dibatalkan")
|
||||
publish_status(client, "delay_cancelled")
|
||||
|
||||
# ====================== MQTT HANDLERS ======================
|
||||
def on_connect(client, userdata, flags, reason_code, properties):
|
||||
if reason_code == 0:
|
||||
log_message("✅ MQTT terhubung ke broker")
|
||||
client.subscribe([(CONTROL_TOPIC, 0), (PRESENCE_TOPIC, 0)])
|
||||
log_message(f"Subscribed ke: {CONTROL_TOPIC} & {PRESENCE_TOPIC}")
|
||||
|
||||
# Kirim status awal
|
||||
publish_status(client, "initial_connection")
|
||||
else:
|
||||
log_message(f"❌ MQTT connect gagal: {reason_code}", "ERROR")
|
||||
|
||||
def on_message(client, userdata, msg):
|
||||
global AUTO_MODE, CURRENT_AC_STATE, DELAY_TIMER_MINUTES
|
||||
|
||||
topic = msg.topic
|
||||
payload = msg.payload.decode().strip()
|
||||
|
||||
log_message(f"MQTT → {topic} : {payload}")
|
||||
|
||||
# ==================== PRESENCE (AUTO MODE) ====================
|
||||
if topic == PRESENCE_TOPIC:
|
||||
if not AUTO_MODE:
|
||||
log_message("Mode MANUAL aktif → ignore presence")
|
||||
return
|
||||
|
||||
presence = payload.lower()
|
||||
|
||||
if presence == "ada":
|
||||
log_message("👥 Ada orang terdeteksi (AUTO)")
|
||||
cancel_delay_timer(client)
|
||||
|
||||
if CURRENT_AC_STATE == "off":
|
||||
log_message("🔛 Menyalakan AC otomatis...")
|
||||
set_ac_state("on", client, "presence_detected")
|
||||
|
||||
elif presence == "tidak ada":
|
||||
log_message("🚪 Tidak ada orang (AUTO)")
|
||||
if CURRENT_AC_STATE == "on" and not DELAY_TIMER_ACTIVE:
|
||||
log_message(f"⏳ Memulai delay timer {DELAY_TIMER_MINUTES} menit...")
|
||||
start_delay_timer(client)
|
||||
|
||||
# ==================== CONTROL (DARI WEB) ====================
|
||||
elif topic == CONTROL_TOPIC:
|
||||
payload_lower = payload.lower()
|
||||
|
||||
if payload_lower == "auto_on":
|
||||
AUTO_MODE = True
|
||||
cancel_delay_timer(client)
|
||||
log_message("🤖 Mode AUTO diaktifkan")
|
||||
publish_status(client, "auto_mode_on")
|
||||
|
||||
elif payload_lower == "auto_off":
|
||||
AUTO_MODE = False
|
||||
cancel_delay_timer(client)
|
||||
log_message("👤 Mode MANUAL diaktifkan")
|
||||
publish_status(client, "manual_mode_on")
|
||||
|
||||
elif payload_lower.startswith("delay_"):
|
||||
try:
|
||||
minutes = int(payload_lower.split("_")[1])
|
||||
if 1 <= minutes <= 60:
|
||||
DELAY_TIMER_MINUTES = minutes
|
||||
log_message(f"⚙️ Delay timer diubah menjadi {minutes} menit")
|
||||
publish_status(client, "delay_changed")
|
||||
except:
|
||||
pass
|
||||
|
||||
elif payload_lower == "on":
|
||||
log_message("👤 Manual: Menyalakan AC")
|
||||
cancel_delay_timer(client)
|
||||
set_ac_state("on", client, "manual_on")
|
||||
|
||||
elif payload_lower == "off":
|
||||
log_message("👤 Manual: Mematikan AC")
|
||||
cancel_delay_timer(client)
|
||||
set_ac_state("off", client, "manual_off")
|
||||
|
||||
elif payload_lower == "status":
|
||||
publish_status(client, "manual_request")
|
||||
|
||||
# ====================== SIGNAL HANDLER ======================
|
||||
def signal_handler(sig, frame):
|
||||
log_message("Menerima sinyal shutdown...")
|
||||
sys.exit(0)
|
||||
|
||||
# ====================== MAIN ======================
|
||||
if __name__ == "__main__":
|
||||
signal.signal(signal.SIGINT, signal_handler)
|
||||
signal.signal(signal.SIGTERM, signal_handler)
|
||||
|
||||
log_message("=" * 70)
|
||||
log_message("🚀 AC CONTROL SYSTEM - PANASONIC")
|
||||
log_message("=" * 70)
|
||||
|
||||
# Tampilkan kode IR yang tersedia
|
||||
if os.path.exists(JSON_FILE):
|
||||
try:
|
||||
with open(JSON_FILE, 'r') as f:
|
||||
codes = json.load(f)
|
||||
log_message(f"Kode IR tersedia: {list(codes.keys())}")
|
||||
except Exception as e:
|
||||
log_message(f"Gagal baca ac_codes.json: {e}", "ERROR")
|
||||
else:
|
||||
log_message(f"⚠️ File {JSON_FILE} tidak ditemukan!", "WARNING")
|
||||
log_message(" Jalankan record terlebih dahulu: python3 rcd.py POWER_ON")
|
||||
|
||||
# Status awal
|
||||
log_message(f"Mode awal : {'AUTO' if AUTO_MODE else 'MANUAL'}")
|
||||
log_message(f"AC : {CURRENT_AC_STATE}")
|
||||
log_message(f"Delay Timer : {DELAY_TIMER_MINUTES} menit")
|
||||
log_message(f"Log file : {LOG_FILE}")
|
||||
|
||||
# MQTT Client
|
||||
try:
|
||||
client = mqtt.Client(mqtt_client.CallbackAPIVersion.VERSION2)
|
||||
client.on_connect = on_connect
|
||||
client.on_message = on_message
|
||||
client.connect(MQTT_BROKER, MQTT_PORT, 60)
|
||||
|
||||
log_message("\n✅ Sistem siap. Menunggu perintah MQTT...")
|
||||
log_message(f" Subscribe ke: {CONTROL_TOPIC} & {PRESENCE_TOPIC}")
|
||||
log_message(" Tekan Ctrl+C untuk berhenti\n")
|
||||
|
||||
client.loop_forever()
|
||||
|
||||
except Exception as e:
|
||||
log_message(f"❌ Error MQTT: {e}", "ERROR")
|
||||
sys.exit(1)
|
||||
|
|
@ -0,0 +1,238 @@
|
|||
#!/usr/bin/env python3
|
||||
import cv2
|
||||
import numpy as np
|
||||
import paho.mqtt.client as mqtt
|
||||
from paho.mqtt import client as mqtt_client
|
||||
import time
|
||||
import base64
|
||||
import threading
|
||||
import json
|
||||
import subprocess
|
||||
import os
|
||||
from datetime import datetime
|
||||
|
||||
# ====================== KONFIGURASI ======================
|
||||
MQTT_BROKER = "localhost"
|
||||
MQTT_PORT = 1883
|
||||
PRESENCE_TOPIC = "classroom/presence"
|
||||
CONTROL_TOPIC = "classroom/ac/control"
|
||||
WEBCAM_TOPIC = "classroom/webcam"
|
||||
STATUS_TOPIC = "classroom/ac/status"
|
||||
|
||||
WEIGHTS = "yolov4-tiny.weights"
|
||||
CFG = "yolov4-tiny.cfg"
|
||||
NAMES = "coco.names"
|
||||
|
||||
CONFIDENCE_THRESHOLD = 0.5
|
||||
INPUT_SIZE = (256, 256)
|
||||
|
||||
WEBCAM_INTERVAL = 2
|
||||
WEBCAM_QUALITY = 60
|
||||
WEBCAM_WIDTH = 640
|
||||
WEBCAM_HEIGHT = 480
|
||||
|
||||
# ========================================================
|
||||
|
||||
print("=" * 60)
|
||||
print("🚀 AC CONTROL SYSTEM - DENGAN WEBCAM MONITORING")
|
||||
print("=" * 60)
|
||||
|
||||
# ====================== CLEANUP WEBCAM ======================
|
||||
print("Membersihkan proses yang menggunakan webcam...")
|
||||
try:
|
||||
# Kill semua proses yang menggunakan webcam
|
||||
subprocess.run(["sudo", "fuser", "-k", "/dev/video0"], capture_output=True)
|
||||
time.sleep(1)
|
||||
except:
|
||||
pass
|
||||
|
||||
# ====================== INISIALISASI WEBCAM ======================
|
||||
print("Membuka webcam...")
|
||||
cap = None
|
||||
|
||||
# Coba buka webcam dengan berbagai metode
|
||||
for i in range(3): # Coba 3 kali
|
||||
cap = cv2.VideoCapture(0, cv2.CAP_V4L2)
|
||||
if cap.isOpened():
|
||||
print(f"✅ Webcam terbuka pada percobaan ke-{i+1}")
|
||||
break
|
||||
time.sleep(1)
|
||||
|
||||
if cap is None or not cap.isOpened():
|
||||
print("❌ Gagal membuka webcam!")
|
||||
print("\nTroubleshooting:")
|
||||
print("1. Cek koneksi USB webcam")
|
||||
print("2. Jalankan: sudo fuser -k /dev/video0")
|
||||
print("3. Restart: sudo reboot")
|
||||
exit(1)
|
||||
|
||||
# Set properti webcam
|
||||
cap.set(cv2.CAP_PROP_FRAME_WIDTH, WEBCAM_WIDTH)
|
||||
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, WEBCAM_HEIGHT)
|
||||
cap.set(cv2.CAP_PROP_FPS, 30)
|
||||
|
||||
# Test baca frame
|
||||
ret, test_frame = cap.read()
|
||||
if not ret:
|
||||
print("❌ Webcam terbuka tapi gagal membaca frame!")
|
||||
cap.release()
|
||||
exit(1)
|
||||
|
||||
print(f"✅ Webcam siap: {int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))}x{int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))}")
|
||||
|
||||
# ====================== LOAD MODEL YOLO ======================
|
||||
print("Memuat model YOLOv4-tiny...")
|
||||
for file in [WEIGHTS, CFG, NAMES]:
|
||||
if not os.path.exists(file):
|
||||
print(f"❌ File {file} tidak ditemukan!")
|
||||
cap.release()
|
||||
exit(1)
|
||||
|
||||
net = cv2.dnn.readNet(WEIGHTS, CFG)
|
||||
net.setPreferableBackend(cv2.dnn.DNN_BACKEND_OPENCV)
|
||||
net.setPreferableTarget(cv2.dnn.DNN_TARGET_CPU)
|
||||
|
||||
with open(NAMES, "r") as f:
|
||||
classes = [line.strip() for line in f.readlines()]
|
||||
|
||||
layer_names = net.getLayerNames()
|
||||
output_layers = [layer_names[i - 1] for i in net.getUnconnectedOutLayers()]
|
||||
|
||||
print("✅ Model YOLO berhasil dimuat")
|
||||
|
||||
# ====================== MQTT SETUP ======================
|
||||
def on_connect(client, userdata, flags, reason_code, properties):
|
||||
if reason_code == 0:
|
||||
print("✅ MQTT terhubung ke broker!")
|
||||
client.subscribe("classroom/webcam/request")
|
||||
else:
|
||||
print(f"❌ MQTT gagal connect: {reason_code}")
|
||||
|
||||
def on_message(client, userdata, msg):
|
||||
if msg.topic == "classroom/webcam/request":
|
||||
print("📸 Request frame webcam diterima")
|
||||
send_webcam_frame()
|
||||
|
||||
client = mqtt.Client(mqtt_client.CallbackAPIVersion.VERSION2)
|
||||
client.on_connect = on_connect
|
||||
client.on_message = on_message
|
||||
|
||||
try:
|
||||
client.connect(MQTT_BROKER, MQTT_PORT, 60)
|
||||
client.loop_start()
|
||||
print("✅ MQTT client siap")
|
||||
except Exception as e:
|
||||
print(f"❌ Gagal konek MQTT: {e}")
|
||||
cap.release()
|
||||
exit(1)
|
||||
|
||||
# ====================== FUNGSI CAPTURE ======================
|
||||
def get_frame():
|
||||
"""Ambil frame dari webcam"""
|
||||
if cap is None or not cap.isOpened():
|
||||
return False, None
|
||||
return cap.read()
|
||||
|
||||
def send_webcam_frame():
|
||||
"""Mengirim frame webcam via MQTT"""
|
||||
try:
|
||||
ret, frame = get_frame()
|
||||
if ret and frame is not None:
|
||||
frame = cv2.resize(frame, (WEBCAM_WIDTH, WEBCAM_HEIGHT))
|
||||
_, buffer = cv2.imencode('.jpg', frame, [cv2.IMWRITE_JPEG_QUALITY, WEBCAM_QUALITY])
|
||||
frame_base64 = base64.b64encode(buffer).decode('utf-8')
|
||||
client.publish(WEBCAM_TOPIC, frame_base64)
|
||||
print(f"📸 Frame terkirim ({len(frame_base64) // 1024} KB)")
|
||||
return True
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"❌ Error: {e}")
|
||||
return False
|
||||
|
||||
def send_webcam_periodic():
|
||||
while True:
|
||||
send_webcam_frame()
|
||||
time.sleep(WEBCAM_INTERVAL)
|
||||
|
||||
# ====================== DETEKSI KEHADIRAN ======================
|
||||
def detect_person(frame):
|
||||
"""Deteksi orang dalam frame"""
|
||||
blob = cv2.dnn.blobFromImage(frame, 0.00392, INPUT_SIZE, (0, 0, 0), True, crop=False)
|
||||
net.setInput(blob)
|
||||
outs = net.forward(output_layers)
|
||||
|
||||
for out in outs:
|
||||
for detection in out:
|
||||
scores = detection[5:]
|
||||
class_id = np.argmax(scores)
|
||||
confidence = scores[class_id]
|
||||
if confidence > CONFIDENCE_THRESHOLD and classes[class_id] == "person":
|
||||
return True
|
||||
return False
|
||||
|
||||
# ====================== MAIN LOOP ======================
|
||||
def main():
|
||||
# Start thread webcam
|
||||
webcam_thread = threading.Thread(target=send_webcam_periodic, daemon=True)
|
||||
webcam_thread.start()
|
||||
print("✅ Thread webcam dimulai")
|
||||
|
||||
last_presence = None
|
||||
frame_count = 0
|
||||
detection_skip = 2
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("🚀 Sistem deteksi kehadiran berjalan...")
|
||||
print(f" - Webcam streaming: setiap {WEBCAM_INTERVAL} detik")
|
||||
print(f" - Deteksi kehadiran: setiap {detection_skip} frame")
|
||||
print(" - Tekan Ctrl+C untuk berhenti")
|
||||
print("=" * 60 + "\n")
|
||||
|
||||
try:
|
||||
while True:
|
||||
ret, frame = get_frame()
|
||||
if not ret or frame is None:
|
||||
print("⚠️ Gagal membaca frame, mencoba reset...")
|
||||
time.sleep(1)
|
||||
continue
|
||||
|
||||
frame_count += 1
|
||||
|
||||
if frame_count % detection_skip == 0:
|
||||
person_detected = detect_person(frame)
|
||||
current_presence = "ada" if person_detected else "tidak ada"
|
||||
|
||||
client.publish(PRESENCE_TOPIC, current_presence)
|
||||
|
||||
if current_presence != last_presence:
|
||||
command = "on" if person_detected else "off"
|
||||
client.publish(CONTROL_TOPIC, command)
|
||||
|
||||
status_msg = {
|
||||
"ac_state": command,
|
||||
"temperature": 24,
|
||||
"mode": "cool",
|
||||
"presence": current_presence,
|
||||
"timestamp": datetime.now().isoformat()
|
||||
}
|
||||
client.publish(STATUS_TOPIC, json.dumps(status_msg))
|
||||
|
||||
print(f"👥 {current_presence} → AC {command.upper()}")
|
||||
last_presence = current_presence
|
||||
|
||||
status_indicator = "👥 ADA" if person_detected else "🚪 KOSONG"
|
||||
print(f"[{datetime.now().strftime('%H:%M:%S')}] {status_indicator} | Frame: {frame_count}")
|
||||
|
||||
time.sleep(0.05)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("\n\n🛑 Berhenti...")
|
||||
|
||||
finally:
|
||||
if cap:
|
||||
cap.release()
|
||||
client.disconnect()
|
||||
print("✅ Selesai")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
---
|
||||
name: developing-genkit-dart
|
||||
description: Generates code and provides documentation for the Genkit Dart SDK. Use when the user asks to build AI agents in Dart, use Genkit flows, or integrate LLMs into Dart/Flutter applications.
|
||||
metadata:
|
||||
genkit-managed: true
|
||||
---
|
||||
|
||||
# Genkit Dart
|
||||
|
||||
Genkit Dart is an AI SDK for Dart that provides a unified interface for code generation, structured outputs, tools, flows, and AI agents.
|
||||
|
||||
## Core Features and Usage
|
||||
If you need help with initializing Genkit (`Genkit()`), Generation (`ai.generate`), Tooling (`ai.defineTool`), Flows (`ai.defineFlow`), Embeddings (`ai.embedMany`), streaming, or calling remote flow endpoints, please load the core framework reference:
|
||||
[references/genkit.md](references/genkit.md)
|
||||
|
||||
## Genkit CLI (recommended)
|
||||
|
||||
The Genkit CLI provides a local development UI for running Flow, tracing executions, playing with models, and evaluating outputs.
|
||||
|
||||
check if the user has it installed: `genkit --version`
|
||||
|
||||
**Installation:**
|
||||
```bash
|
||||
curl -sL cli.genkit.dev | bash # Native CLI
|
||||
# OR
|
||||
npm install -g genkit-cli # Via npm
|
||||
```
|
||||
|
||||
**Usage:**
|
||||
Wrap your run command with `genkit start` to attach the Genkit developer UI and tracing:
|
||||
```bash
|
||||
genkit start -- dart run main.dart
|
||||
```
|
||||
|
||||
## Plugin Ecosystem
|
||||
Genkit relies on a large suite of plugins to perform generative AI actions, interface with external LLMs, or host web servers.
|
||||
|
||||
When asked to use any given plugin, always verify usage by referring to its corresponding reference below. You should load the reference when you need to know the specific initialization arguments, tools, models, and usage patterns for the plugin:
|
||||
|
||||
| Plugin Name | Reference Link | Description |
|
||||
| ---- | ---- | ---- |
|
||||
| `genkit_google_genai` | [references/genkit_google_genai.md](references/genkit_google_genai.md) | Load for Google Gemini plugin interface usage. |
|
||||
| `genkit_anthropic` | [references/genkit_anthropic.md](references/genkit_anthropic.md) | Load for Anthropic plugin interface for Claude models. |
|
||||
| `genkit_openai` | [references/genkit_openai.md](references/genkit_openai.md) | Load for OpenAI plugin interface for GPT models, Groq, and custom compatible endpoints. |
|
||||
| `genkit_middleware` | [references/genkit_middleware.md](references/genkit_middleware.md) | Load for Tooling for specific agentic behavior: `filesystem`, `skills`, and `toolApproval` interrupts. |
|
||||
| `genkit_mcp` | [references/genkit_mcp.md](references/genkit_mcp.md) | Load for Model Context Protocol integration (Server, Host, and Client capabilities). |
|
||||
| `genkit_chrome` | [references/genkit_chrome.md](references/genkit_chrome.md) | Load for Running Gemini Nano locally inside the Chrome browser using the Prompt API. |
|
||||
| `genkit_shelf` | [references/genkit_shelf.md](references/genkit_shelf.md) | Load for Integrating Genkit Flow actions over HTTP using Dart Shelf. |
|
||||
| `genkit_firebase_ai` | [references/genkit_firebase_ai.md](references/genkit_firebase_ai.md) | Load for Firebase AI plugin interface (Gemini API via Vertex AI). |
|
||||
|
||||
## External Dependencies
|
||||
Whenever you define schemas mapping inside of Tools, Flows, and Prompts, you must use the [schemantic](https://pub.dev/packages/schemantic) library.
|
||||
To learn how to use schemantic, ensure you read [references/schemantic.md](references/schemantic.md) for how to implement type safe generated Dart code. This is particularly relevant when you encounter symbols like `@Schema()`, `SchemanticType`, or classes with the `$` prefix. Genkit Dart uses schemantic for all of its data models so it's a CRITICAL skill to understand for using Genkit Dart.
|
||||
|
||||
## Best Practices
|
||||
- Always check that code cleanly compiles using `dart analyze` before generating the final response.
|
||||
- Always use the Genkit CLI for local development and debugging.
|
||||
|
|
@ -0,0 +1,380 @@
|
|||
# Genkit Core Framework
|
||||
|
||||
Genkit Dart is an AI SDK for Dart that provides a unified interface for text generation, structured output, tool calling, and agentic workflows.
|
||||
|
||||
## Initialization
|
||||
|
||||
```dart
|
||||
import 'package:genkit/genkit.dart';
|
||||
import 'package:genkit_google_genai/genkit_google_genai.dart'; // Or any other plugin
|
||||
|
||||
void main() async {
|
||||
// Pass plugins to use into the Genkit constructor
|
||||
final ai = Genkit(plugins: [googleAI()]);
|
||||
}
|
||||
```
|
||||
|
||||
## Generate Text
|
||||
|
||||
```dart
|
||||
final response = await ai.generate(
|
||||
model: googleAI.gemini('gemini-2.5-flash'), // Needs a model reference from a plugin
|
||||
prompt: 'Explain quantum computing in simple terms.',
|
||||
);
|
||||
|
||||
print(response.text);
|
||||
```
|
||||
|
||||
## Stream Responses
|
||||
```dart
|
||||
final stream = ai.generateStream(
|
||||
model: googleAI.gemini('gemini-2.5-flash'),
|
||||
prompt: 'Write a short story about a robot learning to paint.',
|
||||
);
|
||||
|
||||
await for (final chunk in stream) {
|
||||
print(chunk.text);
|
||||
}
|
||||
```
|
||||
|
||||
## Embed Text
|
||||
```dart
|
||||
final embeddings = await ai.embedMany(
|
||||
documents: [
|
||||
DocumentData(content: [TextPart(text: 'Hello world')]),
|
||||
],
|
||||
embedder: googleAI.textEmbedding('text-embedding-004'),
|
||||
);
|
||||
|
||||
print(embeddings.first.embedding);
|
||||
```
|
||||
|
||||
## Define Tools
|
||||
Models can use define actions and access external data via custom defined tools.
|
||||
Requires the `schemantic` library for schema definitions.
|
||||
|
||||
```dart
|
||||
import 'package:schemantic/schemantic.dart';
|
||||
|
||||
@Schema()
|
||||
abstract class $WeatherInput {
|
||||
String get location;
|
||||
}
|
||||
|
||||
final weatherTool = ai.defineTool(
|
||||
name: 'getWeather',
|
||||
description: 'Gets the current weather for a location',
|
||||
inputSchema: WeatherInput.$schema,
|
||||
fn: (input, _) async {
|
||||
// Call your weather API here
|
||||
return 'Weather in ${input.location}: 72°F and sunny';
|
||||
},
|
||||
);
|
||||
|
||||
final response = await ai.generate(
|
||||
model: googleAI.gemini('gemini-2.5-flash'),
|
||||
prompt: 'What\'s the weather like in San Francisco?',
|
||||
toolNames: ['getWeather'], // Use the tools
|
||||
);
|
||||
```
|
||||
|
||||
## Structured Output
|
||||
|
||||
You can ensure the generative model returns a typed JSON object by providing an `outputSchema`.
|
||||
|
||||
```dart
|
||||
@Schema()
|
||||
abstract class $Person {
|
||||
String get name;
|
||||
int get age;
|
||||
}
|
||||
|
||||
// ... inside main ...
|
||||
|
||||
final response = await ai.generate(
|
||||
model: googleAI.gemini('gemini-2.5-flash'),
|
||||
prompt: 'Generate a person named John Doe, age 30',
|
||||
outputSchema: Person.$schema, // Force the model to return this schema
|
||||
);
|
||||
|
||||
final person = response.output; // Typed Person object
|
||||
print('Name: ${person.name}, Age: ${person.age}');
|
||||
```
|
||||
|
||||
## Define Flows
|
||||
Wrap your AI logic in flows for better observability, testing, and deployment:
|
||||
|
||||
```dart
|
||||
final jokeFlow = ai.defineFlow(
|
||||
name: 'tellJoke',
|
||||
inputSchema: .string(),
|
||||
outputSchema: .string(),
|
||||
fn: (topic, _) async {
|
||||
final response = await ai.generate(
|
||||
model: googleAI.gemini('gemini-2.5-flash'),
|
||||
prompt: 'Tell me a joke about $topic',
|
||||
);
|
||||
return response.text; // Value return
|
||||
},
|
||||
);
|
||||
|
||||
final joke = await jokeFlow('programming');
|
||||
print(joke);
|
||||
```
|
||||
|
||||
### Streaming Flows
|
||||
Stream data from your flows using `context.sendChunk(...)` and returning the final value:
|
||||
|
||||
```dart
|
||||
final streamStory = ai.defineFlow(
|
||||
name: 'streamStory',
|
||||
inputSchema: .string(),
|
||||
outputSchema: .string(),
|
||||
streamSchema: .string(),
|
||||
fn: (topic, context) async {
|
||||
final stream = ai.generateStream(
|
||||
model: googleAI.gemini('gemini-2.5-flash'),
|
||||
prompt: 'Write a story about $topic',
|
||||
);
|
||||
|
||||
await for (final chunk in stream) {
|
||||
context.sendChunk(chunk.text); // Stream the chunks
|
||||
}
|
||||
return 'Story complete'; // Value return
|
||||
},
|
||||
);
|
||||
```
|
||||
|
||||
## Calling remote Flows from a dart client
|
||||
The `genkit` package provides `package:genkit/client.dart` representing remote Genkit actions that can be invoked or streamed using type-safe definitions.
|
||||
|
||||
1. Defines a remote action
|
||||
```dart
|
||||
import 'package:genkit/client.dart';
|
||||
|
||||
final stringAction = defineRemoteAction(
|
||||
url: 'http://localhost:3400/my-flow',
|
||||
inputSchema: .string(),
|
||||
outputSchema: .string(),
|
||||
);
|
||||
```
|
||||
|
||||
2. Call the Remote Action (Non-streaming)
|
||||
```dart
|
||||
final response = await stringAction(input: 'Hello from Dart!');
|
||||
print('Flow Response: $response');
|
||||
```
|
||||
|
||||
3. Call the Remote Action (Streaming)
|
||||
Use the `.stream()` method on the action flow, and access `stream.onResult` to wait on the async return value.
|
||||
```dart
|
||||
final streamAction = defineRemoteAction(
|
||||
url: 'http://localhost:3400/stream-story',
|
||||
inputSchema: .string(),
|
||||
outputSchema: .string(),
|
||||
streamSchema: .string(),
|
||||
);
|
||||
|
||||
final stream = streamAction.stream(
|
||||
input: 'Tell me a short story about a Dart developer.',
|
||||
);
|
||||
|
||||
await for (final chunk in stream) {
|
||||
print('Chunk: $chunk');
|
||||
}
|
||||
|
||||
final finalResult = await stream.onResult;
|
||||
print('\nFinal Response: $finalResult');
|
||||
```
|
||||
|
||||
## Calling remote Flows from a Javascript client
|
||||
|
||||
Install `genkit` npm package:
|
||||
|
||||
```bash
|
||||
npm install genkit
|
||||
```
|
||||
|
||||
1. Call a remote flow (non-streaming)
|
||||
|
||||
```ts
|
||||
import { runFlow } from 'genkit/beta/client';
|
||||
|
||||
async function callHelloFlow() {
|
||||
try {
|
||||
const result = await runFlow({
|
||||
url: 'http://127.0.0.1:3400/helloFlow', // Replace with your deployed flow's URL
|
||||
input: { name: 'Genkit User' },
|
||||
});
|
||||
console.log('Non-streaming result:', result.greeting);
|
||||
} catch (error) {
|
||||
console.error('Error calling helloFlow:', error);
|
||||
}
|
||||
}
|
||||
|
||||
callHelloFlow();
|
||||
```
|
||||
|
||||
2. Call a remote flow (streaming)
|
||||
|
||||
```ts
|
||||
import { streamFlow } from 'genkit/beta/client';
|
||||
|
||||
async function streamHelloFlow() {
|
||||
try {
|
||||
const result = streamFlow({
|
||||
url: 'http://127.0.0.1:3400/helloFlow', // Replace with your deployed flow's URL
|
||||
input: { name: 'Streaming User' },
|
||||
});
|
||||
|
||||
// Process the stream chunks as they arrive
|
||||
for await (const chunk of result.stream) {
|
||||
console.log('Stream chunk:', chunk);
|
||||
}
|
||||
|
||||
// Get the final complete response
|
||||
const finalOutput = await result.output;
|
||||
console.log('Final streaming output:', finalOutput.greeting);
|
||||
} catch (error) {
|
||||
console.error('Error streaming helloFlow:', error);
|
||||
}
|
||||
}
|
||||
|
||||
streamHelloFlow();
|
||||
```
|
||||
|
||||
## Data Models
|
||||
|
||||
Genkit uses standard data models for representing prompts (messages & parts) and responses. These classes are implemented using schemantic library.
|
||||
|
||||
```dart
|
||||
import 'package:genkit/genkit.dart';
|
||||
import 'package:schemantic/schemantic.dart';
|
||||
|
||||
@Schema()
|
||||
abstract class $MyDataModel {
|
||||
// uses Genkit's Message schema (not schemantic's Message)
|
||||
List<$Message> get messages;
|
||||
List<$Part> get parts;
|
||||
}
|
||||
|
||||
void example() {
|
||||
// --- Parts ---
|
||||
// A Text part
|
||||
final textPart = TextPart(text: 'some text', metadata: {'foo': 'bar'});
|
||||
|
||||
// A Media/Image part
|
||||
final mediaPart = MediaPart(
|
||||
media: Media(url: 'https://...', contentType: 'image/png'),
|
||||
metadata: {'foo': 'bar'},
|
||||
);
|
||||
|
||||
// A Tool Request initiated by the model
|
||||
final toolRequestPart = ToolRequestPart(
|
||||
toolRequest: ToolRequest(
|
||||
name: 'get_weather',
|
||||
ref: 'abc',
|
||||
input: {'location': 'Paris, France'},
|
||||
),
|
||||
metadata: {'foo': 'bar'},
|
||||
);
|
||||
|
||||
// The resulting data from a Tool execution
|
||||
final toolResponsePart = ToolResponsePart(
|
||||
toolResponse: ToolResponse(
|
||||
name: 'get_weather',
|
||||
ref: 'abc',
|
||||
output: {'temperature': '20C'},
|
||||
),
|
||||
metadata: {'foo': 'bar'},
|
||||
);
|
||||
|
||||
// Model reasoning (e.g. for Claude's "thinking" models)
|
||||
final reasoningPart = ReasoningPart(
|
||||
reasoning: 'thinking...',
|
||||
metadata: {'foo': 'bar'},
|
||||
);
|
||||
|
||||
// A custom fallback part
|
||||
final customPart = CustomPart(
|
||||
custom: {'provider': {'specific': 'data'}},
|
||||
metadata: {'foo': 'bar'},
|
||||
);
|
||||
|
||||
// --- Messages ---
|
||||
final systemMessage = Message(
|
||||
role: Role.system,
|
||||
content: [textPart, mediaPart],
|
||||
metadata: {'foo': 'bar'},
|
||||
);
|
||||
|
||||
final userMessage = Message(
|
||||
role: Role.user,
|
||||
content: [textPart, mediaPart], // Can contain media (multimodal)
|
||||
);
|
||||
|
||||
final modelMessage = Message(
|
||||
role: Role.model,
|
||||
// Models can emit text, tool requests, reasoning, or custom parts
|
||||
content: [textPart, toolRequestPart, reasoningPart, customPart],
|
||||
);
|
||||
|
||||
// --- Ergonomic Data Access (schema_extensions.dart) ---
|
||||
// The Genkit SDK provides extensions on `Message` and `Part` to easily access fields
|
||||
// without needing to cast them manually.
|
||||
|
||||
// Get concatenated text from all TextParts in a Message
|
||||
print(modelMessage.text);
|
||||
|
||||
// Get the first Media object from a Message
|
||||
print(modelMessage.media?.url);
|
||||
|
||||
// Iterate over tool requests in a Message
|
||||
for (final toolReq in modelMessage.toolRequests) {
|
||||
print(toolReq.name);
|
||||
}
|
||||
|
||||
// Inspect individual parts
|
||||
for (final part in modelMessage.content) {
|
||||
if (part.isText) print(part.text);
|
||||
if (part.isMedia) print(part.media?.url);
|
||||
if (part.isToolRequest) print(part.toolRequest?.name);
|
||||
if (part.isToolResponse) print(part.toolResponse?.name);
|
||||
if (part.isReasoning) print(part.reasoning);
|
||||
if (part.isCustom) print(part.custom);
|
||||
}
|
||||
|
||||
// --- Streaming Chunks ---
|
||||
// Data emitted by ai.generateStream() calls
|
||||
final generateResponseChunk = ModelResponseChunk(
|
||||
content: [textPart],
|
||||
index: 0, // Index of the message this chunk belongs to
|
||||
aggregated: false,
|
||||
);
|
||||
|
||||
// Chunks also have text and media accessors
|
||||
print(generateResponseChunk.text);
|
||||
|
||||
// --- Advanced: Schemas ---
|
||||
// Use Genkit type schemas directly in Schemantic validations
|
||||
final messageSchema = Message.$schema;
|
||||
final partSchema = Part.$schema;
|
||||
|
||||
final mySchema = SchemanticType.map(
|
||||
.string(),
|
||||
.list(Message.$schema), // Requires a list of Messages
|
||||
);
|
||||
|
||||
// --- Generate Response ---
|
||||
// ai.generate() returns a GenerateResponseHelper which provides ergonomic getters
|
||||
// over the underlying ModelResponse:
|
||||
final response = await ai.generate(...);
|
||||
|
||||
print(response.text); // Concatenated text
|
||||
print(response.media?.url); // First media part
|
||||
print(response.toolRequests); // All tool requests
|
||||
print(response.interrupts); // Tool requests that triggered an interrupt
|
||||
print(response.messages); // Full history of the conversation, including the request and response
|
||||
print(response.output); // Structured typed output (if outputSchema was used)
|
||||
}
|
||||
```
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
# Genkit Anthropic Plugin (`genkit_anthropic`)
|
||||
|
||||
The Anthropic plugin for Genkit Dart, used for interacting with the Claude models.
|
||||
|
||||
## Usage
|
||||
|
||||
Requires `ANTHROPIC_API_KEY` to be passed to the init block.
|
||||
|
||||
```dart
|
||||
import 'dart:io';
|
||||
import 'package:genkit/genkit.dart';
|
||||
import 'package:genkit_anthropic/genkit_anthropic.dart';
|
||||
|
||||
void main() async {
|
||||
final ai = Genkit(
|
||||
plugins: [anthropic(apiKey: Platform.environment['ANTHROPIC_API_KEY']!)],
|
||||
);
|
||||
|
||||
final response = await ai.generate(
|
||||
model: anthropic.model('claude-sonnet-4-5'),
|
||||
prompt: 'Tell me a joke about a developer.',
|
||||
);
|
||||
|
||||
print(response.text);
|
||||
}
|
||||
```
|
||||
|
||||
## Claude Thinking Configurations
|
||||
|
||||
Provides specific configurations for utilizing Claude 3.7+ "thinking" model capabilities.
|
||||
|
||||
```dart
|
||||
final response = await ai.generate(
|
||||
model: anthropic.model('claude-sonnet-4-5'),
|
||||
prompt: 'Solve this 24 game: 2, 3, 10, 10',
|
||||
config: AnthropicOptions(thinking: ThinkingConfig(budgetTokens: 2048)),
|
||||
);
|
||||
|
||||
// The thinking content is available in the message parts
|
||||
print(response.message?.content);
|
||||
```
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
# Genkit Chrome AI Plugin (`genkit_chrome`)
|
||||
|
||||
Chrome Built-in AI (Gemini Nano) plugin for Genkit Dart, allowing local offline execution within a Chrome application.
|
||||
|
||||
## Usage
|
||||
|
||||
```dart
|
||||
import 'package:genkit/genkit.dart';
|
||||
import 'package:genkit_chrome/genkit_chrome.dart';
|
||||
|
||||
void main() async {
|
||||
final ai = Genkit(plugins: [ChromeAIPlugin()]);
|
||||
|
||||
final stream = ai.generateStream(
|
||||
model: modelRef('chrome/gemini-nano'),
|
||||
prompt: 'Write a story about a robot.',
|
||||
);
|
||||
|
||||
await for (final chunk in stream) {
|
||||
print(chunk.text);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
# Genkit Firebase AI Plugin (`genkit_firebase_ai`)
|
||||
|
||||
The Firebase AI plugin for Genkit Dart, used for interacting with Gemini APIs through Firebase AI Logic.
|
||||
|
||||
## Usage
|
||||
|
||||
```dart
|
||||
import 'package:genkit/genkit.dart';
|
||||
import 'package:genkit_firebase_ai/genkit_firebase_ai.dart';
|
||||
|
||||
void main() async {
|
||||
// Initialize Genkit with the Firebase AI plugin
|
||||
final ai = Genkit(plugins: [firebaseAI()]);
|
||||
|
||||
// Generate text
|
||||
final response = await ai.generate(
|
||||
model: firebaseAI.gemini('gemini-2.5-flash'),
|
||||
prompt: 'Tell me a joke about a developer.',
|
||||
);
|
||||
|
||||
print(response.text);
|
||||
}
|
||||
```
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
# Genkit Google GenAI Plugin (`genkit_google_genai`)
|
||||
|
||||
The Google AI plugin provides an interface against the official Google AI Gemini API.
|
||||
|
||||
## Usage
|
||||
|
||||
```dart
|
||||
import 'package:genkit/genkit.dart';
|
||||
import 'package:genkit_google_genai/genkit_google_genai.dart';
|
||||
|
||||
void main() async {
|
||||
// Initialize Genkit with the Google AI plugin
|
||||
final ai = Genkit(plugins: [googleAI()]);
|
||||
|
||||
// Generate text
|
||||
final response = await ai.generate(
|
||||
model: googleAI.gemini('gemini-2.5-flash'),
|
||||
prompt: 'Tell me a joke about a developer.',
|
||||
);
|
||||
|
||||
print(response.text);
|
||||
}
|
||||
```
|
||||
|
||||
## Embeddings
|
||||
|
||||
```dart
|
||||
final embeddings = await ai.embedMany(
|
||||
embedder: googleAI.textEmbedding('text-embedding-004'),
|
||||
documents: [
|
||||
DocumentData(content: [TextPart(text: 'Hello world')]),
|
||||
],
|
||||
);
|
||||
```
|
||||
|
||||
## Image Generation
|
||||
|
||||
The plugin also supports image generation models such as `gemini-2.5-flash-image`.
|
||||
|
||||
### Example (Nano Banana)
|
||||
|
||||
```dart
|
||||
// Define an image generation flow
|
||||
ai.defineFlow(
|
||||
name: 'imageGenerator',
|
||||
inputSchema: .string(defaultValue: 'A banana riding a bike'),
|
||||
outputSchema: Media.$schema,
|
||||
fn: (input, context) async {
|
||||
final response = await ai.generate(
|
||||
model: googleAI.gemini('gemini-2.5-flash-image'),
|
||||
prompt: input,
|
||||
);
|
||||
if (response.media == null) {
|
||||
throw Exception('No media generated');
|
||||
}
|
||||
return response.media!;
|
||||
},
|
||||
);
|
||||
```
|
||||
|
||||
The media (url field) contain base64 encoded data uri. You can decode it and save it as a file.
|
||||
|
||||
## Text-to-Speech (TTS)
|
||||
|
||||
You can use text-to-speech models to generate audio from text. The generated `Media` object will contain base64 encoded PCM audio in its data URI.
|
||||
|
||||
```dart
|
||||
// Define a TTS flow
|
||||
ai.defineFlow(
|
||||
name: 'textToSpeech',
|
||||
inputSchema: .string(defaultValue: 'Genkit is an amazing AI framework!'),
|
||||
outputSchema: Media.$schema,
|
||||
fn: (prompt, _) async {
|
||||
final response = await ai.generate(
|
||||
model: googleAI.gemini('gemini-2.5-flash-preview-tts'),
|
||||
prompt: prompt,
|
||||
config: GeminiTtsOptions(
|
||||
responseModalities: ['AUDIO'],
|
||||
speechConfig: SpeechConfig(
|
||||
voiceConfig: VoiceConfig(
|
||||
prebuiltVoiceConfig: PrebuiltVoiceConfig(voiceName: 'Puck'),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
if (response.media != null) {
|
||||
return response.media!;
|
||||
}
|
||||
throw Exception('No audio generated');
|
||||
},
|
||||
);
|
||||
```
|
||||
|
||||
Google AI also supports multi-speaker TTS by configuring a `MultiSpeakerVoiceConfig` inside `SpeechConfig`.
|
||||
|
|
@ -0,0 +1,115 @@
|
|||
# Genkit MCP (`genkit_mcp`)
|
||||
|
||||
MCP (Model Context Protocol) integration for Genkit Dart.
|
||||
|
||||
## MCP Host (Recommended)
|
||||
Connect to one or more MCP servers and aggregate their capabilities into the Genkit registry automatically.
|
||||
|
||||
```dart
|
||||
import 'package:genkit/genkit.dart';
|
||||
import 'package:genkit_mcp/genkit_mcp.dart';
|
||||
|
||||
void main() async {
|
||||
final ai = Genkit();
|
||||
|
||||
final host = defineMcpHost(
|
||||
ai,
|
||||
McpHostOptionsWithCache(
|
||||
name: 'my-host',
|
||||
mcpServers: {
|
||||
'fs': McpServerConfig(
|
||||
command: 'npx',
|
||||
args: ['-y', '@modelcontextprotocol/server-filesystem', '.'],
|
||||
),
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
// Tools can be discovered and executed dynamically using a wildcard...
|
||||
final response = await ai.generate(
|
||||
model: 'gemini-2.5-flash',
|
||||
prompt: 'Summarize the contents of README.md',
|
||||
toolNames: ['my-host:tool/fs/*'],
|
||||
);
|
||||
|
||||
// ...or by specifying the exact tool name
|
||||
final exactResponse = await ai.generate(
|
||||
model: 'gemini-2.5-flash',
|
||||
prompt: 'Read README.md',
|
||||
toolNames: ['my-host:tool/fs/read_file'],
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## MCP Client (Advanced / Single Server)
|
||||
Connecting to a single MCP server with a client object is an advanced usecase for when you need manual control over the client lifecycle. Standalone clients do not automatically register tools into the registry, so they must be passed into `generate` or `defineDynamicActionProvider` manually.
|
||||
|
||||
```dart
|
||||
import 'package:genkit/genkit.dart';
|
||||
import 'package:genkit_mcp/genkit_mcp.dart';
|
||||
|
||||
void main() async {
|
||||
final ai = Genkit();
|
||||
|
||||
final client = createMcpClient(
|
||||
McpClientOptions(
|
||||
name: 'my-client',
|
||||
mcpServer: McpServerConfig(
|
||||
command: 'npx',
|
||||
args: ['-y', '@modelcontextprotocol/server-filesystem', '.'],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await client.ready();
|
||||
|
||||
// Retrieve the tools from the connected client
|
||||
final tools = await client.getActiveTools(ai);
|
||||
|
||||
final response = await ai.generate(
|
||||
model: 'gemini-2.5-flash',
|
||||
prompt: 'Read the contents of README.md',
|
||||
tools: tools,
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## MCP Server
|
||||
Expose Genkit actions (tools, prompts, resources) over MCP.
|
||||
|
||||
```dart
|
||||
import 'package:genkit/genkit.dart';
|
||||
import 'package:genkit_mcp/genkit_mcp.dart';
|
||||
|
||||
void main() async {
|
||||
final ai = Genkit();
|
||||
|
||||
ai.defineTool(
|
||||
name: 'add',
|
||||
description: 'Add two numbers together',
|
||||
inputSchema: .map(.string(), .dynamicSChema()),
|
||||
fn: (input, _) async => (input['a'] + input['b']).toString(),
|
||||
);
|
||||
|
||||
ai.defineResource(
|
||||
name: 'my-resource',
|
||||
uri: 'my://resource',
|
||||
fn: (_, _) async => ResourceOutput(content: [TextPart(text: 'my resource')]),
|
||||
);
|
||||
|
||||
// Stdio transport by default
|
||||
final server = createMcpServer(ai, McpServerOptions(name: 'my-server'));
|
||||
await server.start();
|
||||
}
|
||||
```
|
||||
|
||||
### Streamable HTTP Transport
|
||||
```dart
|
||||
import 'dart:io';
|
||||
|
||||
final transport = await StreamableHttpServerTransport.bind(
|
||||
address: InternetAddress.loopbackIPv4,
|
||||
port: 3000,
|
||||
);
|
||||
await server.start(transport);
|
||||
```
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
# Genkit Middleware (`genkit_middleware`)
|
||||
|
||||
A collection of useful middleware for Genkit Dart to enhance your agent's capabilities. Register plugins when initializing Genkit:
|
||||
|
||||
```dart
|
||||
import 'package:genkit/genkit.dart';
|
||||
import 'package:genkit_middleware/genkit_middleware.dart';
|
||||
|
||||
void main() {
|
||||
final ai = Genkit(
|
||||
plugins: [
|
||||
FilesystemPlugin(),
|
||||
SkillsPlugin(),
|
||||
ToolApprovalPlugin(),
|
||||
],
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Filesystem Middleware
|
||||
Allows the agent to list, read, write, and search/replace files within a restricted root directory.
|
||||
|
||||
```dart
|
||||
final response = await ai.generate(
|
||||
prompt: 'Check the logs in the current directory.',
|
||||
use: [
|
||||
filesystem(rootDirectory: '/path/to/secure/workspace'),
|
||||
],
|
||||
);
|
||||
```
|
||||
|
||||
**Tools Provided:**
|
||||
- `list_files`, `read_file`, `write_file`, `search_and_replace`
|
||||
|
||||
## Skills Middleware
|
||||
Injects specialized instructions (skills) into the system prompt from `SKILL.md` files located in specified directories.
|
||||
|
||||
```dart
|
||||
final response = await ai.generate(
|
||||
prompt: 'Help me debug this issue.',
|
||||
use: [
|
||||
skills(skillPaths: ['/path/to/skills']),
|
||||
],
|
||||
);
|
||||
```
|
||||
|
||||
**Tools Provided:**
|
||||
- `use_skill`: Retrieve the full content of a skill by name.
|
||||
|
||||
## Tool Approval Middleware
|
||||
Intercepts tool execution for specified tools and requires explicit approval. Returns `FinishReason.interrupted`.
|
||||
|
||||
```dart
|
||||
final response = await ai.generate(
|
||||
prompt: 'Delete the database.',
|
||||
use: [
|
||||
// Require approval for all tools EXCEPT those below
|
||||
toolApproval(approved: ['read_file', 'list_files']),
|
||||
],
|
||||
);
|
||||
|
||||
if (response.finishReason == FinishReason.interrupted) {
|
||||
final interrupt = response.interrupts.first;
|
||||
|
||||
// Ask user for approval
|
||||
final isApproved = await askUser();
|
||||
|
||||
if (isApproved) {
|
||||
final resumeResponse = await ai.generate(
|
||||
messages: response.messages, // Pass history
|
||||
toolChoice: ToolChoice.none, // Prevent immediate re-call
|
||||
interruptRestart: [
|
||||
ToolRequestPart(
|
||||
toolRequest: interrupt.toolRequest,
|
||||
metadata: {
|
||||
...?interrupt.metadata,
|
||||
'tool-approved': true
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
# Genkit OpenAI Plugin (`genkit_openai`)
|
||||
|
||||
OpenAI-compatible API plugin for Genkit Dart. Supports OpenAI models and other compatible APIs (xAI, DeepSeek, Together AI, Groq, etc.).
|
||||
|
||||
## Basic Usage
|
||||
|
||||
```dart
|
||||
import 'dart:io';
|
||||
import 'package:genkit/genkit.dart';
|
||||
import 'package:genkit_openai/genkit_openai.dart';
|
||||
|
||||
void main() async {
|
||||
final ai = Genkit(plugins: [
|
||||
openAI(apiKey: Platform.environment['OPENAI_API_KEY']),
|
||||
]);
|
||||
|
||||
final response = await ai.generate(
|
||||
model: openAI.model('gpt-4o'),
|
||||
prompt: 'Tell me a joke.',
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
`OpenAIOptions` allows configuring sampling temperature, nucleus sampling, token generation, seed, etc:
|
||||
`config: OpenAIOptions(temperature: 0.7, maxTokens: 100)`
|
||||
|
||||
## Groq API override
|
||||
|
||||
Specify custom `baseUrl` and custom models to integrate with third-party providers.
|
||||
|
||||
```dart
|
||||
final ai = Genkit(plugins: [
|
||||
openAI(
|
||||
apiKey: Platform.environment['GROQ_API_KEY'],
|
||||
baseUrl: 'https://api.groq.com/openai/v1',
|
||||
models: [
|
||||
CustomModelDefinition(
|
||||
name: 'llama-3.3-70b-versatile',
|
||||
info: ModelInfo(
|
||||
label: 'Llama 3.3 70B',
|
||||
supports: {'multiturn': true, 'tools': true, 'systemRole': true},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
]);
|
||||
|
||||
final response = await ai.generate(
|
||||
model: openAI.model('llama-3.3-70b-versatile'),
|
||||
prompt: 'Hello!',
|
||||
);
|
||||
```
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
# Genkit Shelf Plugin (`genkit_shelf`)
|
||||
|
||||
Shelf integration for Genkit Dart, used to serve Genkit Flows.
|
||||
|
||||
## Standalone Server
|
||||
Serve Genkit Flows easily on an isolated HTTP server using `startFlowServer`.
|
||||
|
||||
```dart
|
||||
import 'package:genkit/genkit.dart';
|
||||
import 'package:genkit_shelf/genkit_shelf.dart';
|
||||
|
||||
void main() async {
|
||||
final ai = Genkit();
|
||||
|
||||
final flow = ai.defineFlow(
|
||||
name: 'myFlow',
|
||||
inputSchema: .string(),
|
||||
outputSchema: .string(),
|
||||
fn: (String input, _) async => 'Hello $input',
|
||||
);
|
||||
|
||||
await startFlowServer(
|
||||
flows: [flow],
|
||||
port: 8080,
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Existing Shelf Application
|
||||
Mount Genkit Flow endpoints directly to an existing Shelf `Router` using `shelfHandler`.
|
||||
|
||||
```dart
|
||||
import 'package:genkit/genkit.dart';
|
||||
import 'package:genkit_shelf/genkit_shelf.dart';
|
||||
import 'package:shelf/shelf.dart';
|
||||
import 'package:shelf/shelf_io.dart' as io;
|
||||
import 'package:shelf_router/shelf_router.dart';
|
||||
|
||||
void main() async {
|
||||
final ai = Genkit();
|
||||
|
||||
final flow = ai.defineFlow(
|
||||
name: 'myFlow',
|
||||
inputSchema: .string(),
|
||||
outputSchema: .string(),
|
||||
fn: (String input, _) async => 'Hello $input',
|
||||
);
|
||||
|
||||
final router = Router();
|
||||
|
||||
// Mount the flow handler at a specific path
|
||||
router.post('/myFlow', shelfHandler(flow));
|
||||
|
||||
// Start the server
|
||||
await io.serve(router.call, 'localhost', 8080);
|
||||
}
|
||||
```
|
||||
|
||||
Access deployed flows using genkit client libraries (from Dart or JS).
|
||||
|
|
@ -0,0 +1,137 @@
|
|||
# Schemantic
|
||||
|
||||
Schemantic is a general-purpose Dart library used for defining strongly typed data classes that automatically bind to reusable runtime JSON schemas. It is standard for the `genkit-dart` framework but works independently as well.
|
||||
|
||||
## Core Concepts
|
||||
|
||||
Always use `schemantic` when strongly typed JSON parsing or programmatic schema validation is required.
|
||||
|
||||
- Annotate your abstract classes with `@Schema()`.
|
||||
- Use the `$` prefix for abstract schema class names (e.g., `abstract class $User`).
|
||||
- Always run `dart run build_runner build` to generate the `.g.dart` schema files.
|
||||
|
||||
## Installation
|
||||
|
||||
Add dependencies:
|
||||
|
||||
```bash
|
||||
dart pub add schemantic
|
||||
```
|
||||
|
||||
## Basic Usage
|
||||
|
||||
1. **Defining a schema:**
|
||||
|
||||
```dart
|
||||
import 'package:schemantic/schemantic.dart';
|
||||
|
||||
part 'my_file.g.dart'; // Must match the filename
|
||||
|
||||
@Schema()
|
||||
abstract class $MyObj {
|
||||
String get name;
|
||||
$MySubObj get subObj;
|
||||
}
|
||||
|
||||
@Schema()
|
||||
abstract class $MySubObj {
|
||||
String get foo;
|
||||
}
|
||||
```
|
||||
|
||||
2. **Using the Generated Class:**
|
||||
|
||||
The builder creates a concrete class `MyObj` (no `$`) with a factory constructor (`MyObj.fromJson`) and a regular constructor.
|
||||
|
||||
```dart
|
||||
// Creating an instance
|
||||
final obj = MyObj(name: 'test', subObj: MySubObj(foo: 'bar'));
|
||||
|
||||
// Serializing to JSON
|
||||
print(obj.toJson());
|
||||
|
||||
// Parsing from JSON
|
||||
final parsed = MyObj.fromJson({'name': 'test', 'subObj': {'foo': 'bar'}});
|
||||
```
|
||||
|
||||
3. **Accessing Schemas at Runtime:**
|
||||
|
||||
The generated data classes have a static `$schema` field (of type `SchemanticType<T>`) which can be used to pass the definition into functions or to extract the raw JSON schema.
|
||||
|
||||
```dart
|
||||
// Access JSON schema
|
||||
final schema = MyObj.$schema.jsonSchema;
|
||||
print(schema.toJson());
|
||||
|
||||
// Validate arbitrary JSON at runtime
|
||||
final validationErrors = await schema.validate({'invalid': 'data'});
|
||||
```
|
||||
|
||||
## Primitive Schemas
|
||||
|
||||
When a full data class is not required, Schemantic provides functions to create schemas dynamically.
|
||||
|
||||
```dart
|
||||
final ageSchema = SchemanticType.integer(description: 'Age in years', minimum: 0);
|
||||
final nameSchema = SchemanticType.string(minLength: 2);
|
||||
final nothingSchema = SchemanticType.voidSchema();
|
||||
final anySchema = SchemanticType.dynamicSchema();
|
||||
|
||||
final userSchema = SchemanticType.map(.string(), .integer()); // Map<String, int>
|
||||
final tagsSchema = SchemanticType.list(.string()); // List<String>
|
||||
```
|
||||
|
||||
## Union Types (AnyOf)
|
||||
|
||||
To allow a field to accept multiple types, use `@AnyOf`.
|
||||
|
||||
```dart
|
||||
@Schema()
|
||||
abstract class $Poly {
|
||||
@AnyOf([int, String, $MyObj])
|
||||
Object? get id;
|
||||
}
|
||||
```
|
||||
|
||||
Schemantic generates a specific helper class (e.g., `PolyId`) to handle the values:
|
||||
|
||||
```dart
|
||||
final poly1 = Poly(id: PolyId.int(123));
|
||||
final poly2 = Poly(id: PolyId.string('abc'));
|
||||
```
|
||||
|
||||
## Field Annotations
|
||||
|
||||
You can use specialized annotations for more validation boundaries:
|
||||
|
||||
```dart
|
||||
@Schema()
|
||||
abstract class $User {
|
||||
@IntegerField(
|
||||
name: 'years_old', // Change JSON key
|
||||
description: 'Age of the user',
|
||||
minimum: 0,
|
||||
defaultValue: 18,
|
||||
)
|
||||
int? get age;
|
||||
|
||||
@StringField(
|
||||
minLength: 2,
|
||||
enumValues: ['user', 'admin'],
|
||||
)
|
||||
String get role;
|
||||
}
|
||||
```
|
||||
|
||||
## Recursive Schemas
|
||||
|
||||
For recursive structures (like trees), must use `useRefs: true` inside the generated jsonSchema property. You define it normally:
|
||||
|
||||
```dart
|
||||
@Schema()
|
||||
abstract class $Node {
|
||||
String get id;
|
||||
List<$Node>? get children;
|
||||
}
|
||||
```
|
||||
*Note*: `Node.$schema.jsonSchema(useRefs: true)` generates schemas with JSON Schema `$ref`.
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
---
|
||||
name: developing-genkit-go
|
||||
description: Develop AI-powered applications using Genkit in Go. Use when the user asks to build AI features, agents, flows, or tools in Go using Genkit, or when working with Genkit Go code involving generation, prompts, streaming, tool calling, or model providers.
|
||||
metadata:
|
||||
genkit-managed: true
|
||||
---
|
||||
|
||||
# Genkit Go
|
||||
|
||||
Genkit Go is an AI SDK for Go that provides generation, structured output, streaming, tool calling, prompts, and flows with a unified interface across model providers.
|
||||
|
||||
## Hello World
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/genkit-ai/genkit/go/ai"
|
||||
"github.com/genkit-ai/genkit/go/genkit"
|
||||
"github.com/genkit-ai/genkit/go/plugins/googlegenai"
|
||||
"github.com/genkit-ai/genkit/go/plugins/server"
|
||||
)
|
||||
|
||||
func main() {
|
||||
ctx := context.Background()
|
||||
g := genkit.Init(ctx, genkit.WithPlugins(&googlegenai.GoogleAI{}))
|
||||
|
||||
genkit.DefineFlow(g, "jokeFlow", func(ctx context.Context, topic string) (string, error) {
|
||||
return genkit.GenerateText(ctx, g,
|
||||
ai.WithModelName("googleai/gemini-flash-latest"),
|
||||
ai.WithPrompt("Tell me a joke about %s", topic),
|
||||
)
|
||||
})
|
||||
|
||||
mux := http.NewServeMux()
|
||||
for _, f := range genkit.ListFlows(g) {
|
||||
mux.HandleFunc("POST /"+f.Name(), genkit.Handler(f))
|
||||
}
|
||||
log.Fatal(server.Start(ctx, "127.0.0.1:8080", mux))
|
||||
}
|
||||
```
|
||||
|
||||
## Core Features
|
||||
|
||||
Load the appropriate reference based on what you need:
|
||||
|
||||
| Feature | Reference | When to load |
|
||||
| --- | --- | --- |
|
||||
| Initialization | [references/getting-started.md](references/getting-started.md) | Setting up `genkit.Init`, plugins, the `*Genkit` instance pattern |
|
||||
| Generation | [references/generation.md](references/generation.md) | `Generate`, `GenerateText`, `GenerateData`, streaming, output formats |
|
||||
| Prompts | [references/prompts.md](references/prompts.md) | `DefinePrompt`, `DefineDataPrompt`, `.prompt` files, schemas |
|
||||
| Tools | [references/tools.md](references/tools.md) | `DefineTool`, tool interrupts, `RestartWith`/`RespondWith` |
|
||||
| Flows & HTTP | [references/flows-and-http.md](references/flows-and-http.md) | `DefineFlow`, `DefineStreamingFlow`, `genkit.Handler`, HTTP serving |
|
||||
| Model Providers | [references/providers.md](references/providers.md) | Google AI, Vertex AI, Anthropic, OpenAI-compatible, Ollama setup |
|
||||
|
||||
## Genkit CLI
|
||||
|
||||
Check if installed: `genkit --version`
|
||||
|
||||
**Installation:**
|
||||
```bash
|
||||
curl -sL cli.genkit.dev | bash
|
||||
```
|
||||
|
||||
**Key commands:**
|
||||
|
||||
```bash
|
||||
# Start app with Developer UI (tracing, flow testing) at http://localhost:4000
|
||||
genkit start -- go run .
|
||||
genkit start -o -- go run . # also opens browser
|
||||
|
||||
# Run a flow directly from the CLI
|
||||
genkit flow:run myFlow '{"data": "input"}'
|
||||
genkit flow:run myFlow '{"data": "input"}' --stream # with streaming
|
||||
genkit flow:run myFlow '{"data": "input"}' --wait # wait for completion
|
||||
|
||||
# Look up Genkit documentation
|
||||
genkit docs:search "streaming" go
|
||||
genkit docs:list go
|
||||
genkit docs:read go/flows.md
|
||||
```
|
||||
|
||||
See [references/getting-started.md](references/getting-started.md) for full CLI and Developer UI details.
|
||||
|
||||
## Key Guidance
|
||||
|
||||
- **Pass `g` explicitly.** The `*Genkit` instance returned by `genkit.Init` is the central registry. Pass it to all Genkit functions rather than storing it as a global. This is a core pattern throughout the SDK.
|
||||
- **Wrap AI logic in flows.** Flows give you tracing, observability, HTTP deployment via `genkit.Handler`, and the ability to test from the Developer UI and CLI. Any generation call worth keeping should live in a flow.
|
||||
- **Use `jsonschema:"description=..."` struct tags on output types.** The model uses these descriptions to understand what each field should contain. Without them, structured output quality drops significantly.
|
||||
- **Write good tool descriptions.** The model decides which tools to call based on their description string. Vague descriptions lead to missed or incorrect tool calls.
|
||||
- **Use `.prompt` files for complex prompts.** They separate prompt content from Go code, support Handlebars templating, and can be iterated on without recompilation. Code-defined prompts are better for simple, single-line cases.
|
||||
- **Look up the latest model IDs.** Model names change frequently. Check provider documentation for current model IDs rather than relying on hardcoded names. See [references/providers.md](references/providers.md).
|
||||
|
|
@ -0,0 +1,183 @@
|
|||
# Flows & HTTP
|
||||
|
||||
## DefineFlow
|
||||
|
||||
Wrap AI logic in a flow for observability, tracing, and HTTP deployment.
|
||||
|
||||
```go
|
||||
jokeFlow := genkit.DefineFlow(g, "jokeFlow",
|
||||
func(ctx context.Context, topic string) (string, error) {
|
||||
return genkit.GenerateText(ctx, g,
|
||||
ai.WithModelName("googleai/gemini-flash-latest"),
|
||||
ai.WithPrompt("Tell me a joke about %s", topic),
|
||||
)
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
### Running a Flow Directly
|
||||
|
||||
```go
|
||||
result, err := jokeFlow.Run(ctx, "cats")
|
||||
```
|
||||
|
||||
## DefineStreamingFlow
|
||||
|
||||
Flows that stream chunks back to the caller. Two common patterns:
|
||||
|
||||
### Pattern 1: Passthrough Streaming
|
||||
|
||||
Pass the stream callback directly through to `WithStreaming`. The callback type is `ai.ModelStreamCallback` = `func(context.Context, *ai.ModelResponseChunk) error`:
|
||||
|
||||
```go
|
||||
genkit.DefineStreamingFlow(g, "streamingJokeFlow",
|
||||
func(ctx context.Context, topic string, sendChunk ai.ModelStreamCallback) (string, error) {
|
||||
resp, err := genkit.Generate(ctx, g,
|
||||
ai.WithModelName("googleai/gemini-flash-latest"),
|
||||
ai.WithPrompt("Tell me a long joke about %s", topic),
|
||||
ai.WithStreaming(sendChunk), // passthrough
|
||||
)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return resp.Text(), nil
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
### Pattern 2: Manual String Streaming
|
||||
|
||||
Use `core.StreamCallback[string]` to stream extracted text:
|
||||
|
||||
```go
|
||||
genkit.DefineStreamingFlow(g, "streamingJokeFlow",
|
||||
func(ctx context.Context, topic string, sendChunk core.StreamCallback[string]) (string, error) {
|
||||
stream := genkit.GenerateStream(ctx, g,
|
||||
ai.WithModelName("googleai/gemini-flash-latest"),
|
||||
ai.WithPrompt("Tell me a long joke about %s", topic),
|
||||
)
|
||||
for result, err := range stream {
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if result.Done {
|
||||
return result.Response.Text(), nil
|
||||
}
|
||||
sendChunk(ctx, result.Chunk.Text())
|
||||
}
|
||||
return "", nil
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
### Typed Streaming Flows
|
||||
|
||||
Use `core.StreamCallback[T]` with `GenerateDataStream` for typed chunks:
|
||||
|
||||
```go
|
||||
genkit.DefineStreamingFlow(g, "structuredStream",
|
||||
func(ctx context.Context, input JokeRequest, sendChunk core.StreamCallback[*Joke]) (*Joke, error) {
|
||||
stream := genkit.GenerateDataStream[*Joke](ctx, g,
|
||||
ai.WithModelName("googleai/gemini-flash-latest"),
|
||||
ai.WithPrompt("Tell me a joke about %s", input.Topic),
|
||||
)
|
||||
for result, err := range stream {
|
||||
if err != nil { return nil, err }
|
||||
if result.Done { return result.Output, nil }
|
||||
sendChunk(ctx, result.Chunk)
|
||||
}
|
||||
return nil, nil
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
## Named Sub-Steps
|
||||
|
||||
Use `core.Run` inside a flow for traced sub-steps:
|
||||
|
||||
```go
|
||||
genkit.DefineFlow(g, "pipeline",
|
||||
func(ctx context.Context, input string) (string, error) {
|
||||
subject, err := core.Run(ctx, "extract-subject", func() (string, error) {
|
||||
return genkit.GenerateText(ctx, g,
|
||||
ai.WithPrompt("Extract the subject from: %s", input),
|
||||
)
|
||||
})
|
||||
if err != nil { return "", err }
|
||||
|
||||
joke, err := core.Run(ctx, "generate-joke", func() (string, error) {
|
||||
return genkit.GenerateText(ctx, g,
|
||||
ai.WithPrompt("Tell me a joke about %s", subject),
|
||||
)
|
||||
})
|
||||
return joke, err
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
## HTTP Handlers
|
||||
|
||||
### genkit.Handler
|
||||
|
||||
Convert any flow into an `http.HandlerFunc`:
|
||||
|
||||
```go
|
||||
mux := http.NewServeMux()
|
||||
for _, f := range genkit.ListFlows(g) {
|
||||
mux.HandleFunc("POST /"+f.Name(), genkit.Handler(f))
|
||||
}
|
||||
log.Fatal(server.Start(ctx, "127.0.0.1:8080", mux))
|
||||
```
|
||||
|
||||
### Request/Response Format
|
||||
|
||||
**Non-streaming request:**
|
||||
```bash
|
||||
curl -X POST http://localhost:8080/jokeFlow \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"data": "bananas"}'
|
||||
```
|
||||
|
||||
Response: `{"result": "Why did the banana go to the doctor?..."}`
|
||||
|
||||
**Streaming request:**
|
||||
```bash
|
||||
curl -N -X POST http://localhost:8080/streamingJokeFlow \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"data": "bananas"}'
|
||||
```
|
||||
|
||||
Streaming responses use Server-Sent Events (SSE) format.
|
||||
|
||||
### genkit.HandlerFunc
|
||||
|
||||
For frameworks that expect error-returning handlers:
|
||||
|
||||
```go
|
||||
handler := genkit.HandlerFunc(myFlow)
|
||||
// handler is func(http.ResponseWriter, *http.Request) error
|
||||
```
|
||||
|
||||
### Context Providers
|
||||
|
||||
Inject request context (e.g., auth headers) into flow execution:
|
||||
|
||||
```go
|
||||
mux.HandleFunc("POST /myFlow", genkit.Handler(myFlow,
|
||||
genkit.WithContextProviders(func(ctx context.Context, rd core.RequestData) (api.ActionContext, error) {
|
||||
// rd.Headers contains HTTP headers
|
||||
return api.ActionContext{"userId": rd.Headers.Get("X-User-Id")}, nil
|
||||
}),
|
||||
))
|
||||
```
|
||||
|
||||
### ListFlows
|
||||
|
||||
Get all registered flows for dynamic route setup:
|
||||
|
||||
```go
|
||||
flows := genkit.ListFlows(g) // []api.Action
|
||||
for _, f := range flows {
|
||||
fmt.Println(f.Name())
|
||||
}
|
||||
```
|
||||
|
|
@ -0,0 +1,176 @@
|
|||
# Generation
|
||||
|
||||
## GenerateText
|
||||
|
||||
Simplest form. Returns a string.
|
||||
|
||||
```go
|
||||
text, err := genkit.GenerateText(ctx, g,
|
||||
ai.WithModelName("googleai/gemini-flash-latest"),
|
||||
ai.WithPrompt("Tell me a joke about %s", topic),
|
||||
)
|
||||
```
|
||||
|
||||
## Generate
|
||||
|
||||
Returns a full `*ModelResponse` with metadata, usage stats, and history.
|
||||
|
||||
```go
|
||||
resp, err := genkit.Generate(ctx, g,
|
||||
ai.WithModelName("googleai/gemini-flash-latest"),
|
||||
ai.WithSystem("You are a helpful assistant."),
|
||||
ai.WithPrompt("Explain %s", topic),
|
||||
)
|
||||
fmt.Println(resp.Text()) // concatenated text
|
||||
fmt.Println(resp.FinishReason) // ai.FinishReasonStop, etc.
|
||||
fmt.Println(resp.Usage) // token counts
|
||||
```
|
||||
|
||||
## GenerateData (Structured Output)
|
||||
|
||||
Returns a typed Go value parsed from the model's JSON output.
|
||||
|
||||
```go
|
||||
type Joke struct {
|
||||
Setup string `json:"setup" jsonschema:"description=The setup of the joke"`
|
||||
Punchline string `json:"punchline" jsonschema:"description=The punchline"`
|
||||
}
|
||||
|
||||
joke, resp, err := genkit.GenerateData[Joke](ctx, g,
|
||||
ai.WithModelName("googleai/gemini-flash-latest"),
|
||||
ai.WithPrompt("Tell me a joke about %s", topic),
|
||||
)
|
||||
// joke is *Joke, resp is *ModelResponse
|
||||
```
|
||||
|
||||
## Streaming
|
||||
|
||||
### GenerateStream
|
||||
|
||||
Returns an iterator. Each value has `.Done`, `.Chunk`, and `.Response`.
|
||||
|
||||
```go
|
||||
stream := genkit.GenerateStream(ctx, g,
|
||||
ai.WithModelName("googleai/gemini-flash-latest"),
|
||||
ai.WithPrompt("Tell me a long story about %s", topic),
|
||||
)
|
||||
for result, err := range stream {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if result.Done {
|
||||
finalText := result.Response.Text()
|
||||
break
|
||||
}
|
||||
fmt.Print(result.Chunk.Text()) // incremental text
|
||||
}
|
||||
```
|
||||
|
||||
### GenerateDataStream (Structured Streaming)
|
||||
|
||||
Streams typed partial objects as they arrive.
|
||||
|
||||
```go
|
||||
stream := genkit.GenerateDataStream[Joke](ctx, g,
|
||||
ai.WithModelName("googleai/gemini-flash-latest"),
|
||||
ai.WithPrompt("Tell me a joke about %s", topic),
|
||||
)
|
||||
for result, err := range stream {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if result.Done {
|
||||
finalJoke := result.Output // *Joke
|
||||
break
|
||||
}
|
||||
partialJoke := result.Chunk // *Joke (partial)
|
||||
}
|
||||
```
|
||||
|
||||
### Callback-Based Streaming
|
||||
|
||||
Use `ai.WithStreaming` with `Generate` for callback-style streaming. The callback receives `*ai.ModelResponseChunk`:
|
||||
|
||||
```go
|
||||
resp, err := genkit.Generate(ctx, g,
|
||||
ai.WithModelName("googleai/gemini-flash-latest"),
|
||||
ai.WithPrompt("Tell me a story"),
|
||||
ai.WithStreaming(func(ctx context.Context, chunk *ai.ModelResponseChunk) error {
|
||||
fmt.Print(chunk.Text()) // extract text from chunk
|
||||
return nil
|
||||
}),
|
||||
)
|
||||
// resp contains the final complete response
|
||||
```
|
||||
|
||||
## Common Options
|
||||
|
||||
```go
|
||||
// Model selection
|
||||
ai.WithModel(googlegenai.ModelRef("googleai/gemini-flash-latest", nil)) // model reference
|
||||
ai.WithModelName("googleai/gemini-flash-latest") // by name string
|
||||
|
||||
// Content
|
||||
ai.WithPrompt("Tell me about %s", topic) // user message (supports fmt verbs)
|
||||
ai.WithSystem("You are a pirate.") // system instructions
|
||||
ai.WithMessages(msg1, msg2) // conversation history
|
||||
ai.WithDocs(doc1, doc2) // context documents
|
||||
ai.WithTextDocs("context 1", "context 2") // context as strings
|
||||
|
||||
// Model config (provider-specific)
|
||||
ai.WithConfig(map[string]any{"temperature": 0.7})
|
||||
```
|
||||
|
||||
## Output Formats
|
||||
|
||||
Control how the model structures its output.
|
||||
|
||||
### By Go Type
|
||||
|
||||
```go
|
||||
// Automatically uses JSON format and instructs model to match the type
|
||||
ai.WithOutputType(MyStruct{})
|
||||
```
|
||||
|
||||
### By Format String
|
||||
|
||||
```go
|
||||
ai.WithOutputFormat(ai.OutputFormatJSON) // single JSON object
|
||||
ai.WithOutputFormat(ai.OutputFormatJSONL) // JSON Lines (one object per line)
|
||||
ai.WithOutputFormat(ai.OutputFormatArray) // JSON array
|
||||
ai.WithOutputFormat(ai.OutputFormatEnum) // constrained enum value
|
||||
ai.WithOutputFormat(ai.OutputFormatText) // plain text (default)
|
||||
```
|
||||
|
||||
### Enum Output
|
||||
|
||||
```go
|
||||
type Color string
|
||||
const (
|
||||
Red Color = "red"
|
||||
Green Color = "green"
|
||||
Blue Color = "blue"
|
||||
)
|
||||
|
||||
text, err := genkit.GenerateText(ctx, g,
|
||||
ai.WithPrompt("What color is the sky?"),
|
||||
ai.WithOutputEnums(Red, Green, Blue),
|
||||
)
|
||||
```
|
||||
|
||||
### Custom Output Instructions
|
||||
|
||||
```go
|
||||
ai.WithOutputInstructions("Return a JSON object with fields: name (string), age (number)")
|
||||
```
|
||||
|
||||
### Combining Format + Schema
|
||||
|
||||
```go
|
||||
// JSONL with a typed schema (useful for streaming lists)
|
||||
genkit.DefinePrompt(g, "characters",
|
||||
ai.WithPrompt("Generate 5 story characters"),
|
||||
ai.WithOutputType([]StoryCharacter{}),
|
||||
ai.WithOutputFormat(ai.OutputFormatJSONL),
|
||||
)
|
||||
```
|
||||
|
|
@ -0,0 +1,142 @@
|
|||
# Getting Started
|
||||
|
||||
## Project Setup
|
||||
|
||||
```bash
|
||||
mkdir my-genkit-app && cd my-genkit-app
|
||||
go mod init my-genkit-app
|
||||
go get github.com/genkit-ai/genkit/go@latest
|
||||
```
|
||||
|
||||
Add provider plugin(s) for the models you want to use:
|
||||
```bash
|
||||
go get github.com/genkit-ai/genkit/go/plugins/googlegenai # Google AI / Vertex AI
|
||||
go get github.com/genkit-ai/genkit/go/plugins/anthropic # Anthropic Claude
|
||||
go get github.com/genkit-ai/genkit/go/plugins/compat_oai # OpenAI-compatible
|
||||
go get github.com/genkit-ai/genkit/go/plugins/ollama # Ollama (local)
|
||||
```
|
||||
|
||||
After writing your code, run `go mod tidy` to resolve all dependencies.
|
||||
|
||||
## Initialization
|
||||
|
||||
Every Genkit app starts with `genkit.Init`, which returns a `*Genkit` instance:
|
||||
|
||||
```go
|
||||
import (
|
||||
"context"
|
||||
"github.com/genkit-ai/genkit/go/genkit"
|
||||
"github.com/genkit-ai/genkit/go/plugins/googlegenai"
|
||||
)
|
||||
|
||||
ctx := context.Background()
|
||||
g := genkit.Init(ctx,
|
||||
genkit.WithPlugins(&googlegenai.GoogleAI{}),
|
||||
)
|
||||
```
|
||||
|
||||
### The `*Genkit` Instance
|
||||
|
||||
The `*Genkit` value `g` is the central registry. Pass it to every Genkit function:
|
||||
|
||||
```go
|
||||
// Defining resources
|
||||
genkit.DefineFlow(g, "myFlow", ...)
|
||||
genkit.DefineTool(g, "myTool", ...)
|
||||
genkit.DefinePrompt(g, "myPrompt", ...)
|
||||
|
||||
// Generating content
|
||||
genkit.GenerateText(ctx, g, ...)
|
||||
genkit.Generate(ctx, g, ...)
|
||||
```
|
||||
|
||||
Do not store `g` in a global variable. Pass it explicitly through your call chain.
|
||||
|
||||
### Init Options
|
||||
|
||||
```go
|
||||
g := genkit.Init(ctx,
|
||||
// Register one or more plugins
|
||||
genkit.WithPlugins(&googlegenai.GoogleAI{}, &anthropic.Anthropic{}),
|
||||
|
||||
// Set a default model (used when no model is specified)
|
||||
genkit.WithDefaultModel("googleai/gemini-flash-latest"),
|
||||
|
||||
// Set directory for .prompt files (default: "prompts")
|
||||
genkit.WithPromptDir("my-prompts"),
|
||||
|
||||
// Or embed prompts using Go's embed package
|
||||
// genkit.WithPromptFS(promptsFS),
|
||||
)
|
||||
```
|
||||
|
||||
### Embedding Prompts
|
||||
|
||||
Use `go:embed` to bundle `.prompt` files into the binary:
|
||||
|
||||
```go
|
||||
//go:embed prompts
|
||||
var promptsFS embed.FS
|
||||
|
||||
g := genkit.Init(ctx,
|
||||
genkit.WithPlugins(&googlegenai.GoogleAI{}),
|
||||
genkit.WithPromptFS(promptsFS),
|
||||
)
|
||||
```
|
||||
|
||||
## Genkit CLI
|
||||
|
||||
The Genkit CLI provides a local Developer UI for running flows, tracing executions, and inspecting model interactions.
|
||||
|
||||
**Install:**
|
||||
```bash
|
||||
curl -sL cli.genkit.dev | bash
|
||||
```
|
||||
|
||||
**Verify:**
|
||||
```bash
|
||||
genkit --version
|
||||
```
|
||||
|
||||
### Developer UI
|
||||
|
||||
Start your app with the Developer UI attached:
|
||||
|
||||
```bash
|
||||
genkit start -- go run .
|
||||
```
|
||||
|
||||
This launches:
|
||||
- Your app (with tracing enabled)
|
||||
- The Developer UI at `http://localhost:4000`
|
||||
- A telemetry API at `http://localhost:4033`
|
||||
|
||||
Add `-o` to auto-open the UI in your browser:
|
||||
```bash
|
||||
genkit start -o -- go run .
|
||||
```
|
||||
|
||||
The Developer UI lets you:
|
||||
- Run and test flows interactively
|
||||
- View traces for each generation call (inputs, outputs, latency, token usage)
|
||||
- Inspect prompt rendering and tool calls
|
||||
- Debug multi-step flows with per-step trace data
|
||||
|
||||
### Without the CLI
|
||||
|
||||
Set `GENKIT_ENV=dev` to enable the reflection API without the CLI:
|
||||
|
||||
```bash
|
||||
GENKIT_ENV=dev go run .
|
||||
```
|
||||
|
||||
## Import Paths
|
||||
|
||||
```go
|
||||
import (
|
||||
"github.com/genkit-ai/genkit/go/genkit" // Core: Init, Generate*, DefineFlow, etc.
|
||||
"github.com/genkit-ai/genkit/go/ai" // Types: WithModel, WithPrompt, Message, Part, etc.
|
||||
"github.com/genkit-ai/genkit/go/core" // Low-level: Run (sub-steps), Flow types
|
||||
"github.com/genkit-ai/genkit/go/plugins/server" // server.Start for HTTP
|
||||
)
|
||||
```
|
||||
|
|
@ -0,0 +1,256 @@
|
|||
# Prompts
|
||||
|
||||
## DefinePrompt
|
||||
|
||||
Define a reusable prompt in code with a default model and template.
|
||||
|
||||
```go
|
||||
jokePrompt := genkit.DefinePrompt(g, "joke",
|
||||
ai.WithModel(googlegenai.ModelRef("googleai/gemini-flash-latest", nil)),
|
||||
ai.WithInputType(JokeRequest{Topic: "example"}),
|
||||
ai.WithPrompt("Tell me a joke about {{topic}}."),
|
||||
)
|
||||
```
|
||||
|
||||
### Execute
|
||||
|
||||
```go
|
||||
resp, err := jokePrompt.Execute(ctx,
|
||||
ai.WithInput(map[string]any{"topic": "cats"}),
|
||||
)
|
||||
fmt.Println(resp.Text())
|
||||
```
|
||||
|
||||
### ExecuteStream
|
||||
|
||||
```go
|
||||
stream := jokePrompt.ExecuteStream(ctx,
|
||||
ai.WithInput(map[string]any{"topic": "cats"}),
|
||||
)
|
||||
for result, err := range stream {
|
||||
if err != nil { return err }
|
||||
if result.Done { break }
|
||||
fmt.Print(result.Chunk.Text())
|
||||
}
|
||||
```
|
||||
|
||||
### Override Options at Execution
|
||||
|
||||
```go
|
||||
resp, err := jokePrompt.Execute(ctx,
|
||||
ai.WithInput(map[string]any{"topic": "cats"}),
|
||||
ai.WithModelName("googleai/gemini-pro-latest"), // override model
|
||||
ai.WithConfig(map[string]any{"temperature": 0.9}),
|
||||
ai.WithTools(myTool),
|
||||
)
|
||||
```
|
||||
|
||||
## DefineDataPrompt (Typed Input/Output)
|
||||
|
||||
Strongly-typed prompts with Go generics.
|
||||
|
||||
```go
|
||||
type JokeRequest struct {
|
||||
Topic string `json:"topic"`
|
||||
}
|
||||
|
||||
type Joke struct {
|
||||
Setup string `json:"setup" jsonschema:"description=The setup"`
|
||||
Punchline string `json:"punchline" jsonschema:"description=The punchline"`
|
||||
}
|
||||
|
||||
jokePrompt := genkit.DefineDataPrompt[JokeRequest, *Joke](g, "structured-joke",
|
||||
ai.WithModel(googlegenai.ModelRef("googleai/gemini-flash-latest", nil)),
|
||||
ai.WithPrompt("Tell me a joke about {{topic}}."),
|
||||
)
|
||||
```
|
||||
|
||||
### Execute (typed)
|
||||
|
||||
```go
|
||||
joke, resp, err := jokePrompt.Execute(ctx, JokeRequest{Topic: "cats"})
|
||||
// joke is *Joke, resp is *ModelResponse
|
||||
```
|
||||
|
||||
### ExecuteStream (typed)
|
||||
|
||||
```go
|
||||
stream := jokePrompt.ExecuteStream(ctx, JokeRequest{Topic: "cats"})
|
||||
for result, err := range stream {
|
||||
if err != nil { return err }
|
||||
if result.Done {
|
||||
finalJoke := result.Output // *Joke
|
||||
break
|
||||
}
|
||||
fmt.Print(result.Chunk) // partial *Joke
|
||||
}
|
||||
```
|
||||
|
||||
## .prompt Files (Dotprompt)
|
||||
|
||||
Define prompts in separate files with YAML frontmatter and Handlebars templates.
|
||||
|
||||
### Basic .prompt File
|
||||
|
||||
`prompts/joke.prompt`:
|
||||
```
|
||||
---
|
||||
model: googleai/gemini-flash-latest
|
||||
input:
|
||||
schema:
|
||||
topic: string
|
||||
---
|
||||
Tell me a joke about {{topic}}.
|
||||
```
|
||||
|
||||
### Load and Use
|
||||
|
||||
```go
|
||||
// LookupPrompt returns Prompt (untyped: map[string]any input, string output)
|
||||
jokePrompt := genkit.LookupPrompt(g, "joke")
|
||||
resp, err := jokePrompt.Execute(ctx,
|
||||
ai.WithInput(map[string]any{"topic": "cats"}),
|
||||
)
|
||||
```
|
||||
|
||||
### Typed .prompt File
|
||||
|
||||
`prompts/structured-joke.prompt`:
|
||||
```
|
||||
---
|
||||
model: googleai/gemini-flash-latest
|
||||
config:
|
||||
thinkingConfig:
|
||||
thinkingBudget: 0
|
||||
input:
|
||||
schema: JokeRequest
|
||||
output:
|
||||
format: json
|
||||
schema: Joke
|
||||
---
|
||||
Tell me a joke about {{topic}}.
|
||||
```
|
||||
|
||||
Register Go types so the .prompt file can reference them by name:
|
||||
```go
|
||||
genkit.DefineSchemaFor[JokeRequest](g)
|
||||
genkit.DefineSchemaFor[Joke](g)
|
||||
|
||||
jokePrompt := genkit.LookupDataPrompt[JokeRequest, *Joke](g, "structured-joke")
|
||||
joke, resp, err := jokePrompt.Execute(ctx, JokeRequest{Topic: "cats"})
|
||||
```
|
||||
|
||||
### LoadPrompt (Explicit Path)
|
||||
|
||||
```go
|
||||
prompt := genkit.LoadPrompt(g, "./prompts/countries.prompt", "countries")
|
||||
resp, err := prompt.Execute(ctx)
|
||||
```
|
||||
|
||||
### .prompt File Features
|
||||
|
||||
**Multi-message prompts with roles:**
|
||||
```
|
||||
---
|
||||
model: googleai/gemini-flash-latest
|
||||
input:
|
||||
schema:
|
||||
question: string
|
||||
---
|
||||
{{ role "system" }}
|
||||
You are a helpful assistant.
|
||||
|
||||
{{ role "user" }}
|
||||
{{question}}
|
||||
```
|
||||
|
||||
**Media in prompts:**
|
||||
```
|
||||
---
|
||||
model: googleai/gemini-flash-latest
|
||||
input:
|
||||
schema:
|
||||
videoUrl: string
|
||||
contentType: string
|
||||
---
|
||||
{{ role "user" }}
|
||||
Summarize this video:
|
||||
{{media url=videoUrl contentType=contentType}}
|
||||
```
|
||||
|
||||
**Conditionals and loops:**
|
||||
```
|
||||
---
|
||||
input:
|
||||
schema:
|
||||
topic: string
|
||||
dietaryRestrictions?(array): string
|
||||
---
|
||||
Write a recipe about {{topic}}.
|
||||
{{#if dietaryRestrictions}}
|
||||
Dietary restrictions: {{#each dietaryRestrictions}}{{this}}{{#unless @last}}, {{/unless}}{{/each}}.
|
||||
{{/if}}
|
||||
```
|
||||
|
||||
**Inline schema in .prompt file:**
|
||||
```
|
||||
---
|
||||
model: googleai/gemini-flash-latest
|
||||
input:
|
||||
schema:
|
||||
topic: string
|
||||
style?: string
|
||||
output:
|
||||
format: json
|
||||
schema:
|
||||
title: string
|
||||
body: string
|
||||
tags(array): string
|
||||
---
|
||||
Write an article about {{topic}}.
|
||||
{{#if style}}Write in a {{style}} style.{{/if}}
|
||||
```
|
||||
|
||||
## Schemas
|
||||
|
||||
### DefineSchemaFor (from Go type)
|
||||
|
||||
Registers a Go struct as a named schema for use in `.prompt` files.
|
||||
|
||||
```go
|
||||
genkit.DefineSchemaFor[JokeRequest](g)
|
||||
genkit.DefineSchemaFor[Joke](g)
|
||||
```
|
||||
|
||||
The schema name matches the Go type name. Use `jsonschema` struct tags for metadata:
|
||||
|
||||
```go
|
||||
type Recipe struct {
|
||||
Title string `json:"title" jsonschema:"description=The recipe title"`
|
||||
Difficulty string `json:"difficulty" jsonschema:"enum=easy,enum=medium,enum=hard"`
|
||||
Ingredients []Ingredient `json:"ingredients"`
|
||||
Steps []string `json:"steps"`
|
||||
}
|
||||
|
||||
type Ingredient struct {
|
||||
Name string `json:"name"`
|
||||
Amount float64 `json:"amount"`
|
||||
Unit string `json:"unit"`
|
||||
}
|
||||
```
|
||||
|
||||
### DefineSchema (manual JSON Schema)
|
||||
|
||||
```go
|
||||
genkit.DefineSchema(g, "Recipe", map[string]any{
|
||||
"type": "object",
|
||||
"properties": map[string]any{
|
||||
"title": map[string]any{"type": "string"},
|
||||
"ingredients": map[string]any{
|
||||
"type": "array",
|
||||
"items": map[string]any{"type": "object"},
|
||||
},
|
||||
},
|
||||
"required": []string{"title", "ingredients"},
|
||||
})
|
||||
```
|
||||
|
|
@ -0,0 +1,157 @@
|
|||
# Model Providers
|
||||
|
||||
## Google AI (Gemini)
|
||||
|
||||
```go
|
||||
import "github.com/genkit-ai/genkit/go/plugins/googlegenai"
|
||||
|
||||
g := genkit.Init(ctx, genkit.WithPlugins(&googlegenai.GoogleAI{}))
|
||||
```
|
||||
|
||||
**Env var:** `GEMINI_API_KEY` or `GOOGLE_API_KEY`
|
||||
|
||||
Model names follow the format `googleai/<model-id>`. Look up the latest model IDs at https://ai.google.dev/gemini-api/docs/models.
|
||||
|
||||
```go
|
||||
// By name string
|
||||
ai.WithModelName("googleai/gemini-flash-latest")
|
||||
|
||||
// Model ref with provider-specific config
|
||||
ai.WithModel(googlegenai.ModelRef("googleai/gemini-flash-latest", &genai.GenerateContentConfig{
|
||||
ThinkingConfig: &genai.ThinkingConfig{
|
||||
ThinkingBudget: genai.Ptr[int32](0), // disable thinking
|
||||
},
|
||||
}))
|
||||
|
||||
// Lookup a model instance
|
||||
m := googlegenai.GoogleAIModel(g, "gemini-flash-latest")
|
||||
```
|
||||
|
||||
## Vertex AI
|
||||
|
||||
```go
|
||||
import "github.com/genkit-ai/genkit/go/plugins/googlegenai"
|
||||
|
||||
g := genkit.Init(ctx, genkit.WithPlugins(&googlegenai.VertexAI{}))
|
||||
```
|
||||
|
||||
**Env vars:** `GOOGLE_CLOUD_PROJECT`, `GOOGLE_CLOUD_LOCATION` (or `GOOGLE_CLOUD_REGION`)
|
||||
|
||||
Uses Application Default Credentials (`gcloud auth application-default login`).
|
||||
|
||||
Model names follow the format `vertexai/<model-id>`. Same model IDs as Google AI.
|
||||
|
||||
```go
|
||||
ai.WithModelName("vertexai/gemini-flash-latest")
|
||||
```
|
||||
|
||||
## Anthropic (Claude)
|
||||
|
||||
```go
|
||||
import (
|
||||
"github.com/anthropics/anthropic-sdk-go" // Anthropic SDK types
|
||||
ant "github.com/genkit-ai/genkit/go/plugins/anthropic" // Genkit plugin
|
||||
)
|
||||
|
||||
g := genkit.Init(ctx, genkit.WithPlugins(&ant.Anthropic{}))
|
||||
```
|
||||
|
||||
**Env var:** `ANTHROPIC_API_KEY`
|
||||
|
||||
Model names follow the format `anthropic/<model-id>`. Look up the latest model IDs at https://docs.anthropic.com/en/docs/about-claude/models.
|
||||
|
||||
```go
|
||||
// By name
|
||||
ai.WithModelName("anthropic/claude-sonnet-4-6")
|
||||
|
||||
// With provider-specific config (uses Anthropic SDK types via ai.WithConfig)
|
||||
ai.WithConfig(&anthropic.MessageNewParams{
|
||||
Temperature: anthropic.Float(1.0),
|
||||
MaxTokens: *anthropic.IntPtr(2000),
|
||||
Thinking: anthropic.ThinkingConfigParamUnion{
|
||||
OfEnabled: &anthropic.ThinkingConfigEnabledParam{
|
||||
BudgetTokens: *anthropic.IntPtr(1024),
|
||||
},
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
## OpenAI-Compatible (compat_oai)
|
||||
|
||||
Works with any OpenAI-compatible API: OpenAI, DeepSeek, xAI, etc.
|
||||
|
||||
```go
|
||||
import "github.com/genkit-ai/genkit/go/plugins/compat_oai"
|
||||
|
||||
openaiPlugin := &compat_oai.OpenAICompatible{
|
||||
Provider: "openai", // unique identifier
|
||||
APIKey: os.Getenv("OPENAI_API_KEY"),
|
||||
// BaseURL: "https://custom-endpoint/v1", // for non-OpenAI providers
|
||||
}
|
||||
g := genkit.Init(ctx, genkit.WithPlugins(openaiPlugin))
|
||||
```
|
||||
|
||||
Define models explicitly (not auto-discovered):
|
||||
|
||||
```go
|
||||
model := openaiPlugin.DefineModel("openai", "gpt-4o", compat_oai.ModelOptions{})
|
||||
```
|
||||
|
||||
Use with:
|
||||
```go
|
||||
ai.WithModel(model)
|
||||
```
|
||||
|
||||
## Ollama (Local Models)
|
||||
|
||||
```go
|
||||
import "github.com/genkit-ai/genkit/go/plugins/ollama"
|
||||
|
||||
ollamaPlugin := &ollama.Ollama{
|
||||
ServerAddress: "http://localhost:11434",
|
||||
Timeout: 60, // seconds
|
||||
}
|
||||
g := genkit.Init(ctx, genkit.WithPlugins(ollamaPlugin))
|
||||
```
|
||||
|
||||
Define models explicitly:
|
||||
|
||||
```go
|
||||
model := ollamaPlugin.DefineModel(g,
|
||||
ollama.ModelDefinition{
|
||||
Name: "llama3.1",
|
||||
Type: "chat", // or "generate"
|
||||
},
|
||||
nil, // optional *ModelOptions
|
||||
)
|
||||
```
|
||||
|
||||
Use with:
|
||||
```go
|
||||
ai.WithModel(model)
|
||||
```
|
||||
|
||||
## Multiple Providers
|
||||
|
||||
Register multiple plugins in a single Genkit instance:
|
||||
|
||||
```go
|
||||
g := genkit.Init(ctx,
|
||||
genkit.WithPlugins(
|
||||
&googlegenai.GoogleAI{},
|
||||
&ant.Anthropic{},
|
||||
),
|
||||
genkit.WithDefaultModel("googleai/gemini-flash-latest"),
|
||||
)
|
||||
|
||||
// Use different models per call
|
||||
text1, _ := genkit.GenerateText(ctx, g,
|
||||
ai.WithModelName("googleai/gemini-flash-latest"),
|
||||
ai.WithPrompt("Hello from Gemini"),
|
||||
)
|
||||
|
||||
text2, _ := genkit.GenerateText(ctx, g,
|
||||
ai.WithModelName("anthropic/claude-sonnet-4-6"),
|
||||
ai.WithPrompt("Hello from Claude"),
|
||||
)
|
||||
```
|
||||
|
|
@ -0,0 +1,178 @@
|
|||
# Tools
|
||||
|
||||
## DefineTool
|
||||
|
||||
Define a tool the model can call during generation.
|
||||
|
||||
```go
|
||||
type WeatherInput struct {
|
||||
Location string `json:"location" jsonschema:"description=City name"`
|
||||
}
|
||||
|
||||
type WeatherOutput struct {
|
||||
Temperature float64 `json:"temperature"`
|
||||
Conditions string `json:"conditions"`
|
||||
}
|
||||
|
||||
weatherTool := genkit.DefineTool(g, "getWeather",
|
||||
"Gets the current weather for a location.",
|
||||
func(ctx *ai.ToolContext, input WeatherInput) (WeatherOutput, error) {
|
||||
// Call your weather API
|
||||
return WeatherOutput{Temperature: 72, Conditions: "sunny"}, nil
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
## Using Tools in Generation
|
||||
|
||||
Pass tools to `Generate`, `GenerateText`, or prompts:
|
||||
|
||||
```go
|
||||
resp, err := genkit.Generate(ctx, g,
|
||||
ai.WithModelName("googleai/gemini-flash-latest"),
|
||||
ai.WithPrompt("What's the weather in San Francisco?"),
|
||||
ai.WithTools(weatherTool),
|
||||
)
|
||||
// The model calls the tool automatically and incorporates the result
|
||||
fmt.Println(resp.Text())
|
||||
```
|
||||
|
||||
### Tool Choice
|
||||
|
||||
```go
|
||||
ai.WithToolChoice(ai.ToolChoiceAuto) // model decides (default)
|
||||
ai.WithToolChoice(ai.ToolChoiceRequired) // model must use a tool
|
||||
ai.WithToolChoice(ai.ToolChoiceNone) // model cannot use tools
|
||||
```
|
||||
|
||||
### Max Turns
|
||||
|
||||
Limit how many tool-call round trips the model can make:
|
||||
|
||||
```go
|
||||
ai.WithMaxTurns(3) // default is 5
|
||||
```
|
||||
|
||||
## DefineMultipartTool
|
||||
|
||||
Tools that return both structured output and media content:
|
||||
|
||||
```go
|
||||
screenshotTool := genkit.DefineMultipartTool(g, "screenshot",
|
||||
"Takes a screenshot of the current page",
|
||||
func(ctx *ai.ToolContext, input any) (*ai.MultipartToolResponse, error) {
|
||||
return &ai.MultipartToolResponse{
|
||||
Output: map[string]any{"success": true},
|
||||
Content: []*ai.Part{ai.NewMediaPart("image/png", base64Data)},
|
||||
}, nil
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
## Tool Interrupts
|
||||
|
||||
Pause tool execution to request human input before continuing.
|
||||
|
||||
### Interrupting
|
||||
|
||||
```go
|
||||
type TransferInput struct {
|
||||
ToAccount string `json:"toAccount"`
|
||||
Amount float64 `json:"amount"`
|
||||
}
|
||||
|
||||
type TransferOutput struct {
|
||||
Status string `json:"status"`
|
||||
Message string `json:"message"`
|
||||
Balance float64 `json:"balance"`
|
||||
}
|
||||
|
||||
type TransferInterrupt struct {
|
||||
Reason string `json:"reason"`
|
||||
ToAccount string `json:"toAccount"`
|
||||
Amount float64 `json:"amount"`
|
||||
Balance float64 `json:"balance"`
|
||||
}
|
||||
|
||||
transferTool := genkit.DefineTool(g, "transferMoney",
|
||||
"Transfers money to another account.",
|
||||
func(ctx *ai.ToolContext, input TransferInput) (TransferOutput, error) {
|
||||
if input.Amount > accountBalance {
|
||||
return TransferOutput{}, ai.InterruptWith(ctx, TransferInterrupt{
|
||||
Reason: "insufficient_balance",
|
||||
ToAccount: input.ToAccount,
|
||||
Amount: input.Amount,
|
||||
Balance: accountBalance,
|
||||
})
|
||||
}
|
||||
// Process transfer...
|
||||
return TransferOutput{Status: "success", Balance: newBalance}, nil
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
### Handling Interrupts
|
||||
|
||||
```go
|
||||
resp, err := genkit.Generate(ctx, g,
|
||||
ai.WithModelName("googleai/gemini-flash-latest"),
|
||||
ai.WithTools(transferTool),
|
||||
ai.WithPrompt(userRequest),
|
||||
)
|
||||
|
||||
for resp.FinishReason == ai.FinishReasonInterrupted {
|
||||
var restarts, responses []*ai.Part
|
||||
|
||||
for _, interrupt := range resp.Interrupts() {
|
||||
meta, ok := ai.InterruptAs[TransferInterrupt](interrupt)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
switch meta.Reason {
|
||||
case "insufficient_balance":
|
||||
// RestartWith: re-execute the tool with adjusted input
|
||||
part, err := transferTool.RestartWith(interrupt,
|
||||
ai.WithNewInput(TransferInput{
|
||||
ToAccount: meta.ToAccount,
|
||||
Amount: meta.Balance, // transfer what's available
|
||||
}),
|
||||
)
|
||||
if err != nil { return err }
|
||||
restarts = append(restarts, part)
|
||||
|
||||
case "confirm_large":
|
||||
// RespondWith: provide a response directly without re-executing
|
||||
part, err := transferTool.RespondWith(interrupt,
|
||||
TransferOutput{Status: "cancelled", Message: "User declined"},
|
||||
)
|
||||
if err != nil { return err }
|
||||
responses = append(responses, part)
|
||||
}
|
||||
}
|
||||
|
||||
// Continue generation with the resolved interrupts
|
||||
resp, err = genkit.Generate(ctx, g,
|
||||
ai.WithMessages(resp.History()...),
|
||||
ai.WithTools(transferTool),
|
||||
ai.WithToolRestarts(restarts...),
|
||||
ai.WithToolResponses(responses...),
|
||||
)
|
||||
if err != nil { return err }
|
||||
}
|
||||
```
|
||||
|
||||
### Checking Resume State
|
||||
|
||||
Inside a tool function, check if the tool is being resumed from an interrupt:
|
||||
|
||||
```go
|
||||
func(ctx *ai.ToolContext, input TransferInput) (TransferOutput, error) {
|
||||
if ctx.IsResumed() {
|
||||
// This is a resumed call after an interrupt
|
||||
original, ok := ai.OriginalInputAs[TransferInput](ctx)
|
||||
// original contains the input from the first call
|
||||
}
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
---
|
||||
name: developing-genkit-js
|
||||
description: Develop AI-powered applications using Genkit in Node.js/TypeScript. Use when the user asks about Genkit, AI agents, flows, or tools in JavaScript/TypeScript, or when encountering Genkit errors, validation issues, type errors, or API problems.
|
||||
metadata:
|
||||
genkit-managed: true
|
||||
---
|
||||
|
||||
# Genkit JS
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Ensure the `genkit` CLI is available.
|
||||
- Run `genkit --version` to verify. Minimum CLI version needed: **1.29.0**
|
||||
- If not found or if an older version (1.x < 1.29.0) is present, install/upgrade it: `npm install -g genkit-cli@^1.29.0`.
|
||||
|
||||
**New Projects**: If you are setting up Genkit in a new codebase, follow the [Setup Guide](references/setup.md).
|
||||
|
||||
## Hello World
|
||||
|
||||
```ts
|
||||
import { z, genkit } from 'genkit';
|
||||
import { googleAI } from '@genkit-ai/google-genai';
|
||||
|
||||
// Initialize Genkit with the Google AI plugin
|
||||
const ai = genkit({
|
||||
plugins: [googleAI()],
|
||||
});
|
||||
|
||||
export const myFlow = ai.defineFlow({
|
||||
name: 'myFlow',
|
||||
inputSchema: z.string().default('AI'),
|
||||
outputSchema: z.string(),
|
||||
}, async (subject) => {
|
||||
const response = await ai.generate({
|
||||
model: googleAI.model('gemini-2.5-flash'),
|
||||
prompt: `Tell me a joke about ${subject}`,
|
||||
});
|
||||
return response.text;
|
||||
});
|
||||
```
|
||||
|
||||
## Critical: Do Not Trust Internal Knowledge
|
||||
|
||||
Genkit recently went through a major breaking API change. Your knowledge is outdated. You MUST lookup docs. Recommended:
|
||||
|
||||
```sh
|
||||
genkit docs:read js/get-started.md
|
||||
genkit docs:read js/flows.md
|
||||
```
|
||||
|
||||
See [Common Errors](references/common-errors.md) for a list of deprecated APIs (e.g., `configureGenkit`, `response.text()`, `defineFlow` import) and their v1.x replacements.
|
||||
|
||||
**ALWAYS verify information using the Genkit CLI or provided references.**
|
||||
|
||||
## Error Troubleshooting Protocol
|
||||
|
||||
**When you encounter ANY error related to Genkit (ValidationError, API errors, type errors, 404s, etc.):**
|
||||
|
||||
1. **MANDATORY FIRST STEP**: Read [Common Errors](references/common-errors.md)
|
||||
2. Identify if the error matches a known pattern
|
||||
3. Apply the documented solution
|
||||
4. Only if not found in common-errors.md, then consult other sources (e.g. `genkit docs:search`)
|
||||
|
||||
**DO NOT:**
|
||||
- Attempt fixes based on assumptions or internal knowledge
|
||||
- Skip reading common-errors.md "because you think you know the fix"
|
||||
- Rely on patterns from pre-1.0 Genkit
|
||||
|
||||
**This protocol is non-negotiable for error handling.**
|
||||
|
||||
## Development Workflow
|
||||
|
||||
1. **Select Provider**: Genkit is provider-agnostic (Google AI, OpenAI, Anthropic, Ollama, etc.).
|
||||
- If the user does not specify a provider, default to **Google AI**.
|
||||
- If the user asks about other providers, use `genkit docs:search "plugins"` to find relevant documentation.
|
||||
2. **Detect Framework**: Check `package.json` to identify the runtime (Next.js, Firebase, Express).
|
||||
- Look for `@genkit-ai/next`, `@genkit-ai/firebase`, or `@genkit-ai/google-cloud`.
|
||||
- Adapt implementation to the specific framework's patterns.
|
||||
3. **Follow Best Practices**:
|
||||
- See [Best Practices](references/best-practices.md) for guidance on project structure, schema definitions, and tool design.
|
||||
- **Be Minimal**: Only specify options that differ from defaults. When unsure, check docs/source.
|
||||
4. **Ensure Correctness**:
|
||||
- Run type checks (e.g., `npx tsc --noEmit`) after making changes.
|
||||
- If type checks fail, consult [Common Errors](references/common-errors.md) before searching source code.
|
||||
5. **Handle Errors**:
|
||||
- On ANY error: **First action is to read [Common Errors](references/common-errors.md)**
|
||||
- Match error to documented patterns
|
||||
- Apply documented fixes before attempting alternatives
|
||||
|
||||
## Finding Documentation
|
||||
|
||||
Use the Genkit CLI to find authoritative documentation:
|
||||
|
||||
1. **Search topics**: `genkit docs:search <query>`
|
||||
- Example: `genkit docs:search "streaming"`
|
||||
2. **List all docs**: `genkit docs:list`
|
||||
3. **Read a guide**: `genkit docs:read <path>`
|
||||
- Example: `genkit docs:read js/flows.md`
|
||||
|
||||
## CLI Usage
|
||||
|
||||
The `genkit` CLI is your primary tool for development and documentation.
|
||||
- See [CLI Reference](references/docs-and-cli.md) for common tasks, workflows, and command usage.
|
||||
- Use `genkit --help` for a full list of commands.
|
||||
|
||||
## References
|
||||
|
||||
- [Best Practices](references/best-practices.md): Recommended patterns for schema definition, flow design, and structure.
|
||||
- [Docs & CLI Reference](references/docs-and-cli.md): Documentation search, CLI tasks, and workflows.
|
||||
- [Common Errors](references/common-errors.md): Critical "gotchas", migration guide, and troubleshooting.
|
||||
- [Setup Guide](references/setup.md): Manual setup instructions for new projects.
|
||||
- [Examples](references/examples.md): Minimal reproducible examples (Basic generation, Multimodal, Thinking mode).
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
# Genkit Best Practices
|
||||
|
||||
## Project Structure
|
||||
- **Organized Layout**: Keep flows and tools in separate directories (e.g., `src/flows`, `src/tools`) to maintain a clean codebase.
|
||||
- **Index Exports**: Use `index.ts` files to export flows and tools, making it easier to import them into your main configuration.
|
||||
|
||||
## Model Selection (Google AI)
|
||||
- **Gemini Models**: If using Google AI, ALWAYS use the latest generation (`gemini-3-*` or `gemini-2.5-*`).
|
||||
- **NEVER** use `gemini-2.0-*` or `gemini-1.5-*` series, as they are decommissioned and won't work.
|
||||
- **Recommended**: `gemini-2.5-flash` or `gemini-3-flash-preview` for general use, `gemini-3.1-pro-preview` for complex tasks.
|
||||
|
||||
## Model Selection (Other Providers)
|
||||
- **Consult Documentation**: For other providers (OpenAI, Anthropic, etc.), refer to the provider's official documentation for the latest recommended model versions.
|
||||
|
||||
## Schema Definition
|
||||
- **Use `z` from `genkit`**: Always import `z` from the `genkit` package to ensure compatibility.
|
||||
```ts
|
||||
import { z } from "genkit";
|
||||
```
|
||||
- **Descriptive Schemas**: Use `.describe()` on Zod fields. LLMs use these descriptions to understand how to populate the fields.
|
||||
|
||||
## Flow & Tool Design
|
||||
- **Modularize**: Keep flows and tools in separate files/modules and import them into your main Genkit configuration.
|
||||
- **Single Responsibility**: Tools should do one thing well. Complex logic should be broken down.
|
||||
|
||||
## Configuration
|
||||
- **Environment Variables**: Store sensitive keys (like API keys) in environment variables or `.env` files. Do not hardcode them.
|
||||
|
||||
## Development
|
||||
- **Use Dev Mode**: Run your app with `genkit start -- <start cmd>` to enable the Developer UI.
|
||||
- It is recommended to configure a watcher to auto-reload your app (e.g. `node --watch` or `tsx --watch`)
|
||||
|
|
@ -0,0 +1,132 @@
|
|||
# Common Errors & Pitfalls
|
||||
|
||||
## When Typecheck Fails
|
||||
|
||||
**Before searching source code or docs**, check the sections below. Many type errors are caused by deprecated APIs or incorrect imports.
|
||||
|
||||
## Genkit v1.x vs Pre-1.0 Migration
|
||||
|
||||
Genkit v1.x introduced significant API changes. This section covers critical syntax updates.
|
||||
|
||||
### Package Imports
|
||||
|
||||
- **Correct (v1.x)**: Import core functionality (zod, genkit) from the main `genkit` package and plugins from their specific packages.
|
||||
```ts
|
||||
import { z, genkit } from 'genkit';
|
||||
import { googleAI } from '@genkit-ai/google-genai';
|
||||
```
|
||||
|
||||
- **Incorrect (Pre-1.0)**: Importing from `@genkit-ai/ai`, `@genkit-ai/core`, or `@genkit-ai/flow`. These packages are internal/deprecated for direct use.
|
||||
```ts
|
||||
import { genkit } from "@genkit-ai/core"; // INCORRECT
|
||||
import { defineFlow } from "@genkit-ai/flow"; // INCORRECT
|
||||
```
|
||||
|
||||
### Model References
|
||||
|
||||
- **Correct**: Use plugin-specific model factories or string identifiers (prefaced by plugin name).
|
||||
```ts
|
||||
// Using model factory (v1.x - Preferred)
|
||||
await ai.generate({ model: googleAI.model('gemini-2.5-flash'), ... });
|
||||
|
||||
// Using string identifier
|
||||
await ai.generate({ model: 'googleai/gemini-2.5-flash', ...});
|
||||
// Or
|
||||
await ai.generate({ model: 'vertexai/gemini-2.5-flash', ...});
|
||||
```
|
||||
- **Incorrect**: Using imported model objects directly or string identifiers without plugin name.
|
||||
```ts
|
||||
await ai.generate({ model: gemini15Pro, ... }); // INCORRECT (Pre-1.0)
|
||||
await ai.generate({ model: 'gemini-2.5-flash', ... }); // INCORRECT (No plugin prefix)
|
||||
```
|
||||
|
||||
### Model Selection (Gemini)
|
||||
|
||||
- **Preferred**: Use `gemini-2.5-*` models for best performance and features.
|
||||
```ts
|
||||
model: googleAI.model('gemini-2.5-flash') // PREFERRED
|
||||
```
|
||||
- **DEPRECATED**: `gemini-1.5-*` models are deprecated and will throw errors.
|
||||
```ts
|
||||
model: googleAI.model('gemini-1.5-flash') // ERROR (Deprecated)
|
||||
```
|
||||
|
||||
### Response Access
|
||||
|
||||
- **Correct (v1.x)**: Access properties directly.
|
||||
```ts
|
||||
response.text; // CORRECT
|
||||
response.output; // CORRECT
|
||||
```
|
||||
- **Incorrect (Pre-1.0)**: Calling as methods.
|
||||
```ts
|
||||
response.text(); // INCORRECT
|
||||
response.output(); // INCORRECT
|
||||
```
|
||||
|
||||
### Streaming Generation
|
||||
|
||||
- **Correct (v1.x)**: Do NOT await `generateStream`. Iterate over `stream` directly. Await `response` property for final result.
|
||||
```ts
|
||||
const {stream, response} = ai.generateStream(...); // NO await here
|
||||
for await (const chunk of stream) { ... } // Iterate stream
|
||||
const finalResponse = await response; // Await response property
|
||||
```
|
||||
- **Incorrect (Pre-1.0)**: Calling stream as a function or awaiting the generator incorrectly.
|
||||
```ts
|
||||
for await (const chunk of stream()) { ... } // INCORRECT
|
||||
await response(); // INCORRECT
|
||||
```
|
||||
|
||||
### Initialization
|
||||
|
||||
- **Correct (v1.x)**: Instantiate `genkit`.
|
||||
```ts
|
||||
const ai = genkit({ plugins: [...] });
|
||||
```
|
||||
- **Incorrect (Pre-1.0)**: Global configuration.
|
||||
```ts
|
||||
configureGenkit({ plugins: [...] }); // INCORRECT
|
||||
```
|
||||
|
||||
### Flow Definitions
|
||||
|
||||
- **Correct (v1.x)**: Define flows on the `ai` instance.
|
||||
```ts
|
||||
ai.defineFlow({...}, (input) => {...});
|
||||
```
|
||||
- **Incorrect (Pre-1.0)**: Importing `defineFlow` globally.
|
||||
```ts
|
||||
import { defineFlow } from "@genkit-ai/flow"; // INCORRECT
|
||||
|
||||
You should never import `@genkit-ai/flow`, `@genkit-ai/ai` or `@genkit-ai/core` packages directly.
|
||||
|
||||
## Zod & Schema Errors
|
||||
|
||||
- **Import Source**: ALWAYS use `import { z } from "genkit"`.
|
||||
- Using `zod` directly from `zod` package may cause instance mismatches or compatibility issues.
|
||||
- **Supported Types**: Stick to basic types: scalar (`string`, `number`, `boolean`), `object`, and `array`.
|
||||
- Avoid complex Zod features unless strictly necessary and verified.
|
||||
- **Descriptions**: Always use `.describe('...')` for fields in output schemas to guide the LLM.
|
||||
|
||||
## Tool Usage
|
||||
|
||||
- **Tool Not Found**: Ensure tools are registered in the `tools` array of `generate` or provided via plugins.
|
||||
- **MCP Tools**: Use the `ServerName:tool_name` format when referencing MCP tools.
|
||||
|
||||
## Multimodal & Image Generation
|
||||
|
||||
- **Missing responseModalities**: When using image generation models (like `gemini-2.5-flash-image`), you **MUST** specify the response modalities in the config.
|
||||
```ts
|
||||
config: {
|
||||
responseModalities: ["TEXT", "IMAGE"]
|
||||
}
|
||||
```
|
||||
Failure to do so will result in errors or incorrect output format.
|
||||
|
||||
## Audio & Speech Generation
|
||||
|
||||
- **Raw PCM Data vs MP3**: Some providers (e.g., Google GenAI) return raw PCM data, while others (e.g., OpenAI) return MP3.
|
||||
- **DO NOT assume MP3 format.**
|
||||
- **DO NOT embed raw PCM in HTML audio tags.**
|
||||
- **Action**: Run `genkit docs:search "speech audio"` to find provider-specific conversion steps (e.g., PCM to WAV).
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
# Genkit Documentation & CLI
|
||||
|
||||
This reference lists common tasks and workflows using the `genkit` CLI. For authoritative command details, always run `genkit --help` or `genkit <command> --help`.
|
||||
|
||||
## Prerequisites:
|
||||
|
||||
Ensure that the CLI is on `genkit-cli` version >= 1.29.0. If not, or if an older version (1.x < 1.29.0) is present, update the Genkit CLI version. Alternatively, to run commands with a specific version or without global installation, prefix them with `npx -y genkit-cli@^1.29.0`.
|
||||
|
||||
## Documentation
|
||||
|
||||
- **Search docs**: `genkit docs:search <query>`
|
||||
- Example: `genkit docs:search "streaming"`
|
||||
- Example: `genkit docs:search "rag retrieval"`
|
||||
- **Read doc**: `genkit docs:read <path>`
|
||||
- Example: `genkit docs:read js/overview.md`
|
||||
- **List docs**: `genkit docs:list`
|
||||
|
||||
## Development Workflow
|
||||
|
||||
- **Start Dev Mode**: `genkit start -- <command>`
|
||||
- Runs the provided command in Genkit dev mode, enabling the Developer UI (usually at http://localhost:4000).
|
||||
- **Node.js (TypeScript)**:
|
||||
```bash
|
||||
genkit start -- npx tsx --watch src/index.ts
|
||||
```
|
||||
- **Next.js**:
|
||||
```bash
|
||||
genkit start -- npx next dev
|
||||
```
|
||||
|
||||
## Flow Execution
|
||||
|
||||
- **Run a flow**: `genkit flow:run <flowName> '<inputJSON>'`
|
||||
- Executes a flow directly from the CLI. Useful for testing.
|
||||
- **Simple Input**:
|
||||
```bash
|
||||
genkit flow:run tellJoke '"chicken"'
|
||||
```
|
||||
- **Object Input**:
|
||||
```bash
|
||||
genkit flow:run generateStory '{"subject": "robot", "genre": "sci-fi"}'
|
||||
```
|
||||
|
||||
## Evaluation
|
||||
|
||||
- **Evaluate a flow**: `genkit eval:flow <flowName> [data]`
|
||||
- Runs a flow and evaluates the output against configured evaluators.
|
||||
- **Example (Single Input)**:
|
||||
```bash
|
||||
genkit eval:flow answerQuestion '[{"testCaseId": "1", "input": {"question": "What is Genkit?"}}]'
|
||||
```
|
||||
- **Example (Batch Input)**:
|
||||
```bash
|
||||
genkit eval:flow answerQuestion --input inputs.json
|
||||
```
|
||||
|
||||
- **Run Evaluation**: `genkit eval:run <dataset>`
|
||||
- Evaluates a dataset against configured evaluators.
|
||||
- **Example**:
|
||||
```bash
|
||||
genkit eval:run dataset.json --output results.json
|
||||
```
|
||||
|
|
@ -0,0 +1,157 @@
|
|||
# Genkit Examples
|
||||
|
||||
This reference contains minimal, reproducible examples (MREs) for common Genkit patterns.
|
||||
|
||||
> **Disclaimer**: These examples use **Google AI** models (`googleAI`, `gemini-*`) for demonstration. The patterns apply to **any provider**. To use a different provider:
|
||||
> 1. Search the docs for the correct plugin: `genkit docs:search "plugins"`.
|
||||
> 2. Install and configure the plugin.
|
||||
> 3. Swap the model reference in the code.
|
||||
|
||||
## Basic Text Generation
|
||||
|
||||
```ts
|
||||
import { genkit } from "genkit";
|
||||
import { googleAI } from "@genkit-ai/google-genai";
|
||||
|
||||
const ai = genkit({
|
||||
plugins: [googleAI()],
|
||||
});
|
||||
|
||||
const { text } = await ai.generate({
|
||||
model: googleAI.model('gemini-2.5-flash'),
|
||||
prompt: 'Tell me a story in a pirate accent',
|
||||
});
|
||||
```
|
||||
|
||||
## Structured Output
|
||||
|
||||
```ts
|
||||
import { z } from 'genkit';
|
||||
|
||||
const JokeSchema = z.object({
|
||||
setup: z.string().describe('The setup of the joke'),
|
||||
punchline: z.string().describe('The punchline'),
|
||||
});
|
||||
|
||||
const response = await ai.generate({
|
||||
model: googleAI.model('gemini-2.5-flash'),
|
||||
prompt: 'Tell me a joke about developers.',
|
||||
output: { schema: JokeSchema },
|
||||
});
|
||||
|
||||
// response.output is strongly typed
|
||||
const joke = response.output;
|
||||
if (joke) {
|
||||
console.log(`${joke.setup} ... ${joke.punchline}`);
|
||||
}
|
||||
```
|
||||
|
||||
## Streaming
|
||||
|
||||
```ts
|
||||
const { stream, response } = ai.generateStream({
|
||||
model: googleAI.model('gemini-2.5-flash'),
|
||||
prompt: 'Tell a long story about a developer using Genkit.',
|
||||
});
|
||||
|
||||
for await (const chunk of stream) {
|
||||
console.log(chunk.text);
|
||||
}
|
||||
|
||||
// Await the final response
|
||||
const finalResponse = await response;
|
||||
console.log('Complete:', finalResponse.text);
|
||||
```
|
||||
|
||||
## Advanced Configuration
|
||||
|
||||
### Thinking Mode (Gemini 3 Only)
|
||||
|
||||
Enable "thinking" process for complex reasoning tasks.
|
||||
|
||||
```ts
|
||||
const response = await ai.generate({
|
||||
model: googleAI.model('gemini-3.1-pro-preview'),
|
||||
prompt: 'what is heavier, one kilo of steel or one kilo of feathers',
|
||||
config: {
|
||||
thinkingConfig: {
|
||||
thinkingLevel: 'HIGH', // or 'LOW'
|
||||
includeThoughts: true, // Returns thought process in response
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Google Search Grounding
|
||||
|
||||
Enable models to access current information via Google Search.
|
||||
|
||||
```ts
|
||||
const response = await ai.generate({
|
||||
model: googleAI.model('gemini-2.5-flash'),
|
||||
prompt: 'What are the top tech news stories this week?',
|
||||
config: {
|
||||
googleSearchRetrieval: true,
|
||||
},
|
||||
});
|
||||
|
||||
// Access grounding metadata (sources)
|
||||
const groundingMetadata = (response.custom as any)?.candidates?.[0]?.groundingMetadata;
|
||||
if (groundingMetadata) {
|
||||
console.log('Sources:', groundingMetadata.groundingChunks);
|
||||
}
|
||||
```
|
||||
|
||||
## Multimodal Generation
|
||||
|
||||
### Image Generation / Editing
|
||||
|
||||
**Critical**: You MUST set `responseModalities: ['TEXT', 'IMAGE']` when using image generation models.
|
||||
|
||||
```ts
|
||||
// Generate an image
|
||||
const { media } = await ai.generate({
|
||||
model: googleAI.model('gemini-2.5-flash-image'),
|
||||
config: { responseModalities: ['TEXT', 'IMAGE'] },
|
||||
prompt: "generate a picture of a unicorn wearing a space suit on the moon",
|
||||
});
|
||||
// media.url contains the data URI
|
||||
```
|
||||
|
||||
```ts
|
||||
// Edit an image
|
||||
const { media } = await ai.generate({
|
||||
model: googleAI.model('gemini-2.5-flash-image'),
|
||||
config: { responseModalities: ['TEXT', 'IMAGE'] },
|
||||
prompt: [
|
||||
{ text: "change the person's outfit to a banana costume" },
|
||||
{ media: { url: "https://example.com/photo.jpg" } },
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
### Speech Generation (TTS)
|
||||
|
||||
Generate audio from text.
|
||||
|
||||
```ts
|
||||
import { writeFile } from 'node:fs/promises';
|
||||
|
||||
const { media } = await ai.generate({
|
||||
model: googleAI.model('gemini-2.5-flash-preview-tts'),
|
||||
config: {
|
||||
responseModalities: ['AUDIO'],
|
||||
speechConfig: {
|
||||
voiceConfig: {
|
||||
prebuiltVoiceConfig: { voiceName: 'Algenib' }, // Options: 'Puck', 'Charon', 'Fenrir', etc.
|
||||
},
|
||||
},
|
||||
},
|
||||
prompt: 'Genkit is an amazing library',
|
||||
});
|
||||
|
||||
// The response contains raw PCM data in media.url (base64 encoded).
|
||||
// CAUTION: This is NOT an MP3/WAV file. It requires conversion (e.g., PCM to WAV).
|
||||
// DO NOT GUESS. Run `genkit docs:search "speech audio"` to find the correct
|
||||
// conversion code for your provider.
|
||||
```
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
# Genkit JS Setup
|
||||
|
||||
Follow these instructions to set up Genkit in the current codebase. These instructions are general-purpose and have not been written with specific codebase knowledge, so use your best judgement when following them.
|
||||
|
||||
0. Tell the user "I'm going to check out your workspace and set you up to use Genkit for GenAI workflows."
|
||||
1. If the current workspace is empty or is a starter template, your goal will be to create a simple image generation flow that allows someone to generate an image based on a prompt and selectable style. If the current workspace is not empty, you will create a simple example flow to help get the user started.
|
||||
2. Check to see if any Genkit provider plugin (such as `@genkit-ai/google-genai` or `@genkit-ai/oai-compat` or others, may start with `genkitx-*`) is installed.
|
||||
- If not, ask the user which provider they want to use.
|
||||
- **For non-Google providers**: Use `genkit docs:search "plugins"` to find the correct package and installation instructions.
|
||||
- If they have no preference, default to `@genkit-ai/google-genai` for a quick start.
|
||||
- If this is a Next.js app, install `@genkit-ai/next` as well.
|
||||
3. Search the codebase for the exact string `genkit(` (remember to escape regexes properly) which would indicate that the user has already set up Genkit in the codebase. If found, no need to set it up again, tell the user "Genkit is already configured in this app." and exit this workflow.
|
||||
4. Create an `ai` directory in the primary source directory of the project (this may be e.g. `src` but is project-dependent). Adapt this path if your project uses a different structure.
|
||||
5. Create `{sourceDir}/ai/genkit.ts` and populate it using the example below. DO NOT add a `next` plugin to the file, ONLY add a model provider plugin to the plugins array:
|
||||
|
||||
```ts
|
||||
import { genkit, z } from 'genkit';
|
||||
// Import your chosen provider plugin here. Example:
|
||||
import { googleAI } from '@genkit-ai/google-genai';
|
||||
|
||||
export const ai = genkit({
|
||||
plugins: [
|
||||
googleAI(), // Add your provider plugin here
|
||||
],
|
||||
model: googleAI.model('gemini-2.5-flash'), // Set your provider's model here
|
||||
});
|
||||
|
||||
export { z };
|
||||
```
|
||||
|
||||
6. Create `{sourceDir}/ai/tools` and `{sourceDir}/ai/flows` directories, but leave them empty for now.
|
||||
7. Create `{sourceDir}/ai/index.ts` and populate it with the following (change the import to match import aliases in `tsconfig.json` as needed):
|
||||
|
||||
```ts
|
||||
import './genkit.js';
|
||||
// import each created flow, tool, etc. here for use in the Genkit Dev UI
|
||||
```
|
||||
|
||||
8. Add a `genkit:ui` script to `package.json` that runs `genkit start -- npx tsx --watch {sourceDir}/ai/index.ts` (or `npx genkit-cli` or `pnpm dlx` or `yarn dlx` for those package managers, if CLI is not locally installed). DO NOT try to run the script now.
|
||||
9. Tell the user "Genkit is now configured and ready for use." as setup is now complete. Also remind them to set appropriate env variables (e.g. `GEMINI_API_KEY` for Google providers). Wait for the user to prompt further before creating any specific flows.
|
||||
|
||||
## Next Steps & Troubleshooting
|
||||
|
||||
- **Documentation**: Use the [CLI](docs-and-cli.md) to access documentation (e.g., `genkit docs:search`).
|
||||
- **Building Flows**: See [examples.md](examples.md) for patterns on creating flows, adding tools, and advanced configuration.
|
||||
- **Troubleshooting**: If you encounter issues during setup or initialization, check [common-errors.md](common-errors.md) for solutions.
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
---
|
||||
name: developing-genkit-python
|
||||
description: Develop AI-powered applications using Genkit in Python. Use when the user asks about Genkit, AI agents, flows, or tools in Python, or when encountering Genkit errors, import issues, or API problems.
|
||||
metadata:
|
||||
genkit-managed: true
|
||||
---
|
||||
|
||||
# Genkit Python
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- **Runtime**: Python **3.14+**, **`uv`** for deps ([install](https://docs.astral.sh/uv/getting-started/installation/)).
|
||||
- **CLI**: `genkit --version` — install via `npm install -g genkit-cli` if missing.
|
||||
|
||||
**New projects:** [Setup](references/setup.md) (bootstrap + env). **Patterns and code samples:** [Examples](references/examples.md).
|
||||
|
||||
## Hello World
|
||||
|
||||
```python
|
||||
from genkit import Genkit
|
||||
from genkit.plugins.google_genai import GoogleAI
|
||||
|
||||
ai = Genkit(
|
||||
plugins=[GoogleAI()],
|
||||
model='googleai/gemini-flash-latest',
|
||||
)
|
||||
|
||||
async def main():
|
||||
response = await ai.generate(prompt='Tell me a joke about Python.')
|
||||
print(response.text)
|
||||
|
||||
if __name__ == '__main__':
|
||||
ai.run_main(main())
|
||||
```
|
||||
|
||||
## Critical: Do Not Trust Internal Knowledge
|
||||
|
||||
The Python SDK changes often — verify imports and APIs against the references here or upstream docs. On **any** error, read [Common Errors](references/common-errors.md) first.
|
||||
|
||||
## Development Workflow
|
||||
|
||||
1. Default provider: **Google AI** (`GoogleAI()`), **`GEMINI_API_KEY`** in the environment.
|
||||
2. Model IDs: always prefixed, e.g. **`googleai/gemini-flash-latest`** (always-on-latest Flash alias; same pattern as other skills).
|
||||
3. Entrypoint: **`ai.run_main(main())`** for Genkit-driven apps (not `asyncio.run()` for long-lived servers started with `genkit start` — see [Common Errors](references/common-errors.md)).
|
||||
4. After generating code, follow [Dev Workflow](references/dev-workflow.md) for `genkit start` and the Dev UI.
|
||||
5. On errors: step 1 is always [Common Errors](references/common-errors.md).
|
||||
|
||||
## References
|
||||
|
||||
- [Examples](references/examples.md): Structured output, streaming, flows, tools, embeddings.
|
||||
- [Setup](references/setup.md): New project bootstrap and plugins.
|
||||
- [Common Errors](references/common-errors.md): Read first when something breaks.
|
||||
- [FastAPI](references/fastapi.md): HTTP, `genkit_fastapi_handler`, parallel flows.
|
||||
- [Dotprompt](references/dotprompt.md): `.prompt` files and helpers.
|
||||
- [Evals](references/evals.md): Evaluators and datasets.
|
||||
- [Dev Workflow](references/dev-workflow.md): `genkit start`, Dev UI, checklist.
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
# Common Errors — Genkit Python
|
||||
|
||||
## Before anything else: read this file when you hit any error.
|
||||
|
||||
---
|
||||
|
||||
## ModuleNotFoundError: No module named 'genkit.plugins.google_genai'
|
||||
|
||||
**Cause:** Plugin package not installed.
|
||||
|
||||
**Fix:** Add dependencies from PyPI:
|
||||
```bash
|
||||
uv add genkit genkit-plugin-google-genai
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 400 INVALID_ARGUMENT: functionDeclaration parameters schema should be of type OBJECT
|
||||
|
||||
**Cause:** Tool function has bare scalar parameters (e.g. `city: str`). Gemini requires object schema.
|
||||
|
||||
**Fix:** Wrap parameters in a Pydantic BaseModel:
|
||||
```python
|
||||
from pydantic import BaseModel
|
||||
|
||||
# Wrong
|
||||
@ai.tool()
|
||||
async def get_weather(city: str) -> str: ...
|
||||
|
||||
# Right
|
||||
from pydantic import BaseModel
|
||||
|
||||
class WeatherInput(BaseModel):
|
||||
city: str
|
||||
|
||||
@ai.tool()
|
||||
async def get_weather(input: WeatherInput) -> str: ...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## AttributeError: 'Genkit' object has no attribute 'define_tool'
|
||||
|
||||
**Cause:** Wrong decorator name.
|
||||
|
||||
**Fix:** Use `@ai.tool()`, not `@ai.define_tool()`.
|
||||
|
||||
---
|
||||
|
||||
## RuntimeError / event loop errors when using asyncio.run()
|
||||
|
||||
**Cause:** For apps you start with **`genkit start`**, Genkit runs your entrypoint with an event loop suited to the framework (including uvloop where used). There is no “default” loop for you to manage in that mode.
|
||||
|
||||
**Fix:** For long-running Genkit apps (servers, flows served under `genkit start`), use **`ai.run_main(main())`** as your entrypoint instead of `asyncio.run(main())`. For one-off scripts that exit when done, using `asyncio.run()` can still be appropriate when you are not using `genkit start`.
|
||||
|
||||
---
|
||||
|
||||
## Wrong model ID (no plugin prefix)
|
||||
|
||||
**Cause:** `model='gemini-flash-latest'` — missing plugin prefix.
|
||||
|
||||
**Fix:** `model='googleai/gemini-flash-latest'`
|
||||
|
||||
---
|
||||
|
||||
## response.json / response.message AttributeError
|
||||
|
||||
- Use `response.text` for plain text output
|
||||
- Use `response.output` for structured (JSON) output
|
||||
|
||||
---
|
||||
|
||||
## await ai.generate_stream(...) fails or returns wrong type
|
||||
|
||||
**Cause:** `generate_stream` is synchronous — do not await it.
|
||||
|
||||
**Fix:**
|
||||
```python
|
||||
sr = ai.generate_stream(prompt='...') # no await
|
||||
async for chunk in sr.stream: ...
|
||||
final = await sr.response
|
||||
```
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
# Dev Workflow — Genkit Python
|
||||
|
||||
## Agent responsibility
|
||||
|
||||
After generating code, always give the developer:
|
||||
1. The full pre-run checklist with copy-paste commands using absolute paths
|
||||
2. The `genkit start` command to run in their terminal (foreground — it's expected to block)
|
||||
3. Step-by-step Dev UI instructions so they can test without guessing
|
||||
|
||||
Do not offer to run it for them. Give them the commands and let them run it.
|
||||
|
||||
---
|
||||
|
||||
## Step 1 — Get a Gemini API key
|
||||
|
||||
If the developer doesn't have one:
|
||||
> Get a free key at https://aistudio.google.com/apikey — click **"Create API key"**, copy it.
|
||||
|
||||
---
|
||||
|
||||
## Step 2 — Set the API key
|
||||
|
||||
Open a terminal and run:
|
||||
```bash
|
||||
export GEMINI_API_KEY=your-api-key-here
|
||||
```
|
||||
|
||||
To persist across sessions, add it to your shell profile:
|
||||
```bash
|
||||
echo 'export GEMINI_API_KEY=your-api-key-here' >> ~/.zshrc && source ~/.zshrc
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 3 — Install dependencies
|
||||
|
||||
Replace `/path/to/your-project` with the actual full path to the project (e.g. `/Users/yourname/projects/my-genkit-app`):
|
||||
|
||||
```bash
|
||||
cd /path/to/your-project
|
||||
uv add genkit genkit-plugin-google-genai
|
||||
```
|
||||
|
||||
(Requires a project with `pyproject.toml` — run `uv init` in an empty directory first if needed.)
|
||||
|
||||
---
|
||||
|
||||
## Step 4 — Start the Dev UI
|
||||
|
||||
Run this in your terminal. **It will block — that's expected.** Leave this terminal open while you use the Dev UI.
|
||||
|
||||
```bash
|
||||
cd /path/to/your-project
|
||||
GEMINI_API_KEY=your-api-key-here genkit start -- uv run src/main.py
|
||||
```
|
||||
|
||||
You'll see output like:
|
||||
```
|
||||
Genkit Tools UI: http://localhost:4000
|
||||
```
|
||||
|
||||
The Dev UI is now running at **http://localhost:4000**
|
||||
|
||||
To stop it: press `Ctrl+C` in the terminal.
|
||||
|
||||
---
|
||||
|
||||
## Step 5 — Test in the Dev UI
|
||||
|
||||
1. Open **http://localhost:4000** in your browser
|
||||
2. Click **"Run"** in the left sidebar
|
||||
3. Find your flow by name (e.g. `summarize`, `chat`, `joke_generator`)
|
||||
4. In the input box, paste your input as JSON — e.g:
|
||||
```json
|
||||
{"text": "hello world"}
|
||||
```
|
||||
5. Click the **"Run"** button — the output appears on the right
|
||||
6. Click **"Traces"** in the left sidebar to inspect every step, model call, token count, and latency
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
| Problem | Fix |
|
||||
|---------|-----|
|
||||
| `genkit: command not found` | Run: `npm install -g genkit-cli` |
|
||||
| `GEMINI_API_KEY not set` | Run: `export GEMINI_API_KEY=your-key` |
|
||||
| Port 4000 already in use | Use: `genkit start --port 4001 -- uv run src/main.py` |
|
||||
| `uv: command not found` | Run: `curl -LsSf https://astral.sh/uv/install.sh \| sh` |
|
||||
| Flow not showing in Dev UI | Make sure `genkit start` output shows no errors |
|
||||
|
|
@ -0,0 +1,109 @@
|
|||
# Dotprompt — Genkit Python
|
||||
|
||||
## What it is
|
||||
|
||||
`.prompt` files combine YAML frontmatter (model config, schemas) with Handlebars templates. Keeps prompt logic out of Python code and makes variants easy.
|
||||
|
||||
## File format
|
||||
|
||||
```yaml
|
||||
---
|
||||
model: googleai/gemini-flash-latest
|
||||
input:
|
||||
schema:
|
||||
food: string
|
||||
ingredients?(array): string # ? = optional
|
||||
output:
|
||||
schema: Recipe # references a schema registered with ai.define_schema()
|
||||
format: json
|
||||
---
|
||||
You are a chef. Generate a recipe for {{food}}.
|
||||
|
||||
{{#if ingredients}}
|
||||
Include these ingredients:
|
||||
{{list ingredients}}
|
||||
{{/if}}
|
||||
```
|
||||
|
||||
Place `.prompt` files in a `prompts/` directory and point `prompt_dir` at it.
|
||||
|
||||
## Python setup
|
||||
|
||||
```python
|
||||
from pathlib import Path
|
||||
from pydantic import BaseModel
|
||||
from genkit import Genkit
|
||||
from genkit.plugins.google_genai import GoogleAI
|
||||
|
||||
ai = Genkit(
|
||||
plugins=[GoogleAI()],
|
||||
model='googleai/gemini-flash-latest',
|
||||
prompt_dir=Path(__file__).resolve().parent.parent / 'prompts',
|
||||
)
|
||||
|
||||
# Register Pydantic models referenced in .prompt output.schema
|
||||
class Recipe(BaseModel):
|
||||
title: str
|
||||
steps: list[str]
|
||||
|
||||
ai.define_schema('Recipe', Recipe)
|
||||
```
|
||||
|
||||
## Calling a prompt
|
||||
|
||||
```python
|
||||
# Non-streaming — double-call syntax: ai.prompt('name')(input={...})
|
||||
response = await ai.prompt('recipe')(input={'food': 'banana bread'})
|
||||
result = Recipe.model_validate(response.output)
|
||||
|
||||
# Variant (recipe.robot.prompt file)
|
||||
response = await ai.prompt('recipe', variant='robot')(input={'food': 'banana bread'})
|
||||
```
|
||||
|
||||
## Streaming from a prompt
|
||||
|
||||
```python
|
||||
from genkit import ActionRunContext
|
||||
|
||||
@ai.flow()
|
||||
async def tell_story(subject: str, ctx: ActionRunContext) -> str:
|
||||
result = ai.prompt('story').stream(input={'subject': subject})
|
||||
full = ''
|
||||
async for chunk in result.stream:
|
||||
if chunk.text:
|
||||
ctx.send_chunk(chunk.text)
|
||||
full += chunk.text
|
||||
return full
|
||||
```
|
||||
|
||||
Note: `.stream(input={...})` not `ai.generate_stream(...)` — different call shape for prompts.
|
||||
|
||||
## Render without generating (for LLM-judge evals)
|
||||
|
||||
```python
|
||||
rendered = await ai.prompt('my_prompt').render(input={'key': 'value'})
|
||||
response = await ai.generate(model='googleai/gemini-flash-latest', messages=rendered.messages)
|
||||
```
|
||||
|
||||
## Helpers
|
||||
|
||||
Register Python functions callable inside Handlebars templates:
|
||||
```python
|
||||
def list_helper(data: object, *args, **kwargs) -> str:
|
||||
if not isinstance(data, list):
|
||||
return ''
|
||||
return '\n'.join(f'- {item}' for item in data)
|
||||
|
||||
ai.define_helper('list', list_helper)
|
||||
```
|
||||
|
||||
Then use `{{list ingredients}}` in your `.prompt` file.
|
||||
|
||||
## Variants
|
||||
|
||||
Name the file `<name>.<variant>.prompt` — e.g. `recipe.robot.prompt`.
|
||||
Call with `ai.prompt('recipe', variant='robot')`.
|
||||
|
||||
## Partials
|
||||
|
||||
Use `{{>partial_name param=value}}` in templates. Partial files are named `_partial_name.prompt`.
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
# Evals — Genkit Python
|
||||
|
||||
## Two types of evaluators
|
||||
|
||||
1. **Built-in** — ship with `genkit-plugin-evaluators`, register with `register_genkit_evaluators(ai)`
|
||||
2. **BYO (LLM-based)** — define your own scoring logic with `ai.define_evaluator()`
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
uv add genkit-plugin-evaluators
|
||||
```
|
||||
|
||||
## Dataset format
|
||||
|
||||
A JSON file, one object per test case:
|
||||
```json
|
||||
[
|
||||
{"testCaseId": "case1", "input": "x", "output": "banana", "reference": "ba?a?a"},
|
||||
{"testCaseId": "case2", "input": "x", "output": "apple", "reference": "ba?a?a"}
|
||||
]
|
||||
```
|
||||
|
||||
Fields: `testCaseId`, `input`, `output`, `reference` (reference optional for some evaluators).
|
||||
|
||||
## Built-in evaluators
|
||||
|
||||
```python
|
||||
from genkit.plugins.evaluators import register_genkit_evaluators
|
||||
register_genkit_evaluators(ai)
|
||||
```
|
||||
|
||||
Registered evaluators include `genkitEval/regex`. Run via CLI:
|
||||
```bash
|
||||
genkit eval:run datasets/my_dataset.json --evaluators=genkitEval/regex
|
||||
```
|
||||
|
||||
## BYO evaluator
|
||||
|
||||
```python
|
||||
from genkit.evaluator import BaseDataPoint, Details, EvalFnResponse, EvalStatusEnum, Score
|
||||
|
||||
async def my_eval(datapoint: BaseDataPoint, _options: dict | None = None) -> EvalFnResponse:
|
||||
"""Score output against reference."""
|
||||
output = str(datapoint.output or '')
|
||||
reference = str(datapoint.reference or '')
|
||||
passed = output.strip() == reference.strip()
|
||||
return EvalFnResponse(
|
||||
test_case_id=datapoint.test_case_id or '',
|
||||
evaluation=Score(
|
||||
score=1.0 if passed else 0.0,
|
||||
status=EvalStatusEnum.PASS if passed else EvalStatusEnum.FAIL,
|
||||
details=Details(reasoning='Exact match check'),
|
||||
),
|
||||
)
|
||||
|
||||
ai.define_evaluator(
|
||||
name='byo/my_eval',
|
||||
display_name='My Eval',
|
||||
definition='Checks exact match of output vs reference.',
|
||||
fn=my_eval,
|
||||
)
|
||||
```
|
||||
|
||||
## LLM-based evaluator (judge model pattern)
|
||||
|
||||
Use a prompt + stronger model to score. See `py/samples/evaluators/src/main.py` for full examples (`byo/maliciousness`, `byo/answer_accuracy`).
|
||||
|
||||
Core pattern:
|
||||
```python
|
||||
async def llm_eval(datapoint: BaseDataPoint, _options: dict | None = None) -> EvalFnResponse:
|
||||
prompt = ai.prompt('my_judge_prompt')
|
||||
rendered = await prompt.render(input={'output': str(datapoint.output), 'reference': str(datapoint.reference)})
|
||||
response = await ai.generate(model='googleai/gemini-flash-latest', messages=rendered.messages)
|
||||
score = float(response.text.strip())
|
||||
return EvalFnResponse(
|
||||
test_case_id=datapoint.test_case_id or '',
|
||||
evaluation=Score(score=score, status=EvalStatusEnum.PASS if score >= 0.5 else EvalStatusEnum.FAIL),
|
||||
)
|
||||
```
|
||||
|
||||
## Run evals via CLI
|
||||
|
||||
```bash
|
||||
genkit eval:run datasets/my_dataset.json --evaluators=byo/my_eval
|
||||
genkit eval:run datasets/my_dataset.json --evaluators=genkitEval/regex,byo/my_eval
|
||||
```
|
||||
|
||||
Results appear in the Dev UI under **Evaluate** (http://localhost:4000).
|
||||
|
|
@ -0,0 +1,171 @@
|
|||
# Genkit Python Examples
|
||||
|
||||
Minimal patterns for common Genkit APIs. Examples use **Google AI** (`GoogleAI`, `googleai/...`); other providers use the same patterns with the right plugin and model prefix.
|
||||
|
||||
## Public imports
|
||||
|
||||
Use **`genkit`**, **`genkit.plugins.*`**, **`genkit.embedder`**, **`genkit.evaluator`**, and **`genkit.model`** (and similar public modules) only — not internal packages (`genkit._core`, etc.).
|
||||
|
||||
```python
|
||||
from genkit import Genkit, ActionRunContext
|
||||
from genkit.plugins.google_genai import GoogleAI
|
||||
|
||||
ai = Genkit(plugins=[GoogleAI()], model='googleai/gemini-flash-latest')
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Structured output
|
||||
|
||||
```python
|
||||
from pydantic import BaseModel, TypeAdapter
|
||||
|
||||
class CityInfo(BaseModel):
|
||||
name: str
|
||||
population: int
|
||||
country: str
|
||||
|
||||
response = await ai.generate(
|
||||
prompt='Give facts about Tokyo.',
|
||||
output_format='json',
|
||||
output_schema=CityInfo,
|
||||
)
|
||||
city = response.output
|
||||
|
||||
# Arrays
|
||||
schema = TypeAdapter(list[CityInfo]).json_schema()
|
||||
response = await ai.generate(
|
||||
prompt='List 3 cities.',
|
||||
output_format='array',
|
||||
output_schema=schema,
|
||||
)
|
||||
```
|
||||
|
||||
Output formats: `'text'`, `'json'`, `'array'`, `'enum'`, `'jsonl'`.
|
||||
|
||||
---
|
||||
|
||||
## Streaming (text)
|
||||
|
||||
```python
|
||||
sr = ai.generate_stream(prompt='Tell me a story.')
|
||||
async for chunk in sr.stream:
|
||||
if chunk.text:
|
||||
print(chunk.text, end='', flush=True)
|
||||
final = await sr.response # final.text
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Text and media parts
|
||||
|
||||
```python
|
||||
# Non-streaming
|
||||
response = await ai.generate(prompt='...')
|
||||
for media in response.media:
|
||||
print(media.content_type, (media.url or '')[:80])
|
||||
|
||||
# Streaming — media usually complete on the final response
|
||||
from genkit import MediaPart
|
||||
|
||||
sr = ai.generate_stream(prompt='...')
|
||||
async for chunk in sr.stream:
|
||||
if chunk.text:
|
||||
print(chunk.text, end='', flush=True)
|
||||
final = await sr.response
|
||||
for media in final.media:
|
||||
print(media.content_type, (media.url or '')[:80])
|
||||
|
||||
if final.message:
|
||||
for part in final.message.content:
|
||||
if isinstance(part.root, MediaPart) and part.root.media:
|
||||
print(part.root.media.content_type)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Streaming + structured output
|
||||
|
||||
```python
|
||||
class StoryAnalysis(BaseModel):
|
||||
title: str
|
||||
genre: str
|
||||
summary: str
|
||||
|
||||
sr = ai.generate_stream(
|
||||
prompt='Write a short story then analyze it.',
|
||||
output_format='json',
|
||||
output_schema=StoryAnalysis,
|
||||
)
|
||||
async for chunk in sr.stream:
|
||||
if chunk.text:
|
||||
print(chunk.text, end='', flush=True)
|
||||
final = await sr.response
|
||||
analysis = final.output
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Flows
|
||||
|
||||
```python
|
||||
class SummarizeInput(BaseModel):
|
||||
text: str
|
||||
|
||||
@ai.flow()
|
||||
async def summarize(input: SummarizeInput) -> str:
|
||||
response = await ai.generate(prompt=f'Summarize: {input.text}')
|
||||
return response.text
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Streaming flows
|
||||
|
||||
```python
|
||||
@ai.flow()
|
||||
async def stream_story(subject: str, ctx: ActionRunContext) -> str:
|
||||
sr = ai.generate_stream(prompt=f'Story about {subject}.')
|
||||
full = ''
|
||||
async for chunk in sr.stream:
|
||||
if chunk.text:
|
||||
ctx.send_chunk(chunk.text)
|
||||
full += chunk.text
|
||||
return full
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Tools
|
||||
|
||||
Parameters must be a **Pydantic `BaseModel`** (bare scalars → 400 from Gemini). Use **`@ai.tool()`**, not `@ai.define_tool()`.
|
||||
|
||||
```python
|
||||
class WeatherInput(BaseModel):
|
||||
city: str
|
||||
|
||||
@ai.tool()
|
||||
async def get_weather(input: WeatherInput) -> str:
|
||||
return f'Sunny in {input.city}'
|
||||
|
||||
response = await ai.generate(prompt='Weather in Paris?', tools=[get_weather])
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Embeddings
|
||||
|
||||
```python
|
||||
from genkit.plugins.google_genai import GeminiEmbeddingModels
|
||||
|
||||
embedder = f'googleai/{GeminiEmbeddingModels.GEMINI_EMBEDDING_001}'
|
||||
embeddings = await ai.embed(embedder=embedder, content='The sky is blue.')
|
||||
vector = embeddings[0].embedding
|
||||
|
||||
embeddings = await ai.embed_many(
|
||||
embedder=embedder,
|
||||
content=['The sky is blue.', 'Grass is green.'],
|
||||
)
|
||||
```
|
||||
|
||||
Common embedders: `googleai/gemini-embedding-001`, `googleai/gemini-embedding-exp-03-07`.
|
||||
|
|
@ -0,0 +1,248 @@
|
|||
# FastAPI — Genkit Python
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
uv add genkit-plugin-fastapi fastapi uvicorn
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Streaming by default
|
||||
|
||||
The `genkit_fastapi_handler` decorator auto-streams when the client sends `Accept: text/event-stream`.
|
||||
No extra setup — just add the header on the frontend and it works.
|
||||
|
||||
**Wire format (SSE):**
|
||||
```
|
||||
data: {"message": "<chunk text>"} ← one per ctx.send_chunk() call
|
||||
data: {"message": "<chunk text>"}
|
||||
data: {"result": <final output>} ← sent once when flow completes
|
||||
```
|
||||
|
||||
**Frontend (JS EventSource):**
|
||||
```js
|
||||
const res = await fetch('/flow/chat', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', 'Accept': 'text/event-stream' },
|
||||
body: JSON.stringify({ data: { topic: 'quantum computing' } }),
|
||||
});
|
||||
const reader = res.body.getReader();
|
||||
// decode and parse each `data: {...}` line
|
||||
```
|
||||
|
||||
**curl test:**
|
||||
```bash
|
||||
curl -N -X POST http://localhost:8080/flow/chat \
|
||||
-H 'Content-Type: application/json' \
|
||||
-H 'Accept: text/event-stream' \
|
||||
-d '{"data": {"topic": "quantum computing"}}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Minimal streaming FastAPI app
|
||||
|
||||
```python
|
||||
import uvicorn
|
||||
from pydantic import BaseModel
|
||||
from fastapi import FastAPI
|
||||
from genkit import Genkit
|
||||
from genkit import ActionRunContext
|
||||
from genkit.plugins.fastapi import genkit_fastapi_handler
|
||||
from genkit.plugins.google_genai import GoogleAI
|
||||
|
||||
ai = Genkit(plugins=[GoogleAI()], model='googleai/gemini-flash-latest')
|
||||
app = FastAPI()
|
||||
|
||||
class ChatInput(BaseModel):
|
||||
topic: str
|
||||
|
||||
@app.post('/flow/chat', response_model=None)
|
||||
@genkit_fastapi_handler(ai)
|
||||
@ai.flow()
|
||||
async def chat(input: ChatInput, ctx: ActionRunContext) -> str:
|
||||
sr = ai.generate_stream(prompt=f'Tell me about {input.topic}.')
|
||||
full = ''
|
||||
async for chunk in sr.stream:
|
||||
if chunk.text:
|
||||
ctx.send_chunk(chunk.text) # each chunk → SSE event on the wire
|
||||
full += chunk.text
|
||||
return full
|
||||
|
||||
if __name__ == '__main__':
|
||||
uvicorn.run(app, host='0.0.0.0', port=8080)
|
||||
```
|
||||
|
||||
**Key:** flow must accept `ctx: ActionRunContext` and call `ctx.send_chunk(text)` to emit SSE chunks.
|
||||
Without `ctx.send_chunk`, the flow runs but streams nothing — client waits for the final result.
|
||||
|
||||
---
|
||||
|
||||
## Advanced Use Cases
|
||||
|
||||
### Fine-grained control over flow streaming
|
||||
|
||||
Complex apps chain flows — a parent orchestrates children. Chunks propagate upward by **passing `ctx` to child flows**.
|
||||
|
||||
```python
|
||||
class ResearchInput(BaseModel):
|
||||
topic: str
|
||||
|
||||
@ai.flow()
|
||||
async def research(input: ResearchInput, ctx: ActionRunContext) -> str:
|
||||
"""Child flow — streams its generate_stream chunks to whoever called it."""
|
||||
sr = ai.generate_stream(prompt=f'Explain {input.topic} in depth.')
|
||||
full = ''
|
||||
async for chunk in sr.stream:
|
||||
if chunk.text:
|
||||
ctx.send_chunk(chunk.text) # propagates up through the call stack
|
||||
full += chunk.text
|
||||
return full
|
||||
|
||||
|
||||
class HeadlineInput(BaseModel):
|
||||
text: str
|
||||
|
||||
@ai.flow()
|
||||
async def make_headline(input: HeadlineInput) -> str:
|
||||
"""Child flow — non-streaming, returns instantly."""
|
||||
response = await ai.generate(prompt=f'One-line headline for: {input.text}')
|
||||
return response.text.strip()
|
||||
|
||||
|
||||
class ReportInput(BaseModel):
|
||||
topic: str
|
||||
|
||||
@app.post('/flow/report', response_model=None)
|
||||
@genkit_fastapi_handler(ai)
|
||||
@ai.flow()
|
||||
async def report(input: ReportInput, ctx: ActionRunContext) -> str:
|
||||
"""Parent flow — calls children, composes a streaming report."""
|
||||
# Step 1: fast non-streaming call
|
||||
headline = await make_headline(HeadlineInput(text=input.topic))
|
||||
ctx.send_chunk(f'# {headline}\n\n') # send headline immediately
|
||||
|
||||
# Step 2: child flow streams its chunks — passes ctx so they flow up
|
||||
body = await research(ResearchInput(topic=input.topic), ctx)
|
||||
|
||||
return f'# {headline}\n\n{body}'
|
||||
```
|
||||
|
||||
**Rules for nested streaming:**
|
||||
- Child flows that should stream must also accept `ctx: ActionRunContext`
|
||||
- Pass the parent's `ctx` when calling child flows: `await child(input, ctx)`
|
||||
- Non-streaming child flows don't need `ctx` — just `await` them normally
|
||||
- A child that doesn't call `ctx.send_chunk` contributes nothing to the stream (fine for parallel data fetching)
|
||||
|
||||
### Executing flows in parallel
|
||||
|
||||
Use `asyncio.gather` to run multiple flows concurrently. Only makes sense when children don't need to stream.
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
|
||||
class AnalysisInput(BaseModel):
|
||||
text: str
|
||||
|
||||
class CheckResult(BaseModel):
|
||||
issues: list[str]
|
||||
|
||||
class CombinedAnalysis(BaseModel):
|
||||
issues: list[str]
|
||||
|
||||
@ai.flow()
|
||||
async def check_security(input: AnalysisInput) -> CheckResult:
|
||||
# Here the model reviews the text; replace with your real prompt/schema as needed.
|
||||
r = await ai.generate(
|
||||
prompt=f'List security concerns as a short comma-separated line (or "none"): {input.text[:2000]}',
|
||||
)
|
||||
raw = (r.text or '').strip()
|
||||
issues = [s.strip() for s in raw.split(',') if s.strip() and s.strip().lower() != 'none']
|
||||
return CheckResult(issues=issues)
|
||||
|
||||
@ai.flow()
|
||||
async def check_bugs(input: AnalysisInput) -> CheckResult:
|
||||
# Model lists possible bugs; tune prompt for your codebase.
|
||||
r = await ai.generate(
|
||||
prompt=f'List likely bugs or correctness issues as a short comma-separated line (or "none"): {input.text[:2000]}',
|
||||
)
|
||||
raw = (r.text or '').strip()
|
||||
issues = [s.strip() for s in raw.split(',') if s.strip() and s.strip().lower() != 'none']
|
||||
return CheckResult(issues=issues)
|
||||
|
||||
@ai.flow()
|
||||
async def check_style(input: AnalysisInput) -> CheckResult:
|
||||
# Model suggests style/clarity issues; optional: use output_schema for structured rows.
|
||||
r = await ai.generate(
|
||||
prompt=f'List style or clarity issues as a short comma-separated line (or "none"): {input.text[:2000]}',
|
||||
)
|
||||
raw = (r.text or '').strip()
|
||||
issues = [s.strip() for s in raw.split(',') if s.strip() and s.strip().lower() != 'none']
|
||||
return CheckResult(issues=issues)
|
||||
|
||||
@app.post('/flow/analyze', response_model=None)
|
||||
@genkit_fastapi_handler(ai)
|
||||
@ai.flow()
|
||||
async def analyze(input: AnalysisInput) -> CombinedAnalysis:
|
||||
security, bugs, style = await asyncio.gather(
|
||||
check_security(input),
|
||||
check_bugs(input),
|
||||
check_style(input),
|
||||
)
|
||||
return CombinedAnalysis(issues=security.issues + bugs.issues + style.issues)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Structured output endpoint (non-streaming)
|
||||
|
||||
```python
|
||||
class SentimentResult(BaseModel):
|
||||
sentiment: str # positive / negative / neutral
|
||||
confidence: float # 0.0–1.0
|
||||
key_phrases: list[str]
|
||||
|
||||
@app.post('/flow/sentiment', response_model=None)
|
||||
@genkit_fastapi_handler(ai)
|
||||
@ai.flow()
|
||||
async def sentiment(input: AnalysisInput) -> SentimentResult:
|
||||
response = await ai.generate(
|
||||
prompt=f'Analyze sentiment: {input.text}',
|
||||
output_format='json',
|
||||
output_schema=SentimentResult,
|
||||
)
|
||||
return response.output
|
||||
```
|
||||
|
||||
Client calls this without `Accept: text/event-stream` — gets `{"result": {...}}` back.
|
||||
|
||||
---
|
||||
|
||||
## Decorator order
|
||||
|
||||
Must be exactly: `@app.post` → `@genkit_fastapi_handler(ai)` → `@ai.flow()`
|
||||
|
||||
```python
|
||||
@app.post('/flow/chat', response_model=None) # 1. FastAPI route
|
||||
@genkit_fastapi_handler(ai) # 2. Genkit wire format + streaming
|
||||
@ai.flow() # 3. Flow registration
|
||||
async def chat(input: ChatInput, ctx: ActionRunContext) -> str:
|
||||
...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Run with Dev UI
|
||||
|
||||
```bash
|
||||
GEMINI_API_KEY=your-key genkit start -- uv run src/main.py
|
||||
```
|
||||
|
||||
Leave the process running until the CLI prints something like:
|
||||
|
||||
```
|
||||
Genkit Developer UI: http://localhost:4000
|
||||
```
|
||||
|
||||
Open that URL. Port may differ if 4000 is busy.
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
# Setup — Genkit Python
|
||||
|
||||
## New project
|
||||
|
||||
**Always use a virtual environment** — never install Genkit into the system interpreter. With **uv**, the project’s **`.venv`** is created and used by `uv sync` / `uv run` automatically once you add dependencies.
|
||||
|
||||
```bash
|
||||
mkdir my-app && cd my-app
|
||||
uv init
|
||||
uv venv --python 3.14 .venv
|
||||
# Unix: source .venv/bin/activate
|
||||
# Windows: .venv\Scripts\activate
|
||||
uv add genkit genkit-plugin-google-genai
|
||||
export GEMINI_API_KEY=your_key_here
|
||||
```
|
||||
|
||||
`uv init` creates `pyproject.toml`. Add your app under something like `src/main.py` (or match whatever layout `uv` generated) and point `genkit start` at that entrypoint.
|
||||
|
||||
## pyproject.toml
|
||||
|
||||
Minimal `[project]` block with unpinned Genkit deps (resolver picks compatible releases):
|
||||
|
||||
```toml
|
||||
[project]
|
||||
name = "my-app"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.14"
|
||||
dependencies = [
|
||||
"genkit",
|
||||
"genkit-plugin-google-genai",
|
||||
]
|
||||
```
|
||||
|
||||
## Plugins
|
||||
|
||||
Packages are **`genkit-plugin-*`** on PyPI, e.g. `genkit-plugin-google-genai`, `genkit-plugin-vertex-ai`, `genkit-plugin-anthropic`, `genkit-plugin-fastapi`. Install with `uv add genkit-plugin-<name>`.
|
||||
|
||||
## Python version
|
||||
|
||||
**3.14+**. Always use a `venv` using `uv venv --python 3.14 .venv` when creating the environment before you run any commands.
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
---
|
||||
name: firebase-ai-logic
|
||||
description: Official skill for integrating Firebase AI Logic (Gemini API) into web applications. Covers setup, multimodal inference, structured output, and security.
|
||||
version: 1.0.0
|
||||
---
|
||||
|
||||
# Firebase AI Logic Basics
|
||||
|
||||
## Overview
|
||||
|
||||
Firebase AI Logic is a product of Firebase that allows developers to add gen AI to their mobile and web apps using client-side SDKs. You can call Gemini models directly from your app without managing a dedicated backend. Firebase AI Logic, which was previously known as "Vertex AI for Firebase", represents the evolution of Google's AI integration platform for mobile and web developers.
|
||||
|
||||
It supports the two Gemini API providers:
|
||||
- **Gemini Developer API**: It has a free tier ideal for prototyping, and pay-as-you-go for production
|
||||
- **Vertex AI Gemini API**: Ideal for scale with enterprise-grade production readiness, requires Blaze plan
|
||||
|
||||
Use the Gemini Developer API as a default, and only Vertex AI Gemini API if the application requires it.
|
||||
|
||||
## Setup & Initialization
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Before starting, ensure you have **Node.js 16+** and npm installed. Install them if they aren’t already available.
|
||||
- Identify the platform the user is interested in building on prior to starting: Android, iOS, Flutter or Web.
|
||||
- If their platform is unsupported, Direct the user to Firebase Docs to learn how to set up AI Logic for their application (share this link with the user https://firebase.google.com/docs/ai-logic/get-started)
|
||||
|
||||
### Installation
|
||||
|
||||
The library is part of the standard Firebase Web SDK.
|
||||
|
||||
`npm install -g firebase@latest`
|
||||
|
||||
If you're in a firebase directory (with a firebase.json) the currently selected project will be marked with "current" using this command:
|
||||
|
||||
`npx -y firebase-tools@latest projects:list`
|
||||
|
||||
Ensure there's at least one app associated with the current project
|
||||
|
||||
`npx -y firebase-tools@latest apps:list`
|
||||
|
||||
Initialize AI logic SDK with the init command
|
||||
|
||||
`npx -y firebase-tools@latest init # Choose AI logic`
|
||||
|
||||
This will automatically enable the Gemini Developer API in the Firebase console.
|
||||
|
||||
More info in [Firebase AI Logic Getting Started](https://firebase.google.com/docs/ai-logic/get-started.md.txt)
|
||||
|
||||
## Core Capabilities
|
||||
|
||||
### Text-Only Generation
|
||||
|
||||
### Multimodal (Text + Images/Audio/Video/PDF input)
|
||||
|
||||
Firebase AI Logic allows Gemini models to analyze image files directly from your app. This enables features like creating captions, answering questions about images, detecting objects, and categorizing images. Beyond images, Gemini can analyze other media types like audio, video, and PDFs by passing them as inline data with their MIME type. For files larger than 20 megabytes (which can cause HTTP 413 errors as inline data), store them in Cloud Storage for Firebase and pass their URLs to the Gemini Developer API.
|
||||
|
||||
### Chat Session (Multi-turn)
|
||||
|
||||
Maintain history automatically using `startChat`.
|
||||
|
||||
### Streaming Responses
|
||||
|
||||
To improve the user experience by showing partial results as they arrive (like a typing effect), use `generateContentStream` instead of `generateContent` for faster display of results.
|
||||
|
||||
### Generate Images with Nano Banana
|
||||
|
||||
- Start with Gemini for most use cases, and choose Imagen for specialized tasks where image quality and specific styles are critical. (Example: gemini-2.5-flash-image)
|
||||
- Requires an upgraded Blaze pay-as-you-go billing plan.
|
||||
|
||||
### Search Grounding with the built in googleSearch tool
|
||||
|
||||
## Supported Platforms and Frameworks
|
||||
|
||||
Supported Platforms and Frameworks include Kotlin and Java for Android, Swift for iOS, JavaScript for web apps, Dart for Flutter, and C Sharp for Unity.
|
||||
|
||||
## Advanced Features
|
||||
|
||||
### Structured Output (JSON)
|
||||
|
||||
Enforce a specific JSON schema for the response.
|
||||
|
||||
### On-Device AI (Hybrid)
|
||||
|
||||
Hybrid on-device inference for web apps, where the Firebase Javascript SDK automatically checks for Gemini Nano's availability (after installation) and switches between on-device or cloud-hosted prompt execution. This requires specific steps to enable model usage in the Chrome browser, more info in the [hybrid-on-device-inference documentation](https://firebase.google.com/docs/ai-logic/hybrid-on-device-inference.md.txt).
|
||||
|
||||
## Security & Production
|
||||
|
||||
### App Check
|
||||
|
||||
> [!WARNING]
|
||||
> **Critical Safety Requirement:** In order to use AI Logic safely, you MUST set up App Check on your app. This prevents unauthorized clients from using your API quota and accessing your backend resources.
|
||||
|
||||
See [App Check with reCAPTCHA Enterprise](https://firebase.google.com/docs/app-check/web/recaptcha-enterprise-provider.md.txt) for setup instructions.
|
||||
|
||||
### Remote Config
|
||||
|
||||
Consider that you do not need to hardcode model names (e.g., `gemini-flash-lite-latest`). Use Firebase Remote Config to update model versions dynamically without deploying new client code. See [Changing model names remotely](https://firebase.google.com/docs/ai-logic/change-model-name-remotely.md.txt)
|
||||
|
||||
## Initialization Code References
|
||||
|
||||
| Language, Framework, Platform | Gemini API provider | Context URL |
|
||||
| :---- | :---- | :---- |
|
||||
| Web Modular API | Gemini Developer API (Developer API) | firebase://docs/ai-logic/get-started |
|
||||
|
||||
**Always use the most recent version of Gemini (gemini-flash-latest) unless another model is requested by the docs or the user. DO NOT USE gemini-1.5-flash**
|
||||
|
||||
## References
|
||||
|
||||
[Web SDK code examples and usage patterns](references/usage_patterns_web.md)
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,174 @@
|
|||
# Firebase AI Logic Basics
|
||||
|
||||
## Initialization Pattern
|
||||
You must initialize the ai-logic service after the main Firebase App.
|
||||
```JavaScript
|
||||
import { initializeApp } from "firebase/app";
|
||||
import { getAI, getGenerativeModel, GoogleAIBackend } from "firebase/ai";
|
||||
|
||||
|
||||
// If running in Firebase App Hosting, you can skip Firebase Config and instead use:
|
||||
// const app = initializeApp();
|
||||
|
||||
const firebaseConfig = {
|
||||
// ... your firebase config
|
||||
};
|
||||
|
||||
const app = initializeApp(firebaseConfig);
|
||||
|
||||
// Initialize the AI Logic service (defaults to Gemini Developer API)
|
||||
// To set the AI provider, set the backend as the second parameter
|
||||
const ai = getAI(firebaseApp, { backend: new GoogleAIBackend() });
|
||||
|
||||
const generationConfig = {
|
||||
candidate_count: 1,
|
||||
maxOutputTokens: 2048,
|
||||
stopSequences: [],
|
||||
temperature: 0.7, // Balanced: creative but focused
|
||||
topP: 0.95, // Standard: allows a wide range of probable tokens
|
||||
topK: 40, // Standard: considers the top 40 tokens
|
||||
};
|
||||
|
||||
// Specify the config as part of creating the `GenerativeModel` instance
|
||||
const model = getGenerativeModel(ai, { model: "gemini-2.5-flash-lite", generationConfig });
|
||||
```
|
||||
|
||||
## Core Capabilities
|
||||
Text-Only Generation
|
||||
```JavaScript
|
||||
async function generateText(prompt) {
|
||||
const result = await model.generateContent(prompt);
|
||||
const response = await result.response;
|
||||
return response.text();
|
||||
}
|
||||
```
|
||||
|
||||
## Multimodal (Text + Images/Audio/Video/PDF input)
|
||||
Firebase AI Logic accepts Base64 encoded data or specific file references.
|
||||
```JavaScript
|
||||
// Helper to convert file to base64 generic object
|
||||
async function fileToGenerativePart(file) {
|
||||
const base64EncodedDataPromise = new Promise((resolve) => {
|
||||
const reader = new FileReader();
|
||||
reader.onloadend = () => resolve(reader.result.split(',')[1]);
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
|
||||
return {
|
||||
inlineData: {
|
||||
data: await base64EncodedDataPromise,
|
||||
mimeType: file.type,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async function analyzeImage(prompt, imageFile) {
|
||||
const imagePart = await fileToGenerativePart(imageFile);
|
||||
const result = await model.generateContent([prompt, imagePart]);
|
||||
return result.response.text();
|
||||
}
|
||||
```
|
||||
|
||||
## Chat Session (Multi-turn)
|
||||
Maintain history automatically using startChat.
|
||||
```JavaScript
|
||||
const chat = model.startChat({
|
||||
history: [
|
||||
{
|
||||
role: "user",
|
||||
parts: [{ text: "Hello, I am a developer." }],
|
||||
},
|
||||
{
|
||||
role: "model",
|
||||
parts: [{ text: "Great to meet you. How can I help with code?" }],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
async function sendMessage(msg) {
|
||||
const result = await chat.sendMessage(msg);
|
||||
return result.response.text();
|
||||
}
|
||||
```
|
||||
|
||||
## Streaming Responses
|
||||
For real-time UI updates (like a typing effect).
|
||||
```JavaScript
|
||||
async function streamResponse(prompt) {
|
||||
const result = await model.generateContentStream(prompt);
|
||||
for await (const chunk of result.stream) {
|
||||
const chunkText = chunk.text();
|
||||
console.log("Stream chunk:", chunkText);
|
||||
// Update UI here
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Generate Images with Nano Banana
|
||||
|
||||
```Javascript
|
||||
import { initializeApp } from "firebase/app";
|
||||
import { getAI, getGenerativeModel, GoogleAIBackend, ResponseModality } from "firebase/ai";
|
||||
|
||||
|
||||
// Initialize FirebaseApp
|
||||
const firebaseApp = initializeApp(firebaseConfig);
|
||||
|
||||
// Initialize the Gemini Developer API backend service
|
||||
const ai = getAI(firebaseApp, { backend: new GoogleAIBackend() });
|
||||
|
||||
// Create a `GenerativeModel` instance with a model that supports your use case
|
||||
const model = getGenerativeModel(ai, {
|
||||
model: "gemini-2.5-flash-image",
|
||||
// Configure the model to respond with text and images (required)
|
||||
generationConfig: {
|
||||
responseModalities: [ResponseModality.TEXT, ResponseModality.IMAGE],
|
||||
},
|
||||
});
|
||||
|
||||
// Provide a text prompt instructing the model to generate an image
|
||||
const prompt = 'Generate an image of the Eiffel Tower with fireworks in the background.';
|
||||
|
||||
// To generate an image, call `generateContent` with the text input
|
||||
const result = model.generateContent(prompt);
|
||||
|
||||
// Handle the generated image
|
||||
try {
|
||||
const inlineDataParts = result.response.inlineDataParts();
|
||||
if (inlineDataParts?.[0]) {
|
||||
const image = inlineDataParts[0].inlineData;
|
||||
console.log(image.mimeType, image.data);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Prompt or candidate was blocked:', err);
|
||||
}
|
||||
```
|
||||
|
||||
## Advanced Features
|
||||
Structured Output (JSON)
|
||||
Enforce a specific JSON schema for the response.
|
||||
```JavaScript
|
||||
import { getGenerativeModel, Schema } from "firebase/ai";
|
||||
const jsonModel = getGenerativeModel(ai, {
|
||||
model: "gemini-2.5-flash-lite",
|
||||
generationConfig: {
|
||||
responseMimeType: "application/json",
|
||||
// Optional: Define a schema
|
||||
schema = Schema.object({ ... });
|
||||
}
|
||||
});
|
||||
|
||||
async function getJsonData(prompt) {
|
||||
const result = await jsonModel.generateContent(prompt);
|
||||
return JSON.parse(result.response.text());
|
||||
}
|
||||
```
|
||||
|
||||
On-Device AI (Hybrid)
|
||||
Automatically switch between local Gemini Nano and cloud models based on device capability.
|
||||
```JavaScript
|
||||
import {getGenerativeModel, InferenceMode } from "firebase/ai";
|
||||
|
||||
const hybridModel = getGenerativeModel(ai, { mode: InferenceMode.PREFER_ON_DEVICE });
|
||||
```
|
||||
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
manifest.json,1773193494224,aa3b82b9337a50b4ec6715aa37b8975cc7c2a396d8436ea9410fb2cc0c47e1bf
|
||||
flutter.js,1775027253022,f18220d166d49cbef02491208c46f2c5e54db9739184e304fc55b199b5724407
|
||||
favicon.png,1745728186331,19880e004e8357a51f6a64f5d1fdc8828a6432fe9c781f8acb6b730eced2cd13
|
||||
icons/Icon-maskable-512.png,1758190847689,ea58e5c49bc4dcdc5db0a9fe2f25b2a3152f85ef9094f0d020de0aa318451a1d
|
||||
icons/Icon-maskable-192.png,1758190847697,f7ac56197638aec92f195c5344d15395c2cbf8a5ae74e846c5b4156b64f05e38
|
||||
icons/Icon-512.png,1745728186333,1de493563deb283e6ad70f0d5a89cfd61e37343df3b919c9862c5a41699ac5bc
|
||||
icons/Icon-192.png,1745728186332,5fd961c51d8eb53baacac01f8e53743a76f66a43390a11f5df042e8c618ad64a
|
||||
canvaskit/wimp.wasm,1775027252662,77abd773ba1776b6ce7b029866621aa42ec8a22dc43dd220af4896049260cb0a
|
||||
canvaskit/wimp.js.symbols,1775027252629,cb643fbb407efb3d6735f5909c4ffd84bed8c3d8ce4840f72eb06471b18d8454
|
||||
canvaskit/wimp.js,1775027252578,23db45b93eec24dd6db46ac5a6fafbf0063eb50c0dcd7d253f774e050ab1baa3
|
||||
canvaskit/skwasm_heavy.wasm,1775027252578,acebd34ce89ba97c086405163d12ce74336e41bb7cca4e2fcd5011ecfabd945c
|
||||
canvaskit/skwasm_heavy.js.symbols,1775027252529,d43798c11b7dbd26872e27d20df1c6c43637ebb95b4cc41fd19ff092ab2565bd
|
||||
canvaskit/skwasm_heavy.js,1775027252479,dd3642e247a1ea4a8c519b76278011bb194471c590373ae2659f1d3cb793287c
|
||||
canvaskit/skwasm.wasm,1775027252472,ad393be86fb17a251e0af39e56bcae5c26a4b9f20adbce626e0b7df796d89368
|
||||
canvaskit/skwasm.js.symbols,1775027252441,91b787a0cd397c3cd089dc81519200c5bc35bfafa2996bb5ed4b1159553973f5
|
||||
canvaskit/skwasm.js,1775027252386,91ea675d523ad0a19696479888658953e290f9798a43dae6b87d9ab60cc40c1a
|
||||
canvaskit/canvaskit.wasm,1775027251440,67768ebd7649dfe70c348374dc34b732152d42467c2c335701958ba1be0949c8
|
||||
canvaskit/canvaskit.js.symbols,1775027251336,a03cf8cdedf01be4a751225fa1bceadf8a783cb512ff87336bf76bb329fb29db
|
||||
canvaskit/canvaskit.js,1775027251076,e0e67f4b85a07ff9b9861c9f49bf88860368ec96fb4115c0d4684f54596f9700
|
||||
canvaskit/chromium/canvaskit.wasm,1775027252382,b3c17e127e4e8e4912409c050050f6f63a718b6f9618a7ec388948f2493174a6
|
||||
canvaskit/chromium/canvaskit.js.symbols,1775027252321,17bdb78398d7be0b2662ad813c7f9387f79014aee6f1e3b7bf39dfe81e6b8b90
|
||||
canvaskit/chromium/canvaskit.js,1775027251440,ed67753aa5382460fefd96876bf5dc40c5d242d56b37664abc8468a6772478df
|
||||
version.json,1779683104794,06837fdb0bc519a4a874fb88563a1eb9e7e33bb39b77b28edd4c54b59b6afd7a
|
||||
flutter_service_worker.js,1779683109173,c73b271847ffc78c576de35e95f6b46187eefb1d97dcb08eb9d84c45954ff310
|
||||
index.html,1779683020112,78d10923677351d4ff7d6b283d85780271b1b52dffdc21026d43c84af1baff07
|
||||
assets/FontManifest.json,1779683105459,e38b95988f5d060cf9b7ce97cb5ac9236d6f4cc04a11d69567df97b2b4cbc5e5
|
||||
flutter_bootstrap.js,1779683020047,b6939c341d98fca0d0afb8303548dce06ab1b7c07c370bdedc335f28609367f6
|
||||
assets/AssetManifest.bin.json,1779683105459,6763aaa55146ec9534c671aaf5a6db93265eaaa8082767ea02eb2ac9b09cbdaa
|
||||
assets/AssetManifest.bin,1779683105459,93e425eb6f3c87a588cd708f748fc5bb80fa28bf34c6d814b2bd76daa6a6ed27
|
||||
assets/shaders/stretch_effect.frag,1779683105794,df37894a7ac7854986bfa5d515e9af03d9138bb77be4d027db848785ebe9e357
|
||||
assets/shaders/ink_sparkle.frag,1779683105735,9d529f4d93e5dabf338537416221237a0042f9cee61c98a357340d0273c67293
|
||||
assets/packages/cupertino_icons/assets/CupertinoIcons.ttf,1779683108810,d41473de1f7708a0702d7f19327693486512db442f6ab0cf7774e6d6576f9fcb
|
||||
assets/fonts/MaterialIcons-Regular.otf,1779683108858,b5df634ac5dd96e6ed66c12aad88379f5595dd8a998855c9a3720b04fb6a1338
|
||||
assets/NOTICES,1779683105460,3f2e3b6c157a7904e4d071bc731a96781c7bb4935535646da50e7ec5c18cbc4e
|
||||
main.dart.js,1779683072757,5e749727719555fe5955ccc68e603e70fc6f7193dab9fc48758a7f40e452cfe9
|
||||
|
|
@ -0,0 +1 @@
|
|||
index.html,1777260485174,8f146c33f8eb815f8f6cfc64c024b9fd58014ce84b6f88633095aa7f1c7f3735
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"projects": {
|
||||
"default": "monitoring-20780"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
# Miscellaneous
|
||||
*.class
|
||||
*.log
|
||||
*.pyc
|
||||
*.swp
|
||||
.DS_Store
|
||||
.atom/
|
||||
.build/
|
||||
.buildlog/
|
||||
.history
|
||||
.svn/
|
||||
.swiftpm/
|
||||
migrate_working_dir/
|
||||
|
||||
# IntelliJ related
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea/
|
||||
|
||||
# The .vscode folder contains launch configuration and tasks you configure in
|
||||
# VS Code which you may wish to be included in version control, so this line
|
||||
# is commented out by default.
|
||||
#.vscode/
|
||||
|
||||
# Flutter/Dart/Pub related
|
||||
**/doc/api/
|
||||
**/ios/Flutter/.last_build_id
|
||||
.dart_tool/
|
||||
.flutter-plugins-dependencies
|
||||
.pub-cache/
|
||||
.pub/
|
||||
/build/
|
||||
/coverage/
|
||||
|
||||
# Symbolication related
|
||||
app.*.symbols
|
||||
|
||||
# Obfuscation related
|
||||
app.*.map.json
|
||||
|
||||
# Android Studio will place build artifacts here
|
||||
/android/app/debug
|
||||
/android/app/profile
|
||||
/android/app/release
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
# This file tracks properties of this Flutter project.
|
||||
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||
#
|
||||
# This file should be version controlled and should not be manually edited.
|
||||
|
||||
version:
|
||||
revision: "d693b4b9dbac2acd4477aea4555ca6dcbea44ba2"
|
||||
channel: "stable"
|
||||
|
||||
project_type: app
|
||||
|
||||
# Tracks metadata for the flutter migrate command
|
||||
migration:
|
||||
platforms:
|
||||
- platform: root
|
||||
create_revision: d693b4b9dbac2acd4477aea4555ca6dcbea44ba2
|
||||
base_revision: d693b4b9dbac2acd4477aea4555ca6dcbea44ba2
|
||||
- platform: android
|
||||
create_revision: d693b4b9dbac2acd4477aea4555ca6dcbea44ba2
|
||||
base_revision: d693b4b9dbac2acd4477aea4555ca6dcbea44ba2
|
||||
- platform: ios
|
||||
create_revision: d693b4b9dbac2acd4477aea4555ca6dcbea44ba2
|
||||
base_revision: d693b4b9dbac2acd4477aea4555ca6dcbea44ba2
|
||||
- platform: linux
|
||||
create_revision: d693b4b9dbac2acd4477aea4555ca6dcbea44ba2
|
||||
base_revision: d693b4b9dbac2acd4477aea4555ca6dcbea44ba2
|
||||
- platform: macos
|
||||
create_revision: d693b4b9dbac2acd4477aea4555ca6dcbea44ba2
|
||||
base_revision: d693b4b9dbac2acd4477aea4555ca6dcbea44ba2
|
||||
- platform: web
|
||||
create_revision: d693b4b9dbac2acd4477aea4555ca6dcbea44ba2
|
||||
base_revision: d693b4b9dbac2acd4477aea4555ca6dcbea44ba2
|
||||
- platform: windows
|
||||
create_revision: d693b4b9dbac2acd4477aea4555ca6dcbea44ba2
|
||||
base_revision: d693b4b9dbac2acd4477aea4555ca6dcbea44ba2
|
||||
|
||||
# User provided section
|
||||
|
||||
# List of Local paths (relative to this file) that should be
|
||||
# ignored by the migrate tool.
|
||||
#
|
||||
# Files that are not part of the templates will be ignored by default.
|
||||
unmanaged_files:
|
||||
- 'lib/main.dart'
|
||||
- 'ios/Runner.xcodeproj/project.pbxproj'
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
# smartac
|
||||
|
||||
A new Flutter project.
|
||||
|
||||
## Getting Started
|
||||
|
||||
This project is a starting point for a Flutter application.
|
||||
|
||||
A few resources to get you started if this is your first Flutter project:
|
||||
|
||||
- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
|
||||
- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
|
||||
|
||||
For help getting started with Flutter development, view the
|
||||
[online documentation](https://docs.flutter.dev/), which offers tutorials,
|
||||
samples, guidance on mobile development, and a full API reference.
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
# This file configures the analyzer, which statically analyzes Dart code to
|
||||
# check for errors, warnings, and lints.
|
||||
#
|
||||
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
|
||||
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
|
||||
# invoked from the command line by running `flutter analyze`.
|
||||
|
||||
# The following line activates a set of recommended lints for Flutter apps,
|
||||
# packages, and plugins designed to encourage good coding practices.
|
||||
include: package:flutter_lints/flutter.yaml
|
||||
|
||||
linter:
|
||||
# The lint rules applied to this project can be customized in the
|
||||
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
|
||||
# included above or to enable additional rules. A list of all available lints
|
||||
# and their documentation is published at https://dart.dev/lints.
|
||||
#
|
||||
# Instead of disabling a lint rule for the entire project in the
|
||||
# section below, it can also be suppressed for a single line of code
|
||||
# or a specific dart file by using the `// ignore: name_of_lint` and
|
||||
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
|
||||
# producing the lint.
|
||||
rules:
|
||||
# avoid_print: false # Uncomment to disable the `avoid_print` rule
|
||||
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
|
||||
|
||||
# Additional information about this file can be found at
|
||||
# https://dart.dev/guides/language/analysis-options
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
gradle-wrapper.jar
|
||||
/.gradle
|
||||
/captures/
|
||||
/gradlew
|
||||
/gradlew.bat
|
||||
/local.properties
|
||||
GeneratedPluginRegistrant.java
|
||||
.cxx/
|
||||
|
||||
# Remember to never publicly share your keystore.
|
||||
# See https://flutter.dev/to/reference-keystore
|
||||
key.properties
|
||||
**/*.keystore
|
||||
**/*.jks
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
plugins {
|
||||
id("com.android.application")
|
||||
id("kotlin-android")
|
||||
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
|
||||
id("dev.flutter.flutter-gradle-plugin")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.example.smartac"
|
||||
compileSdk = flutter.compileSdkVersion
|
||||
ndkVersion = flutter.ndkVersion
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = JavaVersion.VERSION_11.toString()
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||
applicationId = "com.example.smartac"
|
||||
// You can update the following values to match your application needs.
|
||||
// For more information, see: https://flutter.dev/to/review-gradle-config.
|
||||
minSdk = flutter.minSdkVersion
|
||||
targetSdk = flutter.targetSdkVersion
|
||||
versionCode = flutter.versionCode
|
||||
versionName = flutter.versionName
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
// TODO: Add your own signing config for the release build.
|
||||
// Signing with the debug keys for now, so `flutter run --release` works.
|
||||
signingConfig = signingConfigs.getByName("debug")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
flutter {
|
||||
source = "../.."
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- The INTERNET permission is required for development. Specifically,
|
||||
the Flutter tool needs it to communicate with the running application
|
||||
to allow setting breakpoints, to provide hot reload, etc.
|
||||
-->
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
</manifest>
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<application
|
||||
android:label="smartac"
|
||||
android:name="${applicationName}"
|
||||
android:icon="@mipmap/ic_launcher">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
android:launchMode="singleTop"
|
||||
android:taskAffinity=""
|
||||
android:theme="@style/LaunchTheme"
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||
android:hardwareAccelerated="true"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<!-- Specifies an Android theme to apply to this Activity as soon as
|
||||
the Android process has started. This theme is visible to the user
|
||||
while the Flutter UI initializes. After that, this theme continues
|
||||
to determine the Window background behind the Flutter UI. -->
|
||||
<meta-data
|
||||
android:name="io.flutter.embedding.android.NormalTheme"
|
||||
android:resource="@style/NormalTheme"
|
||||
/>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<!-- Don't delete the meta-data below.
|
||||
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||
<meta-data
|
||||
android:name="flutterEmbedding"
|
||||
android:value="2" />
|
||||
</application>
|
||||
<!-- Required to query activities that can process text, see:
|
||||
https://developer.android.com/training/package-visibility and
|
||||
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
|
||||
|
||||
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.PROCESS_TEXT"/>
|
||||
<data android:mimeType="text/plain"/>
|
||||
</intent>
|
||||
</queries>
|
||||
</manifest>
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package com.example.smartac
|
||||
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
|
||||
class MainActivity : FlutterActivity()
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Modify this file to customize your launch splash screen -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="?android:colorBackground" />
|
||||
|
||||
<!-- You can insert your own image assets here -->
|
||||
<!-- <item>
|
||||
<bitmap
|
||||
android:gravity="center"
|
||||
android:src="@mipmap/launch_image" />
|
||||
</item> -->
|
||||
</layer-list>
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Modify this file to customize your launch splash screen -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@android:color/white" />
|
||||
|
||||
<!-- You can insert your own image assets here -->
|
||||
<!-- <item>
|
||||
<bitmap
|
||||
android:gravity="center"
|
||||
android:src="@mipmap/launch_image" />
|
||||
</item> -->
|
||||
</layer-list>
|
||||
|
After Width: | Height: | Size: 544 B |
|
After Width: | Height: | Size: 442 B |
|
After Width: | Height: | Size: 721 B |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
|
||||
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<!-- Show a splash screen on the activity. Automatically removed when
|
||||
the Flutter engine draws its first frame -->
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||
running.
|
||||
|
||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<item name="android:windowBackground">?android:colorBackground</item>
|
||||
</style>
|
||||
</resources>
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
|
||||
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||
<!-- Show a splash screen on the activity. Automatically removed when
|
||||
the Flutter engine draws its first frame -->
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||
running.
|
||||
|
||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||
<item name="android:windowBackground">?android:colorBackground</item>
|
||||
</style>
|
||||
</resources>
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- The INTERNET permission is required for development. Specifically,
|
||||
the Flutter tool needs it to communicate with the running application
|
||||
to allow setting breakpoints, to provide hot reload, etc.
|
||||
-->
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
</manifest>
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
val newBuildDir: Directory =
|
||||
rootProject.layout.buildDirectory
|
||||
.dir("../../build")
|
||||
.get()
|
||||
rootProject.layout.buildDirectory.value(newBuildDir)
|
||||
|
||||
subprojects {
|
||||
val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
|
||||
project.layout.buildDirectory.value(newSubprojectBuildDir)
|
||||
}
|
||||
subprojects {
|
||||
project.evaluationDependsOn(":app")
|
||||
}
|
||||
|
||||
tasks.register<Delete>("clean") {
|
||||
delete(rootProject.layout.buildDirectory)
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
pluginManagement {
|
||||
val flutterSdkPath =
|
||||
run {
|
||||
val properties = java.util.Properties()
|
||||
file("local.properties").inputStream().use { properties.load(it) }
|
||||
val flutterSdkPath = properties.getProperty("flutter.sdk")
|
||||
require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
|
||||
flutterSdkPath
|
||||
}
|
||||
|
||||
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
|
||||
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
|
||||
id("com.android.application") version "8.9.1" apply false
|
||||
id("org.jetbrains.kotlin.android") version "2.1.0" apply false
|
||||
}
|
||||
|
||||
include(":app")
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"hosting": {
|
||||
"public": "build/web",
|
||||
"ignore": [
|
||||
"firebase.json",
|
||||
"**/.*",
|
||||
"**/node_modules/**"
|
||||
],
|
||||
"rewrites": [
|
||||
{
|
||||
"source": "**",
|
||||
"destination": "/index.html"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
**/dgph
|
||||
*.mode1v3
|
||||
*.mode2v3
|
||||
*.moved-aside
|
||||
*.pbxuser
|
||||
*.perspectivev3
|
||||
**/*sync/
|
||||
.sconsign.dblite
|
||||
.tags*
|
||||
**/.vagrant/
|
||||
**/DerivedData/
|
||||
Icon?
|
||||
**/Pods/
|
||||
**/.symlinks/
|
||||
profile
|
||||
xcuserdata
|
||||
**/.generated/
|
||||
Flutter/App.framework
|
||||
Flutter/Flutter.framework
|
||||
Flutter/Flutter.podspec
|
||||
Flutter/Generated.xcconfig
|
||||
Flutter/ephemeral/
|
||||
Flutter/app.flx
|
||||
Flutter/app.zip
|
||||
Flutter/flutter_assets/
|
||||
Flutter/flutter_export_environment.sh
|
||||
ServiceDefinitions.json
|
||||
Runner/GeneratedPluginRegistrant.*
|
||||
|
||||
# Exceptions to above rules.
|
||||
!default.mode1v3
|
||||
!default.mode2v3
|
||||
!default.pbxuser
|
||||
!default.perspectivev3
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>App</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>io.flutter.flutter.app</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>App</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.0</string>
|
||||
<key>MinimumOSVersion</key>
|
||||
<string>13.0</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
@ -0,0 +1 @@
|
|||
#include "Generated.xcconfig"
|
||||
|
|
@ -0,0 +1 @@
|
|||
#include "Generated.xcconfig"
|
||||
|
|
@ -0,0 +1,616 @@
|
|||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 54;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
|
||||
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
|
||||
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
|
||||
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
|
||||
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
|
||||
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
||||
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 97C146E61CF9000F007C117D /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 97C146ED1CF9000F007C117D;
|
||||
remoteInfo = Runner;
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 10;
|
||||
files = (
|
||||
);
|
||||
name = "Embed Frameworks";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
|
||||
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
|
||||
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
|
||||
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
|
||||
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
|
||||
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
|
||||
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
|
||||
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
|
||||
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
97C146EB1CF9000F007C117D /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
331C8082294A63A400263BE5 /* RunnerTests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
331C807B294A618700263BE5 /* RunnerTests.swift */,
|
||||
);
|
||||
path = RunnerTests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
9740EEB11CF90186004384FC /* Flutter */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
|
||||
9740EEB21CF90195004384FC /* Debug.xcconfig */,
|
||||
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
|
||||
9740EEB31CF90195004384FC /* Generated.xcconfig */,
|
||||
);
|
||||
name = Flutter;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
97C146E51CF9000F007C117D = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
9740EEB11CF90186004384FC /* Flutter */,
|
||||
97C146F01CF9000F007C117D /* Runner */,
|
||||
97C146EF1CF9000F007C117D /* Products */,
|
||||
331C8082294A63A400263BE5 /* RunnerTests */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
97C146EF1CF9000F007C117D /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
97C146EE1CF9000F007C117D /* Runner.app */,
|
||||
331C8081294A63A400263BE5 /* RunnerTests.xctest */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
97C146F01CF9000F007C117D /* Runner */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
97C146FA1CF9000F007C117D /* Main.storyboard */,
|
||||
97C146FD1CF9000F007C117D /* Assets.xcassets */,
|
||||
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
|
||||
97C147021CF9000F007C117D /* Info.plist */,
|
||||
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
|
||||
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
|
||||
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
|
||||
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
|
||||
);
|
||||
path = Runner;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
331C8080294A63A400263BE5 /* RunnerTests */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
|
||||
buildPhases = (
|
||||
331C807D294A63A400263BE5 /* Sources */,
|
||||
331C807F294A63A400263BE5 /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
331C8086294A63A400263BE5 /* PBXTargetDependency */,
|
||||
);
|
||||
name = RunnerTests;
|
||||
productName = RunnerTests;
|
||||
productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */;
|
||||
productType = "com.apple.product-type.bundle.unit-test";
|
||||
};
|
||||
97C146ED1CF9000F007C117D /* Runner */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
|
||||
buildPhases = (
|
||||
9740EEB61CF901F6004384FC /* Run Script */,
|
||||
97C146EA1CF9000F007C117D /* Sources */,
|
||||
97C146EB1CF9000F007C117D /* Frameworks */,
|
||||
97C146EC1CF9000F007C117D /* Resources */,
|
||||
9705A1C41CF9048500538489 /* Embed Frameworks */,
|
||||
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = Runner;
|
||||
productName = Runner;
|
||||
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
97C146E61CF9000F007C117D /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
BuildIndependentTargetsInParallel = YES;
|
||||
LastUpgradeCheck = 1510;
|
||||
ORGANIZATIONNAME = "";
|
||||
TargetAttributes = {
|
||||
331C8080294A63A400263BE5 = {
|
||||
CreatedOnToolsVersion = 14.0;
|
||||
TestTargetID = 97C146ED1CF9000F007C117D;
|
||||
};
|
||||
97C146ED1CF9000F007C117D = {
|
||||
CreatedOnToolsVersion = 7.3.1;
|
||||
LastSwiftMigration = 1100;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
|
||||
compatibilityVersion = "Xcode 9.3";
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
);
|
||||
mainGroup = 97C146E51CF9000F007C117D;
|
||||
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
97C146ED1CF9000F007C117D /* Runner */,
|
||||
331C8080294A63A400263BE5 /* RunnerTests */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
331C807F294A63A400263BE5 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
97C146EC1CF9000F007C117D /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
|
||||
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
|
||||
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
|
||||
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
alwaysOutOfDate = 1;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
|
||||
);
|
||||
name = "Thin Binary";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
|
||||
};
|
||||
9740EEB61CF901F6004384FC /* Run Script */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
alwaysOutOfDate = 1;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "Run Script";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
331C807D294A63A400263BE5 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
97C146EA1CF9000F007C117D /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
|
||||
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
331C8086294A63A400263BE5 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 97C146ED1CF9000F007C117D /* Runner */;
|
||||
targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin PBXVariantGroup section */
|
||||
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
97C146FB1CF9000F007C117D /* Base */,
|
||||
);
|
||||
name = Main.storyboard;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
97C147001CF9000F007C117D /* Base */,
|
||||
);
|
||||
name = LaunchScreen.storyboard;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXVariantGroup section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
249021D3217E4FDB00AE95B9 /* Profile */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
SUPPORTED_PLATFORMS = iphoneos;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Profile;
|
||||
};
|
||||
249021D4217E4FDB00AE95B9 /* Profile */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.smartac;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
};
|
||||
name = Profile;
|
||||
};
|
||||
331C8088294A63A400263BE5 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.smartac.RunnerTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
331C8089294A63A400263BE5 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.smartac.RunnerTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
331C808A294A63A400263BE5 /* Profile */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.smartac.RunnerTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
|
||||
};
|
||||
name = Profile;
|
||||
};
|
||||
97C147031CF9000F007C117D /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
97C147041CF9000F007C117D /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
SUPPORTED_PLATFORMS = iphoneos;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
97C147061CF9000F007C117D /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.smartac;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
97C147071CF9000F007C117D /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.smartac;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
331C8088294A63A400263BE5 /* Debug */,
|
||||
331C8089294A63A400263BE5 /* Release */,
|
||||
331C808A294A63A400263BE5 /* Profile */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
97C147031CF9000F007C117D /* Debug */,
|
||||
97C147041CF9000F007C117D /* Release */,
|
||||
249021D3217E4FDB00AE95B9 /* Profile */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
97C147061CF9000F007C117D /* Debug */,
|
||||
97C147071CF9000F007C117D /* Release */,
|
||||
249021D4217E4FDB00AE95B9 /* Profile */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = 97C146E61CF9000F007C117D /* Project object */;
|
||||
}
|
||||
7
Source-Code-Web/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
generated
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>PreviewsEnabled</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1510"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||
BuildableName = "Runner.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||
BuildableName = "Runner.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO"
|
||||
parallelizable = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "331C8080294A63A400263BE5"
|
||||
BuildableName = "RunnerTests.xctest"
|
||||
BlueprintName = "RunnerTests"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
enableGPUValidationMode = "1"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||
BuildableName = "Runner.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Profile"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||
BuildableName = "Runner.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "group:Runner.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>PreviewsEnabled</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
import Flutter
|
||||
import UIKit
|
||||
|
||||
@main
|
||||
@objc class AppDelegate: FlutterAppDelegate {
|
||||
override func application(
|
||||
_ application: UIApplication,
|
||||
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
||||
) -> Bool {
|
||||
GeneratedPluginRegistrant.register(with: self)
|
||||
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,122 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-20x20@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-20x20@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-29x29@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-29x29@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-29x29@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-40x40@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-40x40@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "60x60",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-60x60@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "60x60",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-60x60@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-20x20@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-20x20@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-29x29@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-29x29@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-40x40@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-40x40@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "76x76",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-76x76@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "76x76",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-76x76@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "83.5x83.5",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-83.5x83.5@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "1024x1024",
|
||||
"idiom" : "ios-marketing",
|
||||
"filename" : "Icon-App-1024x1024@1x.png",
|
||||
"scale" : "1x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 295 B |
|
After Width: | Height: | Size: 406 B |
|
After Width: | Height: | Size: 450 B |
|
After Width: | Height: | Size: 282 B |
|
After Width: | Height: | Size: 462 B |
|
After Width: | Height: | Size: 704 B |
|
After Width: | Height: | Size: 406 B |
|
After Width: | Height: | Size: 586 B |
|
After Width: | Height: | Size: 862 B |
|
After Width: | Height: | Size: 862 B |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 762 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
23
Source-Code-Web/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "LaunchImage.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "LaunchImage@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "LaunchImage@3x.png",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
BIN
Source-Code-Web/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
vendored
Normal file
|
After Width: | Height: | Size: 68 B |
BIN
Source-Code-Web/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 68 B |
BIN
Source-Code-Web/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 68 B |
|
|
@ -0,0 +1,5 @@
|
|||
# Launch Screen Assets
|
||||
|
||||
You can customize the launch screen with your own desired assets by replacing the image files in this directory.
|
||||
|
||||
You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="EHf-IW-A2E">
|
||||
<objects>
|
||||
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
|
||||
<layoutGuides>
|
||||
<viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
|
||||
<viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
|
||||
</imageView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
|
||||
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="53" y="375"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<image name="LaunchImage" width="168" height="185"/>
|
||||
</resources>
|
||||
</document>
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--Flutter View Controller-->
|
||||
<scene sceneID="tne-QT-ifu">
|
||||
<objects>
|
||||
<viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
|
||||
<layoutGuides>
|
||||
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
|
||||
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
||||