vbagent 0.1.1__py3-none-any.whl → 0.2.0__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.
- vbagent/__init__.py +220 -2
- vbagent/agents/__init__.py +84 -1
- vbagent/agents/converter.py +2 -2
- vbagent/agents/reviewer.py +2 -2
- vbagent/agents/tikz_checker.py +351 -37
- vbagent/agents/variant.py +74 -0
- vbagent/cli/check.py +750 -34
- vbagent/cli/common.py +87 -25
- vbagent/cli/convert.py +1 -1
- vbagent/models/__init__.py +41 -4
- vbagent/prompts/__init__.py +120 -1
- vbagent/prompts/converter.py +317 -74
- vbagent/prompts/tikz_checker.py +91 -3
- vbagent/references/__init__.py +60 -3
- vbagent-0.2.0.dist-info/METADATA +1049 -0
- {vbagent-0.1.1.dist-info → vbagent-0.2.0.dist-info}/RECORD +18 -18
- vbagent-0.1.1.dist-info/METADATA +0 -383
- {vbagent-0.1.1.dist-info → vbagent-0.2.0.dist-info}/WHEEL +0 -0
- {vbagent-0.1.1.dist-info → vbagent-0.2.0.dist-info}/entry_points.txt +0 -0
vbagent/cli/check.py
CHANGED
|
@@ -34,6 +34,7 @@ from vbagent.cli.common import (
|
|
|
34
34
|
natural_sort_key,
|
|
35
35
|
extract_problem_solution,
|
|
36
36
|
find_image_for_problem,
|
|
37
|
+
has_diagram_placeholder,
|
|
37
38
|
_get_console,
|
|
38
39
|
_get_panel,
|
|
39
40
|
_get_table,
|
|
@@ -501,7 +502,7 @@ def check():
|
|
|
501
502
|
solution - Check solution correctness
|
|
502
503
|
grammar - Check grammar and spelling
|
|
503
504
|
clarity - Check clarity and conciseness
|
|
504
|
-
tikz - Check TikZ
|
|
505
|
+
tikz - Check/generate TikZ diagrams
|
|
505
506
|
apply - Apply a stored suggestion
|
|
506
507
|
history - View suggestion history
|
|
507
508
|
resume - Resume interrupted session
|
|
@@ -518,7 +519,8 @@ def check():
|
|
|
518
519
|
vbagent check solution -d ./src_tex/
|
|
519
520
|
vbagent check grammar -d ./src_tex/
|
|
520
521
|
vbagent check clarity -d ./src_tex/
|
|
521
|
-
vbagent check tikz -d ./
|
|
522
|
+
vbagent check tikz -d ./scans/ # Check/generate TikZ
|
|
523
|
+
vbagent check tikz --patch --ref-type circuit
|
|
522
524
|
"""
|
|
523
525
|
pass
|
|
524
526
|
|
|
@@ -2550,6 +2552,22 @@ def check_clarity_cmd(
|
|
|
2550
2552
|
is_flag=True,
|
|
2551
2553
|
help="Reset progress and re-check all files"
|
|
2552
2554
|
)
|
|
2555
|
+
@click.option(
|
|
2556
|
+
"--patch",
|
|
2557
|
+
is_flag=True,
|
|
2558
|
+
help="Use apply_patch mode for structured diffs (experimental)"
|
|
2559
|
+
)
|
|
2560
|
+
@click.option(
|
|
2561
|
+
"--use-context/--no-context",
|
|
2562
|
+
default=True,
|
|
2563
|
+
help="Include TikZ reference examples in prompt (default: enabled)"
|
|
2564
|
+
)
|
|
2565
|
+
@click.option(
|
|
2566
|
+
"--ref-type",
|
|
2567
|
+
type=str,
|
|
2568
|
+
default=None,
|
|
2569
|
+
help="Filter reference examples by diagram type (e.g., circuit, free_body, graph)"
|
|
2570
|
+
)
|
|
2553
2571
|
def check_tikz_cmd(
|
|
2554
2572
|
output_dir: str,
|
|
2555
2573
|
count: int,
|
|
@@ -2558,39 +2576,636 @@ def check_tikz_cmd(
|
|
|
2558
2576
|
prompt: Optional[str],
|
|
2559
2577
|
only_tikz: bool,
|
|
2560
2578
|
reset: bool,
|
|
2579
|
+
patch: bool,
|
|
2580
|
+
use_context: bool,
|
|
2581
|
+
ref_type: Optional[str],
|
|
2561
2582
|
):
|
|
2562
|
-
"""Check TikZ diagram code
|
|
2583
|
+
"""Check and generate TikZ diagram code.
|
|
2563
2584
|
|
|
2564
|
-
|
|
2565
|
-
and physics diagram conventions. Prompts for approval to apply fixes.
|
|
2585
|
+
This command has two modes:
|
|
2566
2586
|
|
|
2567
|
-
|
|
2568
|
-
|
|
2587
|
+
1. CHECK MODE: Reviews existing TikZ/PGF code for syntax errors,
|
|
2588
|
+
missing libraries, and physics diagram conventions.
|
|
2589
|
+
|
|
2590
|
+
2. GENERATE MODE: If a file has \\input{diagram} placeholder but no
|
|
2591
|
+
TikZ code, automatically generates TikZ from the corresponding
|
|
2592
|
+
image (auto-discovered in images/ directory).
|
|
2593
|
+
|
|
2594
|
+
Images are auto-discovered by filename (e.g., Problem_1.tex -> images/Problem_1.png)
|
|
2595
|
+
or can be specified with --images-dir.
|
|
2596
|
+
|
|
2597
|
+
Reference examples are matched by diagram type from classification metadata,
|
|
2598
|
+
or can be filtered manually with --ref-type.
|
|
2599
|
+
|
|
2600
|
+
Use --patch to enable apply_patch mode for more precise edits.
|
|
2569
2601
|
|
|
2570
2602
|
\b
|
|
2571
2603
|
Examples:
|
|
2572
|
-
vbagent check tikz
|
|
2573
|
-
vbagent check tikz -d ./
|
|
2574
|
-
vbagent check tikz -d ./
|
|
2575
|
-
vbagent check tikz -c 10
|
|
2576
|
-
vbagent check tikz -p Problem_1
|
|
2577
|
-
vbagent check tikz
|
|
2578
|
-
vbagent check tikz --only-tikz
|
|
2579
|
-
vbagent check tikz --reset
|
|
2604
|
+
vbagent check tikz # Check/generate in agentic/
|
|
2605
|
+
vbagent check tikz -d ./scans/ # Check specific directory
|
|
2606
|
+
vbagent check tikz -d ./scans/Problem_1.tex # Check single file
|
|
2607
|
+
vbagent check tikz -c 10 # Process 10 files
|
|
2608
|
+
vbagent check tikz -p Problem_1 # Check specific problem
|
|
2609
|
+
vbagent check tikz -i ./images/ # Explicit images directory
|
|
2610
|
+
vbagent check tikz --only-tikz # Skip files without TikZ
|
|
2611
|
+
vbagent check tikz --reset # Re-check all files
|
|
2612
|
+
vbagent check tikz --patch # Use apply_patch mode
|
|
2613
|
+
vbagent check tikz --ref-type circuit # Use only circuit references
|
|
2614
|
+
vbagent check tikz --prompt "Use circuitikz" # Add instructions
|
|
2580
2615
|
"""
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2616
|
+
if patch:
|
|
2617
|
+
# Use new patch-based checker
|
|
2618
|
+
_run_tikz_patch_session(
|
|
2619
|
+
output_dir=output_dir,
|
|
2620
|
+
count=count,
|
|
2621
|
+
problem_id=problem_id,
|
|
2622
|
+
images_dir=images_dir,
|
|
2623
|
+
extra_prompt=prompt,
|
|
2624
|
+
only_tikz=only_tikz,
|
|
2625
|
+
reset=reset,
|
|
2626
|
+
use_context=use_context,
|
|
2627
|
+
ref_diagram_type=ref_type,
|
|
2628
|
+
)
|
|
2629
|
+
else:
|
|
2630
|
+
# Use legacy checker
|
|
2631
|
+
_run_checker_session(
|
|
2632
|
+
output_dir=output_dir,
|
|
2633
|
+
count=count,
|
|
2634
|
+
problem_id=problem_id,
|
|
2635
|
+
checker_name="tikz",
|
|
2636
|
+
check_func_module="vbagent.agents.tikz_checker",
|
|
2637
|
+
check_func_name="check_tikz",
|
|
2638
|
+
require_solution=False,
|
|
2639
|
+
require_tikz=only_tikz,
|
|
2640
|
+
reset=reset,
|
|
2641
|
+
images_dir=images_dir,
|
|
2642
|
+
extra_prompt=prompt,
|
|
2643
|
+
)
|
|
2644
|
+
|
|
2645
|
+
|
|
2646
|
+
def _load_diagram_type_from_classification(tex_file: Path, output_path: Path) -> Optional[str]:
|
|
2647
|
+
"""Load diagram_type from classification JSON for a problem.
|
|
2648
|
+
|
|
2649
|
+
Looks for classification JSON in common locations:
|
|
2650
|
+
- Same directory as tex file: Problem_X.json
|
|
2651
|
+
- classifications/ subdirectory: classifications/Problem_X.json
|
|
2652
|
+
- Parent's classifications/: ../classifications/Problem_X.json
|
|
2653
|
+
|
|
2654
|
+
Args:
|
|
2655
|
+
tex_file: Path to the .tex file
|
|
2656
|
+
output_path: Base output directory
|
|
2657
|
+
|
|
2658
|
+
Returns:
|
|
2659
|
+
diagram_type string if found, None otherwise
|
|
2660
|
+
"""
|
|
2661
|
+
import json
|
|
2662
|
+
|
|
2663
|
+
problem_name = tex_file.stem
|
|
2664
|
+
|
|
2665
|
+
# Try common classification file locations
|
|
2666
|
+
possible_paths = [
|
|
2667
|
+
tex_file.with_suffix('.json'), # Same dir, same name
|
|
2668
|
+
tex_file.parent / "classifications" / f"{problem_name}.json",
|
|
2669
|
+
output_path / "classifications" / f"{problem_name}.json",
|
|
2670
|
+
tex_file.parent.parent / "classifications" / f"{problem_name}.json",
|
|
2671
|
+
]
|
|
2672
|
+
|
|
2673
|
+
for class_path in possible_paths:
|
|
2674
|
+
if class_path.exists():
|
|
2675
|
+
try:
|
|
2676
|
+
data = json.loads(class_path.read_text())
|
|
2677
|
+
diagram_type = data.get("diagram_type")
|
|
2678
|
+
if diagram_type and diagram_type != "none":
|
|
2679
|
+
return diagram_type
|
|
2680
|
+
except (json.JSONDecodeError, KeyError):
|
|
2681
|
+
continue
|
|
2682
|
+
|
|
2683
|
+
return None
|
|
2684
|
+
|
|
2685
|
+
|
|
2686
|
+
def _generate_tikz_for_placeholder(
|
|
2687
|
+
content: str,
|
|
2688
|
+
image_path: Optional[Path],
|
|
2689
|
+
diagram_type: Optional[str] = None,
|
|
2690
|
+
extra_prompt: Optional[str] = None,
|
|
2691
|
+
console = None,
|
|
2692
|
+
) -> Optional[str]:
|
|
2693
|
+
"""Generate TikZ code and replace \\input{diagram} placeholder.
|
|
2694
|
+
|
|
2695
|
+
Uses the TikZ generator agent to create TikZ code from the problem
|
|
2696
|
+
description and optional image, then replaces the placeholder.
|
|
2697
|
+
|
|
2698
|
+
Args:
|
|
2699
|
+
content: Full LaTeX content with \\input{diagram} placeholder
|
|
2700
|
+
image_path: Optional path to reference image
|
|
2701
|
+
diagram_type: Optional diagram type for reference matching
|
|
2702
|
+
extra_prompt: Optional additional instructions
|
|
2703
|
+
console: Rich console for output (optional)
|
|
2704
|
+
|
|
2705
|
+
Returns:
|
|
2706
|
+
Content with placeholder replaced by generated TikZ, or None on failure
|
|
2707
|
+
"""
|
|
2708
|
+
import re
|
|
2709
|
+
from vbagent.agents.tikz import generate_tikz, validate_tikz_output
|
|
2710
|
+
|
|
2711
|
+
# Extract problem description for the generator
|
|
2712
|
+
# Try to get the problem statement (before solution)
|
|
2713
|
+
problem_match = re.search(r'\\item\s*(.*?)(?=\\begin\{solution\}|$)', content, re.DOTALL)
|
|
2714
|
+
if problem_match:
|
|
2715
|
+
description = problem_match.group(1).strip()
|
|
2716
|
+
# Clean up LaTeX commands for description
|
|
2717
|
+
description = re.sub(r'\\begin\{center\}.*?\\end\{center\}', '', description, flags=re.DOTALL)
|
|
2718
|
+
description = description.strip()
|
|
2719
|
+
else:
|
|
2720
|
+
description = "Generate a physics diagram based on the problem context."
|
|
2721
|
+
|
|
2722
|
+
# Add extra prompt if provided
|
|
2723
|
+
if extra_prompt:
|
|
2724
|
+
description = f"{description}\n\nAdditional instructions: {extra_prompt}"
|
|
2725
|
+
|
|
2726
|
+
if console:
|
|
2727
|
+
console.print(f"[dim]Generating TikZ... (Ctrl+C to quit)[/dim]")
|
|
2728
|
+
|
|
2729
|
+
# Generate TikZ code
|
|
2730
|
+
tikz_code = generate_tikz(
|
|
2731
|
+
description=description,
|
|
2732
|
+
image_path=str(image_path) if image_path else None,
|
|
2733
|
+
use_context=True,
|
|
2593
2734
|
)
|
|
2735
|
+
|
|
2736
|
+
if not tikz_code or not validate_tikz_output(tikz_code):
|
|
2737
|
+
return None
|
|
2738
|
+
|
|
2739
|
+
# Wrap in center environment if not already wrapped
|
|
2740
|
+
if not tikz_code.strip().startswith(r'\begin{center}'):
|
|
2741
|
+
tikz_code = f"\\begin{{center}}\n{tikz_code}\n\\end{{center}}"
|
|
2742
|
+
|
|
2743
|
+
# Escape backslashes in tikz_code for use as replacement string
|
|
2744
|
+
# re.sub interprets \1, \2, etc. as backreferences, so we need to escape
|
|
2745
|
+
tikz_code_escaped = tikz_code.replace('\\', '\\\\')
|
|
2746
|
+
|
|
2747
|
+
# Replace the placeholder patterns
|
|
2748
|
+
# Pattern 1: \begin{center}\input{diagram}\end{center}
|
|
2749
|
+
placeholder_pattern = r'\\begin\{center\}\s*%?\s*\\input\{diagram\}\s*\\end\{center\}'
|
|
2750
|
+
result = re.sub(placeholder_pattern, tikz_code_escaped, content, flags=re.DOTALL)
|
|
2751
|
+
|
|
2752
|
+
# Pattern 2: Simple \input{diagram} (with optional comment)
|
|
2753
|
+
if result == content:
|
|
2754
|
+
simple_pattern = r'%?\s*\\input\{diagram\}'
|
|
2755
|
+
result = re.sub(simple_pattern, tikz_code_escaped, result)
|
|
2756
|
+
|
|
2757
|
+
return result if result != content else None
|
|
2758
|
+
|
|
2759
|
+
|
|
2760
|
+
def _prompt_tikz_action(console) -> str:
|
|
2761
|
+
"""Prompt user for action on TikZ generation/check result.
|
|
2762
|
+
|
|
2763
|
+
Args:
|
|
2764
|
+
console: Rich console for output
|
|
2765
|
+
|
|
2766
|
+
Returns:
|
|
2767
|
+
Action string: 'approve', 'edit', 'reject', 'skip', or 'quit'
|
|
2768
|
+
"""
|
|
2769
|
+
console.print("\n[bold]Actions:[/bold]")
|
|
2770
|
+
console.print(" [green]a[/green]pprove - Apply this change")
|
|
2771
|
+
console.print(" [red]r[/red]eject - Store for later, don't apply")
|
|
2772
|
+
console.print(" [blue]e[/blue]dit - Edit in editor before applying")
|
|
2773
|
+
console.print(" [yellow]s[/yellow]kip - Skip without storing")
|
|
2774
|
+
console.print(" [dim]q[/dim]uit - Exit session")
|
|
2775
|
+
|
|
2776
|
+
Prompt = _get_prompt()
|
|
2777
|
+
try:
|
|
2778
|
+
choice = Prompt.ask(
|
|
2779
|
+
"\nAction",
|
|
2780
|
+
choices=["a", "r", "e", "s", "q", "approve", "reject", "edit", "skip", "quit"],
|
|
2781
|
+
default="a"
|
|
2782
|
+
).lower()
|
|
2783
|
+
except KeyboardInterrupt:
|
|
2784
|
+
return "quit"
|
|
2785
|
+
|
|
2786
|
+
if choice in ["a", "approve"]:
|
|
2787
|
+
return "approve"
|
|
2788
|
+
elif choice in ["r", "reject"]:
|
|
2789
|
+
return "reject"
|
|
2790
|
+
elif choice in ["e", "edit"]:
|
|
2791
|
+
return "edit"
|
|
2792
|
+
elif choice in ["s", "skip"]:
|
|
2793
|
+
return "skip"
|
|
2794
|
+
else:
|
|
2795
|
+
return "quit"
|
|
2796
|
+
|
|
2797
|
+
|
|
2798
|
+
def _run_tikz_patch_session(
|
|
2799
|
+
output_dir: str,
|
|
2800
|
+
count: int,
|
|
2801
|
+
problem_id: Optional[str],
|
|
2802
|
+
images_dir: Optional[str] = None,
|
|
2803
|
+
extra_prompt: Optional[str] = None,
|
|
2804
|
+
only_tikz: bool = False,
|
|
2805
|
+
reset: bool = False,
|
|
2806
|
+
use_context: bool = True,
|
|
2807
|
+
ref_diagram_type: Optional[str] = None,
|
|
2808
|
+
) -> None:
|
|
2809
|
+
"""Run TikZ checker session using apply_patch mode.
|
|
2810
|
+
|
|
2811
|
+
Uses OpenAI's apply_patch tool for structured diffs instead of
|
|
2812
|
+
returning full corrected content.
|
|
2813
|
+
|
|
2814
|
+
Args:
|
|
2815
|
+
output_dir: Directory containing .tex files
|
|
2816
|
+
count: Number of files to process
|
|
2817
|
+
problem_id: Optional specific problem ID to check
|
|
2818
|
+
images_dir: Optional directory containing images for problems
|
|
2819
|
+
extra_prompt: Optional additional instructions for the checker
|
|
2820
|
+
only_tikz: Whether to only check files with TikZ code
|
|
2821
|
+
reset: Whether to reset progress and re-check all files
|
|
2822
|
+
use_context: Whether to include TikZ reference examples
|
|
2823
|
+
ref_diagram_type: Filter reference examples by diagram type (e.g., circuit)
|
|
2824
|
+
"""
|
|
2825
|
+
import re
|
|
2826
|
+
from vbagent.models.version_store import VersionStore, SuggestionStatus
|
|
2827
|
+
from vbagent.models.review import Suggestion, ReviewIssueType as IssueType
|
|
2828
|
+
from vbagent.agents.tikz_checker import (
|
|
2829
|
+
check_tikz_with_patch,
|
|
2830
|
+
has_tikz_environment,
|
|
2831
|
+
PatchResult,
|
|
2832
|
+
)
|
|
2833
|
+
|
|
2834
|
+
console = _get_console()
|
|
2835
|
+
|
|
2836
|
+
console.print("[cyan]Using apply_patch mode (experimental)[/cyan]")
|
|
2837
|
+
if ref_diagram_type:
|
|
2838
|
+
console.print(f"[cyan]Using only '{ref_diagram_type}' references[/cyan]")
|
|
2839
|
+
|
|
2840
|
+
output_path = Path(output_dir)
|
|
2841
|
+
tex_files = _discover_tex_files(output_path)
|
|
2842
|
+
|
|
2843
|
+
if not tex_files:
|
|
2844
|
+
console.print(f"[red]Error:[/red] No .tex files found in {output_dir}")
|
|
2845
|
+
raise SystemExit(1)
|
|
2846
|
+
|
|
2847
|
+
def natural_sort_key(p):
|
|
2848
|
+
return [int(t) if t.isdigit() else t.lower() for t in re.split(r'(\d+)', str(p))]
|
|
2849
|
+
|
|
2850
|
+
tex_files = sorted(tex_files, key=natural_sort_key)
|
|
2851
|
+
|
|
2852
|
+
if problem_id:
|
|
2853
|
+
tex_files = [f for f in tex_files if problem_id in f.stem]
|
|
2854
|
+
if not tex_files:
|
|
2855
|
+
console.print(f"[red]Error:[/red] No files found matching '{problem_id}'")
|
|
2856
|
+
raise SystemExit(1)
|
|
2857
|
+
|
|
2858
|
+
# Filter for TikZ files if required
|
|
2859
|
+
if only_tikz:
|
|
2860
|
+
tikz_files = []
|
|
2861
|
+
for f in tex_files:
|
|
2862
|
+
content = f.read_text()
|
|
2863
|
+
if has_tikz_environment(content):
|
|
2864
|
+
tikz_files.append(f)
|
|
2865
|
+
tex_files = tikz_files
|
|
2866
|
+
if not tex_files:
|
|
2867
|
+
console.print(f"[yellow]No files with TikZ code found in {output_dir}[/yellow]")
|
|
2868
|
+
return
|
|
2869
|
+
|
|
2870
|
+
# Initialize version store for tracking
|
|
2871
|
+
store = VersionStore(base_dir=".")
|
|
2872
|
+
output_dir_normalized = str(output_path.resolve())
|
|
2873
|
+
|
|
2874
|
+
# Reset progress if requested
|
|
2875
|
+
if reset:
|
|
2876
|
+
reset_count = store.reset_checker_progress("tikz_patch", output_dir_normalized)
|
|
2877
|
+
if reset_count > 0:
|
|
2878
|
+
console.print(f"[yellow]Reset progress for {reset_count} file(s)[/yellow]")
|
|
2879
|
+
|
|
2880
|
+
# Filter out already-checked files
|
|
2881
|
+
checked_files = store.get_checked_files("tikz_patch", output_dir_normalized)
|
|
2882
|
+
unchecked_files = [f for f in tex_files if str(f.resolve()) not in checked_files]
|
|
2883
|
+
|
|
2884
|
+
if not unchecked_files:
|
|
2885
|
+
console.print(f"[green]✓ All {len(tex_files)} file(s) have been checked[/green]")
|
|
2886
|
+
stats = store.get_checker_stats("tikz_patch", output_dir_normalized)
|
|
2887
|
+
console.print(f"[dim]Total: {stats['total']}, Passed: {stats['passed']}, Had issues: {stats['failed']}[/dim]")
|
|
2888
|
+
console.print(f"[dim]Use --reset to re-check files[/dim]")
|
|
2889
|
+
store.close()
|
|
2890
|
+
return
|
|
2891
|
+
|
|
2892
|
+
if len(checked_files) > 0:
|
|
2893
|
+
console.print(f"[dim]Skipping {len(checked_files)} already-checked file(s)[/dim]")
|
|
2894
|
+
|
|
2895
|
+
to_process = unchecked_files[:count]
|
|
2896
|
+
console.print(f"[cyan]Checking {len(to_process)} file(s) with apply_patch mode[/cyan]")
|
|
2897
|
+
session_id = store.create_session()
|
|
2898
|
+
|
|
2899
|
+
stats = {
|
|
2900
|
+
"processed": 0,
|
|
2901
|
+
"passed": 0,
|
|
2902
|
+
"approved": 0,
|
|
2903
|
+
"rejected": 0,
|
|
2904
|
+
"skipped": 0,
|
|
2905
|
+
"patch_errors": 0,
|
|
2906
|
+
"session_id": session_id,
|
|
2907
|
+
}
|
|
2908
|
+
|
|
2909
|
+
shutdown_requested = False
|
|
2910
|
+
|
|
2911
|
+
def signal_handler(signum, frame):
|
|
2912
|
+
nonlocal shutdown_requested
|
|
2913
|
+
shutdown_requested = True
|
|
2914
|
+
console.print("\n[yellow]Shutdown requested. Saving progress...[/yellow]")
|
|
2915
|
+
|
|
2916
|
+
original_sigint = signal.signal(signal.SIGINT, signal_handler)
|
|
2917
|
+
original_sigterm = None
|
|
2918
|
+
if sys.platform != "win32":
|
|
2919
|
+
original_sigterm = signal.signal(signal.SIGTERM, signal_handler)
|
|
2920
|
+
|
|
2921
|
+
try:
|
|
2922
|
+
for idx, tex_file in enumerate(to_process):
|
|
2923
|
+
if shutdown_requested:
|
|
2924
|
+
break
|
|
2925
|
+
|
|
2926
|
+
rel_path = tex_file.relative_to(output_path) if output_path.is_dir() else tex_file.name
|
|
2927
|
+
problem_name = tex_file.stem
|
|
2928
|
+
console.print(f"\n[bold cyan]═══ [{idx+1}/{len(to_process)}] {rel_path} ═══[/bold cyan]")
|
|
2929
|
+
|
|
2930
|
+
content = tex_file.read_text()
|
|
2931
|
+
|
|
2932
|
+
# Find corresponding image
|
|
2933
|
+
# 1. Use explicit images_dir if provided
|
|
2934
|
+
# 2. Otherwise, auto-discover if file has \input{diagram} placeholder
|
|
2935
|
+
image_path = None
|
|
2936
|
+
if images_dir:
|
|
2937
|
+
image_path = find_image_for_problem(tex_file, images_dir)
|
|
2938
|
+
if image_path:
|
|
2939
|
+
console.print(f"[dim]Image: {image_path.name}[/dim]")
|
|
2940
|
+
else:
|
|
2941
|
+
# Auto-discover image if file has diagram placeholder
|
|
2942
|
+
if has_diagram_placeholder(content):
|
|
2943
|
+
image_path = find_image_for_problem(tex_file, auto_discover=True)
|
|
2944
|
+
if image_path:
|
|
2945
|
+
console.print(f"[dim]Auto-found image: {image_path.name}[/dim]")
|
|
2946
|
+
|
|
2947
|
+
# Check if this file needs TikZ GENERATION (has placeholder but no TikZ)
|
|
2948
|
+
needs_generation = has_diagram_placeholder(content) and not has_tikz_environment(content)
|
|
2949
|
+
|
|
2950
|
+
if needs_generation:
|
|
2951
|
+
# Generate TikZ instead of checking
|
|
2952
|
+
console.print(f"[cyan]Generating TikZ (found \\input{{diagram}} placeholder)[/cyan]")
|
|
2953
|
+
|
|
2954
|
+
if not image_path:
|
|
2955
|
+
console.print("[yellow]Warning: No image found for generation. Results may be limited.[/yellow]")
|
|
2956
|
+
|
|
2957
|
+
# Auto-detect diagram type from classification
|
|
2958
|
+
auto_diagram_type = ref_diagram_type
|
|
2959
|
+
if not auto_diagram_type and use_context:
|
|
2960
|
+
auto_diagram_type = _load_diagram_type_from_classification(tex_file, output_path)
|
|
2961
|
+
if auto_diagram_type:
|
|
2962
|
+
console.print(f"[dim]Auto-detected diagram type: {auto_diagram_type}[/dim]")
|
|
2963
|
+
|
|
2964
|
+
try:
|
|
2965
|
+
generated_content = _generate_tikz_for_placeholder(
|
|
2966
|
+
content=content,
|
|
2967
|
+
image_path=image_path,
|
|
2968
|
+
diagram_type=auto_diagram_type,
|
|
2969
|
+
extra_prompt=extra_prompt,
|
|
2970
|
+
console=console,
|
|
2971
|
+
)
|
|
2972
|
+
stats["processed"] += 1
|
|
2973
|
+
|
|
2974
|
+
if not generated_content:
|
|
2975
|
+
console.print("[yellow]Failed to generate TikZ[/yellow]")
|
|
2976
|
+
stats["skipped"] += 1
|
|
2977
|
+
continue
|
|
2978
|
+
|
|
2979
|
+
# Show the generated content
|
|
2980
|
+
diff_text = _generate_diff(content, generated_content, str(rel_path))
|
|
2981
|
+
|
|
2982
|
+
if diff_text:
|
|
2983
|
+
console.print(f"\n[bold]Generated TikZ:[/bold]")
|
|
2984
|
+
display_diff(diff_text, console)
|
|
2985
|
+
|
|
2986
|
+
# Create suggestion for tracking
|
|
2987
|
+
suggestion = Suggestion(
|
|
2988
|
+
file_path=str(tex_file),
|
|
2989
|
+
issue_type=IssueType.FORMATTING,
|
|
2990
|
+
description="TikZ generation: replaced \\input{diagram} placeholder",
|
|
2991
|
+
original_content=content,
|
|
2992
|
+
suggested_content=generated_content,
|
|
2993
|
+
diff=diff_text,
|
|
2994
|
+
reasoning="Generated TikZ code from image to replace placeholder.",
|
|
2995
|
+
confidence=0.8,
|
|
2996
|
+
)
|
|
2997
|
+
|
|
2998
|
+
# Prompt for action
|
|
2999
|
+
action = _prompt_tikz_action(console)
|
|
3000
|
+
|
|
3001
|
+
if action == "quit":
|
|
3002
|
+
shutdown_requested = True
|
|
3003
|
+
break
|
|
3004
|
+
elif action == "skip":
|
|
3005
|
+
console.print("[dim]Skipped[/dim]")
|
|
3006
|
+
stats["skipped"] += 1
|
|
3007
|
+
continue
|
|
3008
|
+
elif action == "reject":
|
|
3009
|
+
store.save_suggestion(suggestion, problem_name, SuggestionStatus.REJECTED, session_id)
|
|
3010
|
+
store.mark_file_checked(str(tex_file.resolve()), "tikz_patch", output_dir_normalized, passed=False)
|
|
3011
|
+
console.print("[yellow]Suggestion stored for later[/yellow]")
|
|
3012
|
+
stats["rejected"] += 1
|
|
3013
|
+
continue
|
|
3014
|
+
|
|
3015
|
+
final_content = generated_content
|
|
3016
|
+
if action == "edit":
|
|
3017
|
+
success, edited = open_suggested_in_editor(str(tex_file), generated_content, console)
|
|
3018
|
+
if success and edited:
|
|
3019
|
+
final_content = edited
|
|
3020
|
+
console.print("[cyan]Content edited[/cyan]")
|
|
3021
|
+
|
|
3022
|
+
# Write the generated content
|
|
3023
|
+
try:
|
|
3024
|
+
tex_file.write_text(final_content)
|
|
3025
|
+
console.print(f"[green]✓ TikZ generated and applied to {rel_path}[/green]")
|
|
3026
|
+
store.save_suggestion(suggestion, problem_name, SuggestionStatus.APPROVED, session_id)
|
|
3027
|
+
store.mark_file_checked(str(tex_file.resolve()), "tikz_patch", output_dir_normalized, passed=False)
|
|
3028
|
+
stats["approved"] += 1
|
|
3029
|
+
except (IOError, OSError) as e:
|
|
3030
|
+
console.print(f"[red]✗ Failed to write: {e}[/red]")
|
|
3031
|
+
stats["rejected"] += 1
|
|
3032
|
+
|
|
3033
|
+
continue
|
|
3034
|
+
|
|
3035
|
+
except KeyboardInterrupt:
|
|
3036
|
+
console.print("\n[yellow]Interrupted[/yellow]")
|
|
3037
|
+
shutdown_requested = True
|
|
3038
|
+
break
|
|
3039
|
+
except Exception as e:
|
|
3040
|
+
console.print(f"[red]Error generating TikZ:[/red] {e}")
|
|
3041
|
+
stats["skipped"] += 1
|
|
3042
|
+
continue
|
|
3043
|
+
|
|
3044
|
+
# Normal checking flow (file has existing TikZ or no placeholder)
|
|
3045
|
+
# Auto-detect diagram type from classification if not manually specified
|
|
3046
|
+
auto_diagram_type = ref_diagram_type
|
|
3047
|
+
if not auto_diagram_type and use_context:
|
|
3048
|
+
auto_diagram_type = _load_diagram_type_from_classification(tex_file, output_path)
|
|
3049
|
+
if auto_diagram_type:
|
|
3050
|
+
console.print(f"[dim]Auto-detected diagram type: {auto_diagram_type}[/dim]")
|
|
3051
|
+
|
|
3052
|
+
# Prepare content with extra prompt if provided
|
|
3053
|
+
check_content = content
|
|
3054
|
+
if extra_prompt:
|
|
3055
|
+
console.print(f"[dim]Extra instructions: {extra_prompt}[/dim]")
|
|
3056
|
+
check_content = f"% ADDITIONAL INSTRUCTIONS: {extra_prompt}\n\n{content}"
|
|
3057
|
+
|
|
3058
|
+
try:
|
|
3059
|
+
console.print(f"[dim]Checking with apply_patch... (Ctrl+C to quit)[/dim]")
|
|
3060
|
+
result: PatchResult = check_tikz_with_patch(
|
|
3061
|
+
file_path=str(tex_file),
|
|
3062
|
+
full_content=check_content,
|
|
3063
|
+
image_path=str(image_path) if image_path else None,
|
|
3064
|
+
use_context=use_context,
|
|
3065
|
+
ref_diagram_type=auto_diagram_type,
|
|
3066
|
+
)
|
|
3067
|
+
stats["processed"] += 1
|
|
3068
|
+
except KeyboardInterrupt:
|
|
3069
|
+
console.print("\n[yellow]Interrupted[/yellow]")
|
|
3070
|
+
shutdown_requested = True
|
|
3071
|
+
break
|
|
3072
|
+
except Exception as e:
|
|
3073
|
+
console.print(f"[red]Error checking:[/red] {e}")
|
|
3074
|
+
stats["skipped"] += 1
|
|
3075
|
+
continue
|
|
3076
|
+
|
|
3077
|
+
if result.passed:
|
|
3078
|
+
console.print(f"[green]✓ {result.summary}[/green]")
|
|
3079
|
+
stats["passed"] += 1
|
|
3080
|
+
store.mark_file_checked(str(tex_file.resolve()), "tikz_patch", output_dir_normalized, passed=True)
|
|
3081
|
+
continue
|
|
3082
|
+
|
|
3083
|
+
# Show patch results
|
|
3084
|
+
console.print(f"[yellow]{result.summary}[/yellow]")
|
|
3085
|
+
|
|
3086
|
+
if result.patch_errors:
|
|
3087
|
+
for err in result.patch_errors:
|
|
3088
|
+
console.print(f"[red] Patch error: {err}[/red]")
|
|
3089
|
+
stats["patch_errors"] += len(result.patch_errors)
|
|
3090
|
+
|
|
3091
|
+
if not result.corrected_content:
|
|
3092
|
+
console.print("[yellow]No corrected content available[/yellow]")
|
|
3093
|
+
stats["skipped"] += 1
|
|
3094
|
+
continue
|
|
3095
|
+
|
|
3096
|
+
# Generate diff for display
|
|
3097
|
+
diff_text = _generate_diff(content, result.corrected_content, str(rel_path))
|
|
3098
|
+
|
|
3099
|
+
# Create suggestion object for database storage
|
|
3100
|
+
suggestion = Suggestion(
|
|
3101
|
+
file_path=str(tex_file),
|
|
3102
|
+
issue_type=IssueType.FORMATTING,
|
|
3103
|
+
description=f"TikZ patch check: {result.summary}",
|
|
3104
|
+
original_content=content,
|
|
3105
|
+
suggested_content=result.corrected_content,
|
|
3106
|
+
diff=diff_text,
|
|
3107
|
+
reasoning=f"Applied {result.patches_applied} patch(es) via apply_patch tool.",
|
|
3108
|
+
confidence=0.8,
|
|
3109
|
+
)
|
|
3110
|
+
|
|
3111
|
+
if diff_text:
|
|
3112
|
+
console.print(f"\n[bold]Proposed Changes ({result.patches_applied} patch(es)):[/bold]")
|
|
3113
|
+
display_diff(diff_text, console)
|
|
3114
|
+
|
|
3115
|
+
# Prompt for action
|
|
3116
|
+
console.print("\n[bold]Actions:[/bold]")
|
|
3117
|
+
console.print(" [green]a[/green]pprove - Apply this change")
|
|
3118
|
+
console.print(" [red]r[/red]eject - Store for later, don't apply")
|
|
3119
|
+
console.print(" [blue]e[/blue]dit - Edit in editor before applying")
|
|
3120
|
+
console.print(" [yellow]s[/yellow]kip - Skip without storing")
|
|
3121
|
+
console.print(" [dim]q[/dim]uit - Exit session")
|
|
3122
|
+
|
|
3123
|
+
Prompt = _get_prompt()
|
|
3124
|
+
try:
|
|
3125
|
+
choice = Prompt.ask(
|
|
3126
|
+
"\nAction",
|
|
3127
|
+
choices=["a", "r", "e", "s", "q", "approve", "reject", "edit", "skip", "quit"],
|
|
3128
|
+
default="a"
|
|
3129
|
+
).lower()
|
|
3130
|
+
except KeyboardInterrupt:
|
|
3131
|
+
console.print("\n[yellow]Interrupted[/yellow]")
|
|
3132
|
+
shutdown_requested = True
|
|
3133
|
+
break
|
|
3134
|
+
|
|
3135
|
+
if choice in ["q", "quit"]:
|
|
3136
|
+
shutdown_requested = True
|
|
3137
|
+
break
|
|
3138
|
+
|
|
3139
|
+
if choice in ["s", "skip"]:
|
|
3140
|
+
console.print("[dim]Skipped[/dim]")
|
|
3141
|
+
stats["skipped"] += 1
|
|
3142
|
+
continue
|
|
3143
|
+
|
|
3144
|
+
if choice in ["r", "reject"]:
|
|
3145
|
+
store.save_suggestion(suggestion, problem_name, SuggestionStatus.REJECTED, session_id)
|
|
3146
|
+
store.mark_file_checked(str(tex_file.resolve()), "tikz_patch", output_dir_normalized, passed=False)
|
|
3147
|
+
console.print("[yellow]Suggestion stored for later[/yellow]")
|
|
3148
|
+
stats["rejected"] += 1
|
|
3149
|
+
continue
|
|
3150
|
+
|
|
3151
|
+
final_content = result.corrected_content
|
|
3152
|
+
|
|
3153
|
+
if choice in ["e", "edit"]:
|
|
3154
|
+
success, edited = open_suggested_in_editor(str(tex_file), result.corrected_content, console)
|
|
3155
|
+
if success and edited:
|
|
3156
|
+
final_content = edited
|
|
3157
|
+
console.print("[cyan]Content edited[/cyan]")
|
|
3158
|
+
else:
|
|
3159
|
+
console.print("[yellow]Edit cancelled, using original correction[/yellow]")
|
|
3160
|
+
|
|
3161
|
+
# Write the corrected content
|
|
3162
|
+
try:
|
|
3163
|
+
tex_file.write_text(final_content)
|
|
3164
|
+
console.print(f"[green]✓ Corrections applied to {rel_path}[/green]")
|
|
3165
|
+
store.save_suggestion(suggestion, problem_name, SuggestionStatus.APPROVED, session_id)
|
|
3166
|
+
store.mark_file_checked(str(tex_file.resolve()), "tikz_patch", output_dir_normalized, passed=False)
|
|
3167
|
+
stats["approved"] += 1
|
|
3168
|
+
except (IOError, OSError) as e:
|
|
3169
|
+
console.print(f"[red]✗ Failed to write: {e}[/red]")
|
|
3170
|
+
store.save_suggestion(suggestion, problem_name, SuggestionStatus.REJECTED, session_id)
|
|
3171
|
+
store.mark_file_checked(str(tex_file.resolve()), "tikz_patch", output_dir_normalized, passed=False)
|
|
3172
|
+
stats["rejected"] += 1
|
|
3173
|
+
|
|
3174
|
+
# Update session with final stats
|
|
3175
|
+
store.update_session(
|
|
3176
|
+
session_id,
|
|
3177
|
+
problems_reviewed=stats["processed"],
|
|
3178
|
+
suggestions_made=stats["approved"] + stats["rejected"],
|
|
3179
|
+
approved_count=stats["approved"],
|
|
3180
|
+
rejected_count=stats["rejected"],
|
|
3181
|
+
skipped_count=stats["skipped"],
|
|
3182
|
+
completed=not shutdown_requested,
|
|
3183
|
+
)
|
|
3184
|
+
|
|
3185
|
+
finally:
|
|
3186
|
+
signal.signal(signal.SIGINT, original_sigint)
|
|
3187
|
+
if original_sigterm is not None:
|
|
3188
|
+
signal.signal(signal.SIGTERM, original_sigterm)
|
|
3189
|
+
store.close()
|
|
3190
|
+
|
|
3191
|
+
# Summary
|
|
3192
|
+
console.print("\n[bold]═══ Session Summary (apply_patch mode) ═══[/bold]")
|
|
3193
|
+
table = _get_table(show_header=False, box=None)
|
|
3194
|
+
table.add_column("Metric", style="dim")
|
|
3195
|
+
table.add_column("Value", justify="right")
|
|
3196
|
+
|
|
3197
|
+
table.add_row("Files checked", str(stats["processed"]))
|
|
3198
|
+
table.add_row("Passed", f"[green]{stats['passed']}[/green]")
|
|
3199
|
+
table.add_row("Approved", f"[green]{stats['approved']}[/green]")
|
|
3200
|
+
table.add_row("Rejected", f"[red]{stats['rejected']}[/red]")
|
|
3201
|
+
table.add_row("Skipped", f"[yellow]{stats['skipped']}[/yellow]")
|
|
3202
|
+
if stats["patch_errors"] > 0:
|
|
3203
|
+
table.add_row("Patch errors", f"[red]{stats['patch_errors']}[/red]")
|
|
3204
|
+
|
|
3205
|
+
console.print(table)
|
|
3206
|
+
|
|
3207
|
+
if shutdown_requested:
|
|
3208
|
+
console.print(f"\n[dim]Session {session_id[:8]} saved. View with: vbagent check history[/dim]")
|
|
2594
3209
|
|
|
2595
3210
|
|
|
2596
3211
|
def _run_checker_session(
|
|
@@ -2633,9 +3248,9 @@ def _run_checker_session(
|
|
|
2633
3248
|
module = importlib.import_module(check_func_module)
|
|
2634
3249
|
check_func = getattr(module, check_func_name)
|
|
2635
3250
|
|
|
2636
|
-
# Import has_tikz_environment
|
|
3251
|
+
# Import has_tikz_environment for tikz checker (needed for generation detection)
|
|
2637
3252
|
has_tikz_environment = None
|
|
2638
|
-
if require_tikz:
|
|
3253
|
+
if checker_name == "tikz" or require_tikz:
|
|
2639
3254
|
from vbagent.agents.tikz_checker import has_tikz_environment
|
|
2640
3255
|
|
|
2641
3256
|
console = _get_console()
|
|
@@ -2739,13 +3354,114 @@ def _run_checker_session(
|
|
|
2739
3354
|
problem_name = tex_file.stem
|
|
2740
3355
|
console.print(f"\n[bold cyan]═══ [{idx+1}/{len(to_process)}] {rel_path} ═══[/bold cyan]")
|
|
2741
3356
|
|
|
2742
|
-
# Find corresponding image if images_dir is provided
|
|
2743
|
-
image_path = find_image_for_problem(tex_file, images_dir) if images_dir else None
|
|
2744
|
-
if image_path:
|
|
2745
|
-
console.print(f"[dim]Image: {image_path.name}[/dim]")
|
|
2746
|
-
|
|
2747
3357
|
content = tex_file.read_text()
|
|
2748
3358
|
|
|
3359
|
+
# Find corresponding image
|
|
3360
|
+
# 1. Use explicit images_dir if provided
|
|
3361
|
+
# 2. For tikz checker, auto-discover if file has \input{diagram} placeholder
|
|
3362
|
+
image_path = None
|
|
3363
|
+
if images_dir:
|
|
3364
|
+
image_path = find_image_for_problem(tex_file, images_dir)
|
|
3365
|
+
if image_path:
|
|
3366
|
+
console.print(f"[dim]Image: {image_path.name}[/dim]")
|
|
3367
|
+
elif checker_name == "tikz":
|
|
3368
|
+
# Auto-discover image if file has diagram placeholder
|
|
3369
|
+
if has_diagram_placeholder(content):
|
|
3370
|
+
image_path = find_image_for_problem(tex_file, auto_discover=True)
|
|
3371
|
+
if image_path:
|
|
3372
|
+
console.print(f"[dim]Auto-found image: {image_path.name}[/dim]")
|
|
3373
|
+
|
|
3374
|
+
# For tikz checker: check if generation is needed (has placeholder but no TikZ)
|
|
3375
|
+
if checker_name == "tikz" and has_tikz_environment:
|
|
3376
|
+
needs_generation = has_diagram_placeholder(content) and not has_tikz_environment(content)
|
|
3377
|
+
|
|
3378
|
+
if needs_generation:
|
|
3379
|
+
# Generate TikZ instead of checking
|
|
3380
|
+
console.print(f"[cyan]Generating TikZ (found \\input{{diagram}} placeholder)[/cyan]")
|
|
3381
|
+
|
|
3382
|
+
if not image_path:
|
|
3383
|
+
console.print("[yellow]Warning: No image found for generation. Results may be limited.[/yellow]")
|
|
3384
|
+
|
|
3385
|
+
try:
|
|
3386
|
+
generated_content = _generate_tikz_for_placeholder(
|
|
3387
|
+
content=content,
|
|
3388
|
+
image_path=image_path,
|
|
3389
|
+
diagram_type=None,
|
|
3390
|
+
extra_prompt=extra_prompt,
|
|
3391
|
+
console=console,
|
|
3392
|
+
)
|
|
3393
|
+
stats["processed"] += 1
|
|
3394
|
+
|
|
3395
|
+
if not generated_content:
|
|
3396
|
+
console.print("[yellow]Failed to generate TikZ[/yellow]")
|
|
3397
|
+
stats["skipped"] += 1
|
|
3398
|
+
continue
|
|
3399
|
+
|
|
3400
|
+
# Show the generated content
|
|
3401
|
+
diff_text = _generate_diff(content, generated_content, str(rel_path))
|
|
3402
|
+
|
|
3403
|
+
if diff_text:
|
|
3404
|
+
console.print(f"\n[bold]Generated TikZ:[/bold]")
|
|
3405
|
+
display_diff(diff_text, console)
|
|
3406
|
+
|
|
3407
|
+
# Create suggestion for tracking
|
|
3408
|
+
suggestion = Suggestion(
|
|
3409
|
+
file_path=str(tex_file),
|
|
3410
|
+
issue_type=issue_type,
|
|
3411
|
+
description="TikZ generation: replaced \\input{diagram} placeholder",
|
|
3412
|
+
original_content=content,
|
|
3413
|
+
suggested_content=generated_content,
|
|
3414
|
+
diff=diff_text,
|
|
3415
|
+
reasoning="Generated TikZ code from image to replace placeholder.",
|
|
3416
|
+
confidence=0.8,
|
|
3417
|
+
)
|
|
3418
|
+
|
|
3419
|
+
# Prompt for action
|
|
3420
|
+
action = _prompt_tikz_action(console)
|
|
3421
|
+
|
|
3422
|
+
if action == "quit":
|
|
3423
|
+
shutdown_requested = True
|
|
3424
|
+
break
|
|
3425
|
+
elif action == "skip":
|
|
3426
|
+
console.print("[dim]Skipped[/dim]")
|
|
3427
|
+
stats["skipped"] += 1
|
|
3428
|
+
continue
|
|
3429
|
+
elif action == "reject":
|
|
3430
|
+
store.save_suggestion(suggestion, problem_name, SuggestionStatus.REJECTED, session_id)
|
|
3431
|
+
store.mark_file_checked(str(tex_file.resolve()), checker_name, output_dir_normalized, passed=False)
|
|
3432
|
+
console.print("[yellow]Suggestion stored for later[/yellow]")
|
|
3433
|
+
stats["rejected"] += 1
|
|
3434
|
+
continue
|
|
3435
|
+
|
|
3436
|
+
final_content = generated_content
|
|
3437
|
+
if action == "edit":
|
|
3438
|
+
success, edited = open_suggested_in_editor(str(tex_file), generated_content, console)
|
|
3439
|
+
if success and edited:
|
|
3440
|
+
final_content = edited
|
|
3441
|
+
console.print("[cyan]Content edited[/cyan]")
|
|
3442
|
+
|
|
3443
|
+
# Write the generated content
|
|
3444
|
+
try:
|
|
3445
|
+
tex_file.write_text(final_content)
|
|
3446
|
+
console.print(f"[green]✓ TikZ generated and applied to {rel_path}[/green]")
|
|
3447
|
+
store.save_suggestion(suggestion, problem_name, SuggestionStatus.APPROVED, session_id)
|
|
3448
|
+
store.mark_file_checked(str(tex_file.resolve()), checker_name, output_dir_normalized, passed=False)
|
|
3449
|
+
stats["approved"] += 1
|
|
3450
|
+
except (IOError, OSError) as e:
|
|
3451
|
+
console.print(f"[red]✗ Failed to write: {e}[/red]")
|
|
3452
|
+
stats["rejected"] += 1
|
|
3453
|
+
|
|
3454
|
+
continue
|
|
3455
|
+
|
|
3456
|
+
except KeyboardInterrupt:
|
|
3457
|
+
console.print("\n[yellow]Interrupted[/yellow]")
|
|
3458
|
+
shutdown_requested = True
|
|
3459
|
+
break
|
|
3460
|
+
except Exception as e:
|
|
3461
|
+
console.print(f"[red]Error generating TikZ:[/red] {e}")
|
|
3462
|
+
stats["skipped"] += 1
|
|
3463
|
+
continue
|
|
3464
|
+
|
|
2749
3465
|
if require_solution and r'\begin{solution}' not in content:
|
|
2750
3466
|
console.print("[yellow]No solution environment found, skipping[/yellow]")
|
|
2751
3467
|
stats["skipped"] += 1
|