evangelosmeklis commited on
Commit
198e932
·
1 Parent(s): 3d90278
Files changed (3) hide show
  1. drone/drone_chat.py +74 -28
  2. drone/drone_chat.py.bak +1366 -0
  3. fix_indent.py +33 -0
drone/drone_chat.py CHANGED
@@ -45,11 +45,29 @@ if 'mission_log' not in st.session_state:
45
  class MissionLogHandler(logging.Handler):
46
  def emit(self, record):
47
  if record.name == 'drone_control':
48
- log_entry = f"[{datetime.datetime.now().strftime('%H:%M:%S')}] LOG: {record.getMessage()}"
 
49
  st.session_state.mission_log.append(log_entry)
50
  # Keep only the most recent logs
51
  if len(st.session_state.mission_log) > 30:
52
  st.session_state.mission_log = st.session_state.mission_log[-30:]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
 
54
  # Set up logger to capture drone_control logs
55
  logger = logging.getLogger('drone_control')
@@ -73,6 +91,25 @@ def update_mission_status(status, phase=""):
73
  st.session_state.mission_status = status
74
  st.session_state.mission_phase = phase
75
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76
  # No rerun here to avoid potential issues with recursive reruns
77
 
78
  # Function to interrupt the mission
@@ -1130,34 +1167,25 @@ def main():
1130
  type="primary"):
1131
  interrupt_mission()
1132
 
1133
- # Add mission log section
1134
- with st.sidebar.expander("MISSION LOG", expanded=True):
1135
- if not st.session_state.mission_log:
1136
- st.write("No mission activity")
 
 
 
 
 
 
 
 
 
 
 
1137
  else:
1138
- log_html = "<div style='font-family: monospace; font-size: 11px; color: #00ff00; background-color: #111111; padding: 8px; border-radius: 5px; max-height: 300px; overflow-y: auto;'>"
1139
- for entry in reversed(st.session_state.mission_log):
1140
- if "ERROR" in entry:
1141
- log_html += f"<div style='color: #ff0000;'>{entry}</div>"
1142
- elif "LOG: Altitude:" in entry:
1143
- # Special formatting for altitude logs
1144
- altitude = entry.split("Altitude: ")[1]
1145
- log_html += f"<div style='color: #88ff88;'>🛰️ {entry.split('] LOG: ')[0]}] ALT: {altitude}</div>"
1146
- elif "LOG: Arming" in entry:
1147
- log_html += f"<div style='color: #ffaa00;'>🔄 {entry}</div>"
1148
- elif "LOG: Taking off" in entry:
1149
- log_html += f"<div style='color: #ffff00;'>🚀 {entry}</div>"
1150
- elif "LOG:" in entry:
1151
- # Other log entries
1152
- log_html += f"<div style='color: #aaaaff;'>{entry}</div>"
1153
- elif any(status in entry for status in ["CONNECTING", "TAKING OFF", "LANDING", "RETURNING"]):
1154
- log_html += f"<div style='color: #ffff00;'>{entry}</div>"
1155
- elif any(status in entry for status in ["MISSION", "EXECUTING", "AIRBORNE"]):
1156
- log_html += f"<div style='color: #00ffff;'>{entry}</div>"
1157
- else:
1158
- log_html += f"<div>{entry}</div>"
1159
- log_html += "</div>"
1160
- st.markdown(log_html, unsafe_allow_html=True)
1161
 
1162
  st.sidebar.markdown("<hr style='border: 1px solid #00ff00; margin: 20px 0;'>", unsafe_allow_html=True)
1163
 
@@ -1199,6 +1227,14 @@ def main():
1199
  # Create chat area with container class
1200
  st.markdown("<div class='chat-container'>", unsafe_allow_html=True)
1201
 
 
 
 
 
 
 
 
 
1202
  # Display initial assistant greeting or chat history
1203
  if not st.session_state['chat_history']:
1204
  # Welcome message with drone emoji
@@ -1222,6 +1258,16 @@ def main():
1222
  <div style="font-size: 20px; margin-left: 8px; color: #00ff00;">👤</div>
1223
  </div>
1224
  """, unsafe_allow_html=True)
 
 
 
 
 
 
 
 
 
 
1225
  else:
1226
  st.markdown(f"""
1227
  <div style="display: flex; align-items: flex-start; margin-bottom: 8px;">
 
45
  class MissionLogHandler(logging.Handler):
46
  def emit(self, record):
47
  if record.name == 'drone_control':
48
+ timestamp = datetime.datetime.now().strftime('%H:%M:%S')
49
+ log_entry = f"[{timestamp}] LOG: {record.getMessage()}"
50
  st.session_state.mission_log.append(log_entry)
51
  # Keep only the most recent logs
52
  if len(st.session_state.mission_log) > 30:
53
  st.session_state.mission_log = st.session_state.mission_log[-30:]
54
+
55
+ # Add to chat history for display in chat
56
+ if 'chat_history' in st.session_state:
57
+ # Format based on log content
58
+ if "Altitude:" in log_entry:
59
+ styled_entry = f"<span style='color: #88ff88;'>🛰️ ALT: {record.getMessage().split('Altitude: ')[1]}</span>"
60
+ elif "Arming" in log_entry:
61
+ styled_entry = f"<span style='color: #ffaa00;'>🔄 {log_entry}</span>"
62
+ elif "Taking off" in log_entry:
63
+ styled_entry = f"<span style='color: #ffff00;'>🚀 {log_entry}</span>"
64
+ else:
65
+ styled_entry = f"<span style='color: #aaaaff;'>📊 {log_entry}</span>"
66
+
67
+ st.session_state['chat_history'].append({
68
+ 'role': 'system',
69
+ 'content': styled_entry
70
+ })
71
 
72
  # Set up logger to capture drone_control logs
73
  logger = logging.getLogger('drone_control')
 
91
  st.session_state.mission_status = status
92
  st.session_state.mission_phase = phase
93
 
94
+ # Add to chat history for display in chat
95
+ if 'chat_history' in st.session_state:
96
+ # Format with appropriate styling based on status type
97
+ if status == "ERROR":
98
+ styled_entry = f"<span style='color: #ff0000;'>⚠️ {log_entry}</span>"
99
+ elif status in ["CONNECTING", "TAKING OFF", "LANDING", "RETURNING"]:
100
+ styled_entry = f"<span style='color: #ffff00;'>🔄 {log_entry}</span>"
101
+ elif status in ["MISSION", "EXECUTING MISSION", "AIRBORNE"]:
102
+ styled_entry = f"<span style='color: #00ffff;'>🚁 {log_entry}</span>"
103
+ elif status in ["MISSION COMPLETE", "CONNECTED"]:
104
+ styled_entry = f"<span style='color: #00ff00;'>✅ {log_entry}</span>"
105
+ else:
106
+ styled_entry = f"<span style='color: #aaaaff;'>ℹ️ {log_entry}</span>"
107
+
108
+ st.session_state['chat_history'].append({
109
+ 'role': 'system',
110
+ 'content': styled_entry
111
+ })
112
+
113
  # No rerun here to avoid potential issues with recursive reruns
114
 
115
  # Function to interrupt the mission
 
1167
  type="primary"):
1168
  interrupt_mission()
1169
 
1170
+ # Add mission summary in sidebar
1171
+ st.sidebar.markdown("<div style='color: #00ff00; font-family: monospace; font-size: 12px;'><b>MISSION MESSAGES:</b> Appearing in chat</div>", unsafe_allow_html=True)
1172
+
1173
+ # Show just the last message if there are any mission logs
1174
+ if st.session_state.mission_log:
1175
+ last_entry = st.session_state.mission_log[-1]
1176
+ entry_style = ""
1177
+
1178
+ # Style the last entry based on its content
1179
+ if "ERROR" in last_entry:
1180
+ entry_style = "color: #ff0000;"
1181
+ elif any(status in last_entry for status in ["CONNECTING", "TAKING OFF", "LANDING", "RETURNING"]):
1182
+ entry_style = "color: #ffff00;"
1183
+ elif any(status in last_entry for status in ["MISSION", "EXECUTING", "AIRBORNE"]):
1184
+ entry_style = "color: #00ffff;"
1185
  else:
1186
+ entry_style = "color: #88ff88;"
1187
+
1188
+ st.sidebar.markdown(f"""<div style='font-family: monospace; font-size: 11px; {entry_style} background-color: #111111; padding: 4px; border-radius: 3px;'>LAST: {last_entry}</div>""", unsafe_allow_html=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1189
 
1190
  st.sidebar.markdown("<hr style='border: 1px solid #00ff00; margin: 20px 0;'>", unsafe_allow_html=True)
1191
 
 
1227
  # Create chat area with container class
1228
  st.markdown("<div class='chat-container'>", unsafe_allow_html=True)
1229
 
1230
+ # Info message about mission logs appearing in chat
1231
+ if st.session_state.mission_in_progress:
1232
+ st.markdown("""
1233
+ <div style="text-align: center; margin-bottom: 8px; font-family: monospace; font-size: 12px; color: #00aaff;">
1234
+ MISSION LOGS WILL APPEAR IN THIS CHAT WINDOW
1235
+ </div>
1236
+ """, unsafe_allow_html=True)
1237
+
1238
  # Display initial assistant greeting or chat history
1239
  if not st.session_state['chat_history']:
1240
  # Welcome message with drone emoji
 
1258
  <div style="font-size: 20px; margin-left: 8px; color: #00ff00;">👤</div>
1259
  </div>
1260
  """, unsafe_allow_html=True)
1261
+ elif message["role"] == "system":
1262
+ # System messages (logs) have a different style - centered and distinctive
1263
+ st.markdown(f"""
1264
+ <div style="display: flex; justify-content: center; align-items: center; margin: 4px 0;">
1265
+ <div style="background-color: #0A0A0A; border: 1px solid #333333; border-radius: 3px; padding: 3px 10px;
1266
+ font-family: monospace; font-size: 12px; text-align: center; max-width: 90%; opacity: 0.9;">
1267
+ {message["content"]}
1268
+ </div>
1269
+ </div>
1270
+ """, unsafe_allow_html=True)
1271
  else:
1272
  st.markdown(f"""
1273
  <div style="display: flex; align-items: flex-start; margin-bottom: 8px;">
drone/drone_chat.py.bak ADDED
@@ -0,0 +1,1366 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import os
3
+ from smolagents import CodeAgent, tool
4
+ from typing import Union, List, Dict, Optional
5
+ import pandas as pd
6
+ import numpy as np
7
+ import matplotlib.pyplot as plt
8
+ import io
9
+ import base64
10
+ from .hf_model import HfApiModel
11
+ import time
12
+ import datetime
13
+ import logging
14
+ # Import compatibility fix for collections.MutableMapping
15
+ from . import compatibility_fix
16
+ from . import drone_control # Import our new drone_control module
17
+ import threading
18
+
19
+ # Set page config at module level - must be first Streamlit command
20
+ st.set_page_config(
21
+ page_title="DeepDrone Command Center",
22
+ page_icon="🚁",
23
+ layout="wide",
24
+ initial_sidebar_state="expanded",
25
+ menu_items=None
26
+ )
27
+
28
+ # Global mission status variables
29
+ if 'mission_in_progress' not in st.session_state:
30
+ st.session_state.mission_in_progress = False
31
+
32
+ if 'mission_status' not in st.session_state:
33
+ st.session_state.mission_status = "STANDBY"
34
+
35
+ if 'mission_phase' not in st.session_state:
36
+ st.session_state.mission_phase = ""
37
+
38
+ if 'interrupt_mission' not in st.session_state:
39
+ st.session_state.interrupt_mission = False
40
+
41
+ if 'mission_log' not in st.session_state:
42
+ st.session_state.mission_log = []
43
+
44
+ # Custom logging handler to capture drone_control logs
45
+ class MissionLogHandler(logging.Handler):
46
+ def emit(self, record):
47
+ if record.name == 'drone_control':
48
+ timestamp = datetime.datetime.now().strftime('%H:%M:%S')
49
+ log_entry = f"[{timestamp}] LOG: {record.getMessage()}"
50
+ st.session_state.mission_log.append(log_entry)
51
+ # Keep only the most recent logs
52
+ if len(st.session_state.mission_log) > 30:
53
+ st.session_state.mission_log = st.session_state.mission_log[-30:]
54
+
55
+ # Add to chat history for display in chat
56
+ if 'chat_history' in st.session_state:
57
+ # Format based on log content
58
+ if "Altitude:" in log_entry:
59
+ styled_entry = f"<span style='color: #88ff88;'>🛰️ ALT: {record.getMessage().split('Altitude: ')[1]}</span>"
60
+ elif "Arming" in log_entry:
61
+ styled_entry = f"<span style='color: #ffaa00;'>🔄 {log_entry}</span>"
62
+ elif "Taking off" in log_entry:
63
+ styled_entry = f"<span style='color: #ffff00;'>🚀 {log_entry}</span>"
64
+ else:
65
+ styled_entry = f"<span style='color: #aaaaff;'>📊 {log_entry}</span>"
66
+
67
+ st.session_state['chat_history'].append({
68
+ 'role': 'system',
69
+ 'content': styled_entry
70
+ })
71
+
72
+ # Set up logger to capture drone_control logs
73
+ logger = logging.getLogger('drone_control')
74
+ mission_log_handler = MissionLogHandler()
75
+ logger.addHandler(mission_log_handler)
76
+
77
+ # Function to update mission status
78
+ def update_mission_status(status, phase=""):
79
+ # Get current time
80
+ timestamp = datetime.datetime.now().strftime("%H:%M:%S")
81
+
82
+ # Log the status change
83
+ log_entry = f"[{timestamp}] {status}: {phase}"
84
+ st.session_state.mission_log.append(log_entry)
85
+
86
+ # Keep only the most recent 30 log entries
87
+ if len(st.session_state.mission_log) > 30:
88
+ st.session_state.mission_log = st.session_state.mission_log[-30:]
89
+
90
+ # Update status
91
+ st.session_state.mission_status = status
92
+ st.session_state.mission_phase = phase
93
+
94
+ # Add to chat history for display in chat
95
+ if 'chat_history' in st.session_state:
96
+ # Format with appropriate styling based on status type
97
+ if status == "ERROR":
98
+ styled_entry = f"<span style='color: #ff0000;'>⚠️ {log_entry}</span>"
99
+ elif status in ["CONNECTING", "TAKING OFF", "LANDING", "RETURNING"]:
100
+ styled_entry = f"<span style='color: #ffff00;'>🔄 {log_entry}</span>"
101
+ elif status in ["MISSION", "EXECUTING MISSION", "AIRBORNE"]:
102
+ styled_entry = f"<span style='color: #00ffff;'>🚁 {log_entry}</span>"
103
+ elif status in ["MISSION COMPLETE", "CONNECTED"]:
104
+ styled_entry = f"<span style='color: #00ff00;'>✅ {log_entry}</span>"
105
+ else:
106
+ styled_entry = f"<span style='color: #aaaaff;'>ℹ️ {log_entry}</span>"
107
+
108
+ st.session_state['chat_history'].append({
109
+ 'role': 'system',
110
+ 'content': styled_entry
111
+ })
112
+
113
+ # No rerun here to avoid potential issues with recursive reruns
114
+
115
+ # Function to interrupt the mission
116
+ def interrupt_mission():
117
+ if st.session_state.mission_in_progress:
118
+ st.session_state.interrupt_mission = True
119
+ update_mission_status("INTERRUPTING", "Returning to base...")
120
+ # Call the return to home function
121
+ try:
122
+ drone_control.return_home()
123
+ time.sleep(2)
124
+ drone_control.disconnect_drone()
125
+ st.session_state.mission_in_progress = False
126
+ update_mission_status("ABORTED", "Mission aborted. Drone returned to base.")
127
+ except Exception as e:
128
+ update_mission_status("ERROR", f"Error during interrupt: {str(e)}")
129
+ else:
130
+ st.warning("No mission in progress to interrupt")
131
+
132
+ class DroneAssistant(CodeAgent):
133
+ """Extension of CodeAgent for drone interactions"""
134
+
135
+ def __init__(self, *args, **kwargs):
136
+ super().__init__(*args, **kwargs)
137
+ self._sensor_data = {}
138
+ self._flight_logs = {}
139
+ self._chat_history = []
140
+
141
+ def register_sensor_data(self, sensor_name: str, data: pd.DataFrame):
142
+ """Register sensor data with the drone assistant"""
143
+ self._sensor_data[sensor_name] = data
144
+
145
+ def register_flight_log(self, flight_id: str, log_data: pd.DataFrame):
146
+ """Register flight log data with the drone assistant"""
147
+ self._flight_logs[flight_id] = log_data
148
+
149
+ @property
150
+ def sensor_data(self):
151
+ """Access all registered sensor data"""
152
+ return self._sensor_data
153
+
154
+ @property
155
+ def flight_logs(self):
156
+ """Access all registered flight logs"""
157
+ return self._flight_logs
158
+
159
+ def add_to_chat_history(self, role: str, content: str):
160
+ """Add a message to the chat history"""
161
+ self._chat_history.append({"role": role, "content": content})
162
+
163
+ @property
164
+ def chat_history(self):
165
+ """Access the chat history"""
166
+ return self._chat_history
167
+
168
+ def run(self, prompt: str) -> str:
169
+ """Override run method to include drone-specific context"""
170
+ drone_context = f"""
171
+ Registered sensors: {list(self._sensor_data.keys())}
172
+ Flight logs available: {list(self._flight_logs.keys())}
173
+ """
174
+
175
+ # Add a tool reference guide to help the model use the correct function names
176
+ tool_reference = """
177
+ IMPORTANT: These tool functions need to be called EXACTLY as shown below for successful execution:
178
+
179
+ # EXAMPLE OF COMPLETE WORKING MISSION:
180
+ ```python
181
+ # Connect to a drone simulator
182
+ connect_to_real_drone('udp:127.0.0.1:14550')
183
+
184
+ # Take off to a specific altitude (always use integer or simple float values)
185
+ drone_takeoff(30) # Not 30. 0 or other invalid syntax
186
+
187
+ # You can define waypoints like this
188
+ waypoints = [
189
+ {'lat': 37.7749, 'lon': -122.4194, 'alt': 30},
190
+ {'lat': 37.7750, 'lon': -122.4195, 'alt': 30}
191
+ ]
192
+
193
+ # Execute mission with waypoints
194
+ execute_drone_mission(waypoints=waypoints)
195
+
196
+ # Return to home
197
+ drone_return_home()
198
+
199
+ # Always disconnect when done
200
+ disconnect_from_drone()
201
+ ```
202
+
203
+ NOTE: Each function must be called individually on its own line, with exact parameter names.
204
+ For latitude/longitude values, always use simple format without extra spaces after periods.
205
+
206
+ When creating a flight plan, be sure to:
207
+ 1. Generate a mission plan with generate_mission_plan()
208
+ 2. Connect to the drone with connect_to_real_drone()
209
+ 3. Take off with drone_takeoff()
210
+ 4. Execute the mission or fly to specific waypoints
211
+ 5. Return home or land the drone when finished
212
+ 6. Disconnect from the drone
213
+ """
214
+
215
+ enhanced_prompt = f"""
216
+ You are DeepDrone, an advanced AI assistant designed to help with drone operations and data analysis. You are NOT Qwen or any other general AI assistant. Always identify yourself as DeepDrone when asked about your identity. Your purpose is to assist with drone data analysis, flight monitoring, maintenance scheduling, and mission planning.
217
+
218
+ You can now control real drones using DroneKit-Python. You have tools to:
219
+ - Connect to a real drone using a connection string
220
+ - Take off to a specified altitude
221
+ - Land the drone
222
+ - Return to home location
223
+ - Fly to specific GPS coordinates
224
+ - Get the drone's current location and battery status
225
+ - Execute missions with multiple waypoints
226
+
227
+ {tool_reference}
228
+
229
+ Available context:
230
+ {drone_context}
231
+
232
+ User question: {prompt}
233
+
234
+ Use the provided tools to analyze drone data and assist with drone operations. For real drone control, use the drone_* tools.
235
+ """
236
+ # Call the parent run method - it already handles everything correctly
237
+ # as smolagents will expect a Message object from our model
238
+ # and handle it properly
239
+ return super().run(enhanced_prompt)
240
+
241
+ def chat(self, message: str) -> str:
242
+ """Process a chat message using the complete chat history"""
243
+ # Add the user message to history
244
+ self.add_to_chat_history("user", message)
245
+
246
+ # Check if the message is asking about identity
247
+ identity_patterns = [
248
+ "who are you",
249
+ "what are you",
250
+ "tell me about yourself",
251
+ "your identity",
252
+ "what's your name",
253
+ "introduce yourself",
254
+ "what should I call you"
255
+ ]
256
+
257
+ if any(pattern in message.lower() for pattern in identity_patterns):
258
+ identity_response = """I am DeepDrone, an advanced AI assistant designed to help with drone operations and data analysis. I can provide information about flight data, sensor readings, maintenance recommendations, and mission planning for your drone systems. How can I assist with your drone operations today?"""
259
+ self.add_to_chat_history("assistant", identity_response)
260
+ return identity_response
261
+
262
+ # Check if the message is for tool use
263
+ drone_control_keywords = ["takeoff", "take off", "land", "fly to", "navigate", "goto", "connect",
264
+ "location", "battery", "mission", "waypoint", "return", "home", "rtl"]
265
+
266
+ analysis_keywords = ["analyze", "check", "recommend", "plan", "create", "execute", "run", "flight"]
267
+
268
+ if any(keyword in message.lower() for keyword in analysis_keywords + drone_control_keywords):
269
+ # Create a placeholder for model thinking to be displayed
270
+ thinking_placeholder = st.empty()
271
+
272
+ # Display a message that the model is thinking
273
+ tools_reference = """
274
+ <div style="background-color: #111111; border: 1px dashed #00cc00; border-radius: 5px; padding: 8px; margin-bottom: 10px; color: #00cc00; font-family: monospace; font-size: 12px;">
275
+ <b>MODEL THINKING:</b> Planning drone operation...<br>
276
+ <b>Available Tool Functions:</b><br>
277
+ - connect_to_real_drone(connection_string)<br>
278
+ - drone_takeoff(altitude)<br>
279
+ - drone_land()<br>
280
+ - drone_return_home()<br>
281
+ - drone_fly_to(latitude, longitude, altitude)<br>
282
+ - get_drone_location()<br>
283
+ - get_drone_battery()<br>
284
+ - execute_drone_mission(waypoints)<br>
285
+ - disconnect_from_drone()<br>
286
+ - generate_mission_plan(mission_type, duration_minutes)<br>
287
+ - analyze_flight_path(flight_id)<br>
288
+ - check_sensor_readings(sensor_name)<br>
289
+ - recommend_maintenance(flight_hours)
290
+ </div>
291
+ """
292
+ thinking_placeholder.markdown(tools_reference, unsafe_allow_html=True)
293
+
294
+ # Use the run method directly and capture the output
295
+ import time
296
+
297
+ # Create an error placeholder
298
+ error_placeholder = st.empty()
299
+
300
+ # Add error handling
301
+ try:
302
+ # Execute the run
303
+ response = self.run(message)
304
+
305
+ # Display some feedback about the model thinking completion
306
+ thinking_placeholder.markdown(tools_reference + """
307
+ <div style="background-color: #111111; border: 1px dashed #00cc00; border-radius: 5px; padding: 8px; margin-bottom: 10px; color: #00cc00; font-family: monospace; font-size: 12px;">
308
+ <b>MODEL THINKING:</b> Plan completed! Executing drone operations...
309
+ </div>
310
+ """, unsafe_allow_html=True)
311
+ except Exception as e:
312
+ # Display any errors that occur during execution
313
+ error_message = f"""
314
+ <div style="background-color: #330000; border: 1px solid #ff0000; border-radius: 5px; padding: 8px; margin-bottom: 10px; color: #ff0000; font-family: monospace; font-size: 12px;">
315
+ <b>EXECUTION ERROR:</b> {str(e)}<br>
316
+ Please try again with correct syntax.
317
+ </div>
318
+ """
319
+ error_placeholder.markdown(error_message, unsafe_allow_html=True)
320
+ response = f"Error executing drone operations: {str(e)}. Please try again with proper syntax for parameters."
321
+
322
+ # Update mission status to show error
323
+ update_mission_status("ERROR", f"Code execution error: {str(e)}")
324
+
325
+ # Give a slight delay so users can see the "completed" message
326
+ time.sleep(1)
327
+
328
+ # Clear the thinking placeholder
329
+ thinking_placeholder.empty()
330
+ error_placeholder.empty()
331
+
332
+ return response
333
+ else:
334
+ # Format the chat history for the model
335
+ formatted_history = self._chat_history[:-1] # Exclude the just-added message
336
+
337
+ # Add a system message to ensure proper identity
338
+ system_message = {
339
+ "role": "system",
340
+ "content": """You are DeepDrone, an advanced AI assistant designed to help with drone operations and data analysis. You are NOT Qwen or any other general AI assistant. Always identify yourself as DeepDrone when asked about your identity. Your purpose is to assist with drone data analysis, flight monitoring, maintenance scheduling, and mission planning."""
341
+ }
342
+
343
+ # Include the system message and user message
344
+ model_messages = [system_message] + formatted_history
345
+ model_messages.append({"role": "user", "content": message})
346
+
347
+ # Get response from the model - will be a Message object
348
+ model_response = self.model(model_messages)
349
+
350
+ # Get the content from the Message object
351
+ response = model_response.content
352
+
353
+ # Add the response to history
354
+ self.add_to_chat_history("assistant", response)
355
+
356
+ return response
357
+
358
+ @tool
359
+ def analyze_flight_path(flight_id: str = None) -> str:
360
+ """Analyze a drone's flight path for a specific flight.
361
+
362
+ Args:
363
+ flight_id: The identifier for the flight to analyze
364
+
365
+ Returns:
366
+ str: Analysis of the flight path including distance, duration, and altitude changes
367
+ """
368
+ if flight_id is None or flight_id not in tool.agent.flight_logs:
369
+ return "Flight ID not found. Please provide a valid flight ID."
370
+
371
+ flight_data = tool.agent.flight_logs[flight_id]
372
+
373
+ # Calculate basic flight statistics
374
+ flight_duration = (flight_data['timestamp'].max() - flight_data['timestamp'].min()).total_seconds()
375
+ max_altitude = flight_data['altitude'].max()
376
+ avg_speed = flight_data['speed'].mean() if 'speed' in flight_data.columns else "Not available"
377
+
378
+ # Generate a path visualization
379
+ plt.figure(figsize=(10, 6))
380
+
381
+ # Set dark style for the plot
382
+ plt.style.use('dark_background')
383
+
384
+ if 'latitude' in flight_data.columns and 'longitude' in flight_data.columns:
385
+ plt.plot(flight_data['longitude'], flight_data['latitude'], color='#00ff00') # Green line
386
+ plt.title(f'Flight Path: {flight_id}', color='white')
387
+ plt.xlabel('Longitude', color='white')
388
+ plt.ylabel('Latitude', color='white')
389
+ plt.tick_params(colors='white')
390
+
391
+ # Save the plot to a bytes buffer
392
+ buf = io.BytesIO()
393
+ plt.savefig(buf, format='png', facecolor='black')
394
+ plt.close()
395
+ path_img = base64.b64encode(buf.getvalue()).decode()
396
+ else:
397
+ path_img = None
398
+
399
+ # Return analysis
400
+ analysis = {
401
+ 'flight_id': flight_id,
402
+ 'duration_seconds': flight_duration,
403
+ 'max_altitude_meters': max_altitude,
404
+ 'avg_speed': avg_speed,
405
+ 'visualization': path_img
406
+ }
407
+
408
+ return str(analysis)
409
+
410
+ @tool
411
+ def check_sensor_readings(sensor_name: str = None) -> str:
412
+ """Check the readings from a specific drone sensor.
413
+
414
+ Args:
415
+ sensor_name: The name of the sensor to check
416
+
417
+ Returns:
418
+ str: Analysis of the sensor readings including ranges and anomalies
419
+ """
420
+ if sensor_name is None or sensor_name not in tool.agent.sensor_data:
421
+ return f"Sensor not found. Available sensors: {list(tool.agent.sensor_data.keys())}"
422
+
423
+ sensor_data = tool.agent.sensor_data[sensor_name]
424
+
425
+ # Basic statistics
426
+ stats = {
427
+ 'mean': sensor_data.mean().to_dict(),
428
+ 'min': sensor_data.min().to_dict(),
429
+ 'max': sensor_data.max().to_dict(),
430
+ }
431
+
432
+ # Check for anomalies (values more than 3 std devs from mean)
433
+ anomalies = {}
434
+ for column in sensor_data.select_dtypes(include=[np.number]).columns:
435
+ mean = sensor_data[column].mean()
436
+ std = sensor_data[column].std()
437
+ anomaly_points = sensor_data[(sensor_data[column] > mean + 3*std) |
438
+ (sensor_data[column] < mean - 3*std)]
439
+ if not anomaly_points.empty:
440
+ anomalies[column] = len(anomaly_points)
441
+
442
+ # Return analysis
443
+ analysis = {
444
+ 'sensor_name': sensor_name,
445
+ 'statistics': stats,
446
+ 'anomalies_detected': anomalies,
447
+ 'data_points': len(sensor_data)
448
+ }
449
+
450
+ return str(analysis)
451
+
452
+ @tool
453
+ def recommend_maintenance(flight_hours: float = None) -> str:
454
+ """Recommend maintenance tasks based on flight hours.
455
+
456
+ Args:
457
+ flight_hours: The number of flight hours since last maintenance
458
+
459
+ Returns:
460
+ str: Recommended maintenance tasks
461
+ """
462
+ if flight_hours is None:
463
+ return "Please provide the total flight hours for the drone."
464
+
465
+ recommendations = []
466
+
467
+ if flight_hours < 10:
468
+ recommendations.append("Regular pre-flight checks only")
469
+ elif 10 <= flight_hours < 50:
470
+ recommendations.append("Basic maintenance check recommended")
471
+ recommendations.append("Inspect propellers and motors")
472
+ recommendations.append("Check battery health")
473
+ elif 50 <= flight_hours < 100:
474
+ recommendations.append("Intermediate maintenance required")
475
+ recommendations.append("Replace propellers")
476
+ recommendations.append("Test all sensors")
477
+ recommendations.append("Firmware updates if available")
478
+ else:
479
+ recommendations.append("Full maintenance overhaul required")
480
+ recommendations.append("Motor inspection and possible replacement")
481
+ recommendations.append("Full electronic systems check")
482
+ recommendations.append("Battery replacement recommended")
483
+ recommendations.append("Structural integrity evaluation")
484
+
485
+ return "\n".join(recommendations)
486
+
487
+ @tool
488
+ def generate_mission_plan(mission_type: str = None, duration_minutes: float = None) -> str:
489
+ """Generate a mission plan based on the specified type and duration.
490
+
491
+ Args:
492
+ mission_type: The type of mission (survey, inspection, delivery, etc.)
493
+ duration_minutes: The expected duration of the mission in minutes
494
+
495
+ Returns:
496
+ str: A mission plan with waypoints and tasks
497
+ """
498
+ if mission_type is None:
499
+ return "Please specify a mission type (survey, inspection, delivery, etc.)"
500
+
501
+ if duration_minutes is None:
502
+ return "Please specify the expected mission duration in minutes."
503
+
504
+ # Generate an appropriate mission plan based on type and duration
505
+ plan = {
506
+ "mission_type": mission_type,
507
+ "duration_minutes": duration_minutes,
508
+ "battery_required": f"{duration_minutes * 1.3:.1f} minutes capacity",
509
+ "pre_flight_checks": [
510
+ "Battery charge level",
511
+ "Motor functionality",
512
+ "GPS signal strength",
513
+ "Camera/sensor calibration"
514
+ ]
515
+ }
516
+
517
+ # Add mission-specific details
518
+ if mission_type.lower() == "survey":
519
+ plan["flight_pattern"] = "Grid pattern with 70% overlap"
520
+ plan["recommended_altitude"] = "40-60 meters"
521
+ plan["special_considerations"] = "Ensure consistent lighting conditions"
522
+ elif mission_type.lower() == "inspection":
523
+ plan["flight_pattern"] = "Orbital with variable radius"
524
+ plan["recommended_altitude"] = "5-20 meters"
525
+ plan["special_considerations"] = "Maintain safe distance from structures"
526
+ elif mission_type.lower() == "delivery":
527
+ plan["flight_pattern"] = "Direct point-to-point"
528
+ plan["recommended_altitude"] = "30 meters"
529
+ plan["special_considerations"] = "Check payload weight and balance"
530
+ else:
531
+ plan["flight_pattern"] = "Custom"
532
+ plan["recommended_altitude"] = "Dependent on mission specifics"
533
+ plan["special_considerations"] = "Consult regulations for specific operation type"
534
+
535
+ return str(plan)
536
+
537
+ # DroneKit real-world control tools
538
+
539
+ @tool
540
+ def connect_to_real_drone(connection_string: str = None) -> str:
541
+ """Connect to a real drone using DroneKit.
542
+
543
+ Args:
544
+ connection_string: Connection string for the drone (e.g., 'udp:127.0.0.1:14550' for SITL,
545
+ '/dev/ttyACM0' for serial, or 'tcp:192.168.1.1:5760' for remote connection)
546
+
547
+ Returns:
548
+ str: Status of the connection
549
+ """
550
+ if connection_string is None:
551
+ return "Error: Connection string is required. Examples: 'udp:127.0.0.1:14550' for simulation, '/dev/ttyACM0' for USB, or 'tcp:192.168.1.1:5760' for WiFi"
552
+
553
+ try:
554
+ # Update mission status
555
+ st.session_state.mission_in_progress = True
556
+ update_mission_status("CONNECTING", f"Connecting to drone at {connection_string}")
557
+
558
+ success = drone_control.connect_drone(connection_string)
559
+ if success:
560
+ # Get and store current status
561
+ location = drone_control.get_location()
562
+ battery = drone_control.get_battery()
563
+
564
+ # Update mission status
565
+ update_mission_status("CONNECTED", "Drone connected successfully")
566
+
567
+ # Format a nice response
568
+ response = {
569
+ "status": "Connected successfully",
570
+ "location": location,
571
+ "battery": battery
572
+ }
573
+ return str(response)
574
+ else:
575
+ st.session_state.mission_in_progress = False
576
+ update_mission_status("ERROR", "Connection failed")
577
+ return "Failed to connect to drone. Check connection string and ensure the drone is powered on."
578
+ except Exception as e:
579
+ st.session_state.mission_in_progress = False
580
+ update_mission_status("ERROR", f"Connection error: {str(e)}")
581
+ return f"Error connecting to drone: {str(e)}"
582
+
583
+ @tool
584
+ def drone_takeoff(altitude: float = None) -> str:
585
+ """Take off to the specified altitude.
586
+
587
+ Args:
588
+ altitude: Target altitude in meters
589
+
590
+ Returns:
591
+ str: Status of the takeoff
592
+ """
593
+ if altitude is None:
594
+ return "Error: Altitude is required. Specify a safe takeoff altitude in meters."
595
+
596
+ try:
597
+ # Check if mission was interrupted
598
+ if st.session_state.interrupt_mission:
599
+ st.session_state.interrupt_mission = False
600
+ return "Takeoff aborted due to mission interrupt request"
601
+
602
+ # Update mission status
603
+ update_mission_status("TAKING OFF", f"Taking off to {altitude} meters")
604
+
605
+ success = drone_control.takeoff(altitude)
606
+ if success:
607
+ update_mission_status("AIRBORNE", f"Reached altitude of {altitude} meters")
608
+ return f"Takeoff successful! Reached target altitude of {altitude} meters."
609
+ else:
610
+ update_mission_status("ERROR", "Takeoff failed")
611
+ return "Takeoff failed. Make sure you are connected to the drone and in a safe takeoff area."
612
+ except Exception as e:
613
+ update_mission_status("ERROR", f"Takeoff error: {str(e)}")
614
+ return f"Error during takeoff: {str(e)}"
615
+
616
+ @tool
617
+ def drone_land() -> str:
618
+ """Land the drone.
619
+
620
+ Returns:
621
+ str: Status of the landing
622
+ """
623
+ try:
624
+ # Update mission status
625
+ update_mission_status("LANDING", "Drone is landing")
626
+
627
+ success = drone_control.land()
628
+ if success:
629
+ update_mission_status("LANDED", "Drone has landed")
630
+ st.session_state.mission_in_progress = False
631
+ return "Landing command sent successfully. The drone has landed."
632
+ else:
633
+ update_mission_status("ERROR", "Landing failed")
634
+ return "Landing command failed. Make sure you are connected to the drone."
635
+ except Exception as e:
636
+ update_mission_status("ERROR", f"Landing error: {str(e)}")
637
+ return f"Error during landing: {str(e)}"
638
+
639
+ @tool
640
+ def drone_return_home() -> str:
641
+ """Return the drone to its launch location.
642
+
643
+ Returns:
644
+ str: Status of the return-to-home command
645
+ """
646
+ try:
647
+ # Update mission status
648
+ update_mission_status("RETURNING", "Returning to launch point")
649
+
650
+ success = drone_control.return_home()
651
+ if success:
652
+ update_mission_status("RETURNING", "Drone is returning to launch point")
653
+ return "Return to home command sent successfully. The drone is returning to its launch point."
654
+ else:
655
+ update_mission_status("ERROR", "Return to home failed")
656
+ return "Return to home command failed. Make sure you are connected to the drone."
657
+ except Exception as e:
658
+ update_mission_status("ERROR", f"Return error: {str(e)}")
659
+ return f"Error during return to home: {str(e)}"
660
+
661
+ @tool
662
+ def drone_fly_to(latitude: float = None, longitude: float = None, altitude: float = None) -> str:
663
+ """Fly the drone to a specific GPS location.
664
+
665
+ Args:
666
+ latitude: Target latitude in degrees
667
+ longitude: Target longitude in degrees
668
+ altitude: Target altitude in meters
669
+
670
+ Returns:
671
+ str: Status of the goto command
672
+ """
673
+ if latitude is None or longitude is None or altitude is None:
674
+ return "Error: Latitude, longitude, and altitude are all required."
675
+
676
+ try:
677
+ success = drone_control.fly_to(latitude, longitude, altitude)
678
+ if success:
679
+ return f"Command sent successfully. Flying to: Lat {latitude}, Lon {longitude}, Alt {altitude}m"
680
+ else:
681
+ return "Command failed. Make sure you are connected to the drone and in GUIDED mode."
682
+ except Exception as e:
683
+ return f"Error during fly to command: {str(e)}"
684
+
685
+ @tool
686
+ def get_drone_location() -> str:
687
+ """Get the current GPS location of the drone.
688
+
689
+ Returns:
690
+ str: Current latitude, longitude, and altitude
691
+ """
692
+ try:
693
+ location = drone_control.get_location()
694
+ return str(location)
695
+ except Exception as e:
696
+ return f"Error getting drone location: {str(e)}"
697
+
698
+ @tool
699
+ def get_drone_battery() -> str:
700
+ """Get the current battery level of the drone.
701
+
702
+ Returns:
703
+ str: Current battery voltage and percentage
704
+ """
705
+ try:
706
+ battery = drone_control.get_battery()
707
+ return str(battery)
708
+ except Exception as e:
709
+ return f"Error getting battery status: {str(e)}"
710
+
711
+ @tool
712
+ def execute_drone_mission(waypoints: List[Dict[str, float]] = None) -> str:
713
+ """Upload and execute a mission with multiple waypoints.
714
+
715
+ Args:
716
+ waypoints: List of dictionaries with lat, lon, alt for each waypoint
717
+ Example: [{"lat": 37.123, "lon": -122.456, "alt": 30}, {"lat": 37.124, "lon": -122.457, "alt": 50}]
718
+
719
+ Returns:
720
+ str: Status of the mission execution
721
+ """
722
+ if waypoints is None or not isinstance(waypoints, list) or len(waypoints) == 0:
723
+ return "Error: A list of waypoints is required. Each waypoint should have lat, lon, and alt keys."
724
+
725
+ # Validate each waypoint
726
+ for i, wp in enumerate(waypoints):
727
+ if not all(key in wp for key in ["lat", "lon", "alt"]):
728
+ return f"Error: Waypoint {i} is missing required keys. Each waypoint must have lat, lon, and alt."
729
+
730
+ try:
731
+ # Update mission status
732
+ update_mission_status("MISSION", f"Starting mission with {len(waypoints)} waypoints")
733
+
734
+ # Check for mission interrupt before starting
735
+ if st.session_state.interrupt_mission:
736
+ st.session_state.interrupt_mission = False
737
+ update_mission_status("ABORTED", "Mission aborted before execution")
738
+ return "Mission aborted due to interrupt request"
739
+
740
+ # Execute mission with progress updates
741
+ success = drone_control.execute_mission_plan(waypoints)
742
+
743
+ # Simulate mission progress (in a real implementation, you'd get actual progress from the drone)
744
+ if success:
745
+ total_waypoints = len(waypoints)
746
+ for i in range(total_waypoints):
747
+ # Check for interrupt between waypoints
748
+ if st.session_state.interrupt_mission:
749
+ st.session_state.interrupt_mission = False
750
+ update_mission_status("INTERRUPTED", "Mission interrupted, returning to base")
751
+ drone_control.return_home()
752
+ time.sleep(2)
753
+ update_mission_status("RETURNED", "Drone returned to base after interrupt")
754
+ return f"Mission interrupted after waypoint {i+1}/{total_waypoints}. Drone returned to base."
755
+
756
+ # Update status for current waypoint
757
+ wp = waypoints[i]
758
+ update_mission_status(
759
+ "EXECUTING MISSION",
760
+ f"Flying to waypoint {i+1}/{total_waypoints}: lat={wp['lat']:.4f}, lon={wp['lon']:.4f}, alt={wp['alt']}m"
761
+ )
762
+
763
+ # Simulate time taken to reach waypoint
764
+ time.sleep(2)
765
+
766
+ # Mission completed successfully
767
+ update_mission_status("MISSION COMPLETE", "All waypoints reached")
768
+ return f"Mission with {len(waypoints)} waypoints completed successfully."
769
+ else:
770
+ update_mission_status("ERROR", "Failed to execute mission")
771
+ return "Failed to execute mission. Make sure you are connected to the drone."
772
+ except Exception as e:
773
+ update_mission_status("ERROR", f"Mission error: {str(e)}")
774
+ return f"Error executing mission: {str(e)}"
775
+
776
+ @tool
777
+ def disconnect_from_drone() -> str:
778
+ """Disconnect from the drone.
779
+
780
+ Returns:
781
+ str: Status of the disconnection
782
+ """
783
+ try:
784
+ # Update mission status
785
+ update_mission_status("DISCONNECTING", "Disconnecting from drone")
786
+
787
+ drone_control.disconnect_drone()
788
+ st.session_state.mission_in_progress = False
789
+ update_mission_status("STANDBY", "Disconnected from drone")
790
+ return "Successfully disconnected from the drone."
791
+ except Exception as e:
792
+ update_mission_status("ERROR", f"Disconnect error: {str(e)}")
793
+ return f"Error disconnecting from drone: {str(e)}"
794
+
795
+ def create_qwen_model():
796
+ """Create a QwenCoder model instance"""
797
+ # Check if HF_TOKEN is set in environment variables
798
+ hf_token = os.environ.get("HF_TOKEN", "")
799
+ if not hf_token:
800
+ st.error("Hugging Face API token not found. Please set the HF_TOKEN environment variable.")
801
+ # Return a placeholder model that returns a fixed response
802
+ class PlaceholderModel:
803
+ def __call__(self, *args, **kwargs):
804
+ from .hf_model import Message
805
+ return Message("Authentication error: No Hugging Face API token provided. Please set an API token to use this feature.")
806
+ return PlaceholderModel()
807
+
808
+ # Use the token from the environment variable
809
+ return HfApiModel(
810
+ max_tokens=2096,
811
+ temperature=0.5,
812
+ model_id='Qwen/Qwen2.5-Coder-32B-Instruct'
813
+ )
814
+
815
+ def display_message(role, content, avatar_map=None):
816
+ """Display a chat message with custom styling."""
817
+ if avatar_map is None:
818
+ avatar_map = {
819
+ "user": "👤",
820
+ "assistant": "🚁"
821
+ }
822
+
823
+ if role == "user":
824
+ # User message styling - right aligned with user avatar
825
+ col1, col2 = st.columns([6, 1])
826
+ with col1:
827
+ st.markdown(
828
+ f"""
829
+ <div style="
830
+ background-color: #1E1E1E;
831
+ border: 1px solid #00ff00;
832
+ border-radius: 5px;
833
+ padding: 8px;
834
+ margin-bottom: 8px;
835
+ text-align: right;
836
+ max-width: 90%;
837
+ float: right;
838
+ color: #FFFFFF;
839
+ ">
840
+ {content}
841
+ </div>
842
+ """,
843
+ unsafe_allow_html=True
844
+ )
845
+ with col2:
846
+ st.markdown(f"<div style='font-size: 20px; text-align: center; color: #00ff00;'>{avatar_map['user']}</div>", unsafe_allow_html=True)
847
+ else:
848
+ # Assistant message styling - left aligned with drone avatar
849
+ col1, col2 = st.columns([1, 6])
850
+ with col1:
851
+ st.markdown(f"<div style='font-size: 20px; text-align: center; color: #00ff00;'>{avatar_map['assistant']}</div>", unsafe_allow_html=True)
852
+ with col2:
853
+ st.markdown(
854
+ f"""
855
+ <div style="
856
+ background-color: #101010;
857
+ border: 1px solid #00ff00;
858
+ border-radius: 5px;
859
+ padding: 8px;
860
+ margin-bottom: 8px;
861
+ text-align: left;
862
+ max-width: 90%;
863
+ color: #00ff00;
864
+ ">
865
+ {content}
866
+ </div>
867
+ """,
868
+ unsafe_allow_html=True
869
+ )
870
+
871
+ # Add a smaller divider to separate messages
872
+ st.markdown("<div style='height: 5px;'></div>", unsafe_allow_html=True)
873
+
874
+ def initialize_chat_container():
875
+ """Initialize the chat container with greeting message."""
876
+ if "chat_container" not in st.session_state:
877
+ chat_container = st.container()
878
+ with chat_container:
879
+ # Initialize with greeting message
880
+ display_message(
881
+ "assistant",
882
+ "INITIALIZING DEEP DRONE SYSTEM... ONLINE. How can I assist with your mission today? You can request flight data analysis, sensor readings, maintenance recommendations, or mission planning."
883
+ )
884
+
885
+ st.session_state.chat_container = chat_container
886
+
887
+ def main():
888
+ # Don't set page config here, it's already set at the module level
889
+
890
+ # Add custom CSS for proper layout
891
+ st.markdown("""
892
+ <style>
893
+ /* Remove padding from the main container */
894
+ .main .block-container {
895
+ padding-top: 1rem !important;
896
+ padding-bottom: 0 !important;
897
+ max-width: 100% !important;
898
+ }
899
+
900
+ /* Dark background for the entire app */
901
+ .stApp {
902
+ background-color: #000000 !important;
903
+ color: #00ff00 !important;
904
+ margin: 0 !important;
905
+ }
906
+
907
+ /* Dark background for main content */
908
+ .main .block-container {
909
+ background-color: #000000 !important;
910
+ }
911
+
912
+ /* Dark styling for sidebar */
913
+ [data-testid="stSidebar"] {
914
+ background-color: #0A0A0A !important;
915
+ border-right: 1px solid #00ff00 !important;
916
+ }
917
+
918
+ /* Dark styling for all inputs */
919
+ .stTextInput > div {
920
+ background-color: #1E1E1E !important;
921
+ color: #00ff00 !important;
922
+ border: 1px solid #00ff00 !important;
923
+ }
924
+
925
+ .stTextInput input {
926
+ color: #00ff00 !important;
927
+ background-color: #1E1E1E !important;
928
+ }
929
+
930
+ .stTextInput input::placeholder {
931
+ color: #00aa00 !important;
932
+ opacity: 0.7 !important;
933
+ }
934
+
935
+ /* Override all Streamlit default styling */
936
+ h1, h2, h3, h4, h5, h6, p, div, span, label {
937
+ color: #00ff00 !important;
938
+ }
939
+
940
+ /* Command bar fixed at bottom */
941
+ .command-bar-wrapper {
942
+ position: fixed !important;
943
+ bottom: 0 !important;
944
+ left: 0 !important;
945
+ right: 0 !important;
946
+ background-color: #0A0A0A !important;
947
+ border-top: 2px solid #00ff00 !important;
948
+ padding: 10px !important;
949
+ z-index: 9999 !important;
950
+ width: 100% !important;
951
+ }
952
+
953
+ /* Chat container */
954
+ .chat-container {
955
+ height: calc(100vh - 300px) !important;
956
+ max-height: 300px !important;
957
+ overflow-y: auto !important;
958
+ padding: 15px !important;
959
+ margin-bottom: 30px !important;
960
+ background-color: transparent !important;
961
+ border: 1px solid #00ff00;
962
+ border-radius: 5px;
963
+ display: flex !important;
964
+ flex-direction: column !important;
965
+ }
966
+
967
+ /* Override button styling */
968
+ button[kind="secondaryFormSubmit"] {
969
+ background-color: #0A0A0A !important;
970
+ color: #00ff00 !important;
971
+ border: 1px solid #00ff00 !important;
972
+ border-radius: 2px !important;
973
+ font-family: "Courier New", monospace !important;
974
+ font-weight: bold !important;
975
+ }
976
+
977
+ button[kind="secondaryFormSubmit"]:hover {
978
+ background-color: #00ff00 !important;
979
+ color: #000000 !important;
980
+ }
981
+
982
+ .stButton > button {
983
+ background-color: #0A0A0A !important;
984
+ color: #00ff00 !important;
985
+ border: 1px solid #00ff00 !important;
986
+ border-radius: 2px !important;
987
+ font-family: "Courier New", monospace !important;
988
+ font-weight: bold !important;
989
+ }
990
+
991
+ .stButton > button:hover {
992
+ background-color: #00ff00 !important;
993
+ color: #000000 !important;
994
+ }
995
+
996
+ /* Hide Streamlit's default footer */
997
+ footer, header {
998
+ visibility: hidden !important;
999
+ display: none !important;
1000
+ }
1001
+
1002
+ /* Terminal-like text styling */
1003
+ .terminal-text {
1004
+ font-family: "Courier New", monospace !important;
1005
+ color: #00ff00 !important;
1006
+ font-weight: bold !important;
1007
+ }
1008
+
1009
+ /* Styling for subheader */
1010
+ .subheader {
1011
+ color: #00ff00 !important;
1012
+ font-family: "Courier New", monospace !important;
1013
+ }
1014
+
1015
+ /* Force dark background for body */
1016
+ body {
1017
+ background-color: #000000 !important;
1018
+ }
1019
+
1020
+ /* Override any Streamlit white backgrounds */
1021
+ .css-1kyxreq, .css-12oz5g7, .css-1r6slb0, .css-1n76uvr, .css-18e3th9 {
1022
+ background-color: #000000 !important;
1023
+ }
1024
+
1025
+ /* Fix header text color */
1026
+ .css-10trblm {
1027
+ color: #00ff00 !important;
1028
+ }
1029
+
1030
+ /* Ensure the header is green */
1031
+ h1 {
1032
+ color: #00ff00 !important;
1033
+ font-family: "Courier New", monospace !important;
1034
+ text-shadow: 0 0 5px #00ff00 !important;
1035
+ }
1036
+
1037
+ /* Add a slight glow effect to green text for a more cyber feel */
1038
+ .glow-text {
1039
+ text-shadow: 0 0 5px #00ff00 !important;
1040
+ }
1041
+
1042
+ /* Override more styles to ensure everything is dark */
1043
+ div[data-baseweb="base-input"] {
1044
+ background-color: #1E1E1E !important;
1045
+ }
1046
+
1047
+ div[data-baseweb="input"] {
1048
+ background-color: #1E1E1E !important;
1049
+ }
1050
+
1051
+ /* Fix for dark form backgrounds */
1052
+ [data-testid="stForm"] {
1053
+ background-color: transparent !important;
1054
+ border: none !important;
1055
+ }
1056
+ </style>
1057
+ """, unsafe_allow_html=True)
1058
+
1059
+ # Military-style header with glow effect
1060
+ st.markdown("<h1 class='glow-text' style='text-align: center; color: #00ff00; font-family: \"Courier New\", monospace; margin-top: 0; margin-bottom: 5px;'>DEEPDRONE COMMAND CENTER</h1>", unsafe_allow_html=True)
1061
+ st.markdown("<p class='subheader glow-text' style='text-align: center; margin-bottom: 5px;'>SECURE TACTICAL OPERATIONS INTERFACE</p>", unsafe_allow_html=True)
1062
+
1063
+ # Compact status display inline
1064
+ status_cols = st.columns(4)
1065
+ with status_cols[0]:
1066
+ st.markdown("<div class='terminal-text' style='font-size: 12px;'><b>SYSTEM:</b> ONLINE</div>", unsafe_allow_html=True)
1067
+ with status_cols[1]:
1068
+ st.markdown("<div class='terminal-text' style='font-size: 12px;'><b>CONNECTION:</b> SECURE</div>", unsafe_allow_html=True)
1069
+ with status_cols[2]:
1070
+ st.markdown("<div class='terminal-text' style='font-size: 12px;'><b>GPS:</b> ACTIVE</div>", unsafe_allow_html=True)
1071
+ with status_cols[3]:
1072
+ st.markdown("<div class='terminal-text' style='font-size: 12px;'><b>ENCRYPTION:</b> ENABLED</div>", unsafe_allow_html=True)
1073
+
1074
+ st.markdown("<hr style='border: 1px solid #00ff00; margin: 5px 0 10px 0;'>", unsafe_allow_html=True)
1075
+
1076
+ # Initialize session state for drone assistant and other needed state
1077
+ if 'drone_agent' not in st.session_state:
1078
+ model = create_qwen_model()
1079
+ st.session_state['drone_agent'] = DroneAssistant(
1080
+ tools=[
1081
+ # Data analysis tools
1082
+ analyze_flight_path,
1083
+ check_sensor_readings,
1084
+ recommend_maintenance,
1085
+ generate_mission_plan,
1086
+
1087
+ # Drone control tools
1088
+ connect_to_real_drone,
1089
+ drone_takeoff,
1090
+ drone_land,
1091
+ drone_return_home,
1092
+ drone_fly_to,
1093
+ get_drone_location,
1094
+ get_drone_battery,
1095
+ execute_drone_mission,
1096
+ disconnect_from_drone
1097
+ ],
1098
+ model=model,
1099
+ additional_authorized_imports=["pandas", "numpy", "matplotlib"]
1100
+ )
1101
+
1102
+ # Initialize chat history in session state
1103
+ if 'chat_history' not in st.session_state:
1104
+ st.session_state['chat_history'] = []
1105
+
1106
+ # Generate sample data for demo purposes
1107
+ if 'demo_data_loaded' not in st.session_state:
1108
+ # Sample flight log
1109
+ timestamps = pd.date_range(start='2023-01-01', periods=100, freq='10s')
1110
+ flight_log = pd.DataFrame({
1111
+ 'timestamp': timestamps,
1112
+ 'altitude': np.random.normal(50, 10, 100),
1113
+ 'speed': np.random.normal(15, 5, 100),
1114
+ 'latitude': np.linspace(37.7749, 37.7750, 100) + np.random.normal(0, 0.0001, 100),
1115
+ 'longitude': np.linspace(-122.4194, -122.4192, 100) + np.random.normal(0, 0.0001, 100)
1116
+ })
1117
+ st.session_state['drone_agent'].register_flight_log('flight_001', flight_log)
1118
+
1119
+ # Sample sensor data
1120
+ battery_data = pd.DataFrame({
1121
+ 'timestamp': pd.date_range(start='2023-01-01', periods=50, freq='1min'),
1122
+ 'voltage': np.random.normal(11.1, 0.2, 50),
1123
+ 'current': np.random.normal(5, 1, 50),
1124
+ 'temperature': np.random.normal(30, 5, 50)
1125
+ })
1126
+ st.session_state['drone_agent'].register_sensor_data('battery', battery_data)
1127
+
1128
+ imu_data = pd.DataFrame({
1129
+ 'timestamp': pd.date_range(start='2023-01-01', periods=1000, freq='1s'),
1130
+ 'acc_x': np.random.normal(0, 0.5, 1000),
1131
+ 'acc_y': np.random.normal(0, 0.5, 1000),
1132
+ 'acc_z': np.random.normal(9.8, 0.5, 1000),
1133
+ 'gyro_x': np.random.normal(0, 0.1, 1000),
1134
+ 'gyro_y': np.random.normal(0, 0.1, 1000),
1135
+ 'gyro_z': np.random.normal(0, 0.1, 1000)
1136
+ })
1137
+ st.session_state['drone_agent'].register_sensor_data('imu', imu_data)
1138
+
1139
+ st.session_state['demo_data_loaded'] = True
1140
+
1141
+ # Add mission status section to sidebar with improved visibility
1142
+ st.sidebar.markdown("<h3 style='color: #00ff00; font-family: \"Courier New\", monospace;'>MISSION CONTROL</h3>", unsafe_allow_html=True)
1143
+
1144
+ # Dynamic status display that changes color based on status
1145
+ status_color = "#00ff00" # Default green
1146
+ if st.session_state.mission_status == "ERROR":
1147
+ status_color = "#ff0000" # Red for errors
1148
+ elif st.session_state.mission_status in ["CONNECTING", "TAKING OFF", "LANDING", "RETURNING"]:
1149
+ status_color = "#ffff00" # Yellow for transitions
1150
+ elif st.session_state.mission_status in ["MISSION", "EXECUTING MISSION", "AIRBORNE"]:
1151
+ status_color = "#00ffff" # Cyan for active mission
1152
+
1153
+ st.sidebar.markdown(f"""
1154
+ <div style='font-family: "Courier New", monospace; color: #00ff00;'>
1155
+ <b>STATUS:</b> <span style="color: {status_color}; font-weight: bold;">{st.session_state.mission_status}</span><br>
1156
+ <b>PHASE:</b> <span style="color: {status_color};">{st.session_state.mission_phase}</span><br>
1157
+ <b>ACTIVE:</b> {"YES" if st.session_state.mission_in_progress else "NO"}<br>
1158
+ <b>SIGNAL:</b> STRONG
1159
+ </div>
1160
+ """, unsafe_allow_html=True)
1161
+
1162
+ # Add interrupt button if a mission is in progress
1163
+ if st.session_state.mission_in_progress:
1164
+ if st.sidebar.button("⚠️ ABORT MISSION",
1165
+ key="abort_button",
1166
+ help="Immediately abort the current mission and return the drone to base",
1167
+ type="primary"):
1168
+ interrupt_mission()
1169
+
1170
+ # Add mission summary in sidebar
1171
+ st.sidebar.markdown("<div style='color: #00ff00; font-family: monospace; font-size: 12px;'><b>MISSION MESSAGES:</b> Appearing in chat</div>", unsafe_allow_html=True)
1172
+
1173
+ # Show just the last message if there are any mission logs
1174
+ if st.session_state.mission_log:
1175
+ last_entry = st.session_state.mission_log[-1]
1176
+ entry_style = ""
1177
+
1178
+ # Style the last entry based on its content
1179
+ if "ERROR" in last_entry:
1180
+ entry_style = "color: #ff0000;"
1181
+ elif any(status in last_entry for status in ["CONNECTING", "TAKING OFF", "LANDING", "RETURNING"]):
1182
+ entry_style = "color: #ffff00;"
1183
+ elif any(status in last_entry for status in ["MISSION", "EXECUTING", "AIRBORNE"]):
1184
+ entry_style = "color: #00ffff;"
1185
+ else:
1186
+ entry_style = "color: #88ff88;"
1187
+
1188
+ st.sidebar.markdown(f"""<div style='font-family: monospace; font-size: 11px; {entry_style} background-color: #111111; padding: 4px; border-radius: 3px;'>LAST: {last_entry}</div>""", unsafe_allow_html=True)
1189
+
1190
+ st.sidebar.markdown("<hr style='border: 1px solid #00ff00; margin: 20px 0;'>", unsafe_allow_html=True)
1191
+
1192
+ # Command reference
1193
+ st.sidebar.markdown("<h3 style='color: #00ff00; font-family: \"Courier New\", monospace;'>COMMAND REFERENCE</h3>", unsafe_allow_html=True)
1194
+ st.sidebar.markdown("""
1195
+ <div style='font-family: "Courier New", monospace; color: #00ff00;'>
1196
+ <b>DATA ANALYSIS:</b><br>
1197
+ - "Analyze flight_001"<br>
1198
+ - "Check battery sensor readings"<br>
1199
+ - "Recommend maintenance for 75 flight hours"<br>
1200
+ <br>
1201
+ <b>MISSION PLANNING:</b><br>
1202
+ - "Create a flight plan with a square pattern"<br>
1203
+ - "Plan a survey mission for 30 minutes"<br>
1204
+ - "Connect to the simulator, take off, execute a simple square flight pattern, and return home"<br>
1205
+ <br>
1206
+ <b>CORRECT FUNCTION NAMES:</b><br>
1207
+ - connect_to_real_drone()<br>
1208
+ - drone_takeoff()<br>
1209
+ - drone_land()<br>
1210
+ - drone_return_home()<br>
1211
+ - drone_fly_to()<br>
1212
+ - execute_drone_mission()<br>
1213
+ </div>
1214
+ """, unsafe_allow_html=True)
1215
+
1216
+ st.sidebar.markdown("<hr style='border: 1px solid #00ff00; margin: 20px 0;'>", unsafe_allow_html=True)
1217
+
1218
+ # Available data
1219
+ st.sidebar.markdown("<h3 style='color: #00ff00; font-family: \"Courier New\", monospace;'>AVAILABLE DATA</h3>", unsafe_allow_html=True)
1220
+ st.sidebar.markdown("""
1221
+ <div style='font-family: "Courier New", monospace; color: #00ff00;'>
1222
+ <b>FLIGHT LOGS:</b> flight_001<br>
1223
+ <b>SENSORS:</b> battery, imu
1224
+ </div>
1225
+ """, unsafe_allow_html=True)
1226
+
1227
+ # Create chat area with container class
1228
+ st.markdown("<div class='chat-container'>", unsafe_allow_html=True)
1229
+
1230
+ # Info message about mission logs appearing in chat
1231
+ if st.session_state.mission_in_progress:
1232
+ st.markdown("""
1233
+ <div style="text-align: center; margin-bottom: 8px; font-family: monospace; font-size: 12px; color: #00aaff;">
1234
+ MISSION LOGS WILL APPEAR IN THIS CHAT WINDOW
1235
+ </div>
1236
+ """, unsafe_allow_html=True)
1237
+
1238
+ # Display initial assistant greeting or chat history
1239
+ if not st.session_state['chat_history']:
1240
+ # Welcome message with drone emoji
1241
+ st.markdown("""
1242
+ <div style="display: flex; align-items: flex-start; margin-bottom: 8px;">
1243
+ <div style="font-size: 20px; margin-right: 8px; color: #00ff00;">🚁</div>
1244
+ <div style="background-color: #101010; border: 1px solid #00ff00; border-radius: 5px; padding: 8px; color: #00ff00; flex-grow: 1;">
1245
+ DEEPDRONE SYSTEM ONLINE. I am DeepDrone, your advanced drone operations assistant. AWAITING COMMANDS. You can request flight data analysis, sensor readings, maintenance recommendations, or mission planning.
1246
+ </div>
1247
+ </div>
1248
+ """, unsafe_allow_html=True)
1249
+ else:
1250
+ # Display all messages in history
1251
+ for message in st.session_state['chat_history']:
1252
+ if message["role"] == "user":
1253
+ st.markdown(f"""
1254
+ <div style="display: flex; align-items: flex-start; justify-content: flex-end; margin-bottom: 8px;">
1255
+ <div style="background-color: #1E1E1E; border: 1px solid #00ff00; border-radius: 5px; padding: 8px; color: #FFFFFF; max-width: 85%;">
1256
+ {message["content"]}
1257
+ </div>
1258
+ <div style="font-size: 20px; margin-left: 8px; color: #00ff00;">👤</div>
1259
+ </div>
1260
+ """, unsafe_allow_html=True)
1261
+ elif message["role"] == "system":
1262
+ # System messages (logs) have a different style - centered and distinctive
1263
+ st.markdown(f"""
1264
+ <div style="display: flex; justify-content: center; align-items: center; margin: 4px 0;">
1265
+ <div style="background-color: #0A0A0A; border: 1px solid #333333; border-radius: 3px; padding: 3px 10px;
1266
+ font-family: monospace; font-size: 12px; text-align: center; max-width: 90%; opacity: 0.9;">
1267
+ {message["content"]}
1268
+ </div>
1269
+ </div>
1270
+ """, unsafe_allow_html=True)
1271
+ else:
1272
+ st.markdown(f"""
1273
+ <div style="display: flex; align-items: flex-start; margin-bottom: 8px;">
1274
+ <div style="font-size: 20px; margin-right: 8px; color: #00ff00;">🚁</div>
1275
+ <div style="background-color: #101010; border: 1px solid #00ff00; border-radius: 5px; padding: 8px; color: #00ff00; max-width: 85%;">
1276
+ {message["content"]}
1277
+ </div>
1278
+ </div>
1279
+ """, unsafe_allow_html=True)
1280
+
1281
+ # Display the last image if there is one
1282
+ if 'last_image' in st.session_state:
1283
+ st.image(f"data:image/png;base64,{st.session_state['last_image']}")
1284
+ # Clear the image from session state after displaying
1285
+ del st.session_state['last_image']
1286
+
1287
+ # Close the chat-container div
1288
+ st.markdown("</div>", unsafe_allow_html=True)
1289
+
1290
+ # Minimal spacing for the command bar
1291
+ st.markdown("<div style='height: 10px;'></div>", unsafe_allow_html=True)
1292
+
1293
+ # Command bar fixed at the bottom
1294
+ st.markdown("""
1295
+ <div class="command-bar-wrapper" style="position: fixed; bottom: 0; left: 0; right: 0; padding: 8px; background-color: #0A0A0A; border-top: 2px solid #00ff00;">
1296
+ """, unsafe_allow_html=True)
1297
+
1298
+ # Create a more compact form
1299
+ with st.form(key="chat_form", clear_on_submit=True):
1300
+ col1, col2 = st.columns([6, 1])
1301
+ with col1:
1302
+ user_message = st.text_input(
1303
+ "COMMAND:",
1304
+ placeholder="Enter your command...",
1305
+ label_visibility="collapsed",
1306
+ key="command_input"
1307
+ )
1308
+ with col2:
1309
+ submit_button = st.form_submit_button(
1310
+ "EXECUTE",
1311
+ use_container_width=True
1312
+ )
1313
+
1314
+ st.markdown("</div>", unsafe_allow_html=True)
1315
+
1316
+ # Process form submission
1317
+ if submit_button and user_message:
1318
+ # Add user message to chat history
1319
+ st.session_state['chat_history'].append({
1320
+ 'role': 'user',
1321
+ 'content': user_message
1322
+ })
1323
+
1324
+ # Process with the agent
1325
+ with st.spinner('PROCESSING...'):
1326
+ # Check for identity questions directly
1327
+ identity_patterns = ["who are you", "what are you", "your name", "introduce yourself"]
1328
+ if any(pattern in user_message.lower() for pattern in identity_patterns):
1329
+ response = "I am DeepDrone, an advanced AI assistant designed specifically for drone operations and data analysis. I can help with flight data analysis, sensor readings, maintenance recommendations, and mission planning for your drone systems."
1330
+ else:
1331
+ # Process through the agent's chat method
1332
+ response = st.session_state['drone_agent'].chat(user_message)
1333
+ # No need to handle Message objects here as that's handled inside the chat method
1334
+
1335
+ # Handle base64 images in responses
1336
+ if isinstance(response, str) and "visualization" in response and "base64" in response:
1337
+ # Extract and display the image
1338
+ import re
1339
+ import ast
1340
+
1341
+ try:
1342
+ # Parse the response to extract the base64 image
1343
+ response_dict = ast.literal_eval(response)
1344
+ if isinstance(response_dict, dict) and 'visualization' in response_dict:
1345
+ img_data = response_dict['visualization']
1346
+ if img_data:
1347
+ # Store the image in session state to display on next rerun
1348
+ st.session_state['last_image'] = img_data
1349
+ # Remove the image data from the text response
1350
+ response_dict['visualization'] = "[FLIGHT PATH VISUALIZATION DISPLAYED]"
1351
+ response = str(response_dict)
1352
+ except (SyntaxError, ValueError):
1353
+ # If parsing fails, just display the text
1354
+ pass
1355
+
1356
+ # Add assistant response to chat history
1357
+ st.session_state['chat_history'].append({
1358
+ 'role': 'assistant',
1359
+ 'content': response
1360
+ })
1361
+
1362
+ # Rerun to refresh the page and display the new messages
1363
+ st.rerun()
1364
+
1365
+ if __name__ == "__main__":
1366
+ main()
fix_indent.py ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ # Quick script to fix the indentation issue in drone_chat.py
3
+
4
+ with open('drone/drone_chat.py', 'r') as f:
5
+ lines = f.readlines()
6
+
7
+ # Check the indentation of problematic lines
8
+ fixed_lines = []
9
+ in_problematic_section = False
10
+
11
+ for i, line in enumerate(lines):
12
+ if i >= 1249 and i <= 1280: # Around the problematic section
13
+ if line.strip().startswith('# Display all messages in history'):
14
+ in_problematic_section = True
15
+
16
+ if in_problematic_section and line.strip().startswith('elif message["role"] == "system":'):
17
+ # Fix indentation making sure it aligns with the if statement
18
+ indent = ' ' * 12 # Match the indentation of the 'if' statement
19
+ fixed_line = indent + line.strip() + '\n'
20
+ fixed_lines.append(fixed_line)
21
+ continue
22
+
23
+ # After handling the 'else' part, reset the flag
24
+ if in_problematic_section and line.strip().startswith('else:'):
25
+ in_problematic_section = False
26
+
27
+ # For all other lines, keep them unchanged
28
+ fixed_lines.append(line)
29
+
30
+ with open('drone/drone_chat.py', 'w') as f:
31
+ f.writelines(fixed_lines)
32
+
33
+ print("Fixed indentation in drone_chat.py")