demeter

Autonomous Hydroponic Intelligence

frontend/src/api/agentApi.js (4817B)


  1import {
  2  USE_MOCK_DATA,
  3  MOCK_SEARCH_RESULT,
  4  MOCK_DASHBOARD,
  5} from "../data/mockData";
  6
  7const API_URL = process.env.REACT_APP_AGENT_API_URL || "http://localhost:8000";
  8
  9export const agentService = {
 10  /**
 11   * Uploads an image + sensors to create a new FMU (Functional Memory Unit)
 12   */
 13  async uploadFMU(file, sensors) {
 14    if (USE_MOCK_DATA) {
 15      await new Promise((r) => setTimeout(r, 500));
 16      return { status: "success", fmu_id: "mock-fmu-ingest-001" };
 17    }
 18
 19    const formData = new FormData();
 20    formData.append("file", file);
 21    formData.append(
 22      "sensors",
 23      JSON.stringify({
 24        pH: parseFloat(sensors.pH),
 25        EC: parseFloat(sensors.EC),
 26        temp: parseFloat(sensors.temp),
 27        humidity: parseFloat(sensors.humidity),
 28        crop_id: sensors.crop_id || undefined,
 29      }),
 30    );
 31    formData.append(
 32      "metadata",
 33      JSON.stringify({
 34        crop: sensors.crop,
 35        stage: sensors.stage,
 36        crop_id: sensors.crop_id || undefined,
 37      }),
 38    );
 39
 40    const res = await fetch(`${API_URL}/ingest`, {
 41      method: "POST",
 42      body: formData,
 43    });
 44    if (!res.ok) throw new Error(res.statusText);
 45    return res.json();
 46  },
 47
 48  /**
 49   * Searches for similar memories and gets an Agent Decision
 50   */
 51  async searchFMU(file, sensors) {
 52    if (USE_MOCK_DATA) {
 53      await new Promise((r) => setTimeout(r, 1200));
 54      return MOCK_SEARCH_RESULT;
 55    }
 56
 57    const formData = new FormData();
 58    formData.append("file", file);
 59    formData.append(
 60      "sensors",
 61      JSON.stringify({
 62        pH: parseFloat(sensors.pH),
 63        EC: parseFloat(sensors.EC),
 64        temp: parseFloat(sensors.temp),
 65        humidity: parseFloat(sensors.humidity),
 66        crop: sensors.crop,
 67        stage: sensors.stage,
 68        crop_id: sensors.crop_id || undefined,
 69      }),
 70    );
 71
 72    const res = await fetch(`${API_URL}/search`, {
 73      method: "POST",
 74      body: formData,
 75    });
 76    if (!res.ok) throw new Error(res.statusText);
 77    return res.json();
 78  },
 79
 80  /**
 81   * Translates a natural language query into a database filter using LLM
 82   */
 83  async queryText(text, cropId = null) {
 84    if (USE_MOCK_DATA) {
 85      await new Promise((r) => setTimeout(r, 600));
 86      return {
 87        status: "success",
 88        results: MOCK_DASHBOARD.slice(0, 3).map((d, i) => ({
 89          id: d.id,
 90          score: 0.95 - i * 0.08,
 91          payload: d.payload,
 92        })),
 93        query_logic: {
 94          must: [
 95            { key: "crop", match: "Tomato" },
 96            { key: "outcome", match: "Positive" },
 97          ],
 98        },
 99      };
100    }
101
102    const formData = new FormData();
103    formData.append("query", text);
104    if (cropId) formData.append("crop_id", cropId);
105
106    const res = await fetch(`${API_URL}/query-text`, {
107      method: "POST",
108      body: formData,
109    });
110    if (!res.ok) throw new Error(res.statusText);
111    return res.json();
112  },
113
114  /**
115   * Finds cosine-similar crops via Qdrant vector search
116   */
117  async querySimilarCrops(cropId, cropName, payload) {
118    if (USE_MOCK_DATA) {
119      await new Promise((r) => setTimeout(r, 400));
120      return {
121        status: "success",
122        results: MOCK_DASHBOARD.slice(1, 4).map((d, i) => ({
123          id: d.id,
124          score: 0.91 - i * 0.07,
125          payload: d.payload,
126        })),
127      };
128    }
129
130    const formData = new FormData();
131    formData.append("crop_id", cropId);
132    formData.append("crop_name", cropName || "");
133    formData.append("payload", JSON.stringify(payload || {}));
134
135    const res = await fetch(`${API_URL}/query-similar`, {
136      method: "POST",
137      body: formData,
138    });
139    if (!res.ok) throw new Error(res.statusText);
140    return res.json();
141  },
142
143  /**
144   * Processes voice input
145   */
146  async queryAudio(audioBlob) {
147    if (USE_MOCK_DATA) {
148      await new Promise((r) => setTimeout(r, 1500));
149      return {
150        status: "success",
151        transcription: "Find me healthy tomato crops",
152        results: MOCK_DASHBOARD.slice(0, 2).map((d, i) => ({
153          id: d.id,
154          score: 0.98 - i * 0.05,
155          payload: d.payload,
156        })),
157      };
158    }
159
160    const formData = new FormData();
161    formData.append("file", audioBlob, "recording.webm");
162
163    const res = await fetch(`${API_URL}/query-audio`, {
164      method: "POST",
165      body: formData,
166    });
167    if (!res.ok) throw new Error(res.statusText);
168    return res.json();
169  },
170
171  /**
172   * Ask Demeter a natural language question
173   */
174  async askDemeter(query, context, lang) {
175    const formData = new FormData();
176    formData.append("query", query);
177    formData.append("context", context);
178    formData.append("language", lang);
179
180    const res = await fetch(`${API_URL}/ask-demeter`, {
181      method: "POST",
182      body: formData,
183    });
184    return res.json();
185  },
186};