deepcode-hku 1.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cli/__init__.py +18 -0
- cli/cli_app.py +296 -0
- cli/cli_interface.py +744 -0
- cli/cli_launcher.py +155 -0
- cli/main_cli.py +243 -0
- cli/workflows/__init__.py +11 -0
- cli/workflows/cli_workflow_adapter.py +336 -0
- deepcode.py +219 -0
- deepcode_hku-1.0.1.dist-info/METADATA +695 -0
- deepcode_hku-1.0.1.dist-info/RECORD +44 -0
- deepcode_hku-1.0.1.dist-info/WHEEL +5 -0
- deepcode_hku-1.0.1.dist-info/entry_points.txt +2 -0
- deepcode_hku-1.0.1.dist-info/licenses/LICENSE +21 -0
- deepcode_hku-1.0.1.dist-info/top_level.txt +6 -0
- tools/__init__.py +0 -0
- tools/code_implementation_server.py +1045 -0
- tools/code_indexer.py +1657 -0
- tools/code_reference_indexer.py +486 -0
- tools/command_executor.py +324 -0
- tools/git_command.py +356 -0
- tools/pdf_converter.py +640 -0
- tools/pdf_downloader.py +1370 -0
- tools/pdf_utils.py +52 -0
- ui/__init__.py +43 -0
- ui/app.py +13 -0
- ui/components.py +1450 -0
- ui/handlers.py +773 -0
- ui/layout.py +106 -0
- ui/streamlit_app.py +38 -0
- ui/styles.py +2116 -0
- utils/__init__.py +17 -0
- utils/cli_interface.py +459 -0
- utils/dialogue_logger.py +671 -0
- utils/file_processor.py +426 -0
- utils/simple_llm_logger.py +198 -0
- workflows/__init__.py +31 -0
- workflows/agent_orchestration_engine.py +1371 -0
- workflows/agents/__init__.py +13 -0
- workflows/agents/code_implementation_agent.py +1093 -0
- workflows/agents/memory_agent_concise.py +923 -0
- workflows/agents/memory_agent_concise_index.py +935 -0
- workflows/code_implementation_workflow.py +924 -0
- workflows/code_implementation_workflow_index.py +931 -0
- workflows/codebase_index_workflow.py +726 -0
ui/handlers.py
ADDED
|
@@ -0,0 +1,773 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Streamlit Event Handlers Module
|
|
3
|
+
|
|
4
|
+
Contains all event handling and business logic
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
import time
|
|
9
|
+
import os
|
|
10
|
+
import traceback
|
|
11
|
+
import atexit
|
|
12
|
+
import signal
|
|
13
|
+
from datetime import datetime
|
|
14
|
+
from typing import Dict, Any
|
|
15
|
+
|
|
16
|
+
import streamlit as st
|
|
17
|
+
import nest_asyncio
|
|
18
|
+
import concurrent.futures
|
|
19
|
+
|
|
20
|
+
# Import necessary modules
|
|
21
|
+
from mcp_agent.app import MCPApp
|
|
22
|
+
from workflows.agent_orchestration_engine import (
|
|
23
|
+
execute_multi_agent_research_pipeline,
|
|
24
|
+
execute_chat_based_planning_pipeline,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _emergency_cleanup():
|
|
29
|
+
"""
|
|
30
|
+
Emergency resource cleanup function
|
|
31
|
+
Called when program exits abnormally
|
|
32
|
+
"""
|
|
33
|
+
try:
|
|
34
|
+
cleanup_resources()
|
|
35
|
+
except Exception:
|
|
36
|
+
pass # Silent handling to avoid new exceptions during exit
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _signal_handler(signum, frame):
|
|
40
|
+
"""
|
|
41
|
+
Signal handler for program termination signals
|
|
42
|
+
"""
|
|
43
|
+
try:
|
|
44
|
+
cleanup_resources()
|
|
45
|
+
except Exception:
|
|
46
|
+
pass
|
|
47
|
+
finally:
|
|
48
|
+
# Restore default signal handling and resend signal
|
|
49
|
+
signal.signal(signum, signal.SIG_DFL)
|
|
50
|
+
os.kill(os.getpid(), signum)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
# Register exit cleanup function
|
|
54
|
+
atexit.register(_emergency_cleanup)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _safe_register_signal_handlers():
|
|
58
|
+
"""Safely register signal handlers"""
|
|
59
|
+
try:
|
|
60
|
+
# Check if running in main thread
|
|
61
|
+
import threading
|
|
62
|
+
|
|
63
|
+
if threading.current_thread() is not threading.main_thread():
|
|
64
|
+
return # Signal handlers can only be registered in main thread
|
|
65
|
+
|
|
66
|
+
# Try to register signal handlers
|
|
67
|
+
signal.signal(signal.SIGTERM, _signal_handler)
|
|
68
|
+
signal.signal(signal.SIGINT, _signal_handler)
|
|
69
|
+
if hasattr(signal, "SIGBREAK"): # Windows
|
|
70
|
+
signal.signal(signal.SIGBREAK, _signal_handler)
|
|
71
|
+
except (AttributeError, OSError, ValueError):
|
|
72
|
+
# Some signals are not available on certain platforms or disabled in some environments
|
|
73
|
+
# This is common in web frameworks like Streamlit
|
|
74
|
+
pass
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
# Delayed signal handler registration to avoid import-time errors
|
|
78
|
+
try:
|
|
79
|
+
_safe_register_signal_handlers()
|
|
80
|
+
except Exception:
|
|
81
|
+
# If registration fails, silently ignore and don't affect app startup
|
|
82
|
+
pass
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
async def process_input_async(
|
|
86
|
+
input_source: str,
|
|
87
|
+
input_type: str,
|
|
88
|
+
enable_indexing: bool = True,
|
|
89
|
+
progress_callback=None,
|
|
90
|
+
) -> Dict[str, Any]:
|
|
91
|
+
"""
|
|
92
|
+
Process input asynchronously
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
input_source: Input source
|
|
96
|
+
input_type: Input type
|
|
97
|
+
enable_indexing: Whether to enable indexing functionality
|
|
98
|
+
progress_callback: Progress callback function
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
Processing result
|
|
102
|
+
"""
|
|
103
|
+
try:
|
|
104
|
+
# Create and use MCP app in the same async context
|
|
105
|
+
app = MCPApp(name="paper_to_code")
|
|
106
|
+
|
|
107
|
+
async with app.run() as agent_app:
|
|
108
|
+
logger = agent_app.logger
|
|
109
|
+
context = agent_app.context
|
|
110
|
+
context.config.mcp.servers["filesystem"].args.extend([os.getcwd()])
|
|
111
|
+
|
|
112
|
+
# Initialize progress
|
|
113
|
+
if progress_callback:
|
|
114
|
+
if input_type == "chat":
|
|
115
|
+
progress_callback(
|
|
116
|
+
5, "🚀 Initializing chat-based planning pipeline..."
|
|
117
|
+
)
|
|
118
|
+
else:
|
|
119
|
+
progress_callback(5, "🚀 Initializing AI research engine...")
|
|
120
|
+
|
|
121
|
+
# Choose pipeline based on input type
|
|
122
|
+
if input_type == "chat":
|
|
123
|
+
# Use chat-based planning pipeline for user requirements
|
|
124
|
+
repo_result = await execute_chat_based_planning_pipeline(
|
|
125
|
+
input_source, # User's coding requirements
|
|
126
|
+
logger,
|
|
127
|
+
progress_callback,
|
|
128
|
+
enable_indexing=enable_indexing, # Pass indexing control parameter
|
|
129
|
+
)
|
|
130
|
+
else:
|
|
131
|
+
# Use traditional multi-agent research pipeline for files/URLs
|
|
132
|
+
repo_result = await execute_multi_agent_research_pipeline(
|
|
133
|
+
input_source,
|
|
134
|
+
logger,
|
|
135
|
+
progress_callback,
|
|
136
|
+
enable_indexing=enable_indexing, # Pass indexing control parameter
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
return {
|
|
140
|
+
"analysis_result": "Integrated into complete workflow",
|
|
141
|
+
"download_result": "Integrated into complete workflow",
|
|
142
|
+
"repo_result": repo_result,
|
|
143
|
+
"status": "success",
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
except Exception as e:
|
|
147
|
+
error_msg = str(e)
|
|
148
|
+
traceback_msg = traceback.format_exc()
|
|
149
|
+
|
|
150
|
+
return {"error": error_msg, "traceback": traceback_msg, "status": "error"}
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def run_async_task(coro):
|
|
154
|
+
"""
|
|
155
|
+
Helper function to run async tasks
|
|
156
|
+
|
|
157
|
+
Args:
|
|
158
|
+
coro: Coroutine object
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
Task result
|
|
162
|
+
"""
|
|
163
|
+
# Apply nest_asyncio to support nested event loops
|
|
164
|
+
nest_asyncio.apply()
|
|
165
|
+
|
|
166
|
+
# Save current Streamlit context
|
|
167
|
+
try:
|
|
168
|
+
from streamlit.runtime.scriptrunner import get_script_run_ctx
|
|
169
|
+
from streamlit.runtime.scriptrunner.script_run_context import (
|
|
170
|
+
SCRIPT_RUN_CONTEXT_ATTR_NAME,
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
current_ctx = get_script_run_ctx()
|
|
174
|
+
context_available = True
|
|
175
|
+
except ImportError:
|
|
176
|
+
# If Streamlit context modules can't be imported, use fallback method
|
|
177
|
+
current_ctx = None
|
|
178
|
+
context_available = False
|
|
179
|
+
|
|
180
|
+
def run_in_new_loop():
|
|
181
|
+
"""Run coroutine in new event loop"""
|
|
182
|
+
# Set Streamlit context in new thread (if available)
|
|
183
|
+
if context_available and current_ctx:
|
|
184
|
+
try:
|
|
185
|
+
import threading
|
|
186
|
+
|
|
187
|
+
setattr(
|
|
188
|
+
threading.current_thread(),
|
|
189
|
+
SCRIPT_RUN_CONTEXT_ATTR_NAME,
|
|
190
|
+
current_ctx,
|
|
191
|
+
)
|
|
192
|
+
except Exception:
|
|
193
|
+
pass # Ignore context setting errors
|
|
194
|
+
|
|
195
|
+
loop = None
|
|
196
|
+
try:
|
|
197
|
+
loop = asyncio.new_event_loop()
|
|
198
|
+
asyncio.set_event_loop(loop)
|
|
199
|
+
result = loop.run_until_complete(coro)
|
|
200
|
+
return result
|
|
201
|
+
except Exception as e:
|
|
202
|
+
raise e
|
|
203
|
+
finally:
|
|
204
|
+
# Clean up resources
|
|
205
|
+
if loop:
|
|
206
|
+
try:
|
|
207
|
+
loop.close()
|
|
208
|
+
except Exception:
|
|
209
|
+
pass
|
|
210
|
+
asyncio.set_event_loop(None)
|
|
211
|
+
|
|
212
|
+
# Clean up thread context (if available)
|
|
213
|
+
if context_available:
|
|
214
|
+
try:
|
|
215
|
+
import threading
|
|
216
|
+
|
|
217
|
+
if hasattr(
|
|
218
|
+
threading.current_thread(), SCRIPT_RUN_CONTEXT_ATTR_NAME
|
|
219
|
+
):
|
|
220
|
+
delattr(
|
|
221
|
+
threading.current_thread(), SCRIPT_RUN_CONTEXT_ATTR_NAME
|
|
222
|
+
)
|
|
223
|
+
except Exception:
|
|
224
|
+
pass # Ignore cleanup errors
|
|
225
|
+
|
|
226
|
+
# Force garbage collection
|
|
227
|
+
import gc
|
|
228
|
+
|
|
229
|
+
gc.collect()
|
|
230
|
+
|
|
231
|
+
# Use thread pool to run async task, avoiding event loop conflicts
|
|
232
|
+
executor = None
|
|
233
|
+
try:
|
|
234
|
+
executor = concurrent.futures.ThreadPoolExecutor(
|
|
235
|
+
max_workers=1, thread_name_prefix="deepcode_ctx_async"
|
|
236
|
+
)
|
|
237
|
+
future = executor.submit(run_in_new_loop)
|
|
238
|
+
result = future.result(timeout=300) # 5 minute timeout
|
|
239
|
+
return result
|
|
240
|
+
except concurrent.futures.TimeoutError:
|
|
241
|
+
st.error("Processing timeout after 5 minutes. Please try again.")
|
|
242
|
+
raise TimeoutError("Processing timeout")
|
|
243
|
+
except Exception as e:
|
|
244
|
+
# If thread pool execution fails, try direct execution
|
|
245
|
+
st.warning(f"Threaded async execution failed: {e}, trying direct execution...")
|
|
246
|
+
try:
|
|
247
|
+
# Fallback method: run directly in current thread
|
|
248
|
+
loop = None
|
|
249
|
+
try:
|
|
250
|
+
loop = asyncio.new_event_loop()
|
|
251
|
+
asyncio.set_event_loop(loop)
|
|
252
|
+
result = loop.run_until_complete(coro)
|
|
253
|
+
return result
|
|
254
|
+
finally:
|
|
255
|
+
if loop:
|
|
256
|
+
try:
|
|
257
|
+
loop.close()
|
|
258
|
+
except Exception:
|
|
259
|
+
pass
|
|
260
|
+
asyncio.set_event_loop(None)
|
|
261
|
+
import gc
|
|
262
|
+
|
|
263
|
+
gc.collect()
|
|
264
|
+
except Exception as backup_error:
|
|
265
|
+
st.error(f"All execution methods failed: {backup_error}")
|
|
266
|
+
raise backup_error
|
|
267
|
+
finally:
|
|
268
|
+
# Ensure thread pool is properly closed
|
|
269
|
+
if executor:
|
|
270
|
+
try:
|
|
271
|
+
executor.shutdown(wait=True, cancel_futures=True)
|
|
272
|
+
except Exception:
|
|
273
|
+
pass
|
|
274
|
+
# Force garbage collection
|
|
275
|
+
import gc
|
|
276
|
+
|
|
277
|
+
gc.collect()
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def run_async_task_simple(coro):
|
|
281
|
+
"""
|
|
282
|
+
Simple async task runner, avoiding threading issues
|
|
283
|
+
|
|
284
|
+
Args:
|
|
285
|
+
coro: Coroutine object
|
|
286
|
+
|
|
287
|
+
Returns:
|
|
288
|
+
Task result
|
|
289
|
+
"""
|
|
290
|
+
# Apply nest_asyncio to support nested event loops
|
|
291
|
+
nest_asyncio.apply()
|
|
292
|
+
|
|
293
|
+
try:
|
|
294
|
+
# Try to run in current event loop
|
|
295
|
+
loop = asyncio.get_event_loop()
|
|
296
|
+
if loop.is_running():
|
|
297
|
+
# If current loop is running, use improved thread pool method
|
|
298
|
+
import concurrent.futures
|
|
299
|
+
import gc
|
|
300
|
+
|
|
301
|
+
def run_in_thread():
|
|
302
|
+
# Create new event loop and set as current thread's loop
|
|
303
|
+
new_loop = asyncio.new_event_loop()
|
|
304
|
+
asyncio.set_event_loop(new_loop)
|
|
305
|
+
try:
|
|
306
|
+
result = new_loop.run_until_complete(coro)
|
|
307
|
+
return result
|
|
308
|
+
except Exception as e:
|
|
309
|
+
# Ensure exception information is properly passed
|
|
310
|
+
raise e
|
|
311
|
+
finally:
|
|
312
|
+
# Ensure loop is properly closed
|
|
313
|
+
try:
|
|
314
|
+
new_loop.close()
|
|
315
|
+
except Exception:
|
|
316
|
+
pass
|
|
317
|
+
# Clear current thread's event loop reference
|
|
318
|
+
asyncio.set_event_loop(None)
|
|
319
|
+
# Force garbage collection
|
|
320
|
+
gc.collect()
|
|
321
|
+
|
|
322
|
+
# Use context manager to ensure thread pool is properly closed
|
|
323
|
+
executor = None
|
|
324
|
+
try:
|
|
325
|
+
executor = concurrent.futures.ThreadPoolExecutor(
|
|
326
|
+
max_workers=1, thread_name_prefix="deepcode_async"
|
|
327
|
+
)
|
|
328
|
+
future = executor.submit(run_in_thread)
|
|
329
|
+
result = future.result(timeout=300) # 5 minute timeout
|
|
330
|
+
return result
|
|
331
|
+
except concurrent.futures.TimeoutError:
|
|
332
|
+
st.error(
|
|
333
|
+
"Processing timeout after 5 minutes. Please try again with a smaller file."
|
|
334
|
+
)
|
|
335
|
+
raise TimeoutError("Processing timeout")
|
|
336
|
+
except Exception as e:
|
|
337
|
+
st.error(f"Async processing error: {e}")
|
|
338
|
+
raise e
|
|
339
|
+
finally:
|
|
340
|
+
# Ensure thread pool is properly closed
|
|
341
|
+
if executor:
|
|
342
|
+
try:
|
|
343
|
+
executor.shutdown(wait=True, cancel_futures=True)
|
|
344
|
+
except Exception:
|
|
345
|
+
pass
|
|
346
|
+
# Force garbage collection
|
|
347
|
+
gc.collect()
|
|
348
|
+
else:
|
|
349
|
+
# Run directly in current loop
|
|
350
|
+
return loop.run_until_complete(coro)
|
|
351
|
+
except Exception:
|
|
352
|
+
# Final fallback method: create new event loop
|
|
353
|
+
loop = None
|
|
354
|
+
try:
|
|
355
|
+
loop = asyncio.new_event_loop()
|
|
356
|
+
asyncio.set_event_loop(loop)
|
|
357
|
+
result = loop.run_until_complete(coro)
|
|
358
|
+
return result
|
|
359
|
+
except Exception as backup_error:
|
|
360
|
+
st.error(f"All async methods failed: {backup_error}")
|
|
361
|
+
raise backup_error
|
|
362
|
+
finally:
|
|
363
|
+
if loop:
|
|
364
|
+
try:
|
|
365
|
+
loop.close()
|
|
366
|
+
except Exception:
|
|
367
|
+
pass
|
|
368
|
+
asyncio.set_event_loop(None)
|
|
369
|
+
# Force garbage collection
|
|
370
|
+
import gc
|
|
371
|
+
|
|
372
|
+
gc.collect()
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
def handle_processing_workflow(
|
|
376
|
+
input_source: str, input_type: str, enable_indexing: bool = True
|
|
377
|
+
) -> Dict[str, Any]:
|
|
378
|
+
"""
|
|
379
|
+
Main processing function for workflow
|
|
380
|
+
|
|
381
|
+
Args:
|
|
382
|
+
input_source: Input source
|
|
383
|
+
input_type: Input type
|
|
384
|
+
enable_indexing: Whether to enable indexing functionality
|
|
385
|
+
|
|
386
|
+
Returns:
|
|
387
|
+
Processing result
|
|
388
|
+
"""
|
|
389
|
+
from .components import (
|
|
390
|
+
enhanced_progress_display_component,
|
|
391
|
+
update_step_indicator,
|
|
392
|
+
display_status,
|
|
393
|
+
)
|
|
394
|
+
|
|
395
|
+
# Display enhanced progress components
|
|
396
|
+
chat_mode = input_type == "chat"
|
|
397
|
+
progress_bar, status_text, step_indicators, workflow_steps = (
|
|
398
|
+
enhanced_progress_display_component(enable_indexing, chat_mode)
|
|
399
|
+
)
|
|
400
|
+
|
|
401
|
+
# Step mapping: map progress percentages to step indices - adjust based on mode and indexing toggle
|
|
402
|
+
if chat_mode:
|
|
403
|
+
# Chat mode step mapping: Initialize -> Planning -> Setup -> Save Plan -> Implement
|
|
404
|
+
step_mapping = {
|
|
405
|
+
5: 0, # Initialize
|
|
406
|
+
30: 1, # Planning (analyzing requirements)
|
|
407
|
+
50: 2, # Setup (creating workspace)
|
|
408
|
+
70: 3, # Save Plan (saving implementation plan)
|
|
409
|
+
85: 4, # Implement (generating code)
|
|
410
|
+
100: 4, # Complete
|
|
411
|
+
}
|
|
412
|
+
elif not enable_indexing:
|
|
413
|
+
# Skip indexing-related steps progress mapping - fast mode order: Initialize -> Analyze -> Download -> Plan -> Implement
|
|
414
|
+
step_mapping = {
|
|
415
|
+
5: 0, # Initialize
|
|
416
|
+
10: 1, # Analyze
|
|
417
|
+
25: 2, # Download
|
|
418
|
+
40: 3, # Plan (now prioritized over References, 40%)
|
|
419
|
+
85: 4, # Implement (skip References, Repos and Index)
|
|
420
|
+
100: 4, # Complete
|
|
421
|
+
}
|
|
422
|
+
else:
|
|
423
|
+
# Full workflow step mapping - new order: Initialize -> Analyze -> Download -> Plan -> References -> Repos -> Index -> Implement
|
|
424
|
+
step_mapping = {
|
|
425
|
+
5: 0, # Initialize
|
|
426
|
+
10: 1, # Analyze
|
|
427
|
+
25: 2, # Download
|
|
428
|
+
40: 3, # Plan (now 4th position, 40%)
|
|
429
|
+
50: 4, # References (now 5th position, conditional, 50%)
|
|
430
|
+
60: 5, # Repos (GitHub download)
|
|
431
|
+
70: 6, # Index (code indexing)
|
|
432
|
+
85: 7, # Implement (code implementation)
|
|
433
|
+
100: 7, # Complete
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
current_step = 0
|
|
437
|
+
|
|
438
|
+
# Define enhanced progress callback function
|
|
439
|
+
def update_progress(progress: int, message: str):
|
|
440
|
+
nonlocal current_step
|
|
441
|
+
|
|
442
|
+
# Update progress bar
|
|
443
|
+
progress_bar.progress(progress)
|
|
444
|
+
status_text.markdown(f"**{message}**")
|
|
445
|
+
|
|
446
|
+
# Determine current step
|
|
447
|
+
new_step = step_mapping.get(progress, current_step)
|
|
448
|
+
if new_step != current_step:
|
|
449
|
+
current_step = new_step
|
|
450
|
+
update_step_indicator(
|
|
451
|
+
step_indicators, workflow_steps, current_step, "active"
|
|
452
|
+
)
|
|
453
|
+
|
|
454
|
+
time.sleep(0.3) # Brief pause for users to see progress changes
|
|
455
|
+
|
|
456
|
+
# Step 1: Initialization
|
|
457
|
+
if chat_mode:
|
|
458
|
+
update_progress(5, "🚀 Initializing chat-based planning engine...")
|
|
459
|
+
elif enable_indexing:
|
|
460
|
+
update_progress(5, "🚀 Initializing AI research engine and loading models...")
|
|
461
|
+
else:
|
|
462
|
+
update_progress(
|
|
463
|
+
5, "🚀 Initializing AI research engine (Fast mode - indexing disabled)..."
|
|
464
|
+
)
|
|
465
|
+
update_step_indicator(step_indicators, workflow_steps, 0, "active")
|
|
466
|
+
|
|
467
|
+
# Start async processing with progress callback
|
|
468
|
+
with st.spinner("🔄 Processing workflow stages..."):
|
|
469
|
+
try:
|
|
470
|
+
# First try using simple async processing method
|
|
471
|
+
result = run_async_task_simple(
|
|
472
|
+
process_input_async(
|
|
473
|
+
input_source, input_type, enable_indexing, update_progress
|
|
474
|
+
)
|
|
475
|
+
)
|
|
476
|
+
except Exception as e:
|
|
477
|
+
st.warning(f"Primary async method failed: {e}")
|
|
478
|
+
# Fallback method: use original thread pool method
|
|
479
|
+
try:
|
|
480
|
+
result = run_async_task(
|
|
481
|
+
process_input_async(
|
|
482
|
+
input_source, input_type, enable_indexing, update_progress
|
|
483
|
+
)
|
|
484
|
+
)
|
|
485
|
+
except Exception as backup_error:
|
|
486
|
+
st.error(f"Both async methods failed. Error: {backup_error}")
|
|
487
|
+
return {
|
|
488
|
+
"status": "error",
|
|
489
|
+
"error": str(backup_error),
|
|
490
|
+
"traceback": traceback.format_exc(),
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
# Update final status based on results
|
|
494
|
+
if result["status"] == "success":
|
|
495
|
+
# Complete all steps
|
|
496
|
+
update_progress(100, "✅ All processing stages completed successfully!")
|
|
497
|
+
update_step_indicator(
|
|
498
|
+
step_indicators, workflow_steps, len(workflow_steps), "completed"
|
|
499
|
+
)
|
|
500
|
+
|
|
501
|
+
# Display success information
|
|
502
|
+
st.balloons() # Add celebration animation
|
|
503
|
+
if chat_mode:
|
|
504
|
+
display_status(
|
|
505
|
+
"🎉 Chat workflow completed! Your requirements have been analyzed and code has been generated.",
|
|
506
|
+
"success",
|
|
507
|
+
)
|
|
508
|
+
elif enable_indexing:
|
|
509
|
+
display_status(
|
|
510
|
+
"🎉 Workflow completed! Your research paper has been successfully processed and code has been generated.",
|
|
511
|
+
"success",
|
|
512
|
+
)
|
|
513
|
+
else:
|
|
514
|
+
display_status(
|
|
515
|
+
"🎉 Fast workflow completed! Your research paper has been processed (indexing skipped for faster processing).",
|
|
516
|
+
"success",
|
|
517
|
+
)
|
|
518
|
+
|
|
519
|
+
else:
|
|
520
|
+
# Processing failed
|
|
521
|
+
update_progress(0, "❌ Processing failed - see error details below")
|
|
522
|
+
update_step_indicator(step_indicators, workflow_steps, current_step, "error")
|
|
523
|
+
display_status(
|
|
524
|
+
f"❌ Processing encountered an error: {result.get('error', 'Unknown error')}",
|
|
525
|
+
"error",
|
|
526
|
+
)
|
|
527
|
+
|
|
528
|
+
# Wait a moment for users to see completion status
|
|
529
|
+
time.sleep(2.5)
|
|
530
|
+
|
|
531
|
+
return result
|
|
532
|
+
|
|
533
|
+
|
|
534
|
+
def update_session_state_with_result(result: Dict[str, Any], input_type: str):
|
|
535
|
+
"""
|
|
536
|
+
Update session state with result
|
|
537
|
+
|
|
538
|
+
Args:
|
|
539
|
+
result: Processing result
|
|
540
|
+
input_type: Input type
|
|
541
|
+
"""
|
|
542
|
+
if result["status"] == "success":
|
|
543
|
+
# Save result to session state
|
|
544
|
+
st.session_state.last_result = result
|
|
545
|
+
st.session_state.show_results = True
|
|
546
|
+
|
|
547
|
+
# Save to history
|
|
548
|
+
st.session_state.results.append(
|
|
549
|
+
{
|
|
550
|
+
"timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
|
551
|
+
"input_type": input_type,
|
|
552
|
+
"status": "success",
|
|
553
|
+
"result": result,
|
|
554
|
+
}
|
|
555
|
+
)
|
|
556
|
+
else:
|
|
557
|
+
# Save error information to session state for display
|
|
558
|
+
st.session_state.last_error = result.get("error", "Unknown error")
|
|
559
|
+
|
|
560
|
+
# Save error to history
|
|
561
|
+
st.session_state.results.append(
|
|
562
|
+
{
|
|
563
|
+
"timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
|
564
|
+
"input_type": input_type,
|
|
565
|
+
"status": "error",
|
|
566
|
+
"error": result.get("error", "Unknown error"),
|
|
567
|
+
}
|
|
568
|
+
)
|
|
569
|
+
|
|
570
|
+
# Limit history to maximum 50 records
|
|
571
|
+
if len(st.session_state.results) > 50:
|
|
572
|
+
st.session_state.results = st.session_state.results[-50:]
|
|
573
|
+
|
|
574
|
+
|
|
575
|
+
def cleanup_temp_file(input_source: str, input_type: str):
|
|
576
|
+
"""
|
|
577
|
+
Cleanup temporary file
|
|
578
|
+
|
|
579
|
+
Args:
|
|
580
|
+
input_source: Input source
|
|
581
|
+
input_type: Input type
|
|
582
|
+
"""
|
|
583
|
+
if input_type == "file" and input_source and os.path.exists(input_source):
|
|
584
|
+
try:
|
|
585
|
+
os.unlink(input_source)
|
|
586
|
+
except Exception:
|
|
587
|
+
pass
|
|
588
|
+
|
|
589
|
+
|
|
590
|
+
def handle_start_processing_button(input_source: str, input_type: str):
|
|
591
|
+
"""
|
|
592
|
+
Handle start processing button click
|
|
593
|
+
|
|
594
|
+
Args:
|
|
595
|
+
input_source: Input source
|
|
596
|
+
input_type: Input type
|
|
597
|
+
"""
|
|
598
|
+
from .components import display_status
|
|
599
|
+
|
|
600
|
+
st.session_state.processing = True
|
|
601
|
+
|
|
602
|
+
# Get indexing toggle status
|
|
603
|
+
enable_indexing = st.session_state.get("enable_indexing", True)
|
|
604
|
+
|
|
605
|
+
try:
|
|
606
|
+
# Process workflow
|
|
607
|
+
result = handle_processing_workflow(input_source, input_type, enable_indexing)
|
|
608
|
+
|
|
609
|
+
# Display result status
|
|
610
|
+
if result["status"] == "success":
|
|
611
|
+
display_status("All operations completed successfully! 🎉", "success")
|
|
612
|
+
else:
|
|
613
|
+
display_status("Error during processing", "error")
|
|
614
|
+
|
|
615
|
+
# Update session state
|
|
616
|
+
update_session_state_with_result(result, input_type)
|
|
617
|
+
|
|
618
|
+
except Exception as e:
|
|
619
|
+
# Handle exceptional cases
|
|
620
|
+
st.error(f"Unexpected error during processing: {e}")
|
|
621
|
+
result = {"status": "error", "error": str(e)}
|
|
622
|
+
update_session_state_with_result(result, input_type)
|
|
623
|
+
|
|
624
|
+
finally:
|
|
625
|
+
# Reset state and clean up resources after processing
|
|
626
|
+
st.session_state.processing = False
|
|
627
|
+
|
|
628
|
+
# Clean up temporary files
|
|
629
|
+
cleanup_temp_file(input_source, input_type)
|
|
630
|
+
|
|
631
|
+
# Clean up system resources
|
|
632
|
+
cleanup_resources()
|
|
633
|
+
|
|
634
|
+
# Rerun to display results or errors
|
|
635
|
+
st.rerun()
|
|
636
|
+
|
|
637
|
+
|
|
638
|
+
def handle_error_display():
|
|
639
|
+
"""Handle error display"""
|
|
640
|
+
if hasattr(st.session_state, "last_error") and st.session_state.last_error:
|
|
641
|
+
st.error(f"❌ Error: {st.session_state.last_error}")
|
|
642
|
+
if st.button("🔄 Try Again", type="secondary", use_container_width=True):
|
|
643
|
+
st.session_state.last_error = None
|
|
644
|
+
st.session_state.task_counter += 1
|
|
645
|
+
st.rerun()
|
|
646
|
+
|
|
647
|
+
|
|
648
|
+
def initialize_session_state():
|
|
649
|
+
"""Initialize session state"""
|
|
650
|
+
if "processing" not in st.session_state:
|
|
651
|
+
st.session_state.processing = False
|
|
652
|
+
if "results" not in st.session_state:
|
|
653
|
+
st.session_state.results = []
|
|
654
|
+
if "current_step" not in st.session_state:
|
|
655
|
+
st.session_state.current_step = 0
|
|
656
|
+
if "task_counter" not in st.session_state:
|
|
657
|
+
st.session_state.task_counter = 0
|
|
658
|
+
if "show_results" not in st.session_state:
|
|
659
|
+
st.session_state.show_results = False
|
|
660
|
+
if "last_result" not in st.session_state:
|
|
661
|
+
st.session_state.last_result = None
|
|
662
|
+
if "last_error" not in st.session_state:
|
|
663
|
+
st.session_state.last_error = None
|
|
664
|
+
if "enable_indexing" not in st.session_state:
|
|
665
|
+
st.session_state.enable_indexing = (
|
|
666
|
+
False # Default enable indexing functionality
|
|
667
|
+
)
|
|
668
|
+
|
|
669
|
+
|
|
670
|
+
def cleanup_resources():
|
|
671
|
+
"""
|
|
672
|
+
Clean up system resources to prevent memory leaks
|
|
673
|
+
"""
|
|
674
|
+
try:
|
|
675
|
+
import gc
|
|
676
|
+
import threading
|
|
677
|
+
import multiprocessing
|
|
678
|
+
import asyncio
|
|
679
|
+
import sys
|
|
680
|
+
|
|
681
|
+
# 1. Clean up asyncio-related resources
|
|
682
|
+
try:
|
|
683
|
+
# Get current event loop (if exists)
|
|
684
|
+
try:
|
|
685
|
+
loop = asyncio.get_running_loop()
|
|
686
|
+
# Cancel all pending tasks
|
|
687
|
+
if loop and not loop.is_closed():
|
|
688
|
+
pending_tasks = [
|
|
689
|
+
task for task in asyncio.all_tasks(loop) if not task.done()
|
|
690
|
+
]
|
|
691
|
+
if pending_tasks:
|
|
692
|
+
for task in pending_tasks:
|
|
693
|
+
if not task.cancelled():
|
|
694
|
+
task.cancel()
|
|
695
|
+
# Wait for task cancellation to complete
|
|
696
|
+
try:
|
|
697
|
+
if pending_tasks:
|
|
698
|
+
# Use timeout to avoid blocking too long
|
|
699
|
+
import time
|
|
700
|
+
|
|
701
|
+
time.sleep(0.1)
|
|
702
|
+
except Exception:
|
|
703
|
+
pass
|
|
704
|
+
except RuntimeError:
|
|
705
|
+
# No running event loop, continue with other cleanup
|
|
706
|
+
pass
|
|
707
|
+
except Exception:
|
|
708
|
+
pass
|
|
709
|
+
|
|
710
|
+
# 2. Force garbage collection
|
|
711
|
+
gc.collect()
|
|
712
|
+
|
|
713
|
+
# 3. Clean up active threads (except main thread)
|
|
714
|
+
active_threads = threading.active_count()
|
|
715
|
+
if active_threads > 1:
|
|
716
|
+
# Wait some time for threads to naturally finish
|
|
717
|
+
import time
|
|
718
|
+
|
|
719
|
+
time.sleep(0.5)
|
|
720
|
+
|
|
721
|
+
# 4. Clean up multiprocessing resources
|
|
722
|
+
try:
|
|
723
|
+
# Clean up possible multiprocessing resources
|
|
724
|
+
if hasattr(multiprocessing, "active_children"):
|
|
725
|
+
for child in multiprocessing.active_children():
|
|
726
|
+
if child.is_alive():
|
|
727
|
+
child.terminate()
|
|
728
|
+
child.join(timeout=1.0)
|
|
729
|
+
# If join times out, force kill
|
|
730
|
+
if child.is_alive():
|
|
731
|
+
try:
|
|
732
|
+
child.kill()
|
|
733
|
+
child.join(timeout=0.5)
|
|
734
|
+
except Exception:
|
|
735
|
+
pass
|
|
736
|
+
|
|
737
|
+
# Clean up multiprocessing-related resource tracker
|
|
738
|
+
try:
|
|
739
|
+
import multiprocessing.resource_tracker
|
|
740
|
+
|
|
741
|
+
if hasattr(multiprocessing.resource_tracker, "_resource_tracker"):
|
|
742
|
+
tracker = multiprocessing.resource_tracker._resource_tracker
|
|
743
|
+
if tracker and hasattr(tracker, "_stop"):
|
|
744
|
+
tracker._stop()
|
|
745
|
+
except Exception:
|
|
746
|
+
pass
|
|
747
|
+
|
|
748
|
+
except Exception:
|
|
749
|
+
pass
|
|
750
|
+
|
|
751
|
+
# 5. Force clean up Python internal caches
|
|
752
|
+
try:
|
|
753
|
+
# Clean up some temporary objects in module cache
|
|
754
|
+
import sys
|
|
755
|
+
|
|
756
|
+
# Don't delete key modules, only clean up possible temporary resources
|
|
757
|
+
if hasattr(sys, "_clear_type_cache"):
|
|
758
|
+
sys._clear_type_cache()
|
|
759
|
+
except Exception:
|
|
760
|
+
pass
|
|
761
|
+
|
|
762
|
+
# 6. Final garbage collection
|
|
763
|
+
gc.collect()
|
|
764
|
+
|
|
765
|
+
except Exception as e:
|
|
766
|
+
# Silently handle cleanup errors to avoid affecting main flow
|
|
767
|
+
# But can log errors in debug mode
|
|
768
|
+
try:
|
|
769
|
+
import logging
|
|
770
|
+
|
|
771
|
+
logging.getLogger(__name__).debug(f"Resource cleanup warning: {e}")
|
|
772
|
+
except Exception:
|
|
773
|
+
pass
|