2425### Análisis de Imágenes
26- `POST /api/analysis` - Analizar una imagen para detectar plagas/enfermedades
2728### Usuarios
AgriAiUserDashboard.tsx4 matches
509</div>
510
511{record.imageUrl && (
512<div className="mt-3">
513<img
514src={record.imageUrl}
515alt="Imagen de la plaga/enfermedad"
516className="h-24 object-cover rounded-md"
517/>
541onClick={() => setActiveTab("analyze")}
542>
543Analizar una imagen
544</button>
545</div>
AgriAiAnalysisResult.tsx2 matches
1/** @jsxImportSource https://esm.sh/react@18.2.0 */
2import React, { useState } from "https://esm.sh/react@18.2.0";
3import { ImageAnalysisResponse, PestDisease, User } from "../../shared/types.ts";
45interface AnalysisResultProps {
6result: ImageAnalysisResponse;
7pestDiseases: PestDisease[];
8user: User | null;
AgriAiImageUploader.tsx24 matches
2import React, { useState, useRef } from "https://esm.sh/react@18.2.0";
34interface ImageUploaderProps {
5onImageUpload: (imageData: string) => void;
6isLoading: boolean;
7cropType: string;
9}
1011const ImageUploader: React.FC<ImageUploaderProps> = ({
12onImageUpload,
13isLoading,
14cropType,
40
41if (file) {
42// Check if the file is an image
43if (!file.type.startsWith("image/")) {
44alert("Por favor selecciona un archivo de imagen válido.");
45return;
46}
48// Check file size (max 5MB)
49if (file.size > 5 * 1024 * 1024) {
50alert("La imagen es demasiado grande. Por favor selecciona una imagen de menos de 5MB.");
51return;
52}
56setPreviewUrl(objectUrl);
57
58// Convert the image to base64
59const reader = new FileReader();
60reader.onload = (e) => {
61const base64 = e.target?.result as string;
62// We'll use this base64 string when submitting the image for analysis
63};
64reader.readAsDataURL(file);
66};
67
68// Handle image upload button click
69const handleUploadClick = () => {
70if (fileInputRef.current) {
76const handleAnalyzeClick = () => {
77if (previewUrl) {
78// Convert the preview image to base64
79const img = new Image();
80img.crossOrigin = "Anonymous";
81img.onload = () => {
86const ctx = canvas.getContext("2d");
87if (ctx) {
88ctx.drawImage(img, 0, 0);
89const base64 = canvas.toDataURL("image/jpeg");
90onImageUpload(base64);
91}
92};
97return (
98<div className="bg-white p-6 rounded-lg shadow-md">
99<h2 className="text-xl font-semibold mb-4">Analizar Imagen de Cultivo</h2>
100
101<div className="mb-4">
123src={previewUrl}
124alt="Vista previa"
125className="mx-auto image-preview rounded-lg"
126/>
127<button
129onClick={handleUploadClick}
130>
131Cambiar imagen
132</button>
133</div>
136<div className="text-6xl text-gray-300 mx-auto">📷</div>
137<p className="text-gray-500">
138Haz clic para seleccionar una imagen o arrastra y suelta aquí
139</p>
140<button
142onClick={handleUploadClick}
143>
144Seleccionar Imagen
145</button>
146</div>
151ref={fileInputRef}
152onChange={handleFileChange}
153accept="image/*"
154className="hidden"
155/>
168</div>
169) : (
170"Analizar Imagen"
171)}
172</button>
188};
189190export default ImageUploader;
AgriAiHeader.tsx1 match
38onClick={() => onTabChange("analyze")}
39>
40Analizar Imagen
41</button>
42</li>
2import React, { useState, useEffect } from "https://esm.sh/react@18.2.0";
3import Header from "./Header.tsx";
4import ImageUploader from "./ImageUploader.tsx";
5import AnalysisResult from "./AnalysisResult.tsx";
6import PestDiseaseLibrary from "./PestDiseaseLibrary.tsx";
7import UserDashboard from "./UserDashboard.tsx";
8import { ImageAnalysisResponse, PestDisease, User } from "../../shared/types.ts";
910// Define the tabs for the application
16
17// State for the analysis result
18const [analysisResult, setAnalysisResult] = useState<ImageAnalysisResponse | null>(null);
19
20// State for loading status
53}, []);
54
55// Handle image analysis
56const handleAnalyzeImage = async (imageData: string) => {
57setIsLoading(true);
58setError(null);
65},
66body: JSON.stringify({
67imageData,
68cropType: cropType || undefined
69})
75setAnalysisResult(data.data);
76} else {
77setError(data.error || "Error analyzing image");
78setAnalysisResult(null);
79}
80} catch (error) {
81console.error("Error analyzing image:", error);
82setError("Error connecting to the server. Please try again.");
83setAnalysisResult(null);
135return (
136<div className="fade-in">
137<ImageUploader
138onImageUpload={handleAnalyzeImage}
139isLoading={isLoading}
140cropType={cropType}
AgriAiindex.html2 matches
13
14<!-- Favicon -->
15<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🌱</text></svg>">
16
17<!-- Custom styles -->
31}
32
33.image-preview {
34max-height: 300px;
35object-fit: contain;
AgriAianalysis.ts19 matches
1import { Hono } from "https://esm.sh/hono@3.11.7";
2import { analyzeImage } from "../services/aiService.ts";
3import {
4createPestDiseaseRecord,
6} from "../database/queries.ts";
7import { blob } from "https://esm.town/v/std/blob";
8import type { ApiResponse, ImageAnalysisResponse } from "../../shared/types.ts";
910const app = new Hono();
1112// Analyze an image for pests/diseases
13app.post("/", async (c) => {
14try {
16
17// Validate request
18if (!body.imageData && !body.imageUrl) {
19const response: ApiResponse<null> = {
20success: false,
21error: "Either imageData or imageUrl is required"
22};
23return c.json(response, 400);
24}
2526let imageUrl = body.imageUrl;
27
28// If base64 image data is provided, store it in blob storage
29if (body.imageData) {
30// Generate a unique key for the image
31const timestamp = Date.now();
32const randomString = Math.random().toString(36).substring(2, 10);
33const imageKey = `crop_image_${timestamp}_${randomString}`;
34
35// Store the image in blob storage
36await blob.set(imageKey, body.imageData, { contentType: "image/jpeg" });
37
38// Get the URL for the stored image
39imageUrl = await blob.getUrl(imageKey);
40}
4142// Analyze the image
43const analysisResult = await analyzeImage(imageUrl, body.cropType);
44
45// If a crop record ID is provided, save the analysis result
57cropRecordId,
58body.severity || "medium",
59imageUrl,
60analysisResult.diagnosis,
61pestDiseaseId,
65}
6667const response: ApiResponse<ImageAnalysisResponse> = {
68success: true,
69data: analysisResult
71return c.json(response);
72} catch (error) {
73console.error("Error analyzing image:", error);
74const response: ApiResponse<null> = {
75success: false,
AgriAiaiService.ts10 matches
1import type { ImageAnalysisResponse } from "../../shared/types.ts";
2import { getPestDiseases, getTreatmentsByPestDiseaseId } from "../database/queries.ts";
322}
2324export async function analyzeImage(
25imageUrl: string,
26cropType?: string
27): Promise<ImageAnalysisResponse> {
28try {
29// Get the API key from environment variables
3435// Construct the prompt for the AI
36let prompt = "Analyze this image of a plant and identify any pests or diseases. ";
37prompt += "Describe what you see, including any visible symptoms, damage patterns, or signs of infestation. ";
38
61},
62{
63type: "image_url",
64image_url: {
65url: imageUrl
66}
67}
114recommendedTreatments,
115additionalNotes: possibleIssues.length === 0
116? "No specific pest or disease was confidently identified. Consider uploading a clearer image or consulting with an agricultural expert."
117: undefined
118};
119} catch (error) {
120console.error("Error analyzing image:", error);
121throw error;
122}
AgriAiqueries.ts8 matches
21affectedCrops: JSON.parse(row.affected_crops),
22preventionMethods: JSON.parse(row.prevention_methods),
23imageUrls: row.image_urls ? JSON.parse(row.image_urls) : undefined,
24treatments: [] // Will be populated separately
25}));
47affectedCrops: JSON.parse(row.affected_crops),
48preventionMethods: JSON.parse(row.prevention_methods),
49imageUrls: row.image_urls ? JSON.parse(row.image_urls) : undefined,
50treatments
51};
69affectedCrops: JSON.parse(row.affected_crops),
70preventionMethods: JSON.parse(row.prevention_methods),
71imageUrls: row.image_urls ? JSON.parse(row.image_urls) : undefined,
72treatments
73};
199cropRecordId: number,
200severity: 'low' | 'medium' | 'high',
201imageUrl?: string,
202aiDiagnosis?: string,
203pestDiseaseId?: number,
206const result = await sqlite.execute(
207`INSERT INTO ${TABLES.PEST_DISEASE_RECORDS}
208(crop_record_id, pest_disease_id, severity, status, image_url, ai_diagnosis, notes)
209VALUES (?, ?, ?, 'active', ?, ?, ?)`,
210[cropRecordId, pestDiseaseId, severity, imageUrl, aiDiagnosis, notes]
211);
212return result.lastInsertId;
291status: row.status as 'active' | 'treating' | 'resolved',
292notes: row.notes,
293imageUrl: row.image_url,
294aiDiagnosis: row.ai_diagnosis,
295treatmentApplied: row.treatment_applied,
319status: row.status as 'active' | 'treating' | 'resolved',
320notes: row.notes,
321imageUrl: row.image_url,
322aiDiagnosis: row.ai_diagnosis,
323treatmentApplied: row.treatment_applied,