autocoder-nano 0.1.26__py3-none-any.whl → 0.1.28__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.
@@ -2,21 +2,18 @@ import argparse
2
2
  import glob
3
3
  import hashlib
4
4
  import os
5
- import re
6
5
  import json
7
6
  import shutil
8
7
  import subprocess
9
- import tempfile
10
8
  import textwrap
11
9
  import time
12
- import traceback
13
10
  import uuid
14
- from difflib import SequenceMatcher
15
11
 
16
- from autocoder_nano.agent.new.auto_new_project import BuildNewProject
12
+ from autocoder_nano.edit import Dispacher
17
13
  from autocoder_nano.helper import show_help
18
14
  from autocoder_nano.index.entry import build_index_and_filter_files
19
15
  from autocoder_nano.index.index_manager import IndexManager
16
+ from autocoder_nano.index.symbols_utils import extract_symbols
20
17
  from autocoder_nano.llm_client import AutoLLM
21
18
  from autocoder_nano.version import __version__
22
19
  from autocoder_nano.llm_types import *
@@ -55,7 +52,7 @@ base_persist_dir = os.path.join(project_root, ".auto-coder", "plugins", "chat-au
55
52
  # ".vscode", ".idea", ".hg"]
56
53
  commands = [
57
54
  "/add_files", "/remove_files", "/list_files", "/conf", "/coding", "/chat", "/revert", "/index/query",
58
- "/index/build", "/exclude_dirs", "/help", "/shell", "/exit", "/mode", "/models", "/commit", "/new"
55
+ "/index/build", "/exclude_dirs", "/exclude_files", "/help", "/shell", "/exit", "/mode", "/models", "/commit", "/new"
59
56
  ]
60
57
 
61
58
  memory = {
@@ -77,29 +74,6 @@ memory = {
77
74
  args: AutoCoderArgs = AutoCoderArgs()
78
75
 
79
76
 
80
- def extract_symbols(text: str) -> SymbolsInfo:
81
- patterns = {
82
- "usage": r"用途:(.+)",
83
- "functions": r"函数:(.+)",
84
- "variables": r"变量:(.+)",
85
- "classes": r"类:(.+)",
86
- "import_statements": r"导入语句:(.+)",
87
- }
88
-
89
- info = SymbolsInfo()
90
- for field, pattern in patterns.items():
91
- match = re.search(pattern, text)
92
- if match:
93
- value = match.group(1).strip()
94
- if field == "import_statements":
95
- value = [v.strip() for v in value.split("^^")]
96
- elif field == "functions" or field == "variables" or field == "classes":
97
- value = [v.strip() for v in value.split(",")]
98
- setattr(info, field, value)
99
-
100
- return info
101
-
102
-
103
77
  def get_all_file_names_in_project() -> List[str]:
104
78
  file_names = []
105
79
  final_exclude_dirs = default_exclude_dirs + memory.get("exclude_dirs", [])
@@ -223,7 +197,9 @@ COMMANDS = {
223
197
  "/conf": "",
224
198
  "/mode": "",
225
199
  "/models": ""
226
- }
200
+ },
201
+ "/exclude_files": {"/list": "", "/drop": ""},
202
+ "/exclude_dirs": {}
227
203
  }
228
204
 
229
205
 
@@ -701,6 +677,15 @@ class CommandCompleter(Completer):
701
677
  if current_word and current_word in file_name:
702
678
  yield Completion(file_name, start_position=-len(current_word))
703
679
 
680
+ elif words[0] == "/exclude_files":
681
+ new_text = text[len("/exclude_files"):]
682
+ parser = CommandTextParser(new_text, words[0])
683
+ parser.add_files()
684
+ current_word = parser.current_word()
685
+ for command in parser.get_sub_commands():
686
+ if command.startswith(current_word):
687
+ yield Completion(command, start_position=-len(current_word))
688
+
704
689
  elif words[0] == "/models":
705
690
  new_text = text[len("/models"):]
706
691
  parser = CommandTextParser(new_text, words[0])
@@ -798,20 +783,69 @@ def load_memory():
798
783
  completer.update_current_files(memory["current_files"]["files"])
799
784
 
800
785
 
801
- def symbols_info_to_str(info: SymbolsInfo, symbol_types: List[SymbolType]) -> str:
802
- result = []
803
- for symbol_type in symbol_types:
804
- value = getattr(info, symbol_type.value)
805
- if value:
806
- if symbol_type == SymbolType.IMPORT_STATEMENTS:
807
- value_str = "^^".join(value)
808
- elif symbol_type in [SymbolType.FUNCTIONS, SymbolType.VARIABLES, SymbolType.CLASSES,]:
809
- value_str = ",".join(value)
810
- else:
811
- value_str = value
812
- result.append(f"{symbol_type.value}:{value_str}")
786
+ def exclude_dirs(dir_names: List[str]):
787
+ new_dirs = dir_names
788
+ existing_dirs = memory.get("exclude_dirs", [])
789
+ dirs_to_add = [d for d in new_dirs if d not in existing_dirs]
790
+
791
+ if dirs_to_add:
792
+ existing_dirs.extend(dirs_to_add)
793
+ if "exclude_dirs" not in memory:
794
+ memory["exclude_dirs"] = existing_dirs
795
+ print(f"Added exclude dirs: {dirs_to_add}")
796
+ for d in dirs_to_add:
797
+ exclude_files(f"regex://.*/{d}/*.")
798
+ # exclude_files([f"regex://.*/{d}/*." for d in dirs_to_add])
799
+ else:
800
+ print("All specified dirs are already in the exclude list.")
801
+ save_memory()
802
+ completer.refresh_files()
813
803
 
814
- return "\n".join(result)
804
+
805
+ def exclude_files(query: str):
806
+ if "/list" in query:
807
+ query = query.replace("/list", "", 1).strip()
808
+ existing_file_patterns = memory.get("exclude_files", [])
809
+
810
+ # 打印表格
811
+ table = Table(title="Exclude Files")
812
+ table.add_column("File Pattern")
813
+ for file_pattern in existing_file_patterns:
814
+ table.add_row(file_pattern)
815
+ console.print(table)
816
+ return
817
+
818
+ if "/drop" in query:
819
+ query = query.replace("/drop", "", 1).strip()
820
+ existing_file_patterns = memory.get("exclude_files", [])
821
+ existing_file_patterns.remove(query.strip())
822
+ memory["exclude_files"] = existing_file_patterns
823
+ if query.startswith("regex://.*/") and query.endswith("/*."):
824
+ existing_dirs_patterns = memory.get("exclude_dirs", [])
825
+ dir_query = query.replace("regex://.*/", "", 1).replace("/*.", "", 1)
826
+ if dir_query in existing_dirs_patterns:
827
+ existing_dirs_patterns.remove(dir_query.strip())
828
+ save_memory()
829
+ completer.refresh_files()
830
+ return
831
+
832
+ new_file_patterns = query.strip().split(",")
833
+
834
+ existing_file_patterns = memory.get("exclude_files", [])
835
+ file_patterns_to_add = [f for f in new_file_patterns if f not in existing_file_patterns]
836
+
837
+ for file_pattern in file_patterns_to_add:
838
+ if not file_pattern.startswith("regex://"):
839
+ raise
840
+
841
+ if file_patterns_to_add:
842
+ existing_file_patterns.extend(file_patterns_to_add)
843
+ if "exclude_files" not in memory:
844
+ memory["exclude_files"] = existing_file_patterns
845
+ save_memory()
846
+ print(f"Added exclude files: {file_patterns_to_add}")
847
+ else:
848
+ print("All specified files are already in the exclude list.")
815
849
 
816
850
 
817
851
  def index_command(llm):
@@ -1007,7 +1041,8 @@ def update_config_to_args(query, delete_execute_file: bool = False):
1007
1041
  "skip_confirm": conf.get("skip_confirm", "true") == "true",
1008
1042
  "chat_model": conf.get("chat_model", ""),
1009
1043
  "code_model": conf.get("code_model", ""),
1010
- "auto_merge": conf.get("auto_merge", "editblock")
1044
+ "auto_merge": conf.get("auto_merge", "editblock"),
1045
+ "exclude_files": memory.get("exclude_files", [])
1011
1046
  }
1012
1047
  current_files = memory["current_files"]["files"]
1013
1048
  yaml_config["urls"] = current_files
@@ -1220,30 +1255,6 @@ def chat(query: str, llm: AutoLLM):
1220
1255
  return
1221
1256
 
1222
1257
 
1223
- def git_print_commit_info(commit_result: CommitResult):
1224
- table = Table(
1225
- title="Commit Information (Use /revert to revert this commit)", show_header=True, header_style="bold magenta"
1226
- )
1227
- table.add_column("Attribute", style="cyan", no_wrap=True)
1228
- table.add_column("Value", style="green")
1229
-
1230
- table.add_row("Commit Hash", commit_result.commit_hash)
1231
- table.add_row("Commit Message", commit_result.commit_message)
1232
- table.add_row("Changed Files", "\n".join(commit_result.changed_files))
1233
-
1234
- console.print(
1235
- Panel(table, expand=False, border_style="green", title="Git Commit Summary")
1236
- )
1237
-
1238
- if commit_result.diffs:
1239
- for file, diff in commit_result.diffs.items():
1240
- console.print(f"\n[bold blue]File: {file}[/bold blue]")
1241
- syntax = Syntax(diff, "diff", theme="monokai", line_numbers=True)
1242
- console.print(
1243
- Panel(syntax, expand=False, border_style="yellow", title="File Diff")
1244
- )
1245
-
1246
-
1247
1258
  def init_project():
1248
1259
  if not args.project_type:
1249
1260
  logger.error(
@@ -1316,1040 +1327,6 @@ def load_include_files(config, base_path, max_depth=10, current_depth=0):
1316
1327
  return config
1317
1328
 
1318
1329
 
1319
- class CodeAutoGenerateEditBlock:
1320
- def __init__(self, llm: AutoLLM, action=None, fence_0: str = "```", fence_1: str = "```"):
1321
- self.llm = llm
1322
- # self.llm.setup_default_model_name(memory["conf"]["current_code_model"])
1323
- self.args = args
1324
- self.action = action
1325
- self.fence_0 = fence_0
1326
- self.fence_1 = fence_1
1327
- if not self.llm:
1328
- raise ValueError("Please provide a valid model instance to use for code generation.")
1329
- self.llms = [self.llm]
1330
-
1331
- @prompt()
1332
- def single_round_instruction(self, instruction: str, content: str, context: str = ""):
1333
- """
1334
- 如果你需要生成代码,对于每个需要更改的文件,你需要按 *SEARCH/REPLACE block* 的格式进行生成。
1335
-
1336
- # *SEARCH/REPLACE block* Rules:
1337
-
1338
- Every *SEARCH/REPLACE block* must use this format:
1339
- 1. The opening fence and code language, eg: {{ fence_0 }}python
1340
- 2. The file path alone on a line, starting with "##File:" and verbatim. No bold asterisks, no quotes around it,
1341
- no escaping of characters, etc.
1342
- 3. The start of search block: <<<<<<< SEARCH
1343
- 4. A contiguous chunk of lines to search for in the existing source code
1344
- 5. The dividing line: =======
1345
- 6. The lines to replace into the source code
1346
- 7. The end of the replacement block: >>>>>>> REPLACE
1347
- 8. The closing fence: {{ fence_1 }}
1348
-
1349
- Every *SEARCH* section must *EXACTLY MATCH* the existing source code, character for character,
1350
- including all comments, docstrings, etc.
1351
-
1352
- *SEARCH/REPLACE* blocks will replace *all* matching occurrences.
1353
- Include enough lines to make the SEARCH blocks unique.
1354
-
1355
- Include *ALL* the code being searched and replaced!
1356
-
1357
- To move code within a file, use 2 *SEARCH/REPLACE* blocks: 1 to delete it from its current location,
1358
- 1 to insert it in the new location.
1359
-
1360
- If you want to put code in a new file, use a *SEARCH/REPLACE block* with:
1361
- - A new file path, including dir name if needed
1362
- - An empty `SEARCH` section
1363
- - The new file's contents in the `REPLACE` section
1364
-
1365
- ONLY EVER RETURN CODE IN A *SEARCH/REPLACE BLOCK*!
1366
-
1367
- 下面我们来看一个例子:
1368
-
1369
- 当前项目目录结构:
1370
- 1. 项目根目录: /tmp/projects/mathweb
1371
- 2. 项目子目录/文件列表(类似tree 命令输出)
1372
- flask/
1373
- app.py
1374
- templates/
1375
- index.html
1376
- static/
1377
- style.css
1378
-
1379
- 用户需求: Change get_factorial() to use math.factorial
1380
-
1381
- 回答: To make this change we need to modify `/tmp/projects/mathweb/flask/app.py` to:
1382
-
1383
- 1. Import the math package.
1384
- 2. Remove the existing factorial() function.
1385
- 3. Update get_factorial() to call math.factorial instead.
1386
-
1387
- Here are the *SEARCH/REPLACE* blocks:
1388
-
1389
- ```python
1390
- ##File: /tmp/projects/mathweb/flask/app.py
1391
- <<<<<<< SEARCH
1392
- from flask import Flask
1393
- =======
1394
- import math
1395
- from flask import Flask
1396
- >>>>>>> REPLACE
1397
- ```
1398
-
1399
- ```python
1400
- ##File: /tmp/projects/mathweb/flask/app.py
1401
- <<<<<<< SEARCH
1402
- def factorial(n):
1403
- "compute factorial"
1404
-
1405
- if n == 0:
1406
- return 1
1407
- else:
1408
- return n * factorial(n-1)
1409
-
1410
- =======
1411
- >>>>>>> REPLACE
1412
- ```
1413
-
1414
- ```python
1415
- ##File: /tmp/projects/mathweb/flask/app.py
1416
- <<<<<<< SEARCH
1417
- return str(factorial(n))
1418
- =======
1419
- return str(math.factorial(n))
1420
- >>>>>>> REPLACE
1421
- ```
1422
-
1423
- 用户需求: Refactor hello() into its own file.
1424
-
1425
- 回答:To make this change we need to modify `main.py` and make a new file `hello.py`:
1426
-
1427
- 1. Make a new hello.py file with hello() in it.
1428
- 2. Remove hello() from main.py and replace it with an import.
1429
-
1430
- Here are the *SEARCH/REPLACE* blocks:
1431
-
1432
- ```python
1433
- ##File: /tmp/projects/mathweb/hello.py
1434
- <<<<<<< SEARCH
1435
- =======
1436
- def hello():
1437
- "print a greeting"
1438
-
1439
- print("hello")
1440
- >>>>>>> REPLACE
1441
- ```
1442
-
1443
- ```python
1444
- ##File: /tmp/projects/mathweb/main.py
1445
- <<<<<<< SEARCH
1446
- def hello():
1447
- "print a greeting"
1448
-
1449
- print("hello")
1450
- =======
1451
- from hello import hello
1452
- >>>>>>> REPLACE
1453
- ```
1454
-
1455
- 现在让我们开始一个新的任务:
1456
-
1457
- {%- if structure %}
1458
- {{ structure }}
1459
- {%- endif %}
1460
-
1461
- {%- if content %}
1462
- 下面是一些文件路径以及每个文件对应的源码:
1463
- <files>
1464
- {{ content }}
1465
- </files>
1466
- {%- endif %}
1467
-
1468
- {%- if context %}
1469
- <extra_context>
1470
- {{ context }}
1471
- </extra_context>
1472
- {%- endif %}
1473
-
1474
- 下面是用户的需求:
1475
-
1476
- {{ instruction }}
1477
-
1478
- """
1479
-
1480
- @prompt()
1481
- def auto_implement_function(self, instruction: str, content: str) -> str:
1482
- """
1483
- 下面是一些文件路径以及每个文件对应的源码:
1484
-
1485
- {{ content }}
1486
-
1487
- 请参考上面的内容,重新实现所有文件下方法体标记了如下内容的方法:
1488
-
1489
- ```python
1490
- raise NotImplementedError("This function should be implemented by the model.")
1491
- ```
1492
-
1493
- {{ instruction }}
1494
-
1495
- """
1496
-
1497
- def single_round_run(self, query: str, source_content: str) -> CodeGenerateResult:
1498
- init_prompt = ''
1499
- if self.args.template == "common":
1500
- init_prompt = self.single_round_instruction.prompt(
1501
- instruction=query, content=source_content, context=self.args.context
1502
- )
1503
- elif self.args.template == "auto_implement":
1504
- init_prompt = self.auto_implement_function.prompt(
1505
- instruction=query, content=source_content
1506
- )
1507
-
1508
- with open(self.args.target_file, "w") as file:
1509
- file.write(init_prompt)
1510
-
1511
- conversations = [{"role": "user", "content": init_prompt}]
1512
-
1513
- conversations_list = []
1514
- results = []
1515
-
1516
- for llm in self.llms:
1517
- v = llm.chat_ai(conversations=conversations, model=args.code_model)
1518
- results.append(v.output)
1519
- for result in results:
1520
- conversations_list.append(conversations + [{"role": "assistant", "content": result}])
1521
-
1522
- return CodeGenerateResult(contents=results, conversations=conversations_list)
1523
-
1524
- @prompt()
1525
- def multi_round_instruction(self, instruction: str, content: str, context: str = "") -> str:
1526
- """
1527
- 如果你需要生成代码,对于每个需要更改的文件,你需要按 *SEARCH/REPLACE block* 的格式进行生成。
1528
-
1529
- # *SEARCH/REPLACE block* Rules:
1530
-
1531
- Every *SEARCH/REPLACE block* must use this format:
1532
- 1. The opening fence and code language, eg: {{ fence_0 }}python
1533
- 2. The file path alone on a line, starting with "##File:" and verbatim. No bold asterisks, no quotes around it,
1534
- no escaping of characters, etc.
1535
- 3. The start of search block: <<<<<<< SEARCH
1536
- 4. A contiguous chunk of lines to search for in the existing source code
1537
- 5. The dividing line: =======
1538
- 6. The lines to replace into the source code
1539
- 7. The end of the replacement block: >>>>>>> REPLACE
1540
- 8. The closing fence: {{ fence_1 }}
1541
-
1542
- Every *SEARCH* section must *EXACTLY MATCH* the existing source code, character for character,
1543
- including all comments, docstrings, etc.
1544
-
1545
- *SEARCH/REPLACE* blocks will replace *all* matching occurrences.
1546
- Include enough lines to make the SEARCH blocks unique.
1547
-
1548
- Include *ALL* the code being searched and replaced!
1549
-
1550
- To move code within a file, use 2 *SEARCH/REPLACE* blocks: 1 to delete it from its current location,
1551
- 1 to insert it in the new location.
1552
-
1553
- If you want to put code in a new file, use a *SEARCH/REPLACE block* with:
1554
- - A new file path, including dir name if needed
1555
- - An empty `SEARCH` section
1556
- - The new file's contents in the `REPLACE` section
1557
-
1558
- ONLY EVER RETURN CODE IN A *SEARCH/REPLACE BLOCK*!
1559
-
1560
- 下面我们来看一个例子:
1561
-
1562
- 当前项目目录结构:
1563
- 1. 项目根目录: /tmp/projects/mathweb
1564
- 2. 项目子目录/文件列表(类似tree 命令输出)
1565
- flask/
1566
- app.py
1567
- templates/
1568
- index.html
1569
- static/
1570
- style.css
1571
-
1572
- 用户需求: Change get_factorial() to use math.factorial
1573
-
1574
- 回答: To make this change we need to modify `/tmp/projects/mathweb/flask/app.py` to:
1575
-
1576
- 1. Import the math package.
1577
- 2. Remove the existing factorial() function.
1578
- 3. Update get_factorial() to call math.factorial instead.
1579
-
1580
- Here are the *SEARCH/REPLACE* blocks:
1581
-
1582
- {{ fence_0 }}python
1583
- ##File: /tmp/projects/mathweb/flask/app.py
1584
- <<<<<<< SEARCH
1585
- from flask import Flask
1586
- =======
1587
- import math
1588
- from flask import Flask
1589
- >>>>>>> REPLACE
1590
- {{ fence_1 }}
1591
-
1592
- {{ fence_0 }}python
1593
- ##File: /tmp/projects/mathweb/flask/app.py
1594
- <<<<<<< SEARCH
1595
- def factorial(n):
1596
- "compute factorial"
1597
-
1598
- if n == 0:
1599
- return 1
1600
- else:
1601
- return n * factorial(n-1)
1602
-
1603
- =======
1604
- >>>>>>> REPLACE
1605
- {{ fence_1 }}
1606
-
1607
- {{ fence_0 }}python
1608
- ##File: /tmp/projects/mathweb/flask/app.py
1609
- <<<<<<< SEARCH
1610
- return str(factorial(n))
1611
- =======
1612
- return str(math.factorial(n))
1613
- >>>>>>> REPLACE
1614
- {{ fence_1 }}
1615
-
1616
- 用户需求: Refactor hello() into its own file.
1617
-
1618
- 回答:To make this change we need to modify `main.py` and make a new file `hello.py`:
1619
-
1620
- 1. Make a new hello.py file with hello() in it.
1621
- 2. Remove hello() from main.py and replace it with an import.
1622
-
1623
- Here are the *SEARCH/REPLACE* blocks:
1624
-
1625
-
1626
- {{ fence_0 }}python
1627
- ##File: /tmp/projects/mathweb/hello.py
1628
- <<<<<<< SEARCH
1629
- =======
1630
- def hello():
1631
- "print a greeting"
1632
-
1633
- print("hello")
1634
- >>>>>>> REPLACE
1635
- {{ fence_1 }}
1636
-
1637
- {{ fence_0 }}python
1638
- ##File: /tmp/projects/mathweb/main.py
1639
- <<<<<<< SEARCH
1640
- def hello():
1641
- "print a greeting"
1642
-
1643
- print("hello")
1644
- =======
1645
- from hello import hello
1646
- >>>>>>> REPLACE
1647
- {{ fence_1 }}
1648
-
1649
- 现在让我们开始一个新的任务:
1650
-
1651
- {%- if structure %}
1652
- {{ structure }}
1653
- {%- endif %}
1654
-
1655
- {%- if content %}
1656
- 下面是一些文件路径以及每个文件对应的源码:
1657
- <files>
1658
- {{ content }}
1659
- </files>
1660
- {%- endif %}
1661
-
1662
- {%- if context %}
1663
- <extra_context>
1664
- {{ context }}
1665
- </extra_context>
1666
- {%- endif %}
1667
-
1668
- 下面是用户的需求:
1669
-
1670
- {{ instruction }}
1671
-
1672
- 每次生成一个文件的*SEARCH/REPLACE* blocks,然后询问我是否继续,当我回复继续,
1673
- 继续生成下一个文件的*SEARCH/REPLACE* blocks。当没有后续任务时,请回复 "__完成__" 或者 "__EOF__"。
1674
- """
1675
-
1676
- def multi_round_run(self, query: str, source_content: str, max_steps: int = 3) -> CodeGenerateResult:
1677
- init_prompt = ''
1678
- if self.args.template == "common":
1679
- init_prompt = self.multi_round_instruction.prompt(
1680
- instruction=query, content=source_content, context=self.args.context
1681
- )
1682
- elif self.args.template == "auto_implement":
1683
- init_prompt = self.auto_implement_function.prompt(
1684
- instruction=query, content=source_content
1685
- )
1686
-
1687
- with open(self.args.target_file, "w") as file:
1688
- file.write(init_prompt)
1689
-
1690
- results = []
1691
- conversations = [{"role": "user", "content": init_prompt}]
1692
-
1693
- code_llm = self.llms[0]
1694
- v = code_llm.chat_ai(conversations=conversations, model=args.code_model)
1695
- results.append(v.output)
1696
-
1697
- conversations.append({"role": "assistant", "content": v.output})
1698
-
1699
- if "__完成__" in v.output or "/done" in v.output or "__EOF__" in v.output:
1700
- return CodeGenerateResult(contents=["\n\n".join(results)], conversations=[conversations])
1701
-
1702
- current_step = 0
1703
-
1704
- while current_step < max_steps:
1705
- conversations.append({"role": "user", "content": "继续"})
1706
-
1707
- with open(self.args.target_file, "w") as file:
1708
- file.write("继续")
1709
-
1710
- t = code_llm.chat_ai(conversations=conversations, model=args.code_model)
1711
-
1712
- results.append(t.output)
1713
- conversations.append({"role": "assistant", "content": t.output})
1714
- current_step += 1
1715
-
1716
- if "__完成__" in t.output or "/done" in t.output or "__EOF__" in t.output:
1717
- return CodeGenerateResult(contents=["\n\n".join(results)], conversations=[conversations])
1718
-
1719
- return CodeGenerateResult(contents=["\n\n".join(results)], conversations=[conversations])
1720
-
1721
-
1722
- class CodeModificationRanker:
1723
- def __init__(self, llm: AutoLLM):
1724
- self.llm = llm
1725
- self.llm.setup_default_model_name(args.code_model)
1726
- self.args = args
1727
- self.llms = [self.llm]
1728
-
1729
- @prompt()
1730
- def _rank_modifications(self, s: CodeGenerateResult) -> str:
1731
- """
1732
- 对一组代码修改进行质量评估并排序。
1733
-
1734
- 下面是修改需求:
1735
-
1736
- <edit_requirement>
1737
- {{ s.conversations[0][-2]["content"] }}
1738
- </edit_requirement>
1739
-
1740
- 下面是相应的代码修改:
1741
- {% for content in s.contents %}
1742
- <edit_block id="{{ loop.index0 }}">
1743
- {{content}}
1744
- </edit_block>
1745
- {% endfor %}
1746
-
1747
- 请输出如下格式的评估结果,只包含 JSON 数据:
1748
-
1749
- ```json
1750
- {
1751
- "rank_result": [id1, id2, id3] // id 为 edit_block 的 id,按质量从高到低排序
1752
- }
1753
- ```
1754
-
1755
- 注意:
1756
- 1. 只输出前面要求的 Json 格式就好,不要输出其他内容,Json 需要使用 ```json ```包裹
1757
- """
1758
-
1759
- def rank_modifications(self, generate_result: CodeGenerateResult) -> CodeGenerateResult:
1760
- import time
1761
- from collections import defaultdict
1762
-
1763
- start_time = time.time()
1764
- logger.info(f"开始对 {len(generate_result.contents)} 个候选结果进行排序")
1765
-
1766
- try:
1767
- results = []
1768
- for llm in self.llms:
1769
- v = self._rank_modifications.with_llm(llm).with_return_type(RankResult).run(generate_result)
1770
- results.append(v.rank_result)
1771
-
1772
- if not results:
1773
- raise Exception("All ranking requests failed")
1774
-
1775
- # 计算每个候选人的分数
1776
- candidate_scores = defaultdict(float)
1777
- for rank_result in results:
1778
- for idx, candidate_id in enumerate(rank_result):
1779
- # Score is 1/(position + 1) since position starts from 0
1780
- candidate_scores[candidate_id] += 1.0 / (idx + 1)
1781
- # 按分数降序对候选人进行排序
1782
- sorted_candidates = sorted(candidate_scores.keys(),
1783
- key=lambda x: candidate_scores[x],
1784
- reverse=True)
1785
-
1786
- elapsed = time.time() - start_time
1787
- score_details = ", ".join([f"candidate {i}: {candidate_scores[i]:.2f}" for i in sorted_candidates])
1788
- logger.info(
1789
- f"排序完成,耗时 {elapsed:.2f} 秒,最佳候选索引: {sorted_candidates[0]},评分详情: {score_details}"
1790
- )
1791
-
1792
- rerank_contents = [generate_result.contents[i] for i in sorted_candidates]
1793
- rerank_conversations = [generate_result.conversations[i] for i in sorted_candidates]
1794
-
1795
- return CodeGenerateResult(contents=rerank_contents, conversations=rerank_conversations)
1796
-
1797
- except Exception as e:
1798
- logger.error(f"排序过程失败: {str(e)}")
1799
- logger.debug(traceback.format_exc())
1800
- elapsed = time.time() - start_time
1801
- logger.warning(f"排序失败,耗时 {elapsed:.2f} 秒,将使用原始顺序")
1802
- return generate_result
1803
-
1804
-
1805
- class TextSimilarity:
1806
- """
1807
- 找到 text_b 中与 text_a 最相似的部分(滑动窗口)
1808
- 返回相似度分数和最相似的文本片段
1809
- """
1810
-
1811
- def __init__(self, text_a, text_b):
1812
- self.text_a = text_a
1813
- self.text_b = text_b
1814
- self.lines_a = self._split_into_lines(text_a)
1815
- self.lines_b = self._split_into_lines(text_b)
1816
- self.m = len(self.lines_a)
1817
- self.n = len(self.lines_b)
1818
-
1819
- @staticmethod
1820
- def _split_into_lines(text):
1821
- return text.splitlines()
1822
-
1823
- @staticmethod
1824
- def _levenshtein_ratio(s1, s2):
1825
- return SequenceMatcher(None, s1, s2).ratio()
1826
-
1827
- def get_best_matching_window(self):
1828
- best_similarity = 0
1829
- best_window = []
1830
-
1831
- for i in range(self.n - self.m + 1): # 滑动窗口
1832
- window_b = self.lines_b[i:i + self.m]
1833
- similarity = self._levenshtein_ratio("\n".join(self.lines_a), "\n".join(window_b))
1834
-
1835
- if similarity > best_similarity:
1836
- best_similarity = similarity
1837
- best_window = window_b
1838
-
1839
- return best_similarity, "\n".join(best_window)
1840
-
1841
-
1842
- class CodeAutoMergeEditBlock:
1843
- def __init__(self, llm: AutoLLM, fence_0: str = "```", fence_1: str = "```"):
1844
- self.llm = llm
1845
- self.llm.setup_default_model_name(args.code_model)
1846
- self.args = args
1847
- self.fence_0 = fence_0
1848
- self.fence_1 = fence_1
1849
-
1850
- @staticmethod
1851
- def run_pylint(code: str) -> tuple[bool, str]:
1852
- """
1853
- --disable=all 禁用所有 Pylint 的检查规则
1854
- --enable=E0001,W0311,W0312 启用指定的 Pylint 检查规则,
1855
- E0001:语法错误(Syntax Error),
1856
- W0311:代码缩进使用了 Tab 而不是空格(Bad indentation)
1857
- W0312:代码缩进不一致(Mixed indentation)
1858
- :param code:
1859
- :return:
1860
- """
1861
- with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as temp_file:
1862
- temp_file.write(code)
1863
- temp_file_path = temp_file.name
1864
-
1865
- try:
1866
- result = subprocess.run(
1867
- ["pylint", "--disable=all", "--enable=E0001,W0311,W0312", temp_file_path,],
1868
- capture_output=True,
1869
- text=True,
1870
- check=False,
1871
- )
1872
- os.unlink(temp_file_path)
1873
- if result.returncode != 0:
1874
- error_message = result.stdout.strip() or result.stderr.strip()
1875
- logger.warning(f"Pylint 检查代码失败: {error_message}")
1876
- return False, error_message
1877
- return True, ""
1878
- except subprocess.CalledProcessError as e:
1879
- error_message = f"运行 Pylint 时发生错误: {str(e)}"
1880
- logger.error(error_message)
1881
- os.unlink(temp_file_path)
1882
- return False, error_message
1883
-
1884
- def parse_whole_text(self, text: str) -> List[PathAndCode]:
1885
- """
1886
- 从文本中抽取如下格式代码(two_line_mode):
1887
-
1888
- ```python
1889
- ##File: /project/path/src/autocoder/index/index.py
1890
- <<<<<<< SEARCH
1891
- =======
1892
- >>>>>>> REPLACE
1893
- ```
1894
-
1895
- 或者 (one_line_mode)
1896
-
1897
- ```python:/project/path/src/autocoder/index/index.py
1898
- <<<<<<< SEARCH
1899
- =======
1900
- >>>>>>> REPLACE
1901
- ```
1902
- """
1903
- HEAD = "<<<<<<< SEARCH"
1904
- DIVIDER = "======="
1905
- UPDATED = ">>>>>>> REPLACE"
1906
- lines = text.split("\n")
1907
- lines_len = len(lines)
1908
- start_marker_count = 0
1909
- block = []
1910
- path_and_code_list = []
1911
- # two_line_mode or one_line_mode
1912
- current_editblock_mode = "two_line_mode"
1913
- current_editblock_path = None
1914
-
1915
- def guard(_index):
1916
- return _index + 1 < lines_len
1917
-
1918
- def start_marker(_line, _index):
1919
- nonlocal current_editblock_mode
1920
- nonlocal current_editblock_path
1921
- if _line.startswith(self.fence_0) and guard(_index) and ":" in _line and lines[_index + 1].startswith(HEAD):
1922
- current_editblock_mode = "one_line_mode"
1923
- current_editblock_path = _line.split(":", 1)[1].strip()
1924
- return True
1925
- if _line.startswith(self.fence_0) and guard(_index) and lines[_index + 1].startswith("##File:"):
1926
- current_editblock_mode = "two_line_mode"
1927
- current_editblock_path = None
1928
- return True
1929
- return False
1930
-
1931
- def end_marker(_line, _index):
1932
- return _line.startswith(self.fence_1) and UPDATED in lines[_index - 1]
1933
-
1934
- for index, line in enumerate(lines):
1935
- if start_marker(line, index) and start_marker_count == 0:
1936
- start_marker_count += 1
1937
- elif end_marker(line, index) and start_marker_count == 1:
1938
- start_marker_count -= 1
1939
- if block:
1940
- if current_editblock_mode == "two_line_mode":
1941
- path = block[0].split(":", 1)[1].strip()
1942
- content = "\n".join(block[1:])
1943
- else:
1944
- path = current_editblock_path
1945
- content = "\n".join(block)
1946
- block = []
1947
- path_and_code_list.append(PathAndCode(path=path, content=content))
1948
- elif start_marker_count > 0:
1949
- block.append(line)
1950
-
1951
- return path_and_code_list
1952
-
1953
- def get_edits(self, content: str):
1954
- edits = self.parse_whole_text(content)
1955
- HEAD = "<<<<<<< SEARCH"
1956
- DIVIDER = "======="
1957
- UPDATED = ">>>>>>> REPLACE"
1958
- result = []
1959
- for edit in edits:
1960
- heads = []
1961
- updates = []
1962
- c = edit.content
1963
- in_head = False
1964
- in_updated = False
1965
- for line in c.splitlines():
1966
- if line.strip() == HEAD:
1967
- in_head = True
1968
- continue
1969
- if line.strip() == DIVIDER:
1970
- in_head = False
1971
- in_updated = True
1972
- continue
1973
- if line.strip() == UPDATED:
1974
- in_head = False
1975
- in_updated = False
1976
- continue
1977
- if in_head:
1978
- heads.append(line)
1979
- if in_updated:
1980
- updates.append(line)
1981
- result.append((edit.path, "\n".join(heads), "\n".join(updates)))
1982
- return result
1983
-
1984
- @prompt()
1985
- def git_require_msg(self, source_dir: str, error: str) -> str:
1986
- """
1987
- auto_merge only works for git repositories.
1988
-
1989
- Try to use git init in the source directory.
1990
-
1991
- ```shell
1992
- cd {{ source_dir }}
1993
- git init .
1994
- ```
1995
-
1996
- Then try to run auto-coder again.
1997
- Error: {{ error }}
1998
- """
1999
-
2000
- def _merge_code_without_effect(self, content: str) -> MergeCodeWithoutEffect:
2001
- """
2002
- 合并代码时不会产生任何副作用,例如 Git 操作、代码检查或文件写入。
2003
- 返回一个元组,包含:
2004
- - 成功合并的代码块的列表,每个元素是一个 (file_path, new_content) 元组,
2005
- 其中 file_path 是文件路径,new_content 是合并后的新内容。
2006
- - 合并失败的代码块的列表,每个元素是一个 (file_path, head, update) 元组,
2007
- 其中:file_path 是文件路径,head 是原始内容,update 是尝试合并的内容。
2008
- """
2009
- codes = self.get_edits(content)
2010
- file_content_mapping = {}
2011
- failed_blocks = []
2012
-
2013
- for block in codes:
2014
- file_path, head, update = block
2015
- if not os.path.exists(file_path):
2016
- file_content_mapping[file_path] = update
2017
- else:
2018
- if file_path not in file_content_mapping:
2019
- with open(file_path, "r") as f:
2020
- temp = f.read()
2021
- file_content_mapping[file_path] = temp
2022
- existing_content = file_content_mapping[file_path]
2023
-
2024
- # First try exact match
2025
- new_content = (
2026
- existing_content.replace(head, update, 1)
2027
- if head
2028
- else existing_content + "\n" + update
2029
- )
2030
-
2031
- # If exact match fails, try similarity match
2032
- if new_content == existing_content and head:
2033
- similarity, best_window = TextSimilarity(
2034
- head, existing_content
2035
- ).get_best_matching_window()
2036
- if similarity > self.args.editblock_similarity:
2037
- new_content = existing_content.replace(
2038
- best_window, update, 1
2039
- )
2040
-
2041
- if new_content != existing_content:
2042
- file_content_mapping[file_path] = new_content
2043
- else:
2044
- failed_blocks.append((file_path, head, update))
2045
- return MergeCodeWithoutEffect(
2046
- success_blocks=[(path, content) for path, content in file_content_mapping.items()],
2047
- failed_blocks=failed_blocks
2048
- )
2049
-
2050
- def choose_best_choice(self, generate_result: CodeGenerateResult) -> CodeGenerateResult:
2051
- """ 选择最佳代码 """
2052
- if len(generate_result.contents) == 1: # 仅一份代码立即返回
2053
- logger.info("仅有一个候选结果,跳过排序")
2054
- return generate_result
2055
-
2056
- ranker = CodeModificationRanker(self.llm)
2057
- ranked_result = ranker.rank_modifications(generate_result)
2058
- # 过滤掉包含失败块的内容
2059
- for content, conversations in zip(ranked_result.contents, ranked_result.conversations):
2060
- merge_result = self._merge_code_without_effect(content)
2061
- if not merge_result.failed_blocks:
2062
- return CodeGenerateResult(contents=[content], conversations=[conversations])
2063
- # 如果所有内容都包含失败块,则返回第一个
2064
- return CodeGenerateResult(contents=[ranked_result.contents[0]], conversations=[ranked_result.conversations[0]])
2065
-
2066
- def _merge_code(self, content: str, force_skip_git: bool = False):
2067
- file_content = open(self.args.file).read()
2068
- md5 = hashlib.md5(file_content.encode("utf-8")).hexdigest()
2069
- file_name = os.path.basename(self.args.file)
2070
-
2071
- codes = self.get_edits(content)
2072
- changes_to_make = []
2073
- changes_made = False
2074
- unmerged_blocks = []
2075
- merged_blocks = []
2076
-
2077
- # First, check if there are any changes to be made
2078
- file_content_mapping = {}
2079
- for block in codes:
2080
- file_path, head, update = block
2081
- if not os.path.exists(file_path):
2082
- changes_to_make.append((file_path, None, update))
2083
- file_content_mapping[file_path] = update
2084
- merged_blocks.append((file_path, "", update, 1))
2085
- changes_made = True
2086
- else:
2087
- if file_path not in file_content_mapping:
2088
- with open(file_path, "r") as f:
2089
- temp = f.read()
2090
- file_content_mapping[file_path] = temp
2091
- existing_content = file_content_mapping[file_path]
2092
- new_content = (
2093
- existing_content.replace(head, update, 1)
2094
- if head
2095
- else existing_content + "\n" + update
2096
- )
2097
- if new_content != existing_content:
2098
- changes_to_make.append(
2099
- (file_path, existing_content, new_content))
2100
- file_content_mapping[file_path] = new_content
2101
- merged_blocks.append((file_path, head, update, 1))
2102
- changes_made = True
2103
- else:
2104
- # If the SEARCH BLOCK is not found exactly, then try to use
2105
- # the similarity ratio to find the best matching block
2106
- similarity, best_window = TextSimilarity(head, existing_content).get_best_matching_window()
2107
- if similarity > self.args.editblock_similarity: # 相似性比较
2108
- new_content = existing_content.replace(
2109
- best_window, update, 1)
2110
- if new_content != existing_content:
2111
- changes_to_make.append(
2112
- (file_path, existing_content, new_content)
2113
- )
2114
- file_content_mapping[file_path] = new_content
2115
- merged_blocks.append(
2116
- (file_path, head, update, similarity))
2117
- changes_made = True
2118
- else:
2119
- unmerged_blocks.append((file_path, head, update, similarity))
2120
-
2121
- if unmerged_blocks:
2122
- if self.args.request_id and not self.args.skip_events:
2123
- # collect unmerged blocks
2124
- event_data = []
2125
- for file_path, head, update, similarity in unmerged_blocks:
2126
- event_data.append(
2127
- {
2128
- "file_path": file_path,
2129
- "head": head,
2130
- "update": update,
2131
- "similarity": similarity,
2132
- }
2133
- )
2134
- return
2135
- logger.warning(f"发现 {len(unmerged_blocks)} 个未合并的代码块,更改将不会应用,请手动检查这些代码块后重试。")
2136
- self._print_unmerged_blocks(unmerged_blocks)
2137
- return
2138
-
2139
- # lint check
2140
- for file_path, new_content in file_content_mapping.items():
2141
- if file_path.endswith(".py"):
2142
- pylint_passed, error_message = self.run_pylint(new_content)
2143
- if not pylint_passed:
2144
- logger.warning(f"代码文件 {file_path} 的 Pylint 检查未通过,本次更改未应用。错误信息: {error_message}")
2145
-
2146
- if changes_made and not force_skip_git and not self.args.skip_commit:
2147
- try:
2148
- commit_changes(self.args.source_dir, f"auto_coder_pre_{file_name}_{md5}")
2149
- except Exception as e:
2150
- logger.error(
2151
- self.git_require_msg(
2152
- source_dir=self.args.source_dir, error=str(e))
2153
- )
2154
- return
2155
- # Now, apply the changes
2156
- for file_path, new_content in file_content_mapping.items():
2157
- os.makedirs(os.path.dirname(file_path), exist_ok=True)
2158
- with open(file_path, "w") as f:
2159
- f.write(new_content)
2160
-
2161
- if self.args.request_id and not self.args.skip_events:
2162
- # collect modified files
2163
- event_data = []
2164
- for code in merged_blocks:
2165
- file_path, head, update, similarity = code
2166
- event_data.append(
2167
- {
2168
- "file_path": file_path,
2169
- "head": head,
2170
- "update": update,
2171
- "similarity": similarity,
2172
- }
2173
- )
2174
-
2175
- if changes_made:
2176
- if not force_skip_git and not self.args.skip_commit:
2177
- try:
2178
- commit_result = commit_changes(self.args.source_dir, f"auto_coder_{file_name}_{md5}")
2179
- git_print_commit_info(commit_result=commit_result)
2180
- except Exception as e:
2181
- logger.error(
2182
- self.git_require_msg(
2183
- source_dir=self.args.source_dir, error=str(e)
2184
- )
2185
- )
2186
- logger.info(
2187
- f"已在 {len(file_content_mapping.keys())} 个文件中合并更改,"
2188
- f"完成 {len(changes_to_make)}/{len(codes)} 个代码块。"
2189
- )
2190
- else:
2191
- logger.warning("未对任何文件进行更改。")
2192
-
2193
- def merge_code(self, generate_result: CodeGenerateResult, force_skip_git: bool = False):
2194
- result = self.choose_best_choice(generate_result)
2195
- self._merge_code(result.contents[0], force_skip_git)
2196
- return result
2197
-
2198
- @staticmethod
2199
- def _print_unmerged_blocks(unmerged_blocks: List[tuple]):
2200
- console.print(f"\n[bold red]未合并的代码块:[/bold red]")
2201
- for file_path, head, update, similarity in unmerged_blocks:
2202
- console.print(f"\n[bold blue]文件:[/bold blue] {file_path}")
2203
- console.print(
2204
- f"\n[bold green]搜索代码块(相似度:{similarity}):[/bold green]")
2205
- syntax = Syntax(head, "python", theme="monokai", line_numbers=True)
2206
- console.print(Panel(syntax, expand=False))
2207
- console.print("\n[bold yellow]替换代码块:[/bold yellow]")
2208
- syntax = Syntax(update, "python", theme="monokai",
2209
- line_numbers=True)
2210
- console.print(Panel(syntax, expand=False))
2211
- console.print(f"\n[bold red]未合并的代码块总数: {len(unmerged_blocks)}[/bold red]")
2212
-
2213
-
2214
- class BaseAction:
2215
- @staticmethod
2216
- def _get_content_length(content: str) -> int:
2217
- return len(content)
2218
-
2219
-
2220
- class ActionPyProject(BaseAction):
2221
- def __init__(self, llm: Optional[AutoLLM] = None) -> None:
2222
- self.args = args
2223
- self.llm = llm
2224
- self.pp = None
2225
-
2226
- def run(self):
2227
- if self.args.project_type != "py":
2228
- return False
2229
- pp = PyProject(llm=self.llm, args=args)
2230
- self.pp = pp
2231
- pp.run()
2232
- source_code = pp.output()
2233
- if self.llm:
2234
- source_code = build_index_and_filter_files(args=args, llm=self.llm, sources=pp.sources)
2235
- self.process_content(source_code)
2236
- return True
2237
-
2238
- def process_content(self, content: str):
2239
- # args = self.args
2240
- if self.args.execute and self.llm:
2241
- content_length = self._get_content_length(content)
2242
- if content_length > self.args.model_max_input_length:
2243
- logger.warning(
2244
- f"发送给模型的内容长度为 {content_length} 个 token(可能收集了过多文件),"
2245
- f"已超过最大输入长度限制 {self.args.model_max_input_length}。"
2246
- )
2247
-
2248
- if args.execute:
2249
- logger.info("正在自动生成代码...")
2250
- start_time = time.time()
2251
- # diff, strict_diff, editblock 是代码自动生成或合并的不同策略, 通常用于处理代码的变更或生成
2252
- # diff 模式,基于差异生成代码,生成最小的变更集,适用于局部优化,代码重构
2253
- # strict_diff 模式,严格验证差异,确保生成的代码符合规则,适用于代码审查,自动化测试
2254
- # editblock 模式,基于编辑块生成代码,支持较大范围的修改,适用于代码重构,功能扩展
2255
- if args.auto_merge == "editblock":
2256
- generate = CodeAutoGenerateEditBlock(llm=self.llm, action=self)
2257
- else:
2258
- generate = None
2259
-
2260
- if self.args.enable_multi_round_generate:
2261
- generate_result = generate.multi_round_run(query=args.query, source_content=content)
2262
- else:
2263
- generate_result = generate.single_round_run(query=args.query, source_content=content)
2264
- logger.info(f"代码生成完成,耗时 {time.time() - start_time:.2f} 秒")
2265
-
2266
- if args.auto_merge:
2267
- logger.info("正在自动合并代码...")
2268
- if args.auto_merge == "editblock":
2269
- code_merge = CodeAutoMergeEditBlock(llm=self.llm)
2270
- merge_result = code_merge.merge_code(generate_result=generate_result)
2271
- else:
2272
- merge_result = None
2273
-
2274
- content = merge_result.contents[0]
2275
- else:
2276
- content = generate_result.contents[0]
2277
- with open(args.target_file, "w") as file:
2278
- file.write(content)
2279
-
2280
-
2281
- class ActionSuffixProject(BaseAction):
2282
- def __init__(self, llm: Optional[AutoLLM] = None) -> None:
2283
- self.args = args
2284
- self.llm = llm
2285
- self.pp = None
2286
-
2287
- def run(self):
2288
- pp = SuffixProject(llm=self.llm, args=args)
2289
- self.pp = pp
2290
- pp.run()
2291
- source_code = pp.output()
2292
- if self.llm:
2293
- source_code = build_index_and_filter_files(args=args, llm=self.llm, sources=pp.sources)
2294
- self.process_content(source_code)
2295
-
2296
- def process_content(self, content: str):
2297
- if self.args.execute and self.llm:
2298
- content_length = self._get_content_length(content)
2299
- if content_length > self.args.model_max_input_length:
2300
- logger.warning(
2301
- f"发送给模型的内容长度为 {content_length} 个 token(可能收集了过多文件),"
2302
- f"已超过最大输入长度限制 {self.args.model_max_input_length}。"
2303
- )
2304
-
2305
- if args.execute:
2306
- logger.info("正在自动生成代码...")
2307
- start_time = time.time()
2308
- # diff, strict_diff, editblock 是代码自动生成或合并的不同策略, 通常用于处理代码的变更或生成
2309
- # diff 模式,基于差异生成代码,生成最小的变更集,适用于局部优化,代码重构
2310
- # strict_diff 模式,严格验证差异,确保生成的代码符合规则,适用于代码审查,自动化测试
2311
- # editblock 模式,基于编辑块生成代码,支持较大范围的修改,适用于代码重构,功能扩展
2312
- if args.auto_merge == "editblock":
2313
- generate = CodeAutoGenerateEditBlock(llm=self.llm, action=self)
2314
- else:
2315
- generate = None
2316
-
2317
- if self.args.enable_multi_round_generate:
2318
- generate_result = generate.multi_round_run(query=args.query, source_content=content)
2319
- else:
2320
- generate_result = generate.single_round_run(query=args.query, source_content=content)
2321
- logger.info(f"代码生成完成,耗时 {time.time() - start_time:.2f} 秒")
2322
-
2323
- if args.auto_merge:
2324
- logger.info("正在自动合并代码...")
2325
- if args.auto_merge == "editblock":
2326
- code_merge = CodeAutoMergeEditBlock(llm=self.llm)
2327
- merge_result = code_merge.merge_code(generate_result=generate_result)
2328
- else:
2329
- merge_result = None
2330
-
2331
- content = merge_result.contents[0]
2332
- else:
2333
- content = generate_result.contents[0]
2334
- with open(args.target_file, "w") as file:
2335
- file.write(content)
2336
-
2337
-
2338
- class Dispacher:
2339
- def __init__(self, llm: Optional[AutoLLM] = None):
2340
- self.args = args
2341
- self.llm = llm
2342
-
2343
- def dispach(self):
2344
- actions = [
2345
- ActionPyProject(llm=self.llm),
2346
- ActionSuffixProject(llm=self.llm)
2347
- ]
2348
- for action in actions:
2349
- if action.run():
2350
- return
2351
-
2352
-
2353
1330
  def prepare_chat_yaml():
2354
1331
  # auto_coder_main(["next", "chat_action"]) 准备聊天 yaml 文件
2355
1332
  actions_dir = os.path.join(args.source_dir, "actions")
@@ -2476,7 +1453,7 @@ def coding(query: str, llm: AutoLLM):
2476
1453
  f.write(yaml_content)
2477
1454
  convert_yaml_to_config(execute_file)
2478
1455
 
2479
- dispacher = Dispacher(llm)
1456
+ dispacher = Dispacher(args=args, llm=llm)
2480
1457
  dispacher.dispach()
2481
1458
  else:
2482
1459
  logger.warning("创建新的 YAML 文件失败。")
@@ -3609,6 +2586,12 @@ def main():
3609
2586
  print(f"{memory['mode']} [{MODES[memory['mode']]}]")
3610
2587
  else:
3611
2588
  memory["mode"] = conf
2589
+ elif user_input.startswith("/exclude_dirs"):
2590
+ dir_names = user_input[len("/exclude_dirs"):].strip().split(",")
2591
+ exclude_dirs(dir_names)
2592
+ elif user_input.startswith("/exclude_files"):
2593
+ query = user_input[len("/exclude_files"):].strip()
2594
+ exclude_files(query)
3612
2595
  else:
3613
2596
  command = user_input
3614
2597
  if user_input.startswith("/shell"):
@@ -3628,9 +2611,9 @@ def main():
3628
2611
  break
3629
2612
  except Exception as e:
3630
2613
  print(f"\033[91m发生异常:\033[0m \033[93m{type(e).__name__}\033[0m - {str(e)}")
3631
- # if runing_args and runing_args.debug:
3632
- import traceback
3633
- traceback.print_exc()
2614
+ if runing_args and runing_args.debug:
2615
+ import traceback
2616
+ traceback.print_exc()
3634
2617
 
3635
2618
 
3636
2619
  if __name__ == '__main__':