Framework Integration¶
Ready-to-use integrations for popular web frameworks.
Flask¶
Proxy endpoint that forwards requests to Tajiri Vision API.
from flask import Flask, request, jsonify
import requests
app = Flask(__name__)
TAJIRI_API_URL = "https://api.tajirifarm.com/diagnoses/"
@app.route("/api/diagnose", methods=["POST"])
def diagnose():
"""Proxy endpoint for plant diagnosis."""
if "image" not in request.files:
return jsonify({"error": "No image provided"}), 400
image = request.files["image"]
# Forward to Tajiri Vision API
response = requests.post(
TAJIRI_API_URL,
files={"image": (image.filename, image.read(), image.content_type)},
data={
"crop_type": request.form.get("crop_type"),
"region": request.form.get("region"),
"language": request.form.get("language", "en"),
"detail_level": request.form.get("detail_level", "standard"),
},
timeout=60
)
return jsonify(response.json()), response.status_code
@app.errorhandler(requests.exceptions.Timeout)
def handle_timeout(error):
return jsonify({"error": "Diagnosis service timeout"}), 504
if __name__ == "__main__":
app.run(debug=True)
With Error Handling¶
from flask import Flask, request, jsonify
import requests
from functools import wraps
app = Flask(__name__)
TAJIRI_API_URL = "https://api.tajirifarm.com/diagnoses/"
def handle_api_errors(f):
"""Decorator for consistent error handling."""
@wraps(f)
def decorated(*args, **kwargs):
try:
return f(*args, **kwargs)
except requests.exceptions.Timeout:
return jsonify({"error": "Service timeout"}), 504
except requests.exceptions.RequestException as e:
return jsonify({"error": f"Service unavailable: {str(e)}"}), 503
return decorated
@app.route("/api/diagnose", methods=["POST"])
@handle_api_errors
def diagnose():
if "image" not in request.files:
return jsonify({"error": "No image provided"}), 400
image = request.files["image"]
# Validate file type
allowed_types = {"image/jpeg", "image/png", "image/webp"}
if image.content_type not in allowed_types:
return jsonify({"error": f"Invalid file type. Allowed: {allowed_types}"}), 400
response = requests.post(
TAJIRI_API_URL,
files={"image": (image.filename, image.read(), image.content_type)},
data={
"crop_type": request.form.get("crop_type"),
"region": request.form.get("region"),
"language": request.form.get("language", "en"),
},
timeout=60
)
if response.status_code == 429:
return jsonify({"error": "Rate limit exceeded"}), 429
return jsonify(response.json()), response.status_code
Django¶
View-based Approach¶
# views.py
from django.http import JsonResponse
from django.views.decorators.http import require_POST
from django.views.decorators.csrf import csrf_exempt
import requests
TAJIRI_API_URL = "https://api.tajirifarm.com/diagnoses/"
@csrf_exempt
@require_POST
def diagnose_plant(request):
"""Plant diagnosis view."""
if "image" not in request.FILES:
return JsonResponse({"error": "No image provided"}, status=400)
image = request.FILES["image"]
try:
response = requests.post(
TAJIRI_API_URL,
files={"image": (image.name, image.read(), image.content_type)},
data={
"crop_type": request.POST.get("crop_type"),
"region": request.POST.get("region"),
"language": request.POST.get("language", "en"),
},
timeout=60
)
return JsonResponse(response.json(), status=response.status_code)
except requests.exceptions.Timeout:
return JsonResponse({"error": "Service timeout"}, status=504)
except requests.exceptions.RequestException:
return JsonResponse({"error": "Service unavailable"}, status=503)
# urls.py
from django.urls import path
from . import views
urlpatterns = [
path("api/diagnose/", views.diagnose_plant, name="diagnose"),
]
Django REST Framework¶
# serializers.py
from rest_framework import serializers
class DiagnosisRequestSerializer(serializers.Serializer):
image = serializers.ImageField()
crop_type = serializers.CharField(required=False)
region = serializers.CharField(required=False)
language = serializers.ChoiceField(
choices=["en", "fr", "sw", "es", "pt", "it"],
default="en"
)
detail_level = serializers.ChoiceField(
choices=["simple", "standard", "expert"],
default="standard"
)
# views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.parsers import MultiPartParser
import requests
TAJIRI_API_URL = "https://api.tajirifarm.com/diagnoses/"
class DiagnoseView(APIView):
parser_classes = [MultiPartParser]
def post(self, request):
serializer = DiagnosisRequestSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
image = serializer.validated_data["image"]
response = requests.post(
TAJIRI_API_URL,
files={"image": (image.name, image.read(), image.content_type)},
data={
"crop_type": serializer.validated_data.get("crop_type"),
"region": serializer.validated_data.get("region"),
"language": serializer.validated_data["language"],
"detail_level": serializer.validated_data["detail_level"],
},
timeout=60
)
return Response(response.json(), status=response.status_code)
React¶
Custom Hook¶
import { useState, useCallback } from 'react';
interface DiagnosisOptions {
cropType?: string;
region?: string;
language?: string;
detailLevel?: string;
}
interface Diagnosis {
name: string;
scientific_name: string | null;
confidence: number;
urgency: string | null;
description: string;
}
interface DiagnosisResult {
request_id: string;
image_analysis: { is_plant: boolean; quality_issue: string | null };
crop_health: 'healthy' | 'unhealthy' | 'unknown';
diagnoses: Diagnosis[];
treatment: object | null;
}
interface UseDiagnosisReturn {
diagnose: (file: File, options?: DiagnosisOptions) => Promise<void>;
result: DiagnosisResult | null;
loading: boolean;
error: string | null;
reset: () => void;
}
export function useDiagnosis(): UseDiagnosisReturn {
const [result, setResult] = useState<DiagnosisResult | null>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const reset = useCallback(() => {
setResult(null);
setError(null);
}, []);
const diagnose = useCallback(async (file: File, options: DiagnosisOptions = {}) => {
setLoading(true);
setError(null);
const formData = new FormData();
formData.append('image', file);
formData.append('language', options.language || 'en');
formData.append('detail_level', options.detailLevel || 'standard');
if (options.cropType) formData.append('crop_type', options.cropType);
if (options.region) formData.append('region', options.region);
try {
const response = await fetch('https://api.tajirifarm.com/diagnoses/', {
method: 'POST',
body: formData
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.detail || `HTTP ${response.status}`);
}
const data = await response.json();
setResult(data);
} catch (err) {
setError(err instanceof Error ? err.message : 'Unknown error');
} finally {
setLoading(false);
}
}, []);
return { diagnose, result, loading, error, reset };
}
Component Example¶
import { useState } from 'react';
import { useDiagnosis } from './hooks/useDiagnosis';
export function DiagnosisForm() {
const { diagnose, result, loading, error, reset } = useDiagnosis();
const [file, setFile] = useState<File | null>(null);
const [cropType, setCropType] = useState('');
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (file) {
await diagnose(file, { cropType, language: 'en' });
}
};
return (
<div className="diagnosis-form">
<form onSubmit={handleSubmit}>
<input
type="file"
accept="image/*"
onChange={(e) => setFile(e.target.files?.[0] || null)}
/>
<select value={cropType} onChange={(e) => setCropType(e.target.value)}>
<option value="">Select crop type</option>
<option value="tomato">Tomato</option>
<option value="maize">Maize</option>
<option value="cassava">Cassava</option>
</select>
<button type="submit" disabled={loading || !file}>
{loading ? 'Analyzing...' : 'Diagnose'}
</button>
</form>
{error && <div className="error">{error}</div>}
{result && (
<div className="results">
<h3>Health: {result.crop_health}</h3>
{result.diagnoses.map((d, i) => (
<div key={i} className="diagnosis-card">
<h4>{d.name}</h4>
{d.scientific_name && <em>{d.scientific_name}</em>}
<p>Confidence: {(d.confidence * 100).toFixed(0)}%</p>
{d.urgency && <span className={`urgency-${d.urgency}`}>{d.urgency}</span>}
<p>{d.description}</p>
</div>
))}
<button onClick={reset}>New Diagnosis</button>
</div>
)}
</div>
);
}
With Image Preview¶
import { useState, useRef } from 'react';
import { useDiagnosis } from './hooks/useDiagnosis';
export function DiagnosisWithPreview() {
const { diagnose, result, loading, error } = useDiagnosis();
const [preview, setPreview] = useState<string | null>(null);
const inputRef = useRef<HTMLInputElement>(null);
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (file) {
// Create preview URL
const url = URL.createObjectURL(file);
setPreview(url);
// Auto-submit
diagnose(file, { language: 'en' });
}
};
return (
<div className="diagnosis-preview">
<input
ref={inputRef}
type="file"
accept="image/*"
onChange={handleFileChange}
hidden
/>
<button onClick={() => inputRef.current?.click()}>
{preview ? 'Change Image' : 'Select Image'}
</button>
{preview && (
<div className="preview-container">
<img src={preview} alt="Preview" />
{loading && <div className="loading-overlay">Analyzing...</div>}
</div>
)}
{error && <p className="error">{error}</p>}
{result && !loading && (
<div className="result-overlay">
<span className={`health-badge ${result.crop_health}`}>
{result.crop_health}
</span>
</div>
)}
</div>
);
}