Skip to content

Commit 541ef17

Browse files
committed
Add .gitignore and enhance response parsing logic
- Introduced .gitignore to exclude environment and data files. - Improved response parsing in ResponseParser to handle various file formats and ensure essential files are created if missing. - Added error handling for vector store initialization in main.py. - Updated JSON responses for better clarity on build success and failure. Signed-off-by: Acuspeedster <[email protected]>
1 parent c8f8b92 commit 541ef17

File tree

3 files changed

+182
-83
lines changed

3 files changed

+182
-83
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
.env
2+
qdrant_data

app/main.py

Lines changed: 73 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -38,18 +38,25 @@
3838
compiler = RustCompiler()
3939

4040
# Initialize vector store
41-
vector_store = QdrantStore(embedding_size=llm_embed_size)
42-
vector_store.create_collection("project_examples")
43-
vector_store.create_collection("error_examples")
44-
45-
# After initializing vector store
46-
from app.load_data import load_project_examples, load_error_examples
47-
48-
# Check if collections are empty and load data if needed
49-
if vector_store.count("project_examples") == 0:
50-
load_project_examples()
51-
if vector_store.count("error_examples") == 0:
52-
load_error_examples()
41+
try:
42+
vector_store = QdrantStore(embedding_size=llm_embed_size)
43+
if os.getenv("SKIP_VECTOR_SEARCH", "").lower() != "true":
44+
vector_store.create_collection("project_examples")
45+
vector_store.create_collection("error_examples")
46+
47+
# After initializing vector store
48+
from app.load_data import load_project_examples, load_error_examples
49+
50+
# Check if collections are empty and load data if needed
51+
if vector_store.count("project_examples") == 0:
52+
load_project_examples()
53+
if vector_store.count("error_examples") == 0:
54+
load_error_examples()
55+
except Exception as e:
56+
print(f"Warning: Vector store initialization failed: {e}")
57+
print("Continuing without vector store functionality...")
58+
# Create a dummy vector store
59+
vector_store = None
5360

5461
# Project generation request
5562
class ProjectRequest(BaseModel):
@@ -161,10 +168,6 @@ async def compile_and_fix_rust(request: dict):
161168

162169
# Pre-process code to fix common syntax errors
163170
code = request["code"]
164-
# Fix missing parenthesis in println! macro
165-
# if "println!(" in code and ");" not in code:
166-
# code = code.replace("println!(\"", "println!(\"")
167-
# code = code.replace("\" //", "\"); //")
168171

169172
# Create temp directory
170173
with tempfile.TemporaryDirectory() as temp_dir:
@@ -201,8 +204,17 @@ async def compile_and_fix_rust(request: dict):
201204
for filename, content in current_files.items():
202205
output_text += f"[filename: {filename}]\n{content}\n\n"
203206

204-
# For successful fixes, return a text response with the combined code
205-
return PlainTextResponse(content=output_text.strip())
207+
# Return JSON response instead of plain text
208+
return JSONResponse(content={
209+
"status": "success",
210+
"message": "Code fixed and compiled successfully",
211+
"attempts": attempts,
212+
"combined_text": output_text.strip(),
213+
"files": current_files,
214+
"build_output": output or "Build successful",
215+
"run_output": run_output if run_success else None,
216+
"build_success": True
217+
})
206218

207219
# If we've reached max attempts without success, stop
208220
if attempt == max_attempts - 1:
@@ -211,14 +223,15 @@ async def compile_and_fix_rust(request: dict):
211223
# Extract error context for LLM
212224
error_context = compiler.extract_error_context(output)
213225

214-
# Find similar errors in vector DB (commented out for now)
226+
# Find similar errors in vector DB
215227
similar_errors = []
216-
try:
217-
# Find similar errors in vector DB
218-
error_embedding = llm_client.get_embeddings([error_context["full_error"]])[0]
219-
similar_errors = vector_store.search("error_examples", error_embedding, limit=3)
220-
except Exception as e:
221-
print(f"Vector search error (non-critical): {e}")
228+
if vector_store is not None and os.getenv("SKIP_VECTOR_SEARCH", "").lower() != "true":
229+
try:
230+
# Find similar errors in vector DB
231+
error_embedding = llm_client.get_embeddings([error_context["full_error"]])[0]
232+
similar_errors = vector_store.search("error_examples", error_embedding, limit=3)
233+
except Exception as e:
234+
print(f"Vector search error (non-critical): {e}")
222235

223236
# Generate fix prompt
224237
fix_examples = ""
@@ -257,15 +270,26 @@ async def compile_and_fix_rust(request: dict):
257270
for filename, content in current_files.items():
258271
output_text += f"[filename: {filename}]\n{content}\n\n"
259272

273+
# Add explanation for build failure
274+
output_text += "\n# Build failed\n"
275+
output_text += f"\n# Note: The build failed after {max_attempts} fix attempts. Common reasons include:\n"
276+
output_text += "# - Complex syntax errors that are difficult to fix automatically\n"
277+
output_text += "# - Dependencies that cannot be resolved\n"
278+
output_text += "# - Logical errors in the code structure\n"
279+
if len(attempts) > 0:
280+
output_text += f"# The final error was: {attempts[-1]['output'].splitlines()[0] if attempts[-1]['output'] else 'Unknown error'}\n"
281+
260282
# If we've exhausted all attempts, return error
261283
return JSONResponse(content={
262-
"status": "error",
263-
"message": f"Failed to fix code: {attempts[-1]['output']}",
284+
"status": "failed",
285+
"message": f"Failed to fix code after {max_attempts} attempts",
264286
"attempts": attempts,
265287
"combined_text": output_text.strip(),
266-
"final_files": current_files
288+
"files": current_files,
289+
"build_output": attempts[-1]['output'] if attempts else "No compilation attempts were made",
290+
"build_success": False
267291
})
268-
292+
269293
async def handle_project_generation(
270294
project_id: str,
271295
project_dir: str,
@@ -391,7 +415,7 @@ async def handle_project_generation(
391415
fix_examples = "Here are some examples of similar errors and their fixes:\n\n"
392416
for i, err in enumerate(similar_errors):
393417
fix_examples += f"Example {i+1}:\n{err['error']}\nFix: {err['solution']}\n\n"
394-
418+
395419
fix_prompt = f"""
396420
Here is a Rust project that failed to compile. Help me fix the compilation errors.
397421
@@ -592,7 +616,6 @@ async def generate_project_sync(request: ProjectRequest):
592616
})
593617

594618
# DON'T save status here - remove this line
595-
# save_status(temp_dir, status)
596619

597620
# Extract error context
598621
error_context = compiler.extract_error_context(output)
@@ -654,32 +677,26 @@ async def generate_project_sync(request: ProjectRequest):
654677
except Exception as e:
655678
print(f"Error reading file {f}: {e}")
656679

657-
if success:
658-
# Project compiled successfully
659-
status.update({
660-
"status": "completed",
661-
"message": "Project generated successfully",
662-
"build_output": output
663-
})
664-
665-
# Add build status
666-
all_files_content += "\n# Build succeeded\n"
667-
else:
668-
# Build failed
669-
status.update({
670-
"status": "failed",
671-
"message": "Failed to generate working project",
672-
"build_output": output
673-
})
674-
675-
# Add build status
676-
all_files_content += "\n# Build failed\n"
677-
678-
# DON'T save status here - remove this line
679-
# save_status(temp_dir, status)
680+
# Add build status to the combined text
681+
all_files_content += "\n# Build " + ("succeeded" if success else "failed") + "\n"
680682

681-
# Return the response while still inside the with block
682-
return PlainTextResponse(content=all_files_content)
683+
# Add explanation when build fails
684+
if not success:
685+
all_files_content += f"\n# Note: The build failed because of errors in the generated code. Common reasons include:\n"
686+
all_files_content += "# - Incorrect or non-existent crate versions specified in Cargo.toml\n"
687+
all_files_content += "# - Improper API usage in the generated code\n"
688+
all_files_content += "# - Missing or incompatible dependencies\n"
689+
all_files_content += f"# The specific error was: {output.splitlines()[0] if output else 'Unknown error'}\n"
690+
691+
# Return JSON response instead of plain text
692+
return JSONResponse(content={
693+
"status": "success" if success else "failed",
694+
"message": "Project generated successfully" if success else "Failed to generate working project",
695+
"combined_text": all_files_content.strip(),
696+
"files": {f: open(os.path.join(temp_dir, f), 'r').read() for f in file_paths if os.path.exists(os.path.join(temp_dir, f))},
697+
"build_output": output,
698+
"build_success": success
699+
})
683700

684701
except Exception as e:
685702
raise HTTPException(status_code=500, detail=f"Error generating project: {str(e)}")

app/response_parser.py

Lines changed: 107 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -12,40 +12,120 @@ def parse_response(self, response: str) -> Dict[str, str]:
1212
"""Parse response into a dictionary of files"""
1313
files = {}
1414

15-
# Pattern to match both [filename: path] and filename: path formats
16-
file_matches = re.findall(r'\[filename: (.+?)\]|\*\*(.+?):\*\*|```(.+?)\s', response, re.DOTALL)
17-
content_blocks = re.split(r'\[filename: .+?\]|\*\*(.+?):\*\*|```(.+?)\s', response)
15+
# First, try to extract using explicit filename markers
16+
file_blocks = re.findall(r'\[filename:\s*(.*?)\](.*?)(?=\[filename:|$)', response, re.DOTALL)
17+
if file_blocks:
18+
for filename, content in file_blocks:
19+
# Clean the filename and content
20+
clean_filename = filename.strip()
21+
# Remove leading/trailing backticks and language identifiers from content
22+
clean_content = self._clean_code_block(content)
23+
24+
if clean_filename and clean_content:
25+
files[clean_filename] = clean_content
1826

19-
# Clean up content blocks
20-
cleaned_blocks = []
21-
for block in content_blocks:
22-
if block and block.strip():
23-
# Find code block content
24-
code_match = re.search(r'```(?:\w+)?\s*(.*?)```', block, re.DOTALL)
25-
if code_match:
26-
cleaned_blocks.append(code_match.group(1).strip())
27-
else:
28-
cleaned_blocks.append(block.strip())
29-
30-
# Match filenames with content
31-
for i, match in enumerate(file_matches):
32-
if i < len(cleaned_blocks):
33-
# Find first non-empty group in the match
34-
filename = next((name for name in match if name), "").strip()
35-
if filename and not filename.startswith("```"):
36-
files[filename] = cleaned_blocks[i]
37-
38-
# If nothing was parsed but there are code blocks, try simpler parsing
27+
# If no files found with explicit markers, try to identify standard Rust project files
3928
if not files:
29+
# Look for code blocks and try to identify their file type by content
4030
code_blocks = re.findall(r'```(?:\w+)?\s*(.*?)```', response, re.DOTALL)
41-
file_headers = re.findall(r'(?:^|\n)#+\s*(.+\.rs|Cargo\.toml|README\.md)', response)
4231

43-
if len(code_blocks) == len(file_headers):
44-
for i, header in enumerate(file_headers):
45-
files[header.strip()] = code_blocks[i].strip()
32+
cargo_toml = None
33+
main_rs = None
34+
readme_md = None
35+
36+
for block in code_blocks:
37+
clean_block = block.strip()
38+
if "[package]" in clean_block and "name =" in clean_block and "version =" in clean_block:
39+
cargo_toml = clean_block
40+
elif "fn main()" in clean_block:
41+
main_rs = clean_block
42+
elif clean_block.startswith("# ") or "# " in clean_block[:20]:
43+
readme_md = clean_block
44+
45+
if cargo_toml:
46+
files["Cargo.toml"] = cargo_toml
47+
if main_rs:
48+
files["src/main.rs"] = main_rs
49+
if readme_md:
50+
files["README.md"] = readme_md
51+
52+
# If still no files found, use a more aggressive approach to extract content
53+
if not files:
54+
# Last resort: extract based on common patterns in the response
55+
if "Cargo.toml" in response:
56+
cargo_section = self._extract_section(response, "Cargo.toml")
57+
if cargo_section:
58+
files["Cargo.toml"] = cargo_section
59+
60+
if "main.rs" in response:
61+
main_section = self._extract_section(response, "main.rs")
62+
if main_section:
63+
files["src/main.rs"] = main_section
64+
65+
if "README" in response:
66+
readme_section = self._extract_section(response, "README")
67+
if readme_section:
68+
files["README.md"] = readme_section
69+
70+
# Ensure we don't have language identifiers as filenames
71+
common_lang_identifiers = ["toml", "rust", "markdown", "bash"]
72+
for lang in common_lang_identifiers:
73+
if lang in files and len(files[lang]) < 100: # Only remove if it's short (likely a lang identifier)
74+
del files[lang]
75+
76+
# Ensure we have the essential files with proper content
77+
if not files.get("Cargo.toml") or not files.get("src/main.rs"):
78+
# Create fallback files if missing
79+
if not files.get("Cargo.toml"):
80+
files["Cargo.toml"] = """[package]
81+
name = "rust_project"
82+
version = "0.1.0"
83+
edition = "2021"
84+
85+
[dependencies]
86+
"""
87+
88+
if not files.get("src/main.rs"):
89+
files["src/main.rs"] = """fn main() {
90+
println!("Hello, world!");
91+
}
92+
"""
4693

4794
return files
4895

96+
def _clean_code_block(self, text: str) -> str:
97+
"""Clean code blocks by removing backticks and language identifiers"""
98+
# Remove leading/trailing whitespace
99+
text = text.strip()
100+
101+
# Remove triple backticks and language identifier at start
102+
text = re.sub(r'^```\w*\s*', '', text)
103+
104+
# Remove trailing triple backticks
105+
text = re.sub(r'\s*```$', '', text)
106+
107+
return text
108+
109+
def _extract_section(self, response: str, identifier: str) -> str:
110+
"""Extract a section from the response based on an identifier"""
111+
parts = response.split(identifier)
112+
if len(parts) < 2:
113+
return ""
114+
115+
# Get the part after the identifier
116+
section = parts[1]
117+
118+
# Find the next code block or section
119+
next_section = re.search(r'(\[filename:|```\w*)', section)
120+
if next_section:
121+
section = section[:next_section.start()]
122+
123+
# Clean up the section
124+
section = re.sub(r'^[^\w]*', '', section, flags=re.DOTALL) # Remove non-word chars at start
125+
section = section.strip()
126+
127+
return section
128+
49129
def write_files(self, files: Dict[str, str], project_dir: str) -> List[str]:
50130
"""Write files to the project directory"""
51131
file_paths = []

0 commit comments

Comments
 (0)