Add error handling and new API methods for farm assistant features

- Introduced error messages in Persian for image analysis and chat functionalities in FarmAiAssistantChat and PlantPestDetection components.
- Implemented a new postFormData method in ApiClient for handling file uploads.
- Enhanced SmartFertilizationRecommendation and SmartIrrigationRecommendation components to fetch configuration data from the API, improving user experience with loading states and error handling.
- Refactored components to utilize updated API services for better data management and responsiveness.
This commit is contained in:
2026-02-25 23:39:08 +03:30
parent 83b0f88d6e
commit aad5b1c2bd
12 changed files with 778 additions and 160 deletions
+16
View File
@@ -123,6 +123,22 @@ export class ApiClient {
return this.handleResponse<T>(response)
}
/**
* POST request with FormData (e.g. file upload). Does not set Content-Type so browser sets multipart/form-data.
*/
async postFormData<T>(endpoint: string, formData: FormData, customHeaders?: Record<string, string>): Promise<T> {
const url = `${this.baseURL}${endpoint}`
const headers = { ...this.getHeaders(customHeaders) }
delete headers['Content-Type']
const response = await fetch(url, {
method: 'POST',
headers,
body: formData,
})
return this.handleResponse<T>(response)
}
/**
* PUT request
*/
@@ -0,0 +1,72 @@
/**
* Farm AI Assistant API
* GET context (farm bar data), POST chat (user message + optional farm_context/images).
*/
import { apiClient } from '../client'
import type { FarmContext } from '@views/dashboards/farm/farmAiAssistant/farmAiAssistantTypes'
const PREFIX = '/api/farm-ai-assistant'
export interface FarmContextResponse {
soilType: string
waterEC: string
selectedCrop: string
growthStage: string
lastIrrigationStatus: string
}
export interface ChatSection {
type: 'text' | 'list' | 'recommendation' | 'warning'
title?: string
content?: string
items?: string[]
icon?: 'droplet' | 'leaf' | 'warning' | 'fertilizer' | 'calendar'
frequency?: string
amount?: string
timing?: string
expandableExplanation?: string
}
export interface ChatPayload {
content: string
farm_context?: FarmContext
images?: string[]
conversation_id?: string
}
export interface ChatResponseData {
message_id: string
conversation_id: string
content: string
sections: ChatSection[]
}
interface ApiResponse<T> {
status: string
data: T
}
function unwrap<T>(res: ApiResponse<T>): T {
return res.data
}
export const farmAiAssistantService = {
/**
* Returns farm context for the context bar (soilType, waterEC, selectedCrop, growthStage, lastIrrigationStatus).
*/
getContext(): Promise<FarmContextResponse> {
return apiClient
.get<ApiResponse<FarmContextResponse>>(`${PREFIX}/context/`)
.then(unwrap)
},
/**
* Send user message (and optional farm_context, images, conversation_id). Returns message with sections.
*/
chat(payload: ChatPayload): Promise<ChatResponseData> {
return apiClient
.post<ApiResponse<ChatResponseData>>(`${PREFIX}/chat/`, payload)
.then(unwrap)
}
}
@@ -0,0 +1,67 @@
/**
* Fertilization Recommendation API
* @see RECOMMENDATION_APIS.md
*/
import { apiClient } from '../client'
const PREFIX = '/api/fertilization-recommendation'
export interface FarmData {
soilType: string
organicMatter: string
waterEC: string
}
export interface GrowthStage {
id: string
icon: string
}
export interface CropOption {
id: string
labelKey: string
icon: string
}
export interface FertilizationConfigResponse {
farmData: FarmData
growthStages: GrowthStage[]
cropOptions: CropOption[]
}
export interface FertilizationPlan {
npkRatio: string
amountPerHectare: string
applicationMethod: string
applicationInterval: string
reasoning: string
}
export interface FertilizationRecommendPayload {
crop_id?: string
growth_stage?: string
soilType?: string
organicMatter?: string
waterEC?: string
}
interface ApiResponse<T> {
status: string
data: T
}
async function unwrap<T>(promise: Promise<ApiResponse<T>>): Promise<T> {
const res = await promise
return res.data
}
export const fertilizationRecommendationService = {
getConfig(): Promise<FertilizationConfigResponse> {
return unwrap(apiClient.get<ApiResponse<FertilizationConfigResponse>>(`${PREFIX}/config/`))
},
recommend(payload?: FertilizationRecommendPayload): Promise<{ plan: FertilizationPlan }> {
return unwrap(apiClient.post<ApiResponse<{ plan: FertilizationPlan }>>(`${PREFIX}/recommend/`, payload ?? {}))
},
}
@@ -0,0 +1,60 @@
/**
* Irrigation Recommendation API
* @see RECOMMENDATION_APIS.md
*/
import { apiClient } from '../client'
const PREFIX = '/api/irrigation-recommendation'
export interface FarmInfo {
soilType: string
waterQuality: string
climateZone: string
}
export interface CropOption {
id: string
labelKey: string
icon: string
}
export interface IrrigationConfigResponse {
farmInfo: FarmInfo
cropOptions: CropOption[]
}
export interface IrrigationPlan {
frequencyPerWeek: number
durationMinutes: number
bestTimeOfDay: string
moistureLevel: number
warning?: string
}
export interface IrrigationRecommendPayload {
crop_id?: string
soilType?: string
waterQuality?: string
climateZone?: string
}
interface ApiResponse<T> {
status: string
data: T
}
async function unwrap<T>(promise: Promise<ApiResponse<T>>): Promise<T> {
const res = await promise
return res.data
}
export const irrigationRecommendationService = {
getConfig(): Promise<IrrigationConfigResponse> {
return unwrap(apiClient.get<ApiResponse<IrrigationConfigResponse>>(`${PREFIX}/config/`))
},
recommend(payload?: IrrigationRecommendPayload): Promise<{ plan: IrrigationPlan }> {
return unwrap(apiClient.post<ApiResponse<{ plan: IrrigationPlan }>>(`${PREFIX}/recommend/`, payload ?? {}))
},
}
@@ -0,0 +1,37 @@
/**
* Pest Detection API
* @see RECOMMENDATION_APIS.md
*/
import { apiClient } from '../client'
const PREFIX = '/api/pest-detection'
export interface PestAnalyzeResult {
pest: string
confidence: number
description: string
treatment: string
}
interface ApiResponse<T> {
status: string
data: T
}
async function unwrap<T>(promise: Promise<ApiResponse<T>>): Promise<T> {
const res = await promise
return res.data
}
export const pestDetectionService = {
/**
* Analyze image (optional FormData with 'image' or 'file' key). Returns mock result if backend does not use image yet.
*/
analyze(formData?: FormData): Promise<PestAnalyzeResult> {
if (formData) {
return unwrap(apiClient.postFormData<ApiResponse<PestAnalyzeResult>>(`${PREFIX}/analyze/`, formData))
}
return unwrap(apiClient.post<ApiResponse<PestAnalyzeResult>>(`${PREFIX}/analyze/`, {}))
},
}