luigi12345 commited on
Commit
af844fc
Β·
verified Β·
1 Parent(s): 15f68c1

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +1423 -132
app.py CHANGED
@@ -1,147 +1,1438 @@
1
  import streamlit as st
2
  from interpreter import interpreter
3
  import os
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
 
5
- # Page configuration
6
- st.set_page_config(page_title="AutoInterpreter", layout="wide")
7
-
8
-
9
- # Initialize session state for settings if not exists
10
- if "settings" not in st.session_state:
11
- st.session_state.settings = {
12
- "api_key": os.getenv("HF_API_KEY", ""),
13
- "api_base": "https://api-inference.huggingface.co/models/Qwen/Qwen2.5-72B-Instruct",
14
- "model": "huggingface/Qwen/Qwen2.5-Coder-32B-Instruct",
15
- "auto_run": True,
16
- "context_window": 8000,
17
- "max_tokens": 4000
18
- }
19
-
20
- # Create header with title and settings button
21
- col1, col2 = st.columns([0.9, 0.1])
22
- with col1:
23
- st.markdown("# Autointerpreter")
24
- st.markdown("Run Any Code. The Final AI Coding Experience.")
25
- with col2:
26
- settings_button = st.button("βš™οΈ", help="Settings")
27
-
28
- # Settings modal
29
- if settings_button:
30
- settings_modal = st.container()
31
- with settings_modal:
32
- st.markdown("### Settings")
33
- cols = st.columns(2)
34
-
35
- with cols[0]:
36
- # API Settings
37
- st.text_input(
38
- "API Key",
39
- value=st.session_state.settings["api_key"],
40
- type="password",
41
- key="api_key",
42
- on_change=lambda: st.session_state.settings.update({"api_key": st.session_state.api_key})
43
- )
44
- st.text_input(
45
- "Model",
46
- value=st.session_state.settings["model"],
47
- key="model",
48
- on_change=lambda: st.session_state.settings.update({"model": st.session_state.model})
49
- )
50
 
51
- with cols[1]:
52
- # Model Settings
53
- st.toggle(
54
- "Auto Run",
55
- value=st.session_state.settings["auto_run"],
56
- key="auto_run",
57
- on_change=lambda: st.session_state.settings.update({"auto_run": st.session_state.auto_run})
58
- )
59
- st.number_input(
60
- "Max Tokens",
61
- value=st.session_state.settings["max_tokens"],
62
- min_value=100,
63
- max_value=8000,
64
- key="max_tokens",
65
- on_change=lambda: st.session_state.settings.update({"max_tokens": st.session_state.max_tokens})
66
- )
67
 
68
- # Apply settings to interpreter
69
- interpreter.llm.api_key = st.session_state.settings["api_key"]
70
- interpreter.llm.api_base = st.session_state.settings["api_base"]
71
- interpreter.llm.model = st.session_state.settings["model"]
72
- interpreter.auto_run = st.session_state.settings["auto_run"]
73
- interpreter.context_window = st.session_state.settings["context_window"]
74
- interpreter.max_tokens = st.session_state.settings["max_tokens"]
75
-
76
- # Initialize messages session state
77
- if "messages" not in st.session_state:
78
- st.session_state.messages = []
79
-
80
- # Clear button
81
- if st.button("πŸ—‘οΈ Clear", help="Clear chat"):
82
- interpreter.messages = []
83
- st.session_state.messages = []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
  st.rerun()
85
 
86
- # Display chat history
87
- for message in st.session_state.messages:
88
- with st.chat_message(message["role"]):
89
- st.markdown(message["content"])
 
 
 
 
90
 
91
- # User input
92
- user_input = st.chat_input("Enter your message:")
 
 
 
 
93
 
94
- if user_input:
95
- # Display user message
96
- st.chat_message("user").write(user_input)
97
- st.session_state.messages.append({"role": "user", "content": user_input})
98
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
99
  try:
100
- # Create a chat message container for the assistant
101
- with st.chat_message("assistant"):
102
- response_placeholder = st.empty()
103
- message_buffer = []
104
- code_buffer = []
105
-
106
- # Stream the response
107
- for chunk in interpreter.chat(user_input, stream=True):
108
- if isinstance(chunk, dict):
109
- content = chunk.get('content')
110
- if content is not None and not any(skip in str(content) for skip in ["context window", "max_tokens", "<|im_end|>"]):
111
- if chunk.get('type') == 'console':
112
- # Accumulate code separately
113
- code_buffer.append(str(content))
114
- # Show complete message + current code
115
- full_response = []
116
- if message_buffer:
117
- full_response.extend(message_buffer)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
118
  if code_buffer:
119
- full_response.append(f"\n```python\n{''.join(code_buffer)}\n```\n")
120
- response_placeholder.markdown(''.join(full_response))
121
- else:
122
- # Accumulate message until we have a complete thought
123
- current = str(content)
124
- message_buffer.append(current)
125
- if '.' in current or '\n' in current or len(''.join(message_buffer)) > 80:
126
- # Show complete message + current code
127
- full_response = []
128
- if message_buffer:
129
- full_response.extend(message_buffer)
130
- if code_buffer:
131
- full_response.append(f"\n```python\n{''.join(code_buffer)}\n```\n")
132
- response_placeholder.markdown(''.join(full_response))
133
-
134
- # Store the complete response
135
- final_response = []
136
- if message_buffer:
137
- final_response.extend(message_buffer)
138
- if code_buffer:
139
- final_response.append(f"\n```python\n{''.join(code_buffer)}\n```\n")
140
-
141
- st.session_state.messages.append({
142
- "role": "assistant",
143
- "content": ''.join(final_response)
144
- })
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
145
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
146
  except Exception as e:
147
- st.error(f"Error: {str(e)}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import streamlit as st
2
  from interpreter import interpreter
3
  import os
4
+ from streamlit_extras.colored_header import colored_header
5
+ from streamlit_lottie import st_lottie
6
+ import json
7
+ import requests
8
+ import re
9
+ from datetime import datetime, timezone
10
+ from typing import Dict, Any
11
+ from streamlit.runtime.scriptrunner import get_script_run_ctx
12
+ import time
13
+ from tenacity import retry, stop_after_attempt, wait_exponential
14
+ import shutil
15
+ from pathlib import Path
16
+ import hashlib
17
+ import streamlit_file_browser as sfb
18
 
19
+ # Add retry decorator for API calls
20
+ @retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=4, max=10))
21
+ def load_lottieurl(url: str) -> Dict[str, Any]:
22
+ try:
23
+ r = requests.get(url, timeout=10) # Add timeout
24
+ r.raise_for_status() # Raise exception for bad status codes
25
+ return r.json()
26
+ except requests.exceptions.RequestException as e:
27
+ st.error(f"Failed to load animation: {str(e)}")
28
+ return None
29
+
30
+ # Add error handling for interpreter calls
31
+ def safe_interpreter_call(func, *args, **kwargs):
32
+ try:
33
+ return func(*args, **kwargs)
34
+ except Exception as e:
35
+ error_msg = str(e)
36
+ if "API key" in error_msg.lower():
37
+ st.error("❌ API key error. Please check your API key in settings.")
38
+ elif "rate limit" in error_msg.lower():
39
+ st.error("⏳ Rate limit exceeded. Please wait a moment and try again.")
40
+ else:
41
+ st.error(f"❌ Error: {error_msg}")
42
+ return None
43
+
44
+ def get_session_id():
45
+ ctx = get_script_run_ctx()
46
+ return ctx.session_id if ctx else None
47
+
48
+ def save_settings():
49
+ """Save current settings to session state and ensure they persist"""
50
+ settings = st.session_state.settings
51
+ session_id = get_session_id()
52
+ if session_id:
53
+ # Save all relevant state
54
+ st.session_state[f"persistent_settings_{session_id}"] = settings.copy()
55
+ st.session_state[f"persistent_model_{session_id}"] = st.session_state.selected_model
56
+ st.session_state[f"persistent_audio_{session_id}"] = st.session_state.selected_audio_track
 
 
 
 
 
 
 
57
 
58
+ def load_settings():
59
+ """Load settings from persistent storage"""
60
+ session_id = get_session_id()
61
+ if session_id:
62
+ # Load all saved state
63
+ if f"persistent_settings_{session_id}" in st.session_state:
64
+ st.session_state.settings = st.session_state[f"persistent_settings_{session_id}"].copy()
65
+ if f"persistent_model_{session_id}" in st.session_state:
66
+ st.session_state.selected_model = st.session_state[f"persistent_model_{session_id}"]
67
+ if f"persistent_audio_{session_id}" in st.session_state:
68
+ st.session_state.selected_audio_track = st.session_state[f"persistent_audio_{session_id}"]
 
 
 
 
 
69
 
70
+ def init_session_state():
71
+ """Initialize session state with proper error handling and defaults"""
72
+ try:
73
+ # Load persistent settings first
74
+ load_settings()
75
+
76
+ # Define all required session state keys and their defaults
77
+ default_state = {
78
+ "settings": {
79
+ "api_key": os.getenv("HF_API_KEY", ""),
80
+ "api_base": "https://api-inference.huggingface.co/models/Qwen/Qwen2.5-Coder-32B-Instruct",
81
+ "model": "huggingface/Qwen/Qwen2.5-Coder-32B-Instruct",
82
+ "auto_run": True,
83
+ "theme": "light",
84
+ "code_style": "monokai",
85
+ "custom_instructions": "",
86
+ "safe_mode": "off",
87
+ "conversation_history": False,
88
+ "os_mode": False,
89
+ "os_restricted_mode": False,
90
+ "allowed_paths": ["/"],
91
+ "use_workspace": True,
92
+ },
93
+ "selected_model": None, # Will be set from settings["model"]
94
+ "selected_audio_track": "Ambient",
95
+ "uploaded_files": [],
96
+ "settings_open": False,
97
+ "messages": [],
98
+ "original_system_message": interpreter.system_message,
99
+ "session_dir": None,
100
+ "last_request_time": time.time()
101
+ }
102
+
103
+ # Initialize all required state variables
104
+ for key, default_value in default_state.items():
105
+ if key not in st.session_state:
106
+ st.session_state[key] = default_value
107
+
108
+ # Ensure selected_model is set from settings if not already set
109
+ if not st.session_state.selected_model:
110
+ st.session_state.selected_model = st.session_state.settings["model"]
111
+
112
+ # Always reset interpreter for fresh session
113
+ interpreter.reset()
114
+
115
+ # Apply initial settings
116
+ apply_interpreter_settings()
117
+
118
+ except Exception as e:
119
+ st.error(f"Error initializing session state: {str(e)}")
120
+ # Provide fallback values for critical settings
121
+ if "settings" not in st.session_state:
122
+ st.session_state.settings = default_state["settings"]
123
+
124
+ def apply_interpreter_settings():
125
+ """Apply interpreter settings"""
126
+ interpreter.llm.api_key = st.session_state.settings["api_key"]
127
+ interpreter.llm.api_base = st.session_state.settings["api_base"]
128
+ interpreter.llm.model = st.session_state.settings["model"]
129
+ interpreter.auto_run = st.session_state.settings["auto_run"]
130
+ interpreter.safe_mode = "off"
131
+ interpreter.conversation_history = False # Force this to False
132
+ interpreter.os = st.session_state.settings["os_mode"]
133
+
134
+ # Set allowed paths
135
+ interpreter.computer.allowed_paths = [get_session_folder()] if st.session_state.settings["use_workspace"] else \
136
+ st.session_state.settings["allowed_paths"]
137
+
138
+ # Update system message
139
+ interpreter.system_message = st.session_state.original_system_message
140
+ if st.session_state.settings["custom_instructions"]:
141
+ interpreter.system_message += f"\n\nAdditional Instructions:\n{st.session_state.settings['custom_instructions']}"
142
+
143
+ if st.session_state.settings["use_workspace"]:
144
+ workspace_path = get_session_folder()
145
+ interpreter.system_message += f"\n\nWorkspace Path: {workspace_path}\nYou can only access files in this workspace directory."
146
+
147
+ class OutputController:
148
+ def __init__(self):
149
+ self.loop_detection = {
150
+ 'last_content': None,
151
+ 'repeat_count': 0,
152
+ 'last_timestamp': datetime.now(),
153
+ 'number_pattern': re.compile(r'^[\d\s.]+$'),
154
+ 'terminal_spam': re.compile(r'^[0-9\s.]{20,}$'), # Long number sequences
155
+ 'max_repeats': 3,
156
+ 'timeout_seconds': 2
157
+ }
158
+
159
+ def is_loop_detected(self, content: str) -> bool:
160
+ now = datetime.now()
161
+ content = str(content).strip()
162
+
163
+ # Immediately skip terminal spam
164
+ if self.loop_detection['terminal_spam'].match(content):
165
+ return True
166
+
167
+ if (content != self.loop_detection['last_content'] or
168
+ (now - self.loop_detection['last_timestamp']).seconds > self.loop_detection['timeout_seconds']):
169
+ self.loop_detection.update({
170
+ 'repeat_count': 0,
171
+ 'last_content': content,
172
+ 'last_timestamp': now
173
+ })
174
+ return False
175
+
176
+ # More aggressive number detection
177
+ if self.loop_detection['number_pattern'].match(content):
178
+ self.loop_detection['repeat_count'] += 1
179
+ if self.loop_detection['repeat_count'] > 2: # Reduced tolerance
180
+ return True
181
+
182
+ return False
183
+
184
+ # Move clear_chat_history outside main function
185
+ def clear_chat_history():
186
+ """Completely reset the chat state"""
187
+ interpreter.messages = [] # Clear interpreter's message history
188
+ interpreter.reset() # Full reset of interpreter
189
+ st.session_state.messages = [] # Clear UI message history
190
+ save_settings() # Ensure settings persist after clear
191
+ st.success("Chat history cleared!")
192
  st.rerun()
193
 
194
+ # Move update_model_settings outside main function
195
+ def update_model_settings():
196
+ st.session_state.selected_model = st.session_state.model_select
197
+ st.session_state.settings.update({
198
+ "model": st.session_state.model_select,
199
+ "api_base": model_options[st.session_state.model_select]
200
+ })
201
+ save_settings() # Save settings after update
202
 
203
+ # Define audio_tracks at module level
204
+ audio_tracks = {
205
+ "Ambient": "https://cdn.pixabay.com/download/audio/2022/02/22/audio_d1718ab41b.mp3",
206
+ "Lo-Fi": "https://cdn.pixabay.com/download/audio/2022/03/10/audio_2d7b426f87.mp3",
207
+ "Focus": "https://cdn.pixabay.com/download/audio/2022/01/18/audio_d0c6bf3c0e.mp3"
208
+ }
209
 
210
+ # Move to module level (top of file with other constants)
211
+ model_options = {
212
+ # Default and recommended model
213
+ "huggingface/Qwen/Qwen2.5-Coder-32B-Instruct": "https://api-inference.huggingface.co/models/Qwen/Qwen2.5-Coder-32B-Instruct", # Default
214
 
215
+ # Other Qwen 2.5 Coder Series
216
+ "huggingface/Qwen/Qwen2.5-Coder-14B-Instruct": "https://api-inference.huggingface.co/models/Qwen/Qwen2.5-Coder-14B-Instruct",
217
+ "huggingface/Qwen/Qwen2.5-Coder-7B-Instruct": "https://api-inference.huggingface.co/models/Qwen/Qwen2.5-Coder-7B-Instruct",
218
+
219
+ # Qwen 2.5 General Series
220
+ "huggingface/Qwen/Qwen2.5-72B-Instruct": "https://api-inference.huggingface.co/models/Qwen/Qwen2.5-72B-Instruct",
221
+ "huggingface/Qwen/Qwen2.5-32B-Instruct": "https://api-inference.huggingface.co/models/Qwen/Qwen2.5-32B-Instruct",
222
+ "huggingface/Qwen/Qwen2.5-7B-Instruct": "https://api-inference.huggingface.co/models/Qwen/Qwen2.5-7B-Instruct",
223
+
224
+ # Other verified top performers
225
+ "huggingface/mistralai/Mixtral-8x7B-Instruct-v0.1": "https://api-inference.huggingface.co/models/mistralai/Mixtral-8x7B-Instruct-v0.1",
226
+ "huggingface/mistralai/Mistral-7B-Instruct-v0.2": "https://api-inference.huggingface.co/models/mistralai/Mistral-7B-Instruct-v0.2",
227
+ "huggingface/codellama/CodeLlama-34b-Instruct-hf": "https://api-inference.huggingface.co/models/codellama/CodeLlama-34b-Instruct-hf",
228
+ "huggingface/codellama/CodeLlama-13b-Instruct-hf": "https://api-inference.huggingface.co/models/codellama/CodeLlama-13b-Instruct-hf",
229
+ "huggingface/deepseek-ai/deepseek-coder-6.7b-instruct": "https://api-inference.huggingface.co/models/deepseek-ai/deepseek-coder-6.7b-instruct",
230
+ "huggingface/microsoft/phi-2": "https://api-inference.huggingface.co/models/microsoft/phi-2",
231
+ "huggingface/bigcode/starcoder2-15b": "https://api-inference.huggingface.co/models/bigcode/starcoder2-15b",
232
+ }
233
+
234
+ def get_theme_styles(theme: str = "light") -> str:
235
+ """Get theme styles based on Streamlit's native theming"""
236
+ return """
237
+ /* Base styles */
238
+ body {
239
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
240
+ line-height: 1.6;
241
+ }
242
+
243
+ /* Message container styles */
244
+ .stChatMessage {
245
+ margin: 1rem 0;
246
+ padding: 1rem;
247
+ border-radius: 10px;
248
+ border: 1px solid rgba(128, 128, 128, 0.2);
249
+ }
250
+
251
+ /* Code block styles */
252
+ pre {
253
+ border-radius: 8px !important;
254
+ padding: 1rem !important;
255
+ margin: 1rem 0 !important;
256
+ border: 1px solid rgba(128, 128, 128, 0.2) !important;
257
+ overflow-x: auto !important;
258
+ }
259
+
260
+ code {
261
+ font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace !important;
262
+ font-size: 0.9em !important;
263
+ padding: 0.2em 0.4em !important;
264
+ border-radius: 3px !important;
265
+ }
266
+
267
+ /* Output block styles */
268
+ .output-block {
269
+ border-radius: 8px;
270
+ padding: 1rem;
271
+ margin: 0.5rem 0;
272
+ font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace;
273
+ font-size: 0.9em;
274
+ border-left: 4px solid #FF4B4B;
275
+ opacity: 0.9;
276
+ }
277
+
278
+ /* Message spacing */
279
+ .stMarkdown {
280
+ line-height: 1.6;
281
+ margin: 0.5rem 0;
282
+ }
283
+
284
+ .stMarkdown p {
285
+ margin: 0.75rem 0;
286
+ }
287
+
288
+ /* Button styles */
289
+ .stButton button {
290
+ border-radius: 20px !important;
291
+ padding: 0.4rem 1rem !important;
292
+ border: 1px solid rgba(128, 128, 128, 0.2) !important;
293
+ font-weight: 500 !important;
294
+ transition: all 0.3s ease !important;
295
+ }
296
+
297
+ /* Header styles */
298
+ .stMarkdown h1, .stMarkdown h2, .stMarkdown h3 {
299
+ margin: 1.5rem 0 1rem 0;
300
+ font-weight: 600;
301
+ }
302
+
303
+ /* Input field styles */
304
+ .stTextInput > div > div > input {
305
+ border-radius: 10px !important;
306
+ border: 1px solid rgba(128, 128, 128, 0.2) !important;
307
+ padding: 0.75rem 1rem !important;
308
+ font-size: 1rem !important;
309
+ }
310
+
311
+ /* Chat message styles */
312
+ .chat-message {
313
+ padding: 1.25rem;
314
+ border-radius: 12px;
315
+ margin: 1rem 0;
316
+ border: 1px solid rgba(128, 128, 128, 0.2);
317
+ box-shadow: 0 2px 4px rgba(0,0,0,0.05);
318
+ }
319
+
320
+ /* User message specific styles */
321
+ .user-message {
322
+ margin-left: auto;
323
+ max-width: 80%;
324
+ }
325
+
326
+ /* Assistant message specific styles */
327
+ .assistant-message {
328
+ margin-right: auto;
329
+ max-width: 80%;
330
+ }
331
+
332
+ /* Error and warning styles */
333
+ .error-message {
334
+ border-left: 4px solid #DC2626;
335
+ padding: 1rem;
336
+ margin: 1rem 0;
337
+ border-radius: 8px;
338
+ opacity: 0.9;
339
+ }
340
+
341
+ .warning-message {
342
+ border-left: 4px solid #F59E0B;
343
+ padding: 1rem;
344
+ margin: 1rem 0;
345
+ border-radius: 8px;
346
+ opacity: 0.9;
347
+ }
348
+
349
+ /* Success message styles */
350
+ .success-message {
351
+ border-left: 4px solid #059669;
352
+ padding: 1rem;
353
+ margin: 1rem 0;
354
+ border-radius: 8px;
355
+ opacity: 0.9;
356
+ }
357
+
358
+ /* Floating audio player styles */
359
+ .floating-audio {
360
+ position: fixed;
361
+ bottom: 20px;
362
+ right: 20px;
363
+ z-index: 9999;
364
+ padding: 1rem;
365
+ border-radius: 12px;
366
+ box-shadow: 0 4px 6px rgba(0,0,0,0.1);
367
+ backdrop-filter: blur(10px);
368
+ border: 1px solid rgba(128, 128, 128, 0.2);
369
+ max-width: 300px;
370
+ transition: all 0.3s ease;
371
+ }
372
+
373
+ .floating-audio:hover {
374
+ transform: translateY(-2px);
375
+ box-shadow: 0 6px 8px rgba(0,0,0,0.15);
376
+ }
377
+
378
+ .floating-audio audio {
379
+ width: 250px;
380
+ height: 40px;
381
+ opacity: 0.9;
382
+ border-radius: 8px;
383
+ margin-bottom: 0.5rem;
384
+ }
385
+
386
+ .floating-audio select {
387
+ width: 100%;
388
+ padding: 0.5rem;
389
+ border-radius: 8px;
390
+ border: 1px solid rgba(128, 128, 128, 0.2);
391
+ font-size: 0.9rem;
392
+ cursor: pointer;
393
+ }
394
+
395
+ /* File browser styles */
396
+ .file-browser {
397
+ border-radius: 12px;
398
+ padding: 1rem;
399
+ margin: 1rem 0;
400
+ border: 1px solid rgba(128, 128, 128, 0.2);
401
+ }
402
+
403
+ .file-item {
404
+ display: flex;
405
+ align-items: center;
406
+ padding: 0.75rem;
407
+ border-bottom: 1px solid rgba(128, 128, 128, 0.2);
408
+ transition: all 0.2s ease;
409
+ }
410
+
411
+ .file-item:hover {
412
+ opacity: 0.8;
413
+ }
414
+
415
+ /* Settings panel styles */
416
+ .settings-panel {
417
+ border-radius: 12px;
418
+ padding: 1.5rem;
419
+ margin: 1rem 0;
420
+ border: 1px solid rgba(128, 128, 128, 0.2);
421
+ }
422
+
423
+ /* Tab styles */
424
+ .stTabs {
425
+ border-radius: 8px;
426
+ padding: 0.5rem;
427
+ }
428
+
429
+ .stTab {
430
+ border-radius: 8px !important;
431
+ padding: 0.5rem 1rem !important;
432
+ }
433
+ """
434
+
435
+ class ChatMessage:
436
+ def __init__(self, content="", message_type="text", role="assistant"):
437
+ self.content = content
438
+ self.type = message_type
439
+ self.role = role
440
+ self.timestamp = datetime.now(timezone.utc)
441
+ self.id = hashlib.md5(f"{self.timestamp.isoformat()}-{content}".encode()).hexdigest()
442
+ self._processed_content = None # Cache for processed content
443
+
444
+ def to_dict(self):
445
+ return {
446
+ "content": self.content,
447
+ "type": self.type,
448
+ "role": self.role,
449
+ "timestamp": self.timestamp.isoformat(),
450
+ "id": self.id
451
+ }
452
+
453
+ @classmethod
454
+ def from_dict(cls, data):
455
+ msg = cls(
456
+ content=data["content"],
457
+ message_type=data["type"],
458
+ role=data["role"]
459
+ )
460
+ msg.timestamp = datetime.fromisoformat(data["timestamp"])
461
+ msg.id = data["id"]
462
+ return msg
463
+
464
+ def get_formatted_content(self, force_refresh=False):
465
+ """Get formatted content with caching"""
466
+ if self._processed_content is None or force_refresh:
467
+ self._processed_content = format_message(self)
468
+ return self._processed_content
469
+
470
+ def format_message(message: ChatMessage) -> str:
471
+ """Format message with improved markdown and syntax highlighting for large responses"""
472
  try:
473
+ if message.type == "code":
474
+ # Default to Python for code blocks
475
+ lang = "python"
476
+ content = message.content.strip()
477
+
478
+ # Enhanced language detection
479
+ if content.startswith("```"):
480
+ first_line = content.split("\n")[0]
481
+ lang = first_line.replace("```", "").strip() or "python"
482
+ content = "\n".join(content.split("\n")[1:])
483
+ if content.endswith("```"):
484
+ content = content[:-3]
485
+
486
+ # Extended language detection
487
+ if "." in content:
488
+ ext_match = re.search(r'\.(py|js|html|css|json|md|sql|sh|bash|yaml|yml|java|cpp|c|go|rs|ts)$', content.lower())
489
+ if ext_match:
490
+ lang_map = {
491
+ 'py': 'python',
492
+ 'js': 'javascript',
493
+ 'html': 'html',
494
+ 'css': 'css',
495
+ 'json': 'json',
496
+ 'md': 'markdown',
497
+ 'sql': 'sql',
498
+ 'sh': 'bash',
499
+ 'bash': 'bash',
500
+ 'yaml': 'yaml',
501
+ 'yml': 'yaml',
502
+ 'java': 'java',
503
+ 'cpp': 'cpp',
504
+ 'c': 'c',
505
+ 'go': 'go',
506
+ 'rs': 'rust',
507
+ 'ts': 'typescript'
508
+ }
509
+ lang = lang_map.get(ext_match.group(1), lang)
510
+
511
+ # Format code with proper spacing and syntax
512
+ formatted_content = f"```{lang}\n{content.strip()}\n```"
513
+
514
+ # Add visual separator for multiple code blocks
515
+ if "\n\n```" in message.content:
516
+ formatted_content = f"\n{formatted_content}\n"
517
+
518
+ return formatted_content
519
+
520
+ elif message.type == "error":
521
+ return f'<div class="error-message">❌ **Error:** {message.content}</div>'
522
+ elif message.type == "warning":
523
+ return f'<div class="warning-message">⚠️ **Warning:** {message.content}</div>'
524
+ elif message.type == "success":
525
+ return f'<div class="success-message">βœ… {message.content}</div>'
526
+ else:
527
+ # Clean and format regular text
528
+ content = message.content.strip()
529
+
530
+ # Handle inline code with better spacing
531
+ content = re.sub(r'(?<!`)`([^`]+)`(?!`)', r' <code>\1</code> ', content)
532
+ content = re.sub(r'\s+', ' ', content) # Normalize spaces
533
+
534
+ # Handle markdown links with proper spacing
535
+ content = re.sub(r'\[([^\]]+)\]\(([^\)]+)\)', r'<a href="\2" target="_blank">\1</a>', content)
536
+
537
+ # Improve list formatting
538
+ content = re.sub(r'(\n\s*[-*]\s+[^\n]+)(?=\n\s*[^-*]|\Z)', r'\1\n', content)
539
+
540
+ # Enhanced console output formatting
541
+ if "$ " in content or "%" in content:
542
+ lines = content.split("\n")
543
+ formatted_lines = []
544
+ in_output = False
545
+ output_buffer = []
546
+
547
+ for line in lines:
548
+ if line.strip().startswith(("$ ", "%")):
549
+ # Format collected output
550
+ if output_buffer:
551
+ formatted_lines.append(f'<div class="output-block">{"".join(output_buffer)}</div>')
552
+ output_buffer = []
553
+ # Format command with proper styling
554
+ formatted_lines.append(f'<code class="command">{line}</code>')
555
+ in_output = True
556
+ elif in_output:
557
+ # Collect output lines
558
+ output_buffer.append(line + "\n")
559
+ else:
560
+ # Regular text line
561
+ formatted_lines.append(line)
562
+
563
+ # Handle any remaining output
564
+ if output_buffer:
565
+ formatted_lines.append(f'<div class="output-block">{"".join(output_buffer)}</div>')
566
+
567
+ content = "\n".join(formatted_lines)
568
+
569
+ # Clean up excessive newlines
570
+ content = re.sub(r'\n{3,}', '\n\n', content)
571
+
572
+ return content
573
+
574
+ except Exception as e:
575
+ st.error(f"Error formatting message: {str(e)}")
576
+ return message.content
577
+
578
+ def handle_user_input(user_input: str):
579
+ """Handle user input with improved streaming and chunking for large responses"""
580
+ if not user_input.strip():
581
+ return
582
+
583
+ # Rate limiting with exponential backoff
584
+ current_time = time.time()
585
+ if hasattr(st.session_state, 'last_request_time'):
586
+ time_since_last = current_time - st.session_state.last_request_time
587
+ min_interval = 1.0 # Base interval in seconds
588
+
589
+ if hasattr(st.session_state, 'request_count'):
590
+ st.session_state.request_count += 1
591
+ if st.session_state.request_count > 5:
592
+ min_interval = min(5.0, min_interval * 1.5)
593
+ else:
594
+ st.session_state.request_count = 1
595
+
596
+ if time_since_last < min_interval:
597
+ st.warning(f"Please wait {min_interval - time_since_last:.1f} seconds before sending another message...")
598
+ time.sleep(min_interval - time_since_last)
599
+
600
+ st.session_state.last_request_time = current_time
601
+ st.session_state.request_count = 1
602
+
603
+ # Add user message
604
+ user_message = ChatMessage(user_input, "text", "user")
605
+ st.session_state.messages.append(user_message)
606
+
607
+ with st.chat_message("user", avatar="πŸ§‘β€πŸ’»"):
608
+ st.markdown(user_message.get_formatted_content())
609
+
610
+ # Process with interpreter
611
+ try:
612
+ with st.chat_message("assistant", avatar="πŸ€–"):
613
+ message_container = st.container()
614
+ with st.spinner("Thinking..."):
615
+ # Initialize buffers and state
616
+ message_buffer = []
617
+ code_buffer = []
618
+ current_chunk = {
619
+ 'type': 'message',
620
+ 'content': '',
621
+ 'language': None
622
+ }
623
+
624
+ # Create placeholder for streaming updates
625
+ with message_container:
626
+ response_placeholder = st.empty()
627
+
628
+ # Enhanced streaming with chunking
629
+ for chunk in interpreter.chat(user_input, stream=True, display=False):
630
+ if isinstance(chunk, dict):
631
+ content = chunk.get('content', '')
632
+ chunk_type = chunk.get('type', 'message')
633
+
634
+ # Skip empty chunks
635
+ if not content:
636
+ continue
637
+
638
+ # Handle different chunk types
639
+ if chunk_type == 'message':
640
+ # Flush code buffer if exists
641
  if code_buffer:
642
+ code_text = ''.join(code_buffer)
643
+ if code_text.strip():
644
+ message_buffer.append(f"\n```{current_chunk['language'] or 'python'}\n{code_text.strip()}\n```\n")
645
+ code_buffer = []
646
+
647
+ # Add message content
648
+ message_buffer.append(content)
649
+ current_chunk = {'type': 'message', 'content': content}
650
+
651
+ elif chunk_type in ['code', 'console']:
652
+ # Start new code block if needed
653
+ if current_chunk['type'] != 'code':
654
+ if code_buffer: # Flush previous code buffer
655
+ code_text = ''.join(code_buffer)
656
+ if code_text.strip():
657
+ message_buffer.append(f"\n```{current_chunk['language'] or 'python'}\n{code_text.strip()}\n```\n")
658
+ code_buffer = []
659
+
660
+ current_chunk = {
661
+ 'type': 'code',
662
+ 'language': chunk.get('format', 'python')
663
+ }
664
+
665
+ # Accumulate code content
666
+ code_buffer.append(content)
667
+
668
+ # Update display with proper chunking
669
+ try:
670
+ display_content = ''.join(str(item) for item in message_buffer)
671
+ if code_buffer: # Add current code buffer if exists
672
+ code_text = ''.join(str(item) for item in code_buffer)
673
+ if code_text.strip():
674
+ display_content += f"\n```{current_chunk['language'] or 'python'}\n{code_text.strip()}\n```"
675
+
676
+ # Use markdown for display with proper formatting
677
+ response_placeholder.markdown(display_content)
678
+
679
+ except Exception as e:
680
+ st.error(f"Error updating display: {str(e)}")
681
+
682
+ # Final cleanup and display
683
+ try:
684
+ # Handle any remaining code buffer
685
+ if code_buffer:
686
+ code_text = ''.join(code_buffer)
687
+ if code_text.strip():
688
+ message_buffer.append(f"\n```{current_chunk['language'] or 'python'}\n{code_text.strip()}\n```\n")
689
+
690
+ # Prepare final response
691
+ final_response = ''.join(message_buffer)
692
+
693
+ # Create and store assistant message
694
+ assistant_message = ChatMessage(final_response, "text", "assistant")
695
+ st.session_state.messages.append(assistant_message)
696
+
697
+ # Final display update
698
+ response_placeholder.markdown(assistant_message.get_formatted_content())
699
+
700
+ except Exception as e:
701
+ st.error(f"Error in final display update: {str(e)}")
702
+ response_placeholder.markdown(final_response)
703
+
704
+ except Exception as e:
705
+ error_msg = ChatMessage(str(e), "error", "assistant")
706
+ st.session_state.messages.append(error_msg)
707
+ st.error(error_msg.get_formatted_content())
708
+
709
+ # Add file handling functions
710
+ def get_session_folder() -> str:
711
+ """Get or create the session folder for file uploads with proper error handling"""
712
+ if not st.session_state.settings["use_workspace"]:
713
+ return "/"
714
+
715
+ try:
716
+ # If a custom workspace path is set, use it
717
+ if st.session_state.get("session_dir"):
718
+ workspace_dir = Path(st.session_state.session_dir)
719
+ else:
720
+ # Use default workspace directory
721
+ workspace_dir = Path("autointerpreter-workspace")
722
 
723
+ # Create directory if it doesn't exist
724
+ workspace_dir.mkdir(parents=True, exist_ok=True, mode=0o755)
725
+
726
+ # If no session directory is set, create one
727
+ if not st.session_state.get("session_dir"):
728
+ timestamp = datetime.now(timezone.utc).strftime("%Y-%m-%d_%H_%M")
729
+ session_dir = workspace_dir / f"session-{timestamp}"
730
+ session_dir.mkdir(exist_ok=True, mode=0o755)
731
+ st.session_state.session_dir = str(session_dir)
732
+ st.session_state.uploaded_files = []
733
+ update_custom_instructions()
734
+
735
+ # Verify the workspace directory exists and is accessible
736
+ if not workspace_dir.exists():
737
+ st.error(f"Workspace directory not found: {workspace_dir}")
738
+ # Create a new default workspace
739
+ workspace_dir = Path("autointerpreter-workspace")
740
+ workspace_dir.mkdir(parents=True, exist_ok=True, mode=0o755)
741
+ st.session_state.session_dir = str(workspace_dir)
742
+ st.session_state.uploaded_files = []
743
+ update_custom_instructions()
744
+
745
+ return st.session_state.session_dir
746
+
747
  except Exception as e:
748
+ st.error(f"Error managing workspace: {str(e)}")
749
+ return "/"
750
+ def handle_file_upload(uploaded_files):
751
+ """Enhanced file upload handling with better error handling and validation"""
752
+ if not uploaded_files:
753
+ return
754
+
755
+ try:
756
+ session_dir = Path(get_session_folder())
757
+ if str(session_dir) == "/":
758
+ st.error("Invalid workspace configuration!")
759
+ return
760
+
761
+ if not session_dir.exists():
762
+ st.error("Session directory does not exist!")
763
+ try:
764
+ session_dir.mkdir(parents=True, exist_ok=True, mode=0o755)
765
+ st.success("Created new session directory.")
766
+ except Exception as e:
767
+ st.error(f"Failed to create session directory: {str(e)}")
768
+ return
769
+
770
+ for uploaded_file in uploaded_files:
771
+ try:
772
+ # Validate file
773
+ if uploaded_file.size == 0:
774
+ st.warning(f"Skipping empty file: {uploaded_file.name}")
775
+ continue
776
+
777
+ # Sanitize filename
778
+ safe_filename = Path(uploaded_file.name).name
779
+ safe_filename = re.sub(r'[^a-zA-Z0-9._-]', '_', safe_filename)
780
+
781
+ # Validate extension
782
+ allowed_extensions = {'.txt', '.py', '.js', '.html', '.css', '.json', '.md', '.csv', '.yml', '.yaml'}
783
+ file_ext = Path(safe_filename).suffix.lower()
784
+ if file_ext not in allowed_extensions:
785
+ st.warning(f"Unsupported file type: {file_ext}. Skipping {safe_filename}")
786
+ continue
787
+
788
+ file_path = session_dir / safe_filename
789
+
790
+ # Handle file conflicts
791
+ if file_path.exists():
792
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
793
+ name_parts = safe_filename.rsplit('.', 1)
794
+ safe_filename = f"{name_parts[0]}_{timestamp}.{name_parts[1]}" if len(name_parts) > 1 else f"{safe_filename}_{timestamp}"
795
+ file_path = session_dir / safe_filename
796
+
797
+ # Check file size
798
+ file_size = len(uploaded_file.getvalue())
799
+ if file_size > 100 * 1024 * 1024: # 100MB limit
800
+ st.warning(f"File {safe_filename} is too large (>{file_size/(1024*1024):.1f}MB). Skipping...")
801
+ continue
802
+
803
+ # Save file with error handling
804
+ try:
805
+ with open(file_path, "wb") as f:
806
+ f.write(uploaded_file.getbuffer())
807
+
808
+ # Add to session state
809
+ file_info = {
810
+ "name": safe_filename,
811
+ "path": str(file_path),
812
+ "size": uploaded_file.size,
813
+ "type": uploaded_file.type or "application/octet-stream",
814
+ "timestamp": datetime.now(timezone.utc)
815
+ }
816
+
817
+ if "uploaded_files" not in st.session_state:
818
+ st.session_state.uploaded_files = []
819
+
820
+ # Update or append file info
821
+ existing_file = next((f for f in st.session_state.uploaded_files if f["path"] == str(file_path)), None)
822
+ if existing_file:
823
+ existing_file.update(file_info)
824
+ else:
825
+ st.session_state.uploaded_files.append(file_info)
826
+
827
+ st.success(f"Successfully uploaded: {safe_filename}")
828
+
829
+ except Exception as e:
830
+ st.error(f"Error saving {safe_filename}: {str(e)}")
831
+ if file_path.exists():
832
+ try:
833
+ file_path.unlink()
834
+ except Exception as cleanup_error:
835
+ st.error(f"Error cleaning up partial file: {str(cleanup_error)}")
836
+ continue
837
+
838
+ except Exception as e:
839
+ st.error(f"Error processing upload {uploaded_file.name}: {str(e)}")
840
+ continue
841
+
842
+ # Update instructions after successful uploads
843
+ update_custom_instructions()
844
+
845
+ except Exception as e:
846
+ st.error(f"Error handling file uploads: {str(e)}")
847
+
848
+ def update_custom_instructions():
849
+ """Update custom instructions with workspace info"""
850
+ workspace_path = get_session_folder()
851
+ files_info = ""
852
+
853
+ if st.session_state.get("uploaded_files"):
854
+ files = [f"{info['name']} ({info['size'] / 1024:.1f} KB)"
855
+ for info in st.session_state.uploaded_files]
856
+ files_info = "\nFiles: " + ", ".join(files)
857
+
858
+ workspace_info = f"\nWorking Space: {workspace_path}{files_info}"
859
+
860
+ # Update instructions
861
+ current_instructions = st.session_state.settings.get("custom_instructions", "").strip()
862
+ current_instructions = re.sub(r'\nWorking Space:.*?(?=\n|$)', '', current_instructions, flags=re.MULTILINE)
863
+ current_instructions = re.sub(r'\nFiles:.*?(?=\n|$)', '', current_instructions, flags=re.MULTILINE)
864
+
865
+ new_instructions = current_instructions + ("\n" if current_instructions else "") + workspace_info
866
+ st.session_state.settings["custom_instructions"] = new_instructions
867
+ apply_interpreter_settings()
868
+
869
+ def get_file_icon(file_type: str) -> str:
870
+ """Get appropriate icon for file type"""
871
+ if file_type.startswith('image/'):
872
+ return "πŸ–ΌοΈ"
873
+ elif file_type.startswith('text/'):
874
+ return "πŸ“„"
875
+ elif file_type.startswith('application/pdf'):
876
+ return "πŸ“‘"
877
+ elif file_type.startswith('application/json'):
878
+ return "οΏ½οΏ½οΏ½οΏ½"
879
+ elif 'python' in file_type.lower():
880
+ return "🐍"
881
+ elif 'javascript' in file_type.lower():
882
+ return ""
883
+ elif 'spreadsheet' in file_type.lower():
884
+ return "πŸ“Š"
885
+ else:
886
+ return "πŸ“Ž"
887
+
888
+ # Add audio track selection handler
889
+ def update_audio_track():
890
+ """Update the selected audio track"""
891
+ st.session_state.selected_audio_track = st.session_state.audio_select
892
+
893
+ def create_audio_player():
894
+ return f"""
895
+ <div class="floating-audio">
896
+ <audio id="audio-player" controls loop style="width: 250px; height: 40px;"
897
+ onerror="handleAudioError()"
898
+ onloadeddata="handleAudioLoaded()">
899
+ <source id="audio-source" src="{audio_tracks[st.session_state.selected_audio_track]}" type="audio/mpeg">
900
+ Your browser does not support the audio element.
901
+ </audio>
902
+ <select id="audio-select" onchange="updateAudio(this.value)"
903
+ style="width: 100%; padding: 5px; border-radius: 5px; border: 1px solid var(--border-color);">
904
+ {' '.join([f'<option value="{url}" {"selected" if name == st.session_state.selected_audio_track else ""}>{name}</option>'
905
+ for name, url in audio_tracks.items()])}
906
+ </select>
907
+ </div>
908
+ <script>
909
+ function handleAudioError() {{
910
+ console.error('Audio playback error');
911
+ const player = document.getElementById('audio-player');
912
+ player.style.opacity = '0.5';
913
+ player.title = 'Error loading audio';
914
+ }}
915
+
916
+ function handleAudioLoaded() {{
917
+ const player = document.getElementById('audio-player');
918
+ player.style.opacity = '1';
919
+ player.title = '';
920
+ }}
921
+
922
+ function updateAudio(url) {{
923
+ try {{
924
+ const audioPlayer = document.getElementById('audio-player');
925
+ const audioSource = document.getElementById('audio-source');
926
+ const wasPlaying = !audioPlayer.paused;
927
+ audioSource.src = url;
928
+ audioPlayer.load();
929
+ if (wasPlaying) {{
930
+ const playPromise = audioPlayer.play();
931
+ if (playPromise !== undefined) {{
932
+ playPromise.catch(error => {{
933
+ console.error('Error playing audio:', error);
934
+ }});
935
+ }}
936
+ }} catch (error) {{
937
+ console.error('Error updating audio:', error);
938
+ }}
939
+ }}
940
+
941
+ // Add auto-retry for failed audio loads
942
+ document.addEventListener('DOMContentLoaded', function() {{
943
+ const audioPlayer = document.getElementById('audio-player');
944
+ let retryCount = 0;
945
+ const maxRetries = 3;
946
+
947
+ audioPlayer.addEventListener('error', function() {{
948
+ if (retryCount < maxRetries) {{
949
+ setTimeout(() => {{
950
+ console.log('Retrying audio load...');
951
+ audioPlayer.load();
952
+ retryCount++;
953
+ }}, 1000 * retryCount);
954
+ }}
955
+ }});
956
+ }});
957
+ </script>
958
+ """
959
+
960
+ def cleanup_old_sessions():
961
+ """Clean up old session directories with improved error handling and format detection"""
962
+ try:
963
+ workspace_dir = Path("autointerpreter-workspace")
964
+ if not workspace_dir.exists():
965
+ return
966
+
967
+ current_time = datetime.now(timezone.utc)
968
+ current_session = st.session_state.get("session_dir")
969
+
970
+ # Define supported timestamp formats
971
+ timestamp_formats = [
972
+ "%Y-%m-%d_%H_%M", # Standard format: 2024-01-20_14_30
973
+ "%Y%m%d_%H%M%S", # Compact format: 20240120_143000
974
+ ]
975
+
976
+ for session_dir in workspace_dir.glob("session-*"):
977
+ try:
978
+ if str(session_dir) == current_session:
979
+ continue
980
+
981
+ dir_name = session_dir.name
982
+ timestamp_str = None
983
+ session_time = None
984
+
985
+ # Try to extract timestamp based on different patterns
986
+ if dir_name.startswith("session-"):
987
+ # Try standard timestamp format first
988
+ timestamp_str = dir_name.replace("session-", "")
989
+
990
+ # Try each supported format
991
+ for fmt in timestamp_formats:
992
+ try:
993
+ session_time = datetime.strptime(timestamp_str, fmt).replace(tzinfo=timezone.utc)
994
+ break
995
+ except ValueError:
996
+ continue
997
+
998
+ # If no supported format matches, check for UUID-like format
999
+ if not session_time and (
1000
+ len(timestamp_str) == 36 or # Standard UUID
1001
+ len(timestamp_str) == 32 or # Compact UUID
1002
+ '-' in timestamp_str # Any UUID-like string
1003
+ ):
1004
+ # For UUID-based sessions, use file modification time
1005
+ try:
1006
+ mtime = session_dir.stat().st_mtime
1007
+ session_time = datetime.fromtimestamp(mtime, tz=timezone.utc)
1008
+ except Exception:
1009
+ continue
1010
+
1011
+ if not session_time:
1012
+ # Skip without warning for unrecognized formats
1013
+ continue
1014
+
1015
+ # If older than 24 hours
1016
+ if (current_time - session_time).days >= 1:
1017
+ try:
1018
+ if session_dir.exists(): # Double check existence
1019
+ # Check if directory is empty
1020
+ has_files = any(session_dir.iterdir())
1021
+ if has_files:
1022
+ # Move to archive instead of deleting if contains files
1023
+ archive_dir = workspace_dir / "archived_sessions"
1024
+ archive_dir.mkdir(exist_ok=True)
1025
+ new_name = f"archived_{dir_name}_{current_time.strftime('%Y%m%d_%H%M%S')}"
1026
+ shutil.move(str(session_dir), str(archive_dir / new_name))
1027
+ print(f"Archived session with files: {session_dir}")
1028
+ else:
1029
+ # Delete if empty
1030
+ shutil.rmtree(session_dir)
1031
+ print(f"Cleaned up empty session: {session_dir}")
1032
+ except Exception as e:
1033
+ print(f"Error processing session directory {session_dir}: {str(e)}")
1034
+
1035
+ except Exception as e:
1036
+ print(f"Error processing session directory {session_dir}: {str(e)}")
1037
+ continue
1038
+
1039
+ except Exception as e:
1040
+ print(f"Error in cleanup: {str(e)}")
1041
+
1042
+ def main():
1043
+ # 1. Initialize session state
1044
+ init_session_state()
1045
+
1046
+ # 2. Page config (should be at top)
1047
+ st.set_page_config(
1048
+ page_title="AutoInterpreter",
1049
+ layout="wide",
1050
+ initial_sidebar_state="collapsed",
1051
+ menu_items={
1052
+ 'Get Help': 'https://github.com/samihalawa/autointerpreter',
1053
+ 'Report a bug': "https://github.com/samihalawa/autointerpreter/issues",
1054
+ 'About': "# AutoInterpreter\nThe Final AI Coding Experience"
1055
+ }
1056
+ )
1057
+
1058
+ # 3. Load and apply styles
1059
+ st.markdown("""
1060
+ <style>
1061
+ .stButton button {
1062
+ border-radius: 20px;
1063
+ padding: 0.2rem 0.5rem;
1064
+ border: 1px solid rgba(128, 128, 128, 0.2);
1065
+ transition: all 0.3s ease;
1066
+ }
1067
+ .stButton button:hover {
1068
+ opacity: 0.8;
1069
+ }
1070
+ .header-buttons {
1071
+ display: flex;
1072
+ justify-content: flex-end;
1073
+ gap: 0.5rem;
1074
+ }
1075
+ .header-buttons button {
1076
+ min-width: 0 !important;
1077
+ height: auto !important;
1078
+ padding: 0.2rem !important;
1079
+ }
1080
+ .stTextInput > div > div > input {
1081
+ border-radius: 10px;
1082
+ }
1083
+ /* Add floating audio player styles */
1084
+ .floating-audio {
1085
+ position: fixed;
1086
+ bottom: 20px;
1087
+ right: 20px;
1088
+ z-index: 9999;
1089
+ padding: 10px;
1090
+ border-radius: 10px;
1091
+ box-shadow: 0 2px 5px rgba(0,0,0,0.1);
1092
+ backdrop-filter: blur(5px);
1093
+ display: flex;
1094
+ flex-direction: column;
1095
+ gap: 5px;
1096
+ max-width: 300px;
1097
+ border: 1px solid rgba(128, 128, 128, 0.2);
1098
+ }
1099
+
1100
+ .floating-audio audio {
1101
+ width: 250px;
1102
+ height: 40px;
1103
+ opacity: 0.8;
1104
+ }
1105
+
1106
+ .floating-audio select {
1107
+ width: 100%;
1108
+ padding: 5px;
1109
+ border-radius: 5px;
1110
+ border: 1px solid rgba(128, 128, 128, 0.2);
1111
+ }
1112
+ </style>
1113
+ """, unsafe_allow_html=True)
1114
+
1115
+ # 4. Setup UI components
1116
+ lottie_coding = load_lottieurl('https://assets5.lottiefiles.com/packages/lf20_fcfjwiyb.json')
1117
+
1118
+ # 5. Create header and settings UI
1119
+ col1, col2, col3 = st.columns([0.2, 0.6, 0.2])
1120
+ with col2:
1121
+ st_lottie(lottie_coding, height=200, key="coding")
1122
+ colored_header(
1123
+ label="AutoInterpreter",
1124
+ description="Run Any Code. The Final AI Coding Experience.",
1125
+ color_name="red-70"
1126
+ )
1127
+
1128
+ with col3:
1129
+ st.markdown('<div class="header-buttons">', unsafe_allow_html=True)
1130
+ btn_col1, btn_col2 = st.columns([1, 1])
1131
+ with btn_col1:
1132
+ # Toggle settings when button is clicked
1133
+ if st.button("βš™οΈ", help="Configure AutoInterpreter", key="settings_btn"):
1134
+ st.session_state.settings_open = not st.session_state.settings_open
1135
+ with btn_col2:
1136
+ if st.button("❌", help="Clear chat history", key="clear_btn"):
1137
+ if st.button("βœ“", help="Confirm clear", key="confirm_btn"):
1138
+ clear_chat_history()
1139
+ st.markdown('</div>', unsafe_allow_html=True)
1140
+
1141
+ # Move theme application before settings panel
1142
+ theme = st.session_state.settings.get("theme", "light")
1143
+ st.markdown(f"<style>{get_theme_styles(theme)}</style>", unsafe_allow_html=True)
1144
+
1145
+ # Enhanced Settings modal with tabs
1146
+ if st.session_state.settings_open:
1147
+ with st.expander("Settings Panel", expanded=True):
1148
+ # Add close button to top-right of settings panel
1149
+ col1, col2 = st.columns([0.9, 0.1])
1150
+ with col2:
1151
+ if st.button("βœ–οΈ", help="Close settings", key="close_settings"):
1152
+ st.session_state.settings_open = False
1153
+ save_settings() # Save settings before closing
1154
+ st.rerun()
1155
+
1156
+ # Settings tabs
1157
+ tab1, tab2, tab3 = st.tabs(["API & Model", "Code Settings", "Assistant Settings"])
1158
+
1159
+ with tab1:
1160
+ # API Settings
1161
+ st.text_input(
1162
+ "API Key",
1163
+ value=st.session_state.settings["api_key"],
1164
+ type="password",
1165
+ key="api_key",
1166
+ help="Enter your HuggingFace API key"
1167
+ )
1168
+
1169
+ st.markdown("---")
1170
+ st.markdown("### πŸ€– Model Selection")
1171
+
1172
+ # Current model display
1173
+ current_model = st.session_state.selected_model.split('/')[-1]
1174
+ st.info(f"Current Model: **{current_model}**", icon="πŸ€–")
1175
+
1176
+ # Model Selection with categories
1177
+ model_category = st.radio(
1178
+ "Model Category",
1179
+ ["Qwen Coder Series", "Qwen General Series", "Other Models"],
1180
+ help="Select model category"
1181
+ )
1182
+
1183
+ filtered_models = {
1184
+ k: v for k, v in model_options.items()
1185
+ if (
1186
+ (model_category == "Qwen Coder Series" and "Qwen2.5-Coder" in k) or
1187
+ (model_category == "Qwen General Series" and "Qwen2.5-" in k and "Coder" not in k) or
1188
+ (model_category == "Other Models" and "Qwen" not in k)
1189
+ )
1190
+ }
1191
+
1192
+ selected_model = st.selectbox(
1193
+ "Select Model",
1194
+ options=list(filtered_models.keys()),
1195
+ format_func=lambda x: x.split('/')[-1],
1196
+ key="model_select",
1197
+ help="Choose your preferred model"
1198
+ )
1199
+
1200
+ # Theme Selection
1201
+ st.markdown("---")
1202
+ st.markdown("### 🎨 Theme")
1203
+ theme_selection = st.selectbox(
1204
+ "UI Theme",
1205
+ options=["light", "dark"],
1206
+ index=0 if theme == "light" else 1,
1207
+ key="theme_select",
1208
+ help="Select the UI theme"
1209
+ )
1210
+
1211
+ # Save Settings Button - More prominent
1212
+ st.markdown("---")
1213
+ col1, col2 = st.columns([0.7, 0.3])
1214
+ with col2:
1215
+ if st.button("πŸ’Ύ Save Changes", type="primary", use_container_width=True):
1216
+ # Update settings
1217
+ st.session_state.settings.update({
1218
+ "api_key": st.session_state.api_key,
1219
+ "model": st.session_state.model_select,
1220
+ "api_base": model_options[st.session_state.model_select],
1221
+ "theme": st.session_state.theme_select
1222
+ })
1223
+ st.session_state.selected_model = st.session_state.model_select
1224
+ save_settings()
1225
+ st.session_state.settings_open = False
1226
+ st.success("βœ… Settings saved successfully!")
1227
+ time.sleep(0.5)
1228
+ st.rerun()
1229
+
1230
+ with tab2:
1231
+ # Code Execution Settings
1232
+ col1, col2 = st.columns(2)
1233
+ with col1:
1234
+ st.toggle(
1235
+ "Auto Run Code",
1236
+ value=st.session_state.settings["auto_run"],
1237
+ key="auto_run",
1238
+ help="Automatically execute code without confirmation",
1239
+ on_change=lambda: st.session_state.settings.update({"auto_run": st.session_state.auto_run})
1240
+ )
1241
+ st.selectbox(
1242
+ "Code Style",
1243
+ options=["monokai", "github", "dracula"],
1244
+ index=0,
1245
+ key="code_style",
1246
+ help="Code highlighting theme",
1247
+ on_change=lambda: st.session_state.settings.update({"code_style": st.session_state.code_style})
1248
+ )
1249
+
1250
+ with col2:
1251
+ st.selectbox(
1252
+ "Safe Mode",
1253
+ options=["off", "ask", "auto"],
1254
+ index=0,
1255
+ key="safe_mode",
1256
+ help="Code execution safety level",
1257
+ on_change=lambda: st.session_state.settings.update({"safe_mode": st.session_state.safe_mode})
1258
+ )
1259
+
1260
+ with tab3:
1261
+ # Assistant Behavior Settings
1262
+ """
1263
+ st.toggle(
1264
+ "Save Chat History",
1265
+ value=st.session_state.settings["conversation_history"],
1266
+ key="conversation_history",
1267
+ help="Preserve conversation between sessions",
1268
+ on_change=lambda: st.session_state.settings.update({"conversation_history": st.session_state.conversation_history})
1269
+ )
1270
+ """
1271
+
1272
+ # System Message Settings
1273
+ st.text_area(
1274
+ "Default System Message",
1275
+ value=interpreter.system_message,
1276
+ disabled=True,
1277
+ help="Base instructions for the AI assistant",
1278
+ height=100
1279
+ )
1280
+
1281
+ st.text_area(
1282
+ "Custom Instructions",
1283
+ value=st.session_state.settings["custom_instructions"],
1284
+ key="custom_instructions",
1285
+ help="Additional instructions for the assistant",
1286
+ height=100,
1287
+ on_change=lambda: st.session_state.settings.update({"custom_instructions": st.session_state.custom_instructions})
1288
+ )
1289
+
1290
+ # OS Control Settings
1291
+ st.markdown("---")
1292
+ st.markdown("### System Access Settings")
1293
+ st.markdown("**Warning**: OS mode enables system control (mouse, keyboard, screen access)")
1294
+
1295
+ st.toggle(
1296
+ "Enable System Control",
1297
+ value=st.session_state.settings["os_mode"],
1298
+ key="os_mode",
1299
+ help="Allow assistant to control system",
1300
+ on_change=lambda: st.session_state.settings.update({"os_mode": st.session_state.os_mode})
1301
+ )
1302
+
1303
+ if st.session_state.settings["os_mode"]:
1304
+ st.toggle(
1305
+ "Restricted Access",
1306
+ value=st.session_state.settings["os_restricted_mode"],
1307
+ key="os_restricted_mode",
1308
+ help="Limit system access to specific paths",
1309
+ on_change=lambda: st.session_state.settings.update({"os_restricted_mode": st.session_state.os_restricted_mode})
1310
+ )
1311
+
1312
+ if st.session_state.settings["os_restricted_mode"]:
1313
+ st.text_area(
1314
+ "Allowed Paths",
1315
+ value="\n".join(st.session_state.settings["allowed_paths"]),
1316
+ help="One path per line",
1317
+ key="allowed_paths",
1318
+ on_change=lambda: st.session_state.settings.update({
1319
+ "allowed_paths": [p.strip() for p in st.session_state.allowed_paths.split("\n") if p.strip()]
1320
+ })
1321
+ )
1322
+
1323
+ # Add current model display after header
1324
+ col1, col2, col3 = st.columns([0.2, 0.6, 0.2])
1325
+ with col3:
1326
+ st.markdown(
1327
+ f"""<div style="text-align: right; font-size: 0.8em; color: var(--text-color); opacity: 0.8;">
1328
+ πŸ€– Model: {st.session_state.selected_model.split('/')[-1]}
1329
+ </div>""",
1330
+ unsafe_allow_html=True
1331
+ )
1332
+
1333
+ # Initialize selected track in session state if not exists
1334
+ if "selected_audio_track" not in st.session_state:
1335
+ st.session_state.selected_audio_track = "Ambient"
1336
+
1337
+ # Create floating audio player
1338
+ st.markdown(create_audio_player(), unsafe_allow_html=True)
1339
+
1340
+ # Simplified workspace control in sidebar
1341
+ with st.sidebar:
1342
+ st.markdown("### πŸ—‚οΈ Workspace Control")
1343
+
1344
+ # Single workspace toggle in sidebar
1345
+ st.toggle(
1346
+ "Use Workspace",
1347
+ value=st.session_state.settings["use_workspace"],
1348
+ key="use_workspace",
1349
+ help="Restrict file access to workspace directory only",
1350
+ on_change=lambda: (
1351
+ st.session_state.settings.update({"use_workspace": st.session_state.use_workspace}),
1352
+ apply_interpreter_settings()
1353
+ )
1354
+ )
1355
+
1356
+ if st.session_state.settings["use_workspace"]:
1357
+ st.markdown("### πŸ“‚ Workspace")
1358
+
1359
+ # Add workspace path input
1360
+ workspace_path = st.text_input(
1361
+ "Workspace Path",
1362
+ value=str(Path(st.session_state.get("session_dir", "autointerpreter-workspace")).resolve()),
1363
+ help="Enter the full path to your workspace directory",
1364
+ key="workspace_path_input"
1365
+ )
1366
+
1367
+ # Add Set Path button
1368
+ if st.button("πŸ“ Set Workspace Path", use_container_width=True):
1369
+ try:
1370
+ workspace_dir = Path(workspace_path)
1371
+ workspace_dir.mkdir(parents=True, exist_ok=True)
1372
+ st.session_state.session_dir = str(workspace_dir)
1373
+ apply_interpreter_settings()
1374
+ st.success(f"βœ… Workspace set to: {workspace_path}")
1375
+ st.rerun()
1376
+ except Exception as e:
1377
+ st.error(f"❌ Error setting workspace path: {str(e)}")
1378
+
1379
+ # File browser for workspace management
1380
+ event = sfb.st_file_browser(
1381
+ path=st.session_state.get("session_dir", "autointerpreter-workspace"),
1382
+ key="file_browser",
1383
+ show_choose_file=True,
1384
+ show_delete_file=True,
1385
+ show_new_folder=True,
1386
+ show_upload_file=True,
1387
+ show_preview=True
1388
+ )
1389
+
1390
+ if event:
1391
+ if event.get("type") == "file_selected":
1392
+ st.session_state.session_dir = event["path"]
1393
+ st.code(f"Current workspace: {event['path']}", language="bash")
1394
+ apply_interpreter_settings()
1395
+ elif event.get("type") == "folder_created":
1396
+ st.success(f"Created folder: {event['path']}")
1397
+ elif event.get("type") == "file_deleted":
1398
+ st.warning(f"Deleted: {event['path']}")
1399
+ if str(event['path']) == st.session_state.get('session_dir'):
1400
+ st.session_state.pop('session_dir', None)
1401
+ apply_interpreter_settings()
1402
+
1403
+ # After settings panel and before chat history display
1404
+ # Apply interpreter settings
1405
+ apply_interpreter_settings()
1406
+
1407
+ # Display chat history with enhanced formatting
1408
+ for message in st.session_state.messages:
1409
+ with st.chat_message(message.role, avatar="πŸ§‘β€πŸ’»" if message.role == "user" else "οΏ½οΏ½οΏ½"):
1410
+ st.markdown(message.get_formatted_content())
1411
+
1412
+ # Handle new user input
1413
+ user_input = st.chat_input("Ask me anything about coding...", key="chat_input")
1414
+ if user_input:
1415
+ handle_user_input(user_input)
1416
+
1417
+
1418
+ # Add ARIA labels to main UI components
1419
+ st.markdown("""
1420
+ <div role="main" aria-label="AI Code Assistant Interface">
1421
+ <div role="complementary" aria-label="Settings Panel">
1422
+ <!-- Settings content -->
1423
+ </div>
1424
+ <div role="log" aria-label="Chat History">
1425
+ <!-- Chat messages -->
1426
+ </div>
1427
+ <div role="form" aria-label="Chat Input">
1428
+ <!-- Input field -->
1429
+ </div>
1430
+ </div>
1431
+ """, unsafe_allow_html=True)
1432
+
1433
+ # Add cleanup of old sessions at the end
1434
+ if st.session_state.settings["use_workspace"]:
1435
+ cleanup_old_sessions()
1436
+
1437
+ if __name__ == "__main__":
1438
+ main()