File size: 22,334 Bytes
56deb05
294f796
56deb05
 
48f68ce
 
56deb05
 
294f796
48f68ce
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
294f796
56deb05
6cc0e1d
 
 
294f796
56deb05
 
 
 
 
 
 
 
 
 
 
0d452fb
48f68ce
e3e3e6b
2e01d6d
 
 
 
 
0d452fb
 
48f68ce
e3e3e6b
 
 
 
 
 
 
 
56deb05
 
 
 
0d452fb
48f68ce
 
56deb05
 
 
 
0d452fb
48f68ce
 
56deb05
 
 
 
0d452fb
56deb05
 
48f68ce
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56deb05
48f68ce
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56deb05
 
48f68ce
 
 
0d452fb
48f68ce
 
 
 
 
0d452fb
56deb05
0d452fb
48f68ce
 
 
 
 
 
 
 
 
0d452fb
822c8b6
48f68ce
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
822c8b6
56deb05
822c8b6
294f796
 
48f68ce
822c8b6
 
48f68ce
 
 
822c8b6
48f68ce
 
 
 
822c8b6
56deb05
822c8b6
48f68ce
 
 
 
0d452fb
48f68ce
 
 
 
0d452fb
48f68ce
822c8b6
 
48f68ce
 
 
822c8b6
48f68ce
 
 
 
822c8b6
56deb05
822c8b6
48f68ce
 
 
 
0d452fb
48f68ce
 
 
7ac72e2
0d452fb
48f68ce
822c8b6
 
294f796
56deb05
294f796
 
 
822c8b6
 
 
48f68ce
 
 
822c8b6
 
 
 
 
48f68ce
 
 
822c8b6
 
 
 
 
 
 
 
 
 
294f796
56deb05
822c8b6
48f68ce
 
56deb05
48f68ce
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
822c8b6
48f68ce
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
822c8b6
48f68ce
 
294f796
822c8b6
294f796
 
 
 
48f68ce
 
 
 
 
 
 
 
 
 
 
 
 
 
 
294f796
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
import os
import gradio as gr
import numpy as np
import requests
import json
import time
from tensorflow.keras.models import load_model
from PIL import Image

# ===== API Configuration =====
# Try to get API tokens from environment variables
HF_API_TOKEN = os.getenv("HUGGINGFACE_TOKEN")  # Hugging Face API token
GROQ_API_KEY = os.getenv("GROQ_API_KEY")  # Groq API key
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")  # OpenAI API key (fallback)

print(f"API tokens available: HF={'Yes' if HF_API_TOKEN else 'No'}, Groq={'Yes' if GROQ_API_KEY else 'No'}, OpenAI={'Yes' if OPENAI_API_KEY else 'No'}")

# ===== Disease Information Database =====
disease_info = {
    "Tomato Bacterial Spot": {
        "description": "A bacterial disease that causes small, dark spots on leaves, stems, and fruits.",
        "causes": "Caused by Xanthomonas bacteria, spread by water splash, contaminated tools, and seeds.",
        "treatment": [
            "Remove and destroy infected plants",
            "Rotate crops with non-solanaceous plants",
            "Use copper-based fungicides",
            "Avoid overhead irrigation"
        ]
    },
    "Tomato Early Blight": {
        "description": "A fungal disease that causes dark spots with concentric rings on lower leaves first.",
        "causes": "Caused by Alternaria solani fungus, favored by warm, humid conditions.",
        "treatment": [
            "Remove infected leaves promptly",
            "Improve air circulation around plants",
            "Apply fungicides preventatively",
            "Mulch around plants to prevent soil splash"
        ]
    },
    "Tomato Late Blight": {
        "description": "A devastating fungal disease that causes dark, water-soaked lesions on leaves and fruits.",
        "causes": "Caused by Phytophthora infestans, favored by cool, wet conditions.",
        "treatment": [
            "Remove and destroy infected plants immediately",
            "Apply fungicides preventatively in humid conditions",
            "Improve drainage and air circulation",
            "Plant resistant varieties when available"
        ]
    },
    "Tomato Mosaic Virus": {
        "description": "A viral disease that causes mottled green/yellow patterns on leaves and stunted growth.",
        "causes": "Caused by tobacco mosaic virus (TMV), spread by handling, tools, and sometimes seeds.",
        "treatment": [
            "Remove and destroy infected plants",
            "Wash hands and tools after handling infected plants",
            "Control insect vectors like aphids",
            "Plant resistant varieties"
        ]
    },
    "Tomato Yellow Leaf Curl Virus": {
        "description": "A viral disease transmitted by whiteflies that causes yellowing and curling of leaves.",
        "causes": "Caused by a begomovirus, transmitted primarily by whiteflies.",
        "treatment": [
            "Use whitefly control measures",
            "Remove and destroy infected plants",
            "Use reflective mulches to repel whiteflies",
            "Plant resistant varieties"
        ]
    },
    "Tomato___Target_Spot": {
        "description": "A fungal disease causing circular lesions with concentric rings on leaves, stems, and fruits.",
        "causes": "Caused by Corynespora cassiicola fungus, favored by warm, humid conditions.",
        "treatment": [
            "Remove infected plant parts",
            "Improve air circulation",
            "Apply fungicides at first sign of disease",
            "Avoid overhead irrigation"
        ]
    },
    "Tomato___Bacterial_spot": {
        "description": "A bacterial disease causing small, dark, water-soaked spots on leaves, stems, and fruits.",
        "causes": "Caused by Xanthomonas species, spread by water splash and contaminated tools.",
        "treatment": [
            "Remove infected plant debris",
            "Use copper-based bactericides",
            "Rotate crops",
            "Use disease-free seeds and transplants"
        ]
    },
    "Tomato___healthy": {
        "description": "The plant shows no signs of disease and appears to be in good health.",
        "causes": "Proper growing conditions, good management practices, and disease prevention.",
        "treatment": [
            "Continue regular watering and fertilization",
            "Monitor for early signs of disease",
            "Maintain good air circulation",
            "Practice crop rotation"
        ]
    }
}

# ===== Load Trained Models =====
model_a = load_model("Tomato_Leaf_Disease_Model.h5")
model_b = load_model("tomato_leaf_model_final(77%).h5")
classifier_model = load_model("tomato_leaf_classifier_optimized.h5")

# ===== Preprocessing Function =====
def preprocess_image(image, target_size=(224, 224)):
    # Ensure the image is resized and normalized.
    if isinstance(image, Image.Image):
        img = image.resize(target_size)
    else:
        img = Image.fromarray(image).resize(target_size)
    img_array = np.array(img) / 255.0
    img_array = np.expand_dims(img_array, axis=0)
    return img_array

# ===== Disease Label Mappings =====
# Model A labels
disease_labels_a = {
    0: "Tomato Bacterial Spot",
    1: "Tomato Early Blight",
    2: "Tomato Late Blight",
    3: "Tomato Mosaic Virus",
    4: "Tomato Yellow Leaf Curl Virus"
}

# Model B labels
disease_labels_b = {
    0: "Tomato___Target_Spot",
    1: "Tomato___Bacterial_spot",
    2: "Tomato___Early_blight",
    3: "Tomato___healthy",
    4: "Tomato___Late_blight"
}

# ===== Prediction Functions =====
def predict_model_a(image):
    img = preprocess_image(image)
    pred = model_a.predict(img)
    predicted_class = np.argmax(pred)
    confidence = float(np.max(pred) * 100)
    return disease_labels_a.get(predicted_class, "Unknown result"), confidence

def predict_model_b(image):
    img = preprocess_image(image)
    pred = model_b.predict(img)
    predicted_class = np.argmax(pred)
    confidence = float(np.max(pred) * 100)
    return disease_labels_b.get(predicted_class, "Unknown result"), confidence

def predict_classifier(image):
    img = preprocess_image(image)
    pred = classifier_model.predict(img)
    # Here we assume the classifier returns class 1 for "Tomato Leaf"
    return "Tomato Leaf" if np.argmax(pred) == 1 else "Not Tomato Leaf"

# ===== AI Model API Calls =====
def get_ai_advice(prompt, retries=2):
    """Try multiple AI models with fallback mechanisms"""

    # Try Groq API first (if key available)
    if GROQ_API_KEY:
        try:
            headers = {
                "Authorization": f"Bearer {GROQ_API_KEY}",
                "Content-Type": "application/json"
            }

            payload = {
                "model": "llama3-8b-8192",  # Using Llama 3 8B model
                "messages": [
                    {"role": "system", "content": "You are an expert agricultural advisor specializing in tomato farming."},
                    {"role": "user", "content": prompt}
                ],
                "max_tokens": 800,
                "temperature": 0.7
            }

            response = requests.post(
                "https://api.groq.com/openai/v1/chat/completions",
                headers=headers,
                json=payload,
                timeout=30
            )

            if response.status_code == 200:
                result = response.json()
                if "choices" in result and len(result["choices"]) > 0:
                    return result["choices"][0]["message"]["content"]

            print(f"Groq API error: {response.status_code} - {response.text}")

        except Exception as e:
            print(f"Error with Groq API: {str(e)}")

    # Try Hugging Face Inference API as first fallback (if token available)
    if HF_API_TOKEN:
        try:
            headers = {"Authorization": f"Bearer {HF_API_TOKEN}"}

            # Format prompt for instruction-tuned models
            formatted_prompt = f"""<s>[INST] {prompt} [/INST]"""

            payload = {
                "inputs": formatted_prompt,
                "parameters": {
                    "max_new_tokens": 800,
                    "temperature": 0.7,
                    "top_p": 0.95,
                    "do_sample": True
                }
            }

            # Try Mistral model first
            url = "https://api-inference.huggingface.co/models/mistralai/Mistral-7B-Instruct-v0.2"

            response = requests.post(url, headers=headers, json=payload, timeout=30)

            if response.status_code == 200:
                result = response.json()
                if isinstance(result, list) and len(result) > 0:
                    if "generated_text" in result[0]:
                        # Extract just the response part (after the prompt)
                        generated_text = result[0]["generated_text"]
                        # Remove the prompt from the response
                        response_text = generated_text.split("[/INST]")[-1].strip()
                        return response_text

            # If Mistral fails, try Llama 3
            url = "https://api-inference.huggingface.co/models/meta-llama/Meta-Llama-3-8B-Instruct"
            response = requests.post(url, headers=headers, json=payload, timeout=30)

            if response.status_code == 200:
                result = response.json()
                if isinstance(result, list) and len(result) > 0:
                    if "generated_text" in result[0]:
                        generated_text = result[0]["generated_text"]
                        response_text = generated_text.split("[/INST]")[-1].strip()
                        return response_text

        except Exception as e:
            print(f"Error with Hugging Face API: {str(e)}")

    # Try OpenAI API as final fallback (if key available)
    if OPENAI_API_KEY:
        try:
            headers = {
                "Authorization": f"Bearer {OPENAI_API_KEY}",
                "Content-Type": "application/json"
            }

            payload = {
                "model": "gpt-3.5-turbo",
                "messages": [
                    {"role": "system", "content": "You are an expert agricultural advisor specializing in tomato farming."},
                    {"role": "user", "content": prompt}
                ],
                "max_tokens": 800,
                "temperature": 0.7
            }

            response = requests.post(
                "https://api.openai.com/v1/chat/completions",
                headers=headers,
                json=payload,
                timeout=30
            )

            if response.status_code == 200:
                result = response.json()
                if "choices" in result and len(result["choices"]) > 0:
                    return result["choices"][0]["message"]["content"]

        except Exception as e:
            print(f"Error with OpenAI API: {str(e)}")

    # If all API calls fail, use the fallback information from our database
    disease_name = prompt.split("disease has been detected: ")[-1].split(" with")[0] if "disease has been detected:" in prompt else ""

    if disease_name and disease_name in disease_info:
        info = disease_info[disease_name]
        return f"""
# {disease_name}

## Description
{info['description']}

## Causes
{info['causes']}

## Recommended Treatment
{chr(10).join(f"- {rec}" for rec in info['treatment'])}

*Note: This is fallback information as our AI service is currently unavailable.*
"""
    else:
        # Generic fallback response
        return """
# Agricultural Advice

I apologize, but I'm currently unable to connect to our AI service. Here are some general tips for tomato plant care:

## General Tomato Care Tips
- Water consistently, aiming for 1-2 inches per week
- Provide support with stakes or cages
- Fertilize regularly with balanced fertilizer
- Remove suckers for indeterminate varieties
- Monitor for pests and diseases regularly
- Ensure good air circulation between plants
- Mulch to retain moisture and prevent soil-borne diseases

Please try again later for more specific advice.
"""

# ===== AI Assistant Functions =====
def generate_disease_advice(disease_name, confidence):
    """Generate advice for a specific disease with confidence level."""
    if "healthy" in disease_name.lower():
        prompt = (
            "You are an agricultural advisor speaking to a farmer. "
            "The tomato crop appears healthy. "
            "Provide detailed preventive tips and best practices for maintaining tomato crop health. "
            "Include information about watering, fertilization, pest prevention, and optimal growing conditions. "
            "Format your response in clear sections with bullet points where appropriate."
        )
    else:
        prompt = (
            f"You are an agricultural advisor speaking to a farmer. "
            f"A disease has been detected in their tomato crop: {disease_name} with {confidence:.1f}% confidence. "
            f"Provide detailed advice on how to identify, manage and treat this disease. "
            f"Include information about: "
            f"1) What causes this disease "
            f"2) How it spreads "
            f"3) Specific treatments (both organic and chemical options) "
            f"4) Preventive measures for the future "
            f"Format your response in clear sections with bullet points where appropriate."
        )

    return get_ai_advice(prompt)

def chat_with_farmer(message, chat_history):
    """Handle chat interactions with farmers about agricultural topics."""
    if not message.strip():
        return "", chat_history

    # Prepare context from chat history
    context = "\n".join([f"Farmer: {q}\nAdvisor: {a}" for q, a in chat_history[-3:]])  # Use last 3 exchanges for context

    prompt = (
        f"You are FarmAssist, an expert agricultural advisor specializing in tomato farming and plant diseases. "
        f"You provide helpful, accurate, and practical advice to farmers. "
        f"Always be respectful and considerate of farmers' knowledge while providing expert guidance. "
        f"If you're unsure about something, acknowledge it and provide the best information you can. "
        f"Previous conversation:\n{context}\n\n"
        f"Farmer's new question: {message}\n\n"
        f"Provide a helpful, informative response about farming, focusing on tomatoes if relevant."
    )

    response = get_ai_advice(prompt)
    chat_history.append((message, response))
    return "", chat_history

# ===== Process Function Based on Version =====
def process_version(image, version):
    if image is None:
        return "No image provided."

    # --- Version 1.x (Model A) ---
    if version == "1.1":
        result, confidence = predict_model_a(image)
        return f"Model A Prediction: {result} (Confidence: {confidence:.1f}%)\n\nView Model A Training Notebook: https://colab.research.google.com/drive/1FMjs7JmdO6WVoXbzLA-ymwnIKq-GaV6w?usp=sharing"

    elif version == "1.2":
        result, confidence = predict_model_a(image)
        advice = generate_disease_advice(result, confidence)
        return f"Model A Prediction: {result} (Confidence: {confidence:.1f}%)\n\nExpert Advice:\n{advice}"

    elif version == "1.3":
        cls_result = predict_classifier(image)
        if cls_result != "Tomato Leaf":
            return "Classifier: The image is not a tomato leaf. Please try again with a tomato leaf image."

        result, confidence = predict_model_a(image)
        advice = generate_disease_advice(result, confidence)
        return (
            f"Classifier: {cls_result}\n"
            f"Model A Prediction: {result} (Confidence: {confidence:.1f}%)\n\n"
            f"Expert Advice:\n{advice}\n\n"
            f"[View Model A & Classifier Training Notebook](https://colab.research.google.com/drive/1FMjs7JmdO6WVoXbzLA-ymwnIKq-GaV6w?usp=sharing)"
        )

    # --- Version 2.x (Model B) ---
    elif version == "2.1":
        result, confidence = predict_model_b(image)
        return f"Model B Prediction: {result} (Confidence: {confidence:.1f}%)\n\n[View Model B Training Notebook](https://colab.research.google.com/drive/1CvoQY40gK2YsMgt4wq9kM2ZSO2c4lzFU?usp=sharing)"

    elif version == "2.2":
        result, confidence = predict_model_b(image)
        advice = generate_disease_advice(result, confidence)
        return f"Model B Prediction: {result} (Confidence: {confidence:.1f}%)\n\nExpert Advice:\n{advice}"

    elif version == "2.3":
        cls_result = predict_classifier(image)
        if cls_result != "Tomato Leaf":
            return "Classifier: The image is not a tomato leaf. Please try again with a tomato leaf image."

        result, confidence = predict_model_b(image)
        advice = generate_disease_advice(result, confidence)
        return (
            f"Classifier: {cls_result}\n"
            f"Model B Prediction: {result} (Confidence: {confidence:.1f}%)\n\n"
            f"Expert Advice:\n{advice}\n\n"
            f"[View Model B & Classifier Training Notebook](https://colab.research.google.com/drive/1CvoQY40gK2YsMgt4wq9kM2ZSO2c4lzFU?usp=sharing)"
        )

    else:
        return "Invalid version selected."

# ===== Helper Function to Choose Between Uploaded & Camera Image =====
def combine_images(uploaded, camera):
    return camera if camera is not None else uploaded

# ===== CSS for Theme Switching =====
light_css = """
<style>
body { background-color: white; color: black; }
.gr-button { background-color: #4CAF50; color: white; }
.gr-input, .gr-textbox, .gr-dropdown, .gr-radio, .gr-markdown, .gr-container { background-color: white; color: black; }
</style>
"""

dark_css = """
<style>
body { background-color: #121212 !important; color: #e0e0e0 !important; }
.gr-button { background-color: #555 !important; color: white !important; }
.gr-input, .gr-textbox, .gr-dropdown, .gr-radio, .gr-markdown, .gr-container { background-color: #333 !important; color: #e0e0e0 !important; }
</style>
"""

def update_css(theme):
    if theme == "Dark":
        return dark_css
    else:
        return light_css

# ===== Gradio Interface =====
with gr.Blocks() as demo:
    # Hidden element for CSS injection (initially Light theme)
    css_injector = gr.HTML(update_css("Light"))

    gr.Markdown("# 🌿 FarMVi8ioN – AI-powered Crop Monitoring")
    gr.Markdown("Detect tomato leaf diseases and get actionable advice on how to curb them.")

    with gr.Tabs():
        # === Disease Detection Tab ===
        with gr.TabItem("Disease Detection"):
            with gr.Row():
                # ----- Left Column (β‰ˆ30%) -----
                with gr.Column(scale=1):
                    version = gr.Dropdown(
                        choices=["1.1", "1.2", "1.3", "2.1", "2.2", "2.3"],
                        label="Select Version",
                        value="1.3",
                        info="Versions 1.x use Model A; Versions 2.x use Model B."
                    )

                    theme_choice = gr.Radio(
                        choices=["Light", "Dark"],
                        label="Select Theme",
                        value="Light"
                    )

                    gr.Markdown("### Notebook Links")
                    gr.Markdown(
                        """
                        **For Model A:**
                        - Model A Only: [Training Notebook](https://colab.research.google.com/drive/1FMjs7JmdO6WVoXbzLA-ymwnIKq-GaV6w?usp=sharing)
                        - Model A & Classifier: [Training Notebook](https://colab.research.google.com/drive/1CvoQY40gK2YsMgt4wq9kM2ZSO2c4lzFU?usp=sharing)

                        **For Model B:**
                        - Model B Only: [Training Notebook](https://colab.research.google.com/drive/1CvoQY40gK2YsMgt4wq9kM2ZSO2c4lzFU?usp=sharing)
                        - Model B & Classifier: [Training Notebook](https://colab.research.google.com/drive/1CvoQY40gK2YsMgt4wq9kM2ZSO2c4lzFU?usp=sharing)
                        """
                    )

                # ----- Right Column (β‰ˆ70%) -----
                with gr.Column(scale=2):
                    image_input = gr.Image(label="πŸ“‚ Upload Tomato Leaf Image", type="pil")
                    camera_input = gr.Image(label="πŸ“Έ Use Camera (Live Preview)", type="pil", sources=["webcam"])
                    submit = gr.Button("πŸ” Analyze", variant="primary")
                    output = gr.Markdown(label="πŸ“ Diagnosis & Advice")

        # === Farmer Chat Tab ===
        with gr.TabItem("Chat with Farm Assistant"):
            gr.Markdown("# πŸ’¬ Chat with Farm Assistant")
            gr.Markdown("Ask any questions about farming, crop diseases, or agricultural practices.")

            chatbot = gr.Chatbot(
                label="Chat History",
                height=400,
                bubble_full_width=False,
                show_copy_button=True
            )

            with gr.Row():
                chat_input = gr.Textbox(
                    label="Your Question",
                    placeholder="Ask about tomato farming, diseases, or agricultural practices...",
                    lines=2
                )
                chat_button = gr.Button("Send", variant="primary")

            gr.Markdown("""
            ### Example Questions:
            - How often should I water my tomato plants?
            - What's the best fertilizer for tomatoes?
            - How do I prevent early blight?
            - What are the signs of nutrient deficiency in tomatoes?
            """)

    # Update CSS dynamically based on theme selection
    theme_choice.change(fn=update_css, inputs=theme_choice, outputs=css_injector)

    # When submit is clicked, combine image inputs and process the selected version
    submit.click(
        fn=lambda uploaded, camera, ver: process_version(combine_images(uploaded, camera), ver),
        inputs=[image_input, camera_input, version],
        outputs=output
    )

    # Chat functionality
    chat_button.click(
        fn=chat_with_farmer,
        inputs=[chat_input, chatbot],
        outputs=[chat_input, chatbot]
    )

    # Also allow pressing Enter to send chat
    chat_input.submit(
        fn=chat_with_farmer,
        inputs=[chat_input, chatbot],
        outputs=[chat_input, chatbot]
    )

# Launch the app
demo.launch()