File size: 11,385 Bytes
5acf918
 
37c6da0
5acf918
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37c6da0
 
5acf918
 
 
 
 
 
 
37c6da0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5acf918
 
 
 
37c6da0
5acf918
 
37c6da0
5acf918
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37c6da0
5acf918
 
37c6da0
 
5acf918
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37c6da0
5acf918
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37c6da0
5acf918
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import gradio as gr
import os
import re
from openai import OpenAI
import json
import requests
from PIL import Image
import io
import base64

# Constants
DEFAULT_MODEL = "opengvlab/internvl3-14b:free"
# No need for placeholder text as we'll use password type input
DEFAULT_SITE_URL = "https://dynamic-nature-trail-guide.app"
DEFAULT_SITE_NAME = "Dynamic Nature Trail Guide"

# Initialize system prompt for better nature guide responses
SYSTEM_PROMPT = """
You are the Dynamic Nature Trail Guide, an expert in identifying and explaining natural elements 
found on nature trails. For any image sent, please:

1. Identify all visible plants, animals, geological features, and ecosystems
2. Provide educational information about identified elements
3. Mention any seasonal characteristics visible in the image
4. Note any ecological significance or conservation considerations
5. Offer suggestions for what to observe or learn about next on the trail

Keep explanations informative yet accessible to people of all ages and backgrounds.

IMPORTANT: Structure your responses with clear sections and headings.
"""

def encode_image_to_base64(image_path):
    """Convert an image file to base64 encoding"""
    with open(image_path, "rb") as image_file:
        return base64.b64encode(image_file.read()).decode('utf-8')

def format_response_as_html(text):
    """Convert the model's text response to formatted HTML"""
    if not text:
        return ""
    
    # Check if the response is an error message
    if text.startswith("Error analyzing image:"):
        return f'<div style="color: red; padding: 10px; border: 1px solid red; border-radius: 5px;">{text}</div>'
    
    # Replace newlines with HTML breaks
    text = text.replace('\n\n', '</p><p>').replace('\n', '<br>')
    
    # Handle headings - look for patterns like "1. Identification:" or "Species Identified:"
    heading_patterns = [
        (r'([A-Za-z\s]+):(?=<br>|</p>)', r'<h3>\1</h3>'),  # "Category:" at start of line
        (r'(\d+\.\s+[A-Za-z\s]+):(?=<br>|</p>)', r'<h3>\1</h3>'),  # "1. Category:" format
    ]
    
    for pattern, replacement in heading_patterns:
        text = re.sub(pattern, replacement, text)
    
    # Enhance species names with bold
    text = re.sub(r'\b([A-Z][a-z]+\s+[a-z]+)\b(?!\<\/)', r'<strong>\1</strong>', text)
    
    # Add some color to certain keywords
    color_mappings = {
        'endangered': 'red',
        'rare': '#FF6600',
        'native': '#006600',
        'invasive': '#CC0000',
        'ecosystem': '#006699',
        'habitat': '#336699',
    }
    
    for keyword, color in color_mappings.items():
        text = re.sub(r'\b' + keyword + r'\b', f'<span style="color: {color};">{keyword}</span>', text, flags=re.IGNORECASE)
    
    # Wrap the entire content in a styled div
    html = f'''
    <div style="padding: 15px; font-family: Arial, sans-serif; line-height: 1.6;">
        <p>{text}</p>
    </div>
    '''
    
    return html

def analyze_image(api_key, image, prompt="What can you identify in this nature trail image? Provide detailed educational information.", site_url=DEFAULT_SITE_URL, site_name=DEFAULT_SITE_NAME, model=DEFAULT_MODEL):
    """Analyze the uploaded image using the InternVL3 model via OpenRouter"""
    # Remove the placeholder text check
    if not api_key:
        return "<div style='color: red; padding: 10px; border: 1px solid red; border-radius: 5px;'>Please provide an OpenRouter API key.</div>"
    
    if image is None:
        return "<div style='color: red; padding: 10px; border: 1px solid red; border-radius: 5px;'>Please upload an image to analyze.</div>"
    
    # Save the image temporarily
    temp_image_path = "temp_image.jpg"
    image.save(temp_image_path)
    
    try:
        # Convert image to base64
        base64_image = encode_image_to_base64(temp_image_path)
        
        # Initialize OpenAI client
        client = OpenAI(
            base_url="https://openrouter.ai/api/v1",
            api_key=api_key,
        )
        
        # Create message with image and text
        response = client.chat.completions.create(
            extra_headers={
                "HTTP-Referer": site_url, 
                "X-Title": site_name,
            },
            model=model,
            messages=[
                {"role": "system", "content": SYSTEM_PROMPT},
                {
                    "role": "user",
                    "content": [
                        {
                            "type": "text",
                            "text": prompt
                        },
                        {
                            "type": "image_url",
                            "image_url": {
                                "url": f"data:image/jpeg;base64,{base64_image}"
                            }
                        }
                    ]
                }
            ]
        )
        
        analysis_result = response.choices[0].message.content
        return format_response_as_html(analysis_result)
    
    except Exception as e:
        error_message = f"Error analyzing image: {str(e)}"
        return f'<div style="color: red; padding: 10px; border: 1px solid red; border-radius: 5px;">{error_message}</div>'
    
    finally:
        # Clean up the temporary file
        if os.path.exists(temp_image_path):
            os.remove(temp_image_path)

def build_custom_prompt(identification=True, education=True, seasonal=True, conservation=True, suggestions=True, additional_prompt=""):
    """Build a custom prompt based on user preferences"""
    prompt_parts = []
    
    if identification:
        prompt_parts.append("Identify all visible plants, animals, geological features, and ecosystems")
    
    if education:
        prompt_parts.append("Provide educational information about identified elements")
    
    if seasonal:
        prompt_parts.append("Mention any seasonal characteristics visible in the image")
    
    if conservation:
        prompt_parts.append("Note any ecological significance or conservation considerations")
    
    if suggestions:
        prompt_parts.append("Offer suggestions for what to observe or learn next on the trail")
    
    if additional_prompt:
        prompt_parts.append(additional_prompt)
    
    if not prompt_parts:
        return "What can you identify in this nature trail image?"
    
    numbered_prompt = "\n".join([f"{i+1}. {part}" for i, part in enumerate(prompt_parts)])
    return f"For this nature trail image, please: \n{numbered_prompt}\n\nIMPORTANT: Structure your response with clear sections and headings for better readability."

def create_interface():
    """Create the Gradio interface for the Dynamic Nature Trail Guide"""
    with gr.Blocks(title="Dynamic Nature Trail Guide", theme=gr.themes.Soft()) as app:
        gr.Markdown("""
        # ๐ŸŒฟ Dynamic Nature Trail Guide: Accessible Outdoor Education ๐ŸŒฟ
        
        Upload an image from your nature walk to identify plants, animals, geological features, and learn about the ecosystem.
        This application uses the advanced InternVL3 14B multimodal model for nature identification and education.
        """)
        
        with gr.Row():
            with gr.Column(scale=1):
                api_key_input = gr.Textbox(
                    label="OpenRouter API Key", 
                    placeholder="Enter your OpenRouter API key here...",
                    type="password"
                )
                image_input = gr.Image(label="Upload Nature Image", type="pil")
                
                with gr.Accordion("Advanced Settings", open=False):
                    site_url = gr.Textbox(
                        label="Site URL (for OpenRouter)", 
                        value=DEFAULT_SITE_URL
                    )
                    site_name = gr.Textbox(
                        label="Site Name (for OpenRouter)", 
                        value=DEFAULT_SITE_NAME
                    )
                    model_selection = gr.Dropdown(
                        label="Model",
                        choices=[DEFAULT_MODEL],
                        value=DEFAULT_MODEL
                    )
                
                with gr.Accordion("Customize Analysis", open=False):
                    gr.Markdown("Select what information you want to receive about the image:")
                    identification_checkbox = gr.Checkbox(label="Species & Feature Identification", value=True)
                    education_checkbox = gr.Checkbox(label="Educational Information", value=True)
                    seasonal_checkbox = gr.Checkbox(label="Seasonal Characteristics", value=True)
                    conservation_checkbox = gr.Checkbox(label="Conservation Considerations", value=True)
                    suggestions_checkbox = gr.Checkbox(label="Trail Suggestions", value=True)
                    additional_prompt = gr.Textbox(label="Additional Instructions (Optional)")
                
                analyze_button = gr.Button("Analyze Nature Image", variant="primary")
            
            with gr.Column(scale=1):
                output_text = gr.HTML(label="Analysis Results")
        
        # Set up the click event for the analyze button
        analyze_button.click(
            fn=lambda api_key, image, id_check, edu_check, season_check, conserve_check, suggest_check, add_prompt, site_url, site_name, model: 
                analyze_image(
                    api_key, 
                    image, 
                    build_custom_prompt(id_check, edu_check, season_check, conserve_check, suggest_check, add_prompt),
                    site_url,
                    site_name,
                    model
                ),
            inputs=[
                api_key_input, 
                image_input, 
                identification_checkbox,
                education_checkbox,
                seasonal_checkbox,
                conservation_checkbox,
                suggestions_checkbox,
                additional_prompt,
                site_url,
                site_name,
                model_selection
            ],
            outputs=output_text
        )
        
        # Example gallery
        with gr.Accordion("Example Images", open=False):
            gr.Markdown("Click on an example image to analyze it:")
            example_images = gr.Examples(
                examples=[
                    "examples/forest_trail.jpg",
                    "examples/wetland_boardwalk.jpg",
                    "examples/mountain_vista.jpg",
                    "examples/coastal_trail.jpg",
                ],
                inputs=image_input,
                label="Nature Trail Examples"
            )
            
        gr.Markdown("""
        ## How to Use This App
        
        1. Enter your OpenRouter API key (sign up at [openrouter.ai](https://openrouter.ai) if needed)
        2. Upload an image from your nature walk
        3. Customize what kind of information you want (optional)
        4. Click "Analyze Nature Image"
        5. Explore the detailed educational content about what you're seeing
        
        This application is designed to make nature more accessible and educational for everyone!
        """)
    
    return app

# Create and launch the app
if __name__ == "__main__":
    app = create_interface()
    app.launch()