auto-coder 0.1.264__py3-none-any.whl → 0.1.266__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.

Potentially problematic release.


This version of auto-coder might be problematic. Click here for more details.

Files changed (53) hide show
  1. {auto_coder-0.1.264.dist-info → auto_coder-0.1.266.dist-info}/METADATA +2 -2
  2. {auto_coder-0.1.264.dist-info → auto_coder-0.1.266.dist-info}/RECORD +53 -51
  3. autocoder/agent/planner.py +4 -4
  4. autocoder/auto_coder.py +26 -21
  5. autocoder/auto_coder_server.py +7 -7
  6. autocoder/chat_auto_coder.py +150 -49
  7. autocoder/commands/auto_command.py +83 -5
  8. autocoder/commands/tools.py +48 -50
  9. autocoder/common/__init__.py +0 -1
  10. autocoder/common/auto_coder_lang.py +43 -3
  11. autocoder/common/code_auto_generate.py +3 -3
  12. autocoder/common/code_auto_generate_diff.py +3 -6
  13. autocoder/common/code_auto_generate_editblock.py +3 -3
  14. autocoder/common/code_auto_generate_strict_diff.py +3 -3
  15. autocoder/common/code_auto_merge_diff.py +37 -3
  16. autocoder/common/code_auto_merge_editblock.py +43 -1
  17. autocoder/common/code_auto_merge_strict_diff.py +39 -4
  18. autocoder/common/command_completer.py +3 -0
  19. autocoder/common/command_generator.py +24 -8
  20. autocoder/common/command_templates.py +2 -2
  21. autocoder/common/conf_import_export.py +105 -0
  22. autocoder/common/conf_validator.py +1 -1
  23. autocoder/common/files.py +41 -2
  24. autocoder/common/image_to_page.py +11 -11
  25. autocoder/common/index_import_export.py +38 -18
  26. autocoder/common/mcp_hub.py +3 -3
  27. autocoder/common/mcp_server.py +2 -2
  28. autocoder/common/shells.py +254 -13
  29. autocoder/common/stats_panel.py +126 -0
  30. autocoder/dispacher/actions/action.py +6 -18
  31. autocoder/dispacher/actions/copilot.py +2 -2
  32. autocoder/dispacher/actions/plugins/action_regex_project.py +1 -3
  33. autocoder/dispacher/actions/plugins/action_translate.py +1 -1
  34. autocoder/index/entry.py +2 -2
  35. autocoder/index/filter/normal_filter.py +1 -1
  36. autocoder/index/filter/quick_filter.py +1 -1
  37. autocoder/index/index.py +5 -5
  38. autocoder/models.py +2 -2
  39. autocoder/pyproject/__init__.py +5 -5
  40. autocoder/rag/cache/byzer_storage_cache.py +4 -4
  41. autocoder/rag/cache/file_monitor_cache.py +2 -2
  42. autocoder/rag/cache/simple_cache.py +4 -4
  43. autocoder/rag/long_context_rag.py +2 -2
  44. autocoder/regexproject/__init__.py +3 -2
  45. autocoder/suffixproject/__init__.py +3 -2
  46. autocoder/tsproject/__init__.py +3 -2
  47. autocoder/utils/conversation_store.py +1 -1
  48. autocoder/utils/operate_config_api.py +3 -3
  49. autocoder/version.py +1 -1
  50. {auto_coder-0.1.264.dist-info → auto_coder-0.1.266.dist-info}/LICENSE +0 -0
  51. {auto_coder-0.1.264.dist-info → auto_coder-0.1.266.dist-info}/WHEEL +0 -0
  52. {auto_coder-0.1.264.dist-info → auto_coder-0.1.266.dist-info}/entry_points.txt +0 -0
  53. {auto_coder-0.1.264.dist-info → auto_coder-0.1.266.dist-info}/top_level.txt +0 -0
@@ -27,6 +27,7 @@ from autocoder.utils.queue_communicate import (
27
27
  )
28
28
  import sys
29
29
  import io
30
+ from autocoder.common import files as files_utils
30
31
 
31
32
  @byzerllm.prompt()
32
33
  def detect_rm_command(command: str) -> Bool:
@@ -286,34 +287,33 @@ class AutoCommandTools:
286
287
 
287
288
  result = []
288
289
  try:
289
- with open(absolute_path, 'r', encoding='utf-8') as f:
290
- lines = f.readlines()
290
+
291
+ lines = files_utils.read_lines(absolute_path)
292
+ # Find all lines containing the keyword
293
+ keyword_lines = []
294
+ for i, line in enumerate(lines):
295
+ if keyword.lower() in line.lower():
296
+ keyword_lines.append(i)
297
+
298
+ # Process each keyword line and its surrounding range
299
+ processed_ranges = set()
300
+ for line_num in keyword_lines:
301
+ # Calculate range boundaries
302
+ start = max(0, line_num - before_size)
303
+ end = min(len(lines), line_num + after_size + 1)
291
304
 
292
- # Find all lines containing the keyword
293
- keyword_lines = []
294
- for i, line in enumerate(lines):
295
- if keyword.lower() in line.lower():
296
- keyword_lines.append(i)
305
+ # Check if this range overlaps with any previously processed range
306
+ range_key = (start, end)
307
+ if range_key in processed_ranges:
308
+ continue
297
309
 
298
- # Process each keyword line and its surrounding range
299
- processed_ranges = set()
300
- for line_num in keyword_lines:
301
- # Calculate range boundaries
302
- start = max(0, line_num - before_size)
303
- end = min(len(lines), line_num + after_size + 1)
304
-
305
- # Check if this range overlaps with any previously processed range
306
- range_key = (start, end)
307
- if range_key in processed_ranges:
308
- continue
309
-
310
- processed_ranges.add(range_key)
311
-
312
- # Format the content block
313
- content = f"##File: {absolute_path}\n"
314
- content += f"##Line: {start+1}-{end}\n\n"
315
- content += "".join(lines[start:end])
316
- result.append(content)
310
+ processed_ranges.add(range_key)
311
+
312
+ # Format the content block
313
+ content = f"##File: {absolute_path}\n"
314
+ content += f"##Line: {start+1}-{end}\n\n"
315
+ content += "".join(lines[start:end])
316
+ result.append(content)
317
317
 
318
318
  except Exception as e:
319
319
  v = f"Error reading file {absolute_path}: {str(e)}"
@@ -403,23 +403,22 @@ class AutoCommandTools:
403
403
  if path in os.path.join(root, file):
404
404
  absolute_path = os.path.join(root, file)
405
405
  break
406
-
407
- with open(absolute_path, "r", encoding="utf-8") as f:
408
- if path in file_line_ranges:
409
- # Read specific line ranges
410
- lines = f.readlines()
411
- filtered_lines = []
412
- for start, end in file_line_ranges[path]:
413
- # Adjust for 0-based indexing
414
- start = max(0, start - 1)
415
- end = min(len(lines), end)
416
- content = "".join(lines[start:end])
417
- filtered_lines.extend(f"##File: {absolute_path}\n##Line: {start}-{end}\n\n{content}")
418
- source_code = "".join(filtered_lines)
419
- else:
420
- # Read entire file if no range specified
421
- content = f.read()
422
- source_code = f"##File: {absolute_path}\n\n{content}"
406
+
407
+ if path in file_line_ranges:
408
+ # Read specific line ranges
409
+ lines = files_utils.read_lines(absolute_path)
410
+ filtered_lines = []
411
+ for start, end in file_line_ranges[path]:
412
+ # Adjust for 0-based indexing
413
+ start = max(0, start - 1)
414
+ end = min(len(lines), end)
415
+ content = "".join(lines[start:end])
416
+ filtered_lines.extend(f"##File: {absolute_path}\n##Line: {start}-{end}\n\n{content}")
417
+ source_code = "".join(filtered_lines)
418
+ else:
419
+ # Read entire file if no range specified
420
+ content = files_utils.read_file(absolute_path)
421
+ source_code = f"##File: {absolute_path}\n\n{content}"
423
422
 
424
423
  sc = SourceCode(module_name=absolute_path, source_code=source_code)
425
424
  source_code_str += f"{sc.source_code}\n\n"
@@ -510,13 +509,12 @@ class AutoCommandTools:
510
509
  for file in files:
511
510
  file_path = os.path.join(root, file)
512
511
  try:
513
- with open(file_path, "r", encoding="utf-8") as f:
514
- content = f.read()
515
- if keyword.lower() in content.lower():
516
- matched_files.append(file_path)
517
- # Limit to first 10 matches
518
- if len(matched_files) >= 10:
519
- break
512
+ content = files_utils.read_file(file_path)
513
+ if keyword.lower() in content.lower():
514
+ matched_files.append(file_path)
515
+ # Limit to first 10 matches
516
+ if len(matched_files) >= 10:
517
+ break
520
518
  except Exception:
521
519
  # Skip files that can't be read
522
520
  pass
@@ -6,7 +6,6 @@ import os
6
6
  import time
7
7
  from typing import List, Dict, Any, Optional, Union
8
8
 
9
-
10
9
  class SourceCode(pydantic.BaseModel):
11
10
  module_name: str
12
11
  source_code: str
@@ -3,6 +3,7 @@ from byzerllm.utils import format_str_jinja2
3
3
 
4
4
  MESSAGES = {
5
5
  "en": {
6
+ "invalid_file_pattern": "Invalid file pattern: {{file_pattern}}. e.g. regex://.*/package-lock\\.json",
6
7
  "config_validation_error": "Config validation error: {{error}}",
7
8
  "invalid_boolean_value": "Value '{{value}}' is not a valid boolean(true/false)",
8
9
  "invalid_integer_value": "Value '{{value}}' is not a valid integer",
@@ -116,6 +117,7 @@ MESSAGES = {
116
117
  "quick_filter_stats": "{{ model_names }} Quick filter completed in {{ elapsed_time }} seconds, input tokens: {{ input_tokens }}, output tokens: {{ output_tokens }}, input cost: {{ input_cost }}, output cost: {{ output_cost }} speed: {{ speed }} tokens/s",
117
118
  "upsert_file": "✅ Updated file: {{ file_path }}",
118
119
  "unmerged_blocks_title": "Unmerged Blocks",
120
+ "merged_blocks_title": "Merged Changes",
119
121
  "quick_filter_title": "{{ model_name }} is analyzing how to filter context...",
120
122
  "quick_filter_failed": "❌ Quick filter failed: {{ error }}. ",
121
123
  "unmerged_file_path": "File: {{file_path}}",
@@ -139,9 +141,42 @@ MESSAGES = {
139
141
  "conversation_pruning_start": "⚠️ Conversation pruning started, total tokens: {{total_tokens}}, safe zone: {{safe_zone}}",
140
142
  "invalid_file_number": "⚠️ Invalid file number {{file_number}}, total files: {{total_files}}",
141
143
  "all_merge_results_failed": "⚠️ All merge attempts failed, returning first candidate",
142
- "only_one_merge_result_success": "✅ Only one merge result succeeded, returning that candidate"
143
- },
144
+ "only_one_merge_result_success": "✅ Only one merge result succeeded, returning that candidate",
145
+ "conf_import_success": "Successfully imported configuration: {{path}}",
146
+ "conf_export_success": "Successfully exported configuration: {{path}}",
147
+ "conf_import_error": "Error importing configuration: {{error}}",
148
+ "conf_export_error": "Error exporting configuration: {{error}}",
149
+ "conf_import_invalid_format": "Invalid import configuration format, expected 'key:value'",
150
+ "conf_export_invalid_format": "Invalid export configuration format, expected 'key:value'",
151
+ "conf_import_file_not_found": "Import configuration file not found: {{file_path}}",
152
+ "conf_export_file_not_found": "Export configuration file not found: {{file_path}}",
153
+ "conf_import_file_empty": "Import configuration file is empty: {{file_path}}",
154
+ "conf_export_file_empty": "Export configuration file is empty: {{file_path}}",
155
+ "generated_shell_script": "Generated Shell Script",
156
+ "confirm_execute_shell_script": "Do you want to execute this shell script?",
157
+ "shell_script_not_executed": "Shell script was not executed",
158
+ "conf_not_found": "Configuration file not found: {{path}}",
159
+ "index_export_success": "Index exported successfully: {{path}}",
160
+ "index_import_success": "Index imported successfully: {{path}}",
161
+ "edits_title": "edits",
162
+ "diff_blocks_title":"diff blocks"
163
+ },
144
164
  "zh": {
165
+ "invalid_file_pattern": "无效的文件模式: {{file_pattern}}. 例如: regex://.*/package-lock\\.json",
166
+ "conf_not_found": "未找到配置文件: {{path}}",
167
+ "conf_import_success": "成功导入配置: {{path}}",
168
+ "conf_export_success": "成功导出配置: {{path}}",
169
+ "conf_import_error": "导入配置出错: {{error}}",
170
+ "conf_export_error": "导出配置出错: {{error}}",
171
+ "conf_import_invalid_format": "导入配置格式无效, 应为 'key:value' 格式",
172
+ "conf_export_invalid_format": "导出配置格式无效, 应为 'key:value' 格式",
173
+ "conf_import_file_not_found": "未找到导入配置文件: {{file_path}}",
174
+ "conf_export_file_not_found": "未找到导出配置文件: {{file_path}}",
175
+ "conf_import_file_empty": "导入配置文件为空: {{file_path}}",
176
+ "conf_export_file_empty": "导出配置文件为空: {{file_path}}",
177
+ "generated_shell_script": "生成的 Shell 脚本",
178
+ "confirm_execute_shell_script": "您要执行此 shell 脚本吗?",
179
+ "shell_script_not_executed": "Shell 脚本未执行",
145
180
  "config_validation_error": "配置验证错误: {{error}}",
146
181
  "invalid_boolean_value": "值 '{{value}}' 不是有效的布尔值(true/false)",
147
182
  "invalid_integer_value": "值 '{{value}}' 不是有效的整数",
@@ -241,6 +276,7 @@ MESSAGES = {
241
276
  "merge_success": "✅ 成功合并了 {{ num_files }} 个文件中的更改 {{ num_changes }}/{{ total_blocks }} 个代码块。",
242
277
  "no_changes_made": "⚠️ 未对任何文件进行更改。这个原因可能是因为coding函数生成的文本块格式有问题,导致无法合并进项目",
243
278
  "unmerged_blocks_title": "未合并代码块",
279
+ "merged_blocks_title": "合并的更改",
244
280
  "unmerged_file_path": "文件: {{file_path}}",
245
281
  "unmerged_search_block": "Search Block({{similarity}}):",
246
282
  "unmerged_replace_block": "Replace Block:",
@@ -276,7 +312,11 @@ MESSAGES = {
276
312
  "conversation_pruning_start": "⚠️ 对话长度 {{total_tokens}} tokens 超过安全阈值 {{safe_zone}},开始修剪对话。",
277
313
  "invalid_file_number": "⚠️ 无效的文件编号 {{file_number}},总文件数为 {{total_files}}",
278
314
  "all_merge_results_failed": "⚠️ 所有合并尝试都失败,返回第一个候选",
279
- "only_one_merge_result_success": "✅ 只有一个合并结果成功,返回该候选"
315
+ "only_one_merge_result_success": "✅ 只有一个合并结果成功,返回该候选",
316
+ "index_export_success": "索引导出成功: {{path}}",
317
+ "index_import_success": "索引导入成功: {{path}}",
318
+ "edits_title": "编辑块",
319
+ "diff_blocks_title": "差异块",
280
320
  }}
281
321
 
282
322
 
@@ -197,7 +197,7 @@ class CodeAutoGenerate:
197
197
  instruction=query, content=source_content
198
198
  )
199
199
 
200
- with open(self.args.target_file, "w") as file:
200
+ with open(self.args.target_file, "w",encoding="utf-8") as file:
201
201
  file.write(init_prompt)
202
202
 
203
203
  conversations = []
@@ -298,7 +298,7 @@ class CodeAutoGenerate:
298
298
 
299
299
  conversations = [{"role": "user", "content": init_prompt}]
300
300
 
301
- with open(self.args.target_file, "w") as file:
301
+ with open(self.args.target_file, "w",encoding="utf-8") as file:
302
302
  file.write(init_prompt)
303
303
 
304
304
  t = self.llm.chat_oai(conversations=conversations, llm_config=llm_config)
@@ -320,7 +320,7 @@ class CodeAutoGenerate:
320
320
 
321
321
  conversations.append({"role": "user", "content": "继续"})
322
322
 
323
- with open(self.args.target_file, "w") as file:
323
+ with open(self.args.target_file, "w",encoding="utf-8") as file:
324
324
  file.write("继续")
325
325
 
326
326
  t = self.llm.chat_oai(conversations=conversations, llm_config=llm_config)
@@ -315,10 +315,7 @@ class CodeAutoGenerateDiff:
315
315
  elif self.args.template == "auto_implement":
316
316
  init_prompt = self.auto_implement_function.prompt(
317
317
  instruction=query, content=source_content
318
- )
319
-
320
- with open(self.args.target_file, "w") as file:
321
- file.write(init_prompt)
318
+ )
322
319
 
323
320
  conversations = []
324
321
 
@@ -447,7 +444,7 @@ class CodeAutoGenerateDiff:
447
444
  # conversations.append({"role": "system", "content": sys_prompt.prompt()})
448
445
  conversations.append({"role": "user", "content": init_prompt})
449
446
 
450
- with open(self.args.target_file, "w") as file:
447
+ with open(self.args.target_file, "w",encoding="utf-8") as file:
451
448
  file.write(init_prompt)
452
449
 
453
450
  code_llm = self.llms[0]
@@ -467,7 +464,7 @@ class CodeAutoGenerateDiff:
467
464
 
468
465
  conversations.append({"role": "user", "content": "继续"})
469
466
 
470
- with open(self.args.target_file, "w") as file:
467
+ with open(self.args.target_file, "w",encoding="utf-8") as file:
471
468
  file.write("继续")
472
469
 
473
470
  t = code_llm.chat_oai(
@@ -418,7 +418,7 @@ class CodeAutoGenerateEditBlock:
418
418
  instruction=query, content=source_content
419
419
  )
420
420
 
421
- with open(self.args.target_file, "w") as file:
421
+ with open(self.args.target_file, "w",encoding="utf-8") as file:
422
422
  file.write(init_prompt)
423
423
 
424
424
  conversations = []
@@ -538,7 +538,7 @@ class CodeAutoGenerateEditBlock:
538
538
  # conversations.append({"role": "system", "content": sys_prompt.prompt()})
539
539
  conversations.append({"role": "user", "content": init_prompt})
540
540
 
541
- with open(self.args.target_file, "w") as file:
541
+ with open(self.args.target_file, "w",encoding="utf-8") as file:
542
542
  file.write(init_prompt)
543
543
 
544
544
  code_llm = self.llms[0]
@@ -558,7 +558,7 @@ class CodeAutoGenerateEditBlock:
558
558
 
559
559
  conversations.append({"role": "user", "content": "继续"})
560
560
 
561
- with open(self.args.target_file, "w") as file:
561
+ with open(self.args.target_file, "w",encoding="utf-8") as file:
562
562
  file.write("继续")
563
563
 
564
564
  t = code_llm.chat_oai(
@@ -287,7 +287,7 @@ class CodeAutoGenerateStrictDiff:
287
287
  instruction=query, content=source_content
288
288
  )
289
289
 
290
- with open(self.args.target_file, "w") as file:
290
+ with open(self.args.target_file, "w",encoding="utf-8") as file:
291
291
  file.write(init_prompt)
292
292
 
293
293
  conversations = []
@@ -417,7 +417,7 @@ class CodeAutoGenerateStrictDiff:
417
417
  # conversations.append({"role": "system", "content": sys_prompt.prompt()})
418
418
  conversations.append({"role": "user", "content": init_prompt})
419
419
 
420
- with open(self.args.target_file, "w") as file:
420
+ with open(self.args.target_file, "w",encoding="utf-8") as file:
421
421
  file.write(init_prompt)
422
422
 
423
423
  code_llm = self.llms[0]
@@ -437,7 +437,7 @@ class CodeAutoGenerateStrictDiff:
437
437
 
438
438
  conversations.append({"role": "user", "content": "继续"})
439
439
 
440
- with open(self.args.target_file, "w") as file:
440
+ with open(self.args.target_file, "w",encoding="utf-8") as file:
441
441
  file.write("继续")
442
442
 
443
443
  t = code_llm.chat_oai(
@@ -462,7 +462,7 @@ class CodeAutoMergeDiff:
462
462
  full_path = self.abs_root_path(path)
463
463
 
464
464
  if not os.path.exists(full_path):
465
- with open(full_path, "w",encoding="utf-8") as f:
465
+ with open(full_path, "w") as f:
466
466
  f.write("")
467
467
 
468
468
  content = FileUtils.read_file(full_path)
@@ -528,10 +528,41 @@ class CodeAutoMergeDiff:
528
528
  failed_blocks=failed_blocks
529
529
  )
530
530
 
531
+ def print_edits(self, edits: List[Tuple[str, List[str]]]):
532
+ """Print diffs for user review using rich library"""
533
+ from rich.syntax import Syntax
534
+ from rich.panel import Panel
535
+
536
+ # Group edits by file path
537
+ file_edits = {}
538
+ for path, hunk in edits:
539
+ if path not in file_edits:
540
+ file_edits[path] = []
541
+ file_edits[path].append(hunk)
542
+
543
+ # Generate formatted text for each file
544
+ formatted_text = ""
545
+ for path, hunks in file_edits.items():
546
+ formatted_text += f"##File: {path}\n"
547
+ for hunk in hunks:
548
+ formatted_text += "".join(hunk)
549
+ formatted_text += "\n"
550
+
551
+ # Print with rich panel
552
+ self.printer.print_in_terminal("edits_title", style="bold green")
553
+ self.printer.console.print(
554
+ Panel(
555
+ Syntax(formatted_text, "diff", theme="monokai"),
556
+ title="Edits",
557
+ border_style="green",
558
+ expand=False
559
+ )
560
+ )
561
+
531
562
  def _merge_code(self, content: str,force_skip_git:bool=False):
532
563
  total = 0
533
564
 
534
- file_content = open(self.args.file).read()
565
+ file_content = FileUtils.read_file(self.args.file)
535
566
  md5 = hashlib.md5(file_content.encode('utf-8')).hexdigest()
536
567
  # get the file name
537
568
  file_name = os.path.basename(self.args.file)
@@ -544,9 +575,12 @@ class CodeAutoMergeDiff:
544
575
  return
545
576
 
546
577
  edits = self.get_edits(content)
547
- self.apply_edits(edits)
578
+ self.apply_edits(edits)
548
579
 
549
580
  self.printer.print_in_terminal("files_merged_total", total=total)
550
581
  if not force_skip_git and not self.args.skip_commit:
551
582
  commit_result = git_utils.commit_changes(self.args.source_dir, f"auto_coder_{file_name}_{md5}\n{self.args.query}")
552
583
  git_utils.print_commit_info(commit_result=commit_result)
584
+ else:
585
+ # Print edits for review
586
+ self.print_edits(edits)
@@ -287,9 +287,10 @@ class CodeAutoMergeEditBlock:
287
287
  for path, content in file_content_mapping.items()],
288
288
  failed_blocks=failed_blocks
289
289
  )
290
+
290
291
 
291
292
  def _merge_code(self, content: str, force_skip_git: bool = False):
292
- file_content = open(self.args.file).read()
293
+ file_content = FileUtils.read_file(self.args.file)
293
294
  md5 = hashlib.md5(file_content.encode("utf-8")).hexdigest()
294
295
  file_name = os.path.basename(self.args.file)
295
296
 
@@ -391,6 +392,7 @@ class CodeAutoMergeEditBlock:
391
392
  style="red"
392
393
  )
393
394
  return
395
+
394
396
  # Now, apply the changes
395
397
  for file_path, new_content in file_content_mapping.items():
396
398
  os.makedirs(os.path.dirname(file_path), exist_ok=True)
@@ -431,10 +433,14 @@ class CodeAutoMergeEditBlock:
431
433
  self.git_require_msg(source_dir=self.args.source_dir, error=str(e)),
432
434
  style="red"
433
435
  )
436
+ else:
437
+ self.print_merged_blocks(merged_blocks)
438
+
434
439
  self.printer.print_in_terminal("merge_success",
435
440
  num_files=len(file_content_mapping.keys()),
436
441
  num_changes=len(changes_to_make),
437
442
  total_blocks=len(codes))
443
+
438
444
  else:
439
445
  self.printer.print_in_terminal("no_changes_made")
440
446
 
@@ -455,3 +461,39 @@ class CodeAutoMergeEditBlock:
455
461
  syntax = Syntax(update, "python", theme="monokai", line_numbers=True)
456
462
  self.printer.console.print(Panel(syntax, expand=False))
457
463
  self.printer.print_in_terminal("unmerged_blocks_total", num_blocks=len(unmerged_blocks), style="bold red")
464
+
465
+
466
+ def print_merged_blocks(self, merged_blocks: List[tuple]):
467
+ """Print search/replace blocks for user review using rich library"""
468
+ from rich.syntax import Syntax
469
+ from rich.panel import Panel
470
+
471
+ # Group blocks by file path
472
+ file_blocks = {}
473
+ for file_path, head, update, similarity in merged_blocks:
474
+ if file_path not in file_blocks:
475
+ file_blocks[file_path] = []
476
+ file_blocks[file_path].append((head, update, similarity))
477
+
478
+ # Generate formatted text for each file
479
+ formatted_text = ""
480
+ for file_path, blocks in file_blocks.items():
481
+ formatted_text += f"##File: {file_path}\n"
482
+ for head, update, similarity in blocks:
483
+ formatted_text += "<<<<<<< SEARCH\n"
484
+ formatted_text += head + "\n"
485
+ formatted_text += "=======\n"
486
+ formatted_text += update + "\n"
487
+ formatted_text += ">>>>>>> REPLACE\n"
488
+ formatted_text += "\n"
489
+
490
+ # Print with rich panel
491
+ self.printer.print_in_terminal("merged_blocks_title", style="bold green")
492
+ self.printer.console.print(
493
+ Panel(
494
+ Syntax(formatted_text, "diff", theme="monokai"),
495
+ title="Merged Changes",
496
+ border_style="green",
497
+ expand=False
498
+ )
499
+ )
@@ -10,6 +10,7 @@ import hashlib
10
10
  from pathlib import Path
11
11
  from autocoder.common.types import CodeGenerateResult, MergeCodeWithoutEffect
12
12
  from autocoder.common.code_modification_ranker import CodeModificationRanker
13
+ from autocoder.common import files as FileUtils
13
14
 
14
15
  class PathAndCode(pydantic.BaseModel):
15
16
  path: str
@@ -195,8 +196,7 @@ class CodeAutoMergeStrictDiff:
195
196
  continue
196
197
 
197
198
  if full_path not in file_content_mapping:
198
- with open(full_path, "r") as f:
199
- file_content_mapping[full_path] = f.read()
199
+ file_content_mapping[full_path] = FileUtils.read_file(full_path)
200
200
 
201
201
  try:
202
202
  import patch
@@ -221,10 +221,41 @@ class CodeAutoMergeStrictDiff:
221
221
  failed_blocks=failed_blocks
222
222
  )
223
223
 
224
+ def print_diff_blocks(self, diff_blocks: List[PathAndCode]):
225
+ """Print diff blocks for user review using rich library"""
226
+ from rich.syntax import Syntax
227
+ from rich.panel import Panel
228
+
229
+ # Group blocks by file path
230
+ file_blocks = {}
231
+ for block in diff_blocks:
232
+ if block.path not in file_blocks:
233
+ file_blocks[block.path] = []
234
+ file_blocks[block.path].append(block.content)
235
+
236
+ # Generate formatted text for each file
237
+ formatted_text = ""
238
+ for path, contents in file_blocks.items():
239
+ formatted_text += f"##File: {path}\n"
240
+ for content in contents:
241
+ formatted_text += content + "\n"
242
+ formatted_text += "\n"
243
+
244
+ # Print with rich panel
245
+ self.printer.print_in_terminal("diff_blocks_title", style="bold green")
246
+ self.printer.console.print(
247
+ Panel(
248
+ Syntax(formatted_text, "diff", theme="monokai"),
249
+ title="Diff Blocks",
250
+ border_style="green",
251
+ expand=False
252
+ )
253
+ )
254
+
224
255
  def _merge_code(self, content: str, force_skip_git: bool = False):
225
256
  total = 0
226
257
 
227
- file_content = open(self.args.file).read()
258
+ file_content = FileUtils.read_file(self.args.file)
228
259
  md5 = hashlib.md5(file_content.encode('utf-8')).hexdigest()
229
260
  # get the file name
230
261
  file_name = os.path.basename(self.args.file)
@@ -236,7 +267,8 @@ class CodeAutoMergeStrictDiff:
236
267
  self.printer.print_in_terminal("git_init_required", style="red", source_dir=self.args.source_dir, error=str(e))
237
268
  return
238
269
 
239
- diff_blocks = self.parse_diff_block(content)
270
+ diff_blocks = self.parse_diff_block(content)
271
+
240
272
  for diff_blocks in diff_blocks:
241
273
  path = diff_blocks.path
242
274
  content = diff_blocks.content
@@ -255,6 +287,9 @@ class CodeAutoMergeStrictDiff:
255
287
  if not force_skip_git and not self.args.skip_commit:
256
288
  commit_result = git_utils.commit_changes(self.args.source_dir, f"auto_coder_{file_name}_{md5}\n{self.args.query}")
257
289
  git_utils.print_commit_info(commit_result=commit_result)
290
+ else:
291
+ # Print diff blocks for review
292
+ self.print_diff_blocks(diff_blocks)
258
293
 
259
294
  @byzerllm.prompt(render="jinja2")
260
295
  def git_require_msg(self, source_dir: str, error: str) -> str:
@@ -43,6 +43,9 @@ COMMANDS = {
43
43
  "/output_price": "",
44
44
  },
45
45
  "/auto": {
46
+ },
47
+ "/shell": {
48
+ "/chat": "",
46
49
  }
47
50
  }
48
51
 
@@ -4,6 +4,7 @@ from autocoder.utils.auto_coder_utils.chat_stream_out import stream_out
4
4
  from autocoder.common import detect_env
5
5
  from autocoder.common import shells
6
6
  from autocoder.common.printer import Printer
7
+ from typing import Dict,Union
7
8
 
8
9
  @byzerllm.prompt()
9
10
  def _generate_shell_script(user_input: str) -> str:
@@ -14,6 +15,11 @@ def _generate_shell_script(user_input: str) -> str:
14
15
  Python版本: {{ env_info.python_version }}
15
16
  终端类型: {{ env_info.shell_type }}
16
17
  终端编码: {{ env_info.shell_encoding }}
18
+
19
+ {%- if shell_type %}
20
+ 脚本类型:{{ shell_type }}
21
+ {%- endif %}
22
+
17
23
  {%- if env_info.conda_env %}
18
24
  Conda环境: {{ env_info.conda_env }}
19
25
  {%- endif %}
@@ -21,29 +27,39 @@ def _generate_shell_script(user_input: str) -> str:
21
27
  虚拟环境: {{ env_info.virtualenv }}
22
28
  {%- endif %}
23
29
 
24
- 根据用户的输入以及当前的操作系统和Shell类型生成合适的 shell 脚本,注意只能生成一个shell脚本,不要生成多个。
30
+ 根据用户的输入以及当前的操作系统和终端类型以及脚本类型生成脚本,
31
+ 注意只能生成一个shell脚本,不要生成多个。
25
32
 
26
33
  用户输入: {{ user_input }}
27
34
 
28
- 请生成一个适当的 shell 脚本来执行用户的请求。确保脚本是安全的,并且可以在当前Shell环境中运行。
35
+ 请生成一个适当的脚本来执行用户的请求。确保脚本是安全的,并且可以在当前Shell环境中运行。
29
36
  脚本应该包含必要的注释来解释每个步骤。
30
37
  脚本内容请用如下方式返回:
31
38
 
32
- ```shell
33
- # 你的 shell 脚本内容
39
+ ```script
40
+ # 你的 script 脚本内容
34
41
  ```
35
42
  """
36
- env_info = detect_env()
43
+ env_info = detect_env()
44
+ shell_type = "bash"
45
+ if shells.is_running_in_cmd():
46
+ shell_type = "cmd"
47
+ elif shells.is_running_in_powershell():
48
+ shell_type = "powershell"
37
49
  return {
38
50
  "env_info": env_info,
39
- "shell_type": shells.get_terminal_name(),
51
+ "shell_type": shell_type,
40
52
  "shell_encoding": shells.get_terminal_encoding()
41
53
  }
42
54
 
43
55
 
44
- def generate_shell_script(user_input: str, llm: byzerllm.ByzerLLM) -> str:
56
+ def generate_shell_script(user_input: str, llm: Union[byzerllm.ByzerLLM,byzerllm.SimpleByzerLLM]) -> str:
45
57
  # 获取 prompt 内容
46
58
  prompt = _generate_shell_script.prompt(user_input=user_input)
59
+ if llm.get_sub_client("chat_model"):
60
+ shell_llm = llm.get_sub_client("chat_model")
61
+ else:
62
+ shell_llm = llm
47
63
 
48
64
  # 构造对话上下文
49
65
  conversations = [{"role": "user", "content": prompt}]
@@ -52,7 +68,7 @@ def generate_shell_script(user_input: str, llm: byzerllm.ByzerLLM) -> str:
52
68
  printer = Printer()
53
69
  title = printer.get_message_from_key("generating_shell_script")
54
70
  result, _ = stream_out(
55
- llm.stream_chat_oai(conversations=conversations, delta_mode=True),
71
+ shell_llm.stream_chat_oai(conversations=conversations, delta_mode=True),
56
72
  model_name=llm.default_model_name,
57
73
  title=title
58
74
  )
@@ -139,7 +139,7 @@ def create_actions(source_dir:str,params:Dict[str,str]):
139
139
  "000_example": base_000_example.prompt(),
140
140
  }
141
141
  init_file_path = os.path.join(source_dir, "actions", "101_current_work.yml")
142
- with open(init_file_path, "w") as f:
142
+ with open(init_file_path, "w", encoding="utf-8") as f:
143
143
  f.write(init_command_template.prompt(source_dir=source_dir))
144
144
 
145
145
  for k,v in mapping.items():
@@ -152,7 +152,7 @@ def create_actions(source_dir:str,params:Dict[str,str]):
152
152
  if k == "000_example":
153
153
  file_path = os.path.join(source_dir, "actions", f"{k}.yml")
154
154
 
155
- with open(file_path, "w") as f:
155
+ with open(file_path, "w", encoding="utf-8") as f:
156
156
  f.write(v)
157
157
 
158
158
  @byzerllm.prompt()