Skip to content

Commit b5c741a

Browse files
authored
Merge pull request #50 from Jany-M/main
Bug Fixes and Workflow Improvements
2 parents da91485 + f9f0154 commit b5c741a

11 files changed

Lines changed: 1101 additions & 110 deletions

CHANGELOG.md

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# Changelog
2+
3+
All notable changes to DeepCode will be documented in this file.
4+
5+
## [1.0.6-jm] - 2025-10-19
6+
7+
### Added
8+
- **Dynamic Model Limit Detection**: New `utils/model_limits.py` module that automatically detects and adapts to any LLM model's token limits and pricing
9+
- **Loop Detection System**: `utils/loop_detector.py` prevents infinite loops by detecting repeated tool calls, timeouts, and progress stalls
10+
- **Progress Tracking**: 8-phase progress tracking (5% → 100%) with file-level progress indicators in both UI and terminal
11+
- **Abort Mechanism**: "Stop Processing" button in UI with global abort flag for clean process termination
12+
- **Cache Cleanup Scripts**: `start_clean.bat` and `start_clean.ps1` to clear Python cache before starting
13+
- **Enhanced Error Display**: Real-time error messages in both UI and terminal with timestamps
14+
- **File Progress Tracking**: Shows files completed/total with estimated time remaining
15+
16+
### Fixed
17+
- **Critical: False Error Detection**: Fixed overly aggressive error detection that was marking successful operations as failures, causing premature abort and empty file generation
18+
- **Critical: Empty File Generation**: Files now contain actual code instead of being empty (2-byte files)
19+
- **Unique Folder Naming**: Each project run now creates `paper_{timestamp}` folders instead of reusing `pdf_output`
20+
- **PDF Save Location**: PDFs now save to `deepcode_lab/papers/` instead of system temp directory
21+
- **Duplicate Folder Prevention**: Added session state caching to prevent duplicate folder creation on UI reruns
22+
- **Token Limit Compliance**: Fixed `max_tokens` to respect model limits dynamically (e.g., gpt-4o-mini's 16,384 token limit)
23+
- **Empty Plan Detection**: System now fails early with clear error messages when initial plan is empty or invalid
24+
- **Process Hanging**: Fixed infinite loops and hanging on errors - process now exits cleanly
25+
- **Token Cost Tracking**: Restored accurate token usage and cost display (was showing $0.0000)
26+
- **PDF to Markdown Conversion**: Fixed automatic conversion and file location handling
27+
- **Document Segmentation**: Properly uses configured 50K character threshold from `mcp_agent.config.yaml`
28+
- **Error Propagation**: Abort mechanism now properly stops process after 10 consecutive real errors
29+
30+
### Changed
31+
- **Model-Aware Token Management**: Token limits now adapt automatically based on configured model instead of hardcoded values
32+
- **Cost Calculation**: Dynamic pricing based on actual model rates (OpenAI, Anthropic)
33+
- **Retry Logic**: Token limits for retries now respect model maximum (87.5% → 95% → 98% of max)
34+
- **Segmentation Workflow**: Better integration with code implementation phase
35+
- **Error Handling**: Enhanced error propagation - errors no longer reported as "success"
36+
- **UI Display**: Shows project folder name after PDF conversion for better visibility
37+
- **Terminal Logging**: Added timestamps to all progress messages
38+
39+
### Technical Improvements
40+
- Added document-segmentation server to code implementation workflow for better token management
41+
- Improved error handling in agent orchestration engine with proper cleanup
42+
- Enhanced subprocess handling on Windows (hide console windows, prevent hanging)
43+
- Better LibreOffice detection on Windows using direct path checking
44+
- Fixed input data format consistency (JSON with `paper_path` key)
45+
- Added comprehensive logging throughout the pipeline
46+
- Improved resource cleanup on errors and process termination
47+
48+
### Documentation
49+
- Translated Chinese comments to English in core workflow files
50+
- Added inline documentation for new utility modules
51+
- Created startup scripts with clear usage instructions
52+
53+
### Breaking Changes
54+
- None - all changes are backward compatible
55+
56+
### Known Issues
57+
- Terminal may show trailing "Calling Tool..." line after completion (cosmetic display artifact - process completes successfully)
58+
- Some Chinese comments remain in non-critical files (cli, tools) - translation in progress
59+
- tiktoken package optional warning (doesn't affect functionality)
60+
61+
### Success Metrics
62+
- ✅ Complete end-to-end workflow: DOCX upload → PDF conversion → Markdown → Segmentation → Planning → Code generation
63+
- ✅ Files generated with actual code content (15+ files with proper implementation)
64+
- ✅ Single folder per project run (no duplicates)
65+
- ✅ Dynamic token management working across different models
66+
- ✅ Accurate cost tracking per model
67+
- ✅ Clean process termination with proper error handling
68+
69+
---
70+
71+
## [1.0.5] - Previous Release
72+
73+
See previous releases for earlier changes.
74+

__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
__version__ = "1.2.0"
99
__author__ = "DeepCode Team"
1010
__url__ = "https://github.com/HKUDS/DeepCode"
11+
__repo__ = "https://github.com/Jany-M/DeepCode/"
1112

1213
# Import main components for easy access
1314
from utils import FileProcessor, DialogueLogger

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ fastapi>=0.104.0
1010
google-genai
1111
mcp-agent
1212
mcp-server-git
13+
openapi
1314
nest_asyncio
1415
openai
1516
pathlib2

tools/pdf_converter.py

Lines changed: 136 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,9 @@
1818
import tempfile
1919
import shutil
2020
import platform
21+
import os
2122
from pathlib import Path
22-
from typing import Union, Optional, Dict, Any
23+
from typing import Union, Optional, Dict, Any, List
2324

2425

2526
class PDFConverter:
@@ -40,6 +41,39 @@ def __init__(self) -> None:
4041
"""Initialize the PDF converter."""
4142
pass
4243

44+
@staticmethod
45+
def find_libreoffice_windows() -> Optional[str]:
46+
"""
47+
Find LibreOffice installation on Windows.
48+
49+
Returns:
50+
Path to soffice.exe if found, None otherwise
51+
"""
52+
if platform.system() != "Windows":
53+
return None
54+
55+
# Common LibreOffice installation paths on Windows
56+
possible_paths = [
57+
r"C:\Program Files\LibreOffice\program\soffice.exe",
58+
r"C:\Program Files (x86)\LibreOffice\program\soffice.exe",
59+
]
60+
61+
# Also check PROGRAMFILES environment variables
62+
program_files = os.environ.get("PROGRAMFILES")
63+
program_files_x86 = os.environ.get("PROGRAMFILES(X86)")
64+
65+
if program_files:
66+
possible_paths.append(os.path.join(program_files, "LibreOffice", "program", "soffice.exe"))
67+
if program_files_x86:
68+
possible_paths.append(os.path.join(program_files_x86, "LibreOffice", "program", "soffice.exe"))
69+
70+
# Check each path
71+
for path in possible_paths:
72+
if os.path.exists(path):
73+
return path
74+
75+
return None
76+
4377
@staticmethod
4478
def convert_office_to_pdf(
4579
doc_path: Union[str, Path], output_dir: Optional[str] = None
@@ -67,7 +101,15 @@ def convert_office_to_pdf(
67101
if output_dir:
68102
base_output_dir = Path(output_dir)
69103
else:
70-
base_output_dir = doc_path.parent / "pdf_output"
104+
# Generate unique folder name with timestamp to avoid conflicts
105+
import time
106+
timestamp = int(time.time())
107+
folder_name = f"paper_{timestamp}"
108+
109+
# Save to workspace instead of temp directory
110+
workspace_base = Path(os.getcwd()) / "deepcode_lab" / "papers"
111+
workspace_base.mkdir(parents=True, exist_ok=True)
112+
base_output_dir = workspace_base / folder_name
71113

72114
base_output_dir.mkdir(parents=True, exist_ok=True)
73115

@@ -86,26 +128,41 @@ def convert_office_to_pdf(
86128

87129
# Hide console window on Windows
88130
if platform.system() == "Windows":
89-
subprocess_kwargs["creationflags"] = (
90-
0x08000000 # subprocess.CREATE_NO_WINDOW
91-
)
92-
93-
try:
94-
result = subprocess.run(
95-
["libreoffice", "--version"], **subprocess_kwargs
96-
)
97-
libreoffice_available = True
98-
working_libreoffice_cmd = "libreoffice"
99-
logging.info(f"LibreOffice detected: {result.stdout.strip()}") # type: ignore
100-
except (
101-
subprocess.CalledProcessError,
102-
FileNotFoundError,
103-
subprocess.TimeoutExpired,
104-
):
105-
pass
106-
107-
# Try alternative commands for LibreOffice
108-
if not libreoffice_available:
131+
# Use CREATE_NO_WINDOW to prevent console window from appearing
132+
subprocess_kwargs["creationflags"] = 0x08000000
133+
# Also configure startupinfo to hide window
134+
startupinfo = subprocess.STARTUPINFO()
135+
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
136+
startupinfo.wShowWindow = subprocess.SW_HIDE
137+
subprocess_kwargs["startupinfo"] = startupinfo
138+
139+
# On Windows, try to find LibreOffice in standard installation paths first
140+
# Don't run --version check on Windows as it can cause window/hanging issues
141+
if platform.system() == "Windows":
142+
windows_path = PDFConverter.find_libreoffice_windows()
143+
if windows_path:
144+
libreoffice_available = True
145+
working_libreoffice_cmd = windows_path
146+
logging.info(f"LibreOffice detected at {windows_path}")
147+
148+
# On non-Windows systems, try standard commands
149+
if not libreoffice_available and platform.system() != "Windows":
150+
try:
151+
result = subprocess.run(
152+
["libreoffice", "--version"], **subprocess_kwargs
153+
)
154+
libreoffice_available = True
155+
working_libreoffice_cmd = "libreoffice"
156+
logging.info(f"LibreOffice detected: {result.stdout.strip()}") # type: ignore
157+
except (
158+
subprocess.CalledProcessError,
159+
FileNotFoundError,
160+
subprocess.TimeoutExpired,
161+
):
162+
pass
163+
164+
# Try alternative commands for LibreOffice (non-Windows)
165+
if not libreoffice_available and platform.system() != "Windows":
109166
for cmd in ["soffice", "libreoffice"]:
110167
try:
111168
result = subprocess.run([cmd, "--version"], **subprocess_kwargs)
@@ -142,7 +199,13 @@ def convert_office_to_pdf(
142199

143200
# Use the working LibreOffice command first, then try alternatives if it fails
144201
commands_to_try = [working_libreoffice_cmd]
145-
if working_libreoffice_cmd == "libreoffice":
202+
203+
# Add alternative commands based on what was found
204+
if platform.system() == "Windows" and working_libreoffice_cmd:
205+
# If we're using the full Windows path, also try standard commands
206+
if "Program Files" in working_libreoffice_cmd:
207+
commands_to_try.extend(["soffice", "libreoffice"])
208+
elif working_libreoffice_cmd == "libreoffice":
146209
commands_to_try.append("soffice")
147210
else:
148211
commands_to_try.append("libreoffice")
@@ -173,9 +236,12 @@ def convert_office_to_pdf(
173236

174237
# Hide console window on Windows
175238
if platform.system() == "Windows":
176-
convert_subprocess_kwargs["creationflags"] = (
177-
0x08000000 # subprocess.CREATE_NO_WINDOW
178-
)
239+
convert_subprocess_kwargs["creationflags"] = 0x08000000
240+
# Also configure startupinfo to hide window
241+
startupinfo = subprocess.STARTUPINFO()
242+
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
243+
startupinfo.wShowWindow = subprocess.SW_HIDE
244+
convert_subprocess_kwargs["startupinfo"] = startupinfo
179245

180246
result = subprocess.run(
181247
convert_cmd, **convert_subprocess_kwargs
@@ -227,6 +293,10 @@ def convert_office_to_pdf(
227293
# Copy PDF to final output directory
228294
final_pdf_path = base_output_dir / f"{name_without_suff}.pdf"
229295
shutil.copy2(pdf_path, final_pdf_path)
296+
297+
print(f"✅ PDF saved to: {final_pdf_path}")
298+
print(f" File size: {final_pdf_path.stat().st_size} bytes")
299+
print(f" Parent folder: {base_output_dir}")
230300

231301
return final_pdf_path
232302

@@ -281,7 +351,15 @@ def convert_text_to_pdf(
281351
if output_dir:
282352
base_output_dir = Path(output_dir)
283353
else:
284-
base_output_dir = text_path.parent / "pdf_output"
354+
# Generate unique folder name with timestamp to avoid conflicts
355+
import time
356+
timestamp = int(time.time())
357+
folder_name = f"paper_{timestamp}"
358+
359+
# Save to workspace instead of temp directory
360+
workspace_base = Path(os.getcwd()) / "deepcode_lab" / "papers"
361+
workspace_base.mkdir(parents=True, exist_ok=True)
362+
base_output_dir = workspace_base / folder_name
285363

286364
base_output_dir.mkdir(parents=True, exist_ok=True)
287365
pdf_path = base_output_dir / f"{text_path.stem}.pdf"
@@ -435,6 +513,10 @@ def convert_text_to_pdf(
435513
f"PDF conversion failed for {text_path.name} - generated PDF is empty or corrupted."
436514
)
437515

516+
print(f"✅ PDF saved to: {pdf_path}")
517+
print(f" File size: {pdf_path.stat().st_size} bytes")
518+
print(f" Parent folder: {base_output_dir}")
519+
438520
return pdf_path
439521

440522
except Exception as e:
@@ -532,27 +614,34 @@ def check_dependencies(self) -> dict:
532614
}
533615

534616
# Check LibreOffice
535-
try:
536-
subprocess_kwargs: Dict[str, Any] = {
537-
"capture_output": True,
538-
"text": True,
539-
"check": True,
540-
"encoding": "utf-8",
541-
"errors": "ignore",
542-
}
543-
544-
if platform.system() == "Windows":
545-
subprocess_kwargs["creationflags"] = (
546-
0x08000000 # subprocess.CREATE_NO_WINDOW
547-
)
548-
549-
subprocess.run(["libreoffice", "--version"], **subprocess_kwargs)
550-
results["libreoffice"] = True
551-
except (subprocess.CalledProcessError, FileNotFoundError):
552-
try:
553-
subprocess.run(["soffice", "--version"], **subprocess_kwargs)
617+
# On Windows, just check if the executable exists (don't run it to avoid window issues)
618+
if platform.system() == "Windows":
619+
windows_path = PDFConverter.find_libreoffice_windows()
620+
if windows_path:
554621
results["libreoffice"] = True
555-
except (subprocess.CalledProcessError, FileNotFoundError):
622+
else:
623+
# On non-Windows systems, try running the version command
624+
try:
625+
subprocess_kwargs: Dict[str, Any] = {
626+
"capture_output": True,
627+
"text": True,
628+
"check": True,
629+
"timeout": 5,
630+
"encoding": "utf-8",
631+
"errors": "ignore",
632+
}
633+
634+
try:
635+
subprocess.run(["libreoffice", "--version"], **subprocess_kwargs)
636+
results["libreoffice"] = True
637+
except (subprocess.CalledProcessError, FileNotFoundError, subprocess.TimeoutExpired):
638+
try:
639+
subprocess.run(["soffice", "--version"], **subprocess_kwargs)
640+
results["libreoffice"] = True
641+
except (subprocess.CalledProcessError, FileNotFoundError, subprocess.TimeoutExpired):
642+
pass
643+
except Exception:
644+
# If any unexpected error occurs during LibreOffice check, silently pass
556645
pass
557646

558647
# Check ReportLab

0 commit comments

Comments
 (0)