surfiniaburger commited on
Commit
032080e
·
1 Parent(s): 25a899a
Files changed (5) hide show
  1. agent_setup.py +1 -1
  2. app.py +94 -10
  3. requirements.txt +2 -1
  4. story_generator.py +91 -0
  5. utils.py +48 -0
agent_setup.py CHANGED
@@ -22,7 +22,7 @@ def initialize_adk(vision_model, processor, retriever):
22
 
23
  agent = Agent(
24
  name="AuraMindGlowAgent",
25
- model="gemini-2.5-flash",
26
  description="A farming assistant that can diagnose plant health and suggest remedies.",
27
  instruction="You are a friendly farming assistant. Your goal is to help users identify plant health issues and find solutions. Use your tools to diagnose the plant from an image and then find a remedy.",
28
  tools=[diagnosis_tool, remedy_tool]
 
22
 
23
  agent = Agent(
24
  name="AuraMindGlowAgent",
25
+ model="gemini-2.0-flash",
26
  description="A farming assistant that can diagnose plant health and suggest remedies.",
27
  instruction="You are a friendly farming assistant. Your goal is to help users identify plant health issues and find solutions. Use your tools to diagnose the plant from an image and then find a remedy.",
28
  tools=[diagnosis_tool, remedy_tool]
app.py CHANGED
@@ -2,7 +2,7 @@
2
  # Aura Mind Glow - Main Application (Refactored)
3
  # ==============================================================================
4
  """
5
- This script launches the Aura Mind Glow application.
6
  """
7
 
8
  # --- Step 0: Essential Imports ---
@@ -22,6 +22,8 @@ from vision_model import load_vision_model
22
  from knowledge_base import get_retriever
23
  from agent_setup import initialize_adk
24
  from google.genai import types
 
 
25
 
26
  print("✅ All libraries imported successfully.")
27
 
@@ -32,19 +34,35 @@ print("Performing initial setup...")
32
  VISION_MODEL, PROCESSOR = load_vision_model()
33
  RETRIEVER = get_retriever()
34
 
35
- # Initialize ADK components
36
  adk_components = initialize_adk(VISION_MODEL, PROCESSOR, RETRIEVER)
37
  ADK_RUNNER = adk_components["runner"] if adk_components else None
38
  DIAGNOSIS_TOOL = adk_components["diagnosis_tool"] if adk_components else None
39
  REMEDY_TOOL = adk_components["remedy_tool"] if adk_components else None
40
  SESSION_SERVICE = adk_components["session_service"] if adk_components else None
41
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
 
43
  # --- Step 3: Define Gradio UIs ---
44
 
45
  def create_field_mode_ui():
46
  """Creates the Gradio UI for the offline Field Mode."""
47
-
48
  def get_diagnosis_and_remedy(uploaded_image: Image.Image) -> str:
49
  if uploaded_image is None:
50
  return "Please upload an image of a maize plant first."
@@ -104,9 +122,9 @@ def create_field_mode_ui():
104
  css=css
105
  )
106
 
107
-
108
  def create_connected_mode_ui():
109
  """Creates the Gradio UI for the online Connected Mode."""
 
110
  with gr.Blocks(theme=gr.themes.Soft(primary_hue="green", secondary_hue="lime")) as demo:
111
  gr.Markdown("# 🌽 Aura Mind Glow: Connected Mode 🤖")
112
  gr.Markdown("I am an AI farming assistant. Upload an image and ask for a diagnosis and remedy.")
@@ -170,6 +188,51 @@ def create_connected_mode_ui():
170
 
171
  return demo
172
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
173
 
174
  # --- Step 4: App Launcher ---
175
 
@@ -184,11 +247,32 @@ def check_internet_connection(host="8.8.8.8", port=53, timeout=3):
184
 
185
 
186
  if __name__ == "__main__":
187
- if check_internet_connection() and ADK_RUNNER:
188
- print("✅ Internet connection detected. Launching Connected Mode.")
189
- ui = create_connected_mode_ui()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
190
  else:
191
- print("❌ No internet connection or ADK setup failed. Launching Field Mode (Offline).")
192
- ui = create_field_mode_ui()
193
 
194
- ui.launch(share=True, debug=True)
 
2
  # Aura Mind Glow - Main Application (Refactored)
3
  # ==============================================================================
4
  """
5
+ This script launches the Aura Mind Glow application, now with multiple modes.
6
  """
7
 
8
  # --- Step 0: Essential Imports ---
 
22
  from knowledge_base import get_retriever
23
  from agent_setup import initialize_adk
24
  from google.genai import types
25
+ from story_generator import create_story_prompt_from_pdf, generate_video_from_prompt
26
+ from langchain_huggingface import HuggingFaceEndpoint
27
 
28
  print("✅ All libraries imported successfully.")
29
 
 
34
  VISION_MODEL, PROCESSOR = load_vision_model()
35
  RETRIEVER = get_retriever()
36
 
37
+ # Initialize ADK components for Connected Mode
38
  adk_components = initialize_adk(VISION_MODEL, PROCESSOR, RETRIEVER)
39
  ADK_RUNNER = adk_components["runner"] if adk_components else None
40
  DIAGNOSIS_TOOL = adk_components["diagnosis_tool"] if adk_components else None
41
  REMEDY_TOOL = adk_components["remedy_tool"] if adk_components else None
42
  SESSION_SERVICE = adk_components["session_service"] if adk_components else None
43
 
44
+ # Initialize a separate LLM for the Story Generator
45
+ STORY_LLM = None
46
+ if os.environ.get("HF_TOKEN"):
47
+ try:
48
+ STORY_LLM = HuggingFaceEndpoint(
49
+ repo_id="HuggingFaceH4/zephyr-7b-beta",
50
+ huggingfacehub_api_token=os.environ.get("HF_TOKEN"),
51
+ max_new_tokens=150,
52
+ temperature=0.4,
53
+ )
54
+ print("✅ Story Generator LLM initialized successfully.")
55
+ except Exception as e:
56
+ print(f"❌ Could not initialize Story Generator LLM: {e}")
57
+ else:
58
+ print("❌ HF_TOKEN not found. Story Generator Mode will be disabled.")
59
+
60
 
61
  # --- Step 3: Define Gradio UIs ---
62
 
63
  def create_field_mode_ui():
64
  """Creates the Gradio UI for the offline Field Mode."""
65
+ # ... (This function remains unchanged) ...
66
  def get_diagnosis_and_remedy(uploaded_image: Image.Image) -> str:
67
  if uploaded_image is None:
68
  return "Please upload an image of a maize plant first."
 
122
  css=css
123
  )
124
 
 
125
  def create_connected_mode_ui():
126
  """Creates the Gradio UI for the online Connected Mode."""
127
+ # ... (This function remains unchanged) ...
128
  with gr.Blocks(theme=gr.themes.Soft(primary_hue="green", secondary_hue="lime")) as demo:
129
  gr.Markdown("# 🌽 Aura Mind Glow: Connected Mode 🤖")
130
  gr.Markdown("I am an AI farming assistant. Upload an image and ask for a diagnosis and remedy.")
 
188
 
189
  return demo
190
 
191
+ def create_story_mode_ui():
192
+ """Creates the Gradio UI for the Farmer's Story Mode."""
193
+ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="yellow")) as demo:
194
+ gr.Markdown("# 🌽 Aura Mind Glow: Farmer's Story Mode 🎬")
195
+ gr.Markdown("Create a short video story from your farm documents. Upload a PDF, describe the mood, and let the AI create a visual story.")
196
+
197
+ with gr.Row():
198
+ with gr.Column(scale=1):
199
+ pdf_input = gr.File(label="Upload Farm PDF", file_types=[".pdf"])
200
+ image_input = gr.Image(type="filepath", label="Optional: Upload a Starting Image")
201
+ user_prompt_input = gr.Textbox(label="Describe the video's tone or theme", placeholder="e.g., hopeful, a look back at a tough season, etc.")
202
+ submit_btn = gr.Button("Generate Video Story")
203
+ with gr.Column(scale=2):
204
+ video_output = gr.Video(label="Generated Video Story")
205
+ status_output = gr.Textbox(label="Status", interactive=False, lines=3)
206
+
207
+ def story_generation_process(pdf, image, user_prompt):
208
+ if pdf is None:
209
+ yield None, "Please upload a PDF document to begin."
210
+ return
211
+
212
+ yield None, "Step 1: Reading PDF and generating creative prompt..."
213
+
214
+ creative_prompt = create_story_prompt_from_pdf(pdf.name, user_prompt, STORY_LLM)
215
+
216
+ if "Error" in creative_prompt:
217
+ yield None, creative_prompt
218
+ return
219
+
220
+ yield None, f"Step 2: Generating video with prompt: '{creative_prompt[:100]}...' (This may take several minutes)"
221
+
222
+ video_path = generate_video_from_prompt(creative_prompt, image)
223
+
224
+ if "Error" in video_path:
225
+ yield None, video_path
226
+ return
227
+
228
+ yield video_path, "Video generation complete!"
229
+
230
+ submit_btn.click(
231
+ story_generation_process,
232
+ inputs=[pdf_input, image_input, user_prompt_input],
233
+ outputs=[video_output, status_output]
234
+ )
235
+ return demo
236
 
237
  # --- Step 4: App Launcher ---
238
 
 
247
 
248
 
249
  if __name__ == "__main__":
250
+ field_mode_ui = create_field_mode_ui()
251
+ interface_list = [field_mode_ui]
252
+ tab_titles = ["Field Mode (Offline)"]
253
+
254
+ # Conditionally add modes that require an internet connection
255
+ if check_internet_connection():
256
+ if ADK_RUNNER:
257
+ connected_mode_ui = create_connected_mode_ui()
258
+ interface_list.append(connected_mode_ui)
259
+ tab_titles.append("Connected Mode")
260
+ else:
261
+ print("⚠️ Connected Mode disabled: ADK components not initialized.")
262
+
263
+ if STORY_LLM:
264
+ story_mode_ui = create_story_mode_ui()
265
+ interface_list.append(story_mode_ui)
266
+ tab_titles.append("Farmer's Story Mode")
267
+ else:
268
+ print("⚠️ Farmer's Story Mode disabled: Story LLM not initialized.")
269
+ else:
270
+ print("❌ No internet connection. Launching in Offline Mode only.")
271
+
272
+ # Launch the appropriate UI
273
+ if len(interface_list) > 1:
274
+ ui = gr.TabbedInterface(interface_list, tab_titles)
275
  else:
276
+ ui = field_mode_ui
 
277
 
278
+ ui.launch(share=True, debug=True)
requirements.txt CHANGED
@@ -16,4 +16,5 @@ pymupdf
16
  duckduckgo-search
17
  langgraph
18
  google-genai
19
- google-adk
 
 
16
  duckduckgo-search
17
  langgraph
18
  google-genai
19
+ google-adk
20
+ pypdf
story_generator.py ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import time
3
+ import os
4
+ from google import genai
5
+ from google.genai import types
6
+ from langchain_community.document_loaders import PyPDFLoader
7
+ from langchain_huggingface import HuggingFaceEndpoint
8
+ from PIL import Image
9
+ from utils import retry_with_exponential_backoff
10
+
11
+ def create_story_prompt_from_pdf(pdf_path: str, user_prompt: str, llm: HuggingFaceEndpoint) -> str:
12
+ """
13
+ Reads a PDF, summarizes its content, and creates a creative prompt for video generation.
14
+ """
15
+ try:
16
+ # 1. Load and read the PDF
17
+ loader = PyPDFLoader(pdf_path)
18
+ pages = loader.load_and_split()
19
+ # Limit to first 3 pages for brevity and to manage token count
20
+ pdf_content = " ".join(page.page_content for page in pages[:3])
21
+
22
+ # 2. Use an LLM to generate a creative prompt
23
+ system_prompt = """You are a creative assistant for a farmer. Your task is to read the summary of a document and a user's desired tone, and then write a short, visually descriptive prompt for a video generation model (like Google Veo). The prompt should tell a story about a farmer dealing with this paperwork, capturing the user's desired tone. Describe the scene, camera shots, and the farmer's actions.
24
+
25
+ Example:
26
+ - Document Summary: "Invoice for tractor parts, total $2,500. Delivery next week."
27
+ - User Tone: "A feeling of progress and investment in the future."
28
+ - Generated Prompt: "Close up on a farmer's weathered hands circling a date on a calendar in a rustic office. The camera pulls back to reveal invoices on the desk. The farmer looks out the window at the fields, a determined smile on their face. Golden morning light fills the room. Cinematic, hopeful, 4k."
29
+ """
30
+
31
+ human_prompt = f"""
32
+ Document Summary: "{pdf_content[:1500]}"
33
+ User Tone: "{user_prompt}"
34
+
35
+ Generate a creative video prompt based on the summary and tone.
36
+ """
37
+
38
+ # The llm object from HuggingFaceEndpoint expects a string prompt
39
+ creative_prompt = llm.invoke(human_prompt, config={"system_prompt": system_prompt})
40
+
41
+ print(f"Generated creative prompt: {creative_prompt}")
42
+ return creative_prompt
43
+
44
+ except Exception as e:
45
+ print(f"Error creating story from PDF: {e}")
46
+ return f"Error processing PDF: {e}"
47
+
48
+ @retry_with_exponential_backoff
49
+ def generate_video_from_prompt(prompt: str, image_path: str = None) -> str:
50
+ """
51
+ Generates a video using the Veo API from a text prompt and an optional starting image.
52
+ Returns the path to the saved video file.
53
+ """
54
+ # This function is now wrapped with the retry decorator.
55
+ # The try/except block is still useful for catching non-retriable errors.
56
+ try:
57
+ client = genai.Client()
58
+
59
+ if image_path:
60
+ print(f"Generating video with initial image: {image_path}")
61
+ img = Image.open(image_path)
62
+ operation = client.models.generate_videos(
63
+ model="veo-3.0-generate-preview",
64
+ prompt=prompt,
65
+ image=img,
66
+ )
67
+ else:
68
+ print("Generating video from text prompt only.")
69
+ operation = client.models.generate_videos(
70
+ model="veo-3.0-generate-preview",
71
+ prompt=prompt,
72
+ )
73
+
74
+ print("Video generation started. Polling for completion...")
75
+ while not operation.done:
76
+ print("Waiting for video generation to complete...")
77
+ time.sleep(10)
78
+ operation = client.operations.get(operation)
79
+
80
+ generated_video = operation.response.generated_videos[0]
81
+
82
+ video_file_name = "generated_story.mp4"
83
+ client.files.download(file=generated_video.video)
84
+ generated_video.video.save(video_file_name)
85
+
86
+ print(f"Generated video saved to {video_file_name}")
87
+ return video_file_name
88
+
89
+ except Exception as e:
90
+ print(f"Error generating video: {e}")
91
+ return f"Error during video generation: {e}"
utils.py ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # utils.py
2
+ import time
3
+ import random
4
+ from functools import wraps
5
+ from google.api_core import exceptions
6
+ import gradio as gr
7
+
8
+ def retry_with_exponential_backoff(
9
+ func,
10
+ initial_delay: float = 2,
11
+ exponential_base: float = 2,
12
+ jitter: bool = True,
13
+ max_retries: int = 5,
14
+ ):
15
+ """
16
+ A decorator to retry a function with exponential backoff for API calls.
17
+ It specifically catches google.api_core.exceptions.ResourceExhausted.
18
+ """
19
+
20
+ @wraps(func)
21
+ def wrapper(*args, **kwargs):
22
+ num_retries = 0
23
+ delay = initial_delay
24
+
25
+ while True:
26
+ try:
27
+ return func(*args, **kwargs)
28
+ except exceptions.ResourceExhausted as e:
29
+ num_retries += 1
30
+ if num_retries > max_retries:
31
+ # Using gr.Error to raise a user-facing error in the Gradio UI
32
+ raise gr.Error(
33
+ f"Maximum number of retries ({max_retries}) exceeded. The API is still busy. Please try again later."
34
+ ) from e
35
+
36
+ if jitter:
37
+ delay *= exponential_base * (1 + random.random())
38
+ else:
39
+ delay *= exponential_base
40
+
41
+ # It's helpful to print the delay to the console for debugging
42
+ print(f"Rate limit exceeded. Retrying in {delay:.2f} seconds...")
43
+ time.sleep(delay)
44
+ except Exception as e:
45
+ # Raise other exceptions immediately
46
+ raise gr.Error(f"An unexpected error occurred: {e}")
47
+
48
+ return wrapper