Skip to content

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>
  );
}