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

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: auto-coder
3
- Version: 0.1.243
3
+ Version: 0.1.244
4
4
  Summary: AutoCoder: AutoCoder
5
5
  Author: allwefantasy
6
6
  Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
@@ -26,7 +26,7 @@ Requires-Dist: tabulate
26
26
  Requires-Dist: jupyter-client
27
27
  Requires-Dist: prompt-toolkit
28
28
  Requires-Dist: tokenizers
29
- Requires-Dist: byzerllm[saas] >=0.1.159
29
+ Requires-Dist: byzerllm[saas] >=0.1.160
30
30
  Requires-Dist: patch
31
31
  Requires-Dist: diff-match-patch
32
32
  Requires-Dist: GitPython
@@ -1,17 +1,17 @@
1
1
  autocoder/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- autocoder/auto_coder.py,sha256=iQeyrg5L87IgBLFJLZLIJetHFeMuT2uslRlzo_Vjq9s,61829
2
+ autocoder/auto_coder.py,sha256=ghIEJ8NzcFr77fosIZhKxI5F55EZoU6DMHFFpw5OFso,62671
3
3
  autocoder/auto_coder_lang.py,sha256=Rtupq6N3_HT7JRhDKdgCBcwRaiAnyCOR_Gsp4jUomrI,3229
4
4
  autocoder/auto_coder_rag.py,sha256=illKgzP2bv-Tq50ujsofJnOHdI4pzr0ALtfR8NHHWdQ,22351
5
5
  autocoder/auto_coder_rag_client_mcp.py,sha256=WV7j5JUiQge0x4-B7Hp5-pSAFXLbvLpzQMcCovbauIM,6276
6
6
  autocoder/auto_coder_rag_mcp.py,sha256=-RrjNwFaS2e5v8XDIrKR-zlUNUE8UBaeOtojffBrvJo,8521
7
7
  autocoder/auto_coder_server.py,sha256=XU9b4SBH7zjPPXaTWWHV4_zJm-XYa6njuLQaplYJH_c,20290
8
8
  autocoder/benchmark.py,sha256=Ypomkdzd1T3GE6dRICY3Hj547dZ6_inqJbBJIp5QMco,4423
9
- autocoder/chat_auto_coder.py,sha256=s9uMjDQQawXEsq171GO7SVMO4fDjWd4xWT0KYO7nRp4,105295
10
- autocoder/chat_auto_coder_lang.py,sha256=V-VIieyKF5cwlK448B1V2LUbTdrU03tfgDrOk2aBvFk,14891
9
+ autocoder/chat_auto_coder.py,sha256=RJC3Fh_9ywkllETWFgxapHK3aY0M9ORhRON9PNCiKZs,105338
10
+ autocoder/chat_auto_coder_lang.py,sha256=pdGkEMR96TvjYIzgWWB51LYmBYSrS_pO3sjGH-muuDI,15052
11
11
  autocoder/command_args.py,sha256=9aYJ-AmPxP1sQh6ciw04FWHjSn31f2W9afXFwo8wgx4,30441
12
12
  autocoder/lang.py,sha256=U6AjVV8Rs1uLyjFCZ8sT6WWuNUxMBqkXXIOs4S120uk,14511
13
- autocoder/models.py,sha256=FlBrF6HhGao_RiCSgYhCmP7vs0KlG4hI_BI6dyZiL9s,5292
14
- autocoder/version.py,sha256=u0hWeuFclX3Z9nFe5oFsCdX854VeUHOs69Ggv1pvBvk,23
13
+ autocoder/models.py,sha256=8Tabnz3wlVswaeTpfPxnBhN55enP_5sdAJIx4Z79nOo,5342
14
+ autocoder/version.py,sha256=nyjL74IBCp8tNAtI88ddAEba5PkcN70aC5Kdv_PKe7w,23
15
15
  autocoder/agent/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
16
  autocoder/agent/auto_demand_organizer.py,sha256=NWSAEsEk94vT3lGjfo25kKLMwYdPcpy9e-i21txPasQ,6942
17
17
  autocoder/agent/auto_filegroup.py,sha256=CW7bqp0FW1GIEMnl-blyAc2UGT7O9Mom0q66ITz1ckM,6635
@@ -28,7 +28,7 @@ autocoder/common/__init__.py,sha256=2isE_u4VgfogwmcUCnFcussVFlzeNOLHDMFm5z_axbU,
28
28
  autocoder/common/anything2images.py,sha256=0ILBbWzY02M-CiWB-vzuomb_J1hVdxRcenAfIrAXq9M,25283
29
29
  autocoder/common/anything2img.py,sha256=4TREa-sOA-iargieUy7MpyCYVUE-9Mmq0wJtwomPqnE,7662
30
30
  autocoder/common/audio.py,sha256=Kn9nWKQddWnUrAz0a_ZUgjcu4VUU_IcZBigT7n3N3qc,7439
31
- autocoder/common/auto_coder_lang.py,sha256=9FBNhcl6Do4ICh-klevYsCTsDuy5kD99r8EE5Gs1QoM,12592
31
+ autocoder/common/auto_coder_lang.py,sha256=rIr4WJCUHgwkUkGwAaAiEd5kNx1nYRvVArbISbCfXp0,13248
32
32
  autocoder/common/buildin_tokenizer.py,sha256=L7d5t39ZFvUd6EoMPXUhYK1toD0FHlRH1jtjKRGokWU,1236
33
33
  autocoder/common/chunk_validation.py,sha256=BrR_ZWavW8IANuueEE7hS8NFAwEvm8TX34WnPx_1hs8,3030
34
34
  autocoder/common/cleaner.py,sha256=NU72i8C6o9m0vXExab7nao5bstBUsfJFcj11cXa9l4U,1089
@@ -46,7 +46,7 @@ autocoder/common/command_completer.py,sha256=SSeb8MDH0JPvfdyW-S2uaHnui4VBDfSQvQP
46
46
  autocoder/common/command_generator.py,sha256=v4LmU7sO-P7jEZIXCWHUC6P-vT7AvBi_x_PTwCqBAE8,1323
47
47
  autocoder/common/command_templates.py,sha256=mnB3n8i0yjH1mqzyClEg8Wpr9VbZV44kxky66Zu6OJY,8557
48
48
  autocoder/common/const.py,sha256=eTjhjh4Aj4CUzviJ81jaf3Y5cwqsLATySn2wJxaS6RQ,2911
49
- autocoder/common/files.py,sha256=uGpfKASYwIncK_Vt_e_FOjFlO5VyAQOnRJe2SFdSWrg,877
49
+ autocoder/common/files.py,sha256=CguxG9digkWBJpRaILErZmL_G5ryPRahPmPFWGB7X18,1973
50
50
  autocoder/common/git_utils.py,sha256=btK45sxvfm4tX3fBRNUPRZoGQuZuOEQrWSAwLy1yoLw,23095
51
51
  autocoder/common/image_to_page.py,sha256=O0cNO_vHHUP-fP4GXiVojShmNqkPnZXeIyiY1MRLpKg,13936
52
52
  autocoder/common/interpreter.py,sha256=62-dIakOunYB4yjmX8SHC0Gdy2h8NtxdgbpdqRZJ5vk,2833
@@ -79,7 +79,7 @@ autocoder/dispacher/actions/plugins/action_translate.py,sha256=nVAtRSQpdGNmZxg1R
79
79
  autocoder/index/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
80
80
  autocoder/index/entry.py,sha256=KJaxqtaKgL27w8-j7OiAqI0anPpmrJSl7PkfeVF2ipE,11713
81
81
  autocoder/index/for_command.py,sha256=BFvljE4t6VaMBGboZAuhUCzVK0EitCy_n5D_7FEnihw,3204
82
- autocoder/index/index.py,sha256=8AcaELR1FS___7VlNyxPnJsDVQ4wjORbqXvcA6TifCE,20337
82
+ autocoder/index/index.py,sha256=VjfcBYHywU4tjQTA7mpHfzRM8nBPhPHrUnkuBbsj6do,20409
83
83
  autocoder/index/symbols_utils.py,sha256=CjcjUVajmJZB75Ty3a7kMv1BZphrm-tIBAdOJv6uo-0,2037
84
84
  autocoder/index/types.py,sha256=a2s_KV5FJlq7jqA2ELSo9E1sjuLwDB-JJYMhSpzBAhU,596
85
85
  autocoder/index/filter/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -119,7 +119,7 @@ autocoder/rag/stream_event/event_writer.py,sha256=l7kq_LnDDE8E5dZ-73C7J2MgzSL7Wr
119
119
  autocoder/rag/stream_event/types.py,sha256=rtLwOE8rShmi1dJdxyBpAV5ZjLBGG9vptMiSzMxGuIA,318
120
120
  autocoder/regex_project/__init__.py,sha256=EBZeCL5ORyD_9_5u_UuG4s7XtpXOu0y1sWDmxWFtufE,6781
121
121
  autocoder/regexproject/__init__.py,sha256=cEr-ZOaQjLD5sx7T7F2DhD5ips03HcJ02rded9EpSXc,9693
122
- autocoder/suffixproject/__init__.py,sha256=cmP54Y01ditZ83tiJqw5wle0I-uJBC0aZbZ7lYNSVO8,11080
122
+ autocoder/suffixproject/__init__.py,sha256=VcXjUbGf3uQrpoqVCItDvGG9DoeHJ_qEmghKwrVNw9w,11058
123
123
  autocoder/tsproject/__init__.py,sha256=yloVzkGLnbTd4Hcj9fMO-rcjNTTx4wI3Ga41LWOSYrY,11747
124
124
  autocoder/utils/__init__.py,sha256=KtcGElFNBgZPF7dEL8zF9JpXkCAjoyDrzaREJBhJrcs,994
125
125
  autocoder/utils/_markitdown.py,sha256=RU88qn4eZfYIy0GDrPxlI8oYXIypbi63VRJjdlnE0VU,47431
@@ -134,15 +134,15 @@ autocoder/utils/print_table.py,sha256=ZMRhCA9DD0FUfKyJBWd5bDdj1RrtPtgOMWSJwtvZcL
134
134
  autocoder/utils/queue_communicate.py,sha256=buyEzdvab1QA4i2QKbq35rG5v_9x9PWVLWWMTznWcYM,6832
135
135
  autocoder/utils/request_event_queue.py,sha256=r3lo5qGsB1dIjzVQ05dnr0z_9Z3zOkBdP1vmRciKdi4,2095
136
136
  autocoder/utils/request_queue.py,sha256=nwp6PMtgTCiuwJI24p8OLNZjUiprC-TsefQrhMI-yPE,3889
137
- autocoder/utils/rest.py,sha256=opE_kBEdNQdxh350M5lUTMk5TViRfpuKP_qWc0B1lks,8861
137
+ autocoder/utils/rest.py,sha256=hLBhr78y-WVnV0oQf9Rxc22EwqF78KINkScvYa1MuYA,6435
138
138
  autocoder/utils/tests.py,sha256=BqphrwyycGAvs-5mhH8pKtMZdObwhFtJ5MC_ZAOiLq8,1340
139
139
  autocoder/utils/types.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
140
140
  autocoder/utils/auto_coder_utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
141
- autocoder/utils/auto_coder_utils/chat_stream_out.py,sha256=fcXusKEUKMu8WY9Y1_JL5aPkC-soKFxQcFAKThrNZoQ,13338
141
+ autocoder/utils/auto_coder_utils/chat_stream_out.py,sha256=yzh3QYJdYC1CPcdhSTn0fmFHtqV8iSXeH0drV3HjN5A,9053
142
142
  autocoder/utils/chat_auto_coder_utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
143
- auto_coder-0.1.243.dist-info/LICENSE,sha256=HrhfyXIkWY2tGFK11kg7vPCqhgh5DcxleloqdhrpyMY,11558
144
- auto_coder-0.1.243.dist-info/METADATA,sha256=zq_UlYzkagreMYmIrWVkCmf43Zr3_jIX1w577MlSXQE,2616
145
- auto_coder-0.1.243.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
146
- auto_coder-0.1.243.dist-info/entry_points.txt,sha256=0nzHtHH4pNcM7xq4EBA2toS28Qelrvcbrr59GqD_0Ak,350
147
- auto_coder-0.1.243.dist-info/top_level.txt,sha256=Jqc0_uJSw2GwoFQAa9iJxYns-2mWla-9ok_Y3Gcznjk,10
148
- auto_coder-0.1.243.dist-info/RECORD,,
143
+ auto_coder-0.1.244.dist-info/LICENSE,sha256=HrhfyXIkWY2tGFK11kg7vPCqhgh5DcxleloqdhrpyMY,11558
144
+ auto_coder-0.1.244.dist-info/METADATA,sha256=EVj3424BKEVPU6GAnoHC-NQ4J2VQFT7kVt3ANVby28A,2616
145
+ auto_coder-0.1.244.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
146
+ auto_coder-0.1.244.dist-info/entry_points.txt,sha256=0nzHtHH4pNcM7xq4EBA2toS28Qelrvcbrr59GqD_0Ak,350
147
+ auto_coder-0.1.244.dist-info/top_level.txt,sha256=Jqc0_uJSw2GwoFQAa9iJxYns-2mWla-9ok_Y3Gcznjk,10
148
+ auto_coder-0.1.244.dist-info/RECORD,,
autocoder/auto_coder.py CHANGED
@@ -692,6 +692,23 @@ def main(input_args: Optional[List[str]] = None):
692
692
  )
693
693
  llm.setup_sub_client("vl_model", vl_model)
694
694
 
695
+ if args.index_model:
696
+ model_name = args.index_model.strip()
697
+ model_info = models_module.get_model_by_name(model_name)
698
+ index_model = byzerllm.SimpleByzerLLM(default_model_name=model_name)
699
+ index_model.deploy(
700
+ model_path="",
701
+ pretrained_model_type="saas/openai",
702
+ udf_name=model_name,
703
+ infer_params={
704
+ "saas.base_url": model_info["base_url"],
705
+ "saas.api_key": model_info["api_key"],
706
+ "saas.model": model_info["model_name"],
707
+ "saas.is_reasoning": model_info["is_reasoning"]
708
+ }
709
+ )
710
+ llm.setup_sub_client("index_model", index_model)
711
+
695
712
  if args.sd_model:
696
713
  model_name = args.sd_model.strip()
697
714
  model_info = models_module.get_model_by_name(model_name)
@@ -1605,8 +1605,8 @@ def code_next(query: str):
1605
1605
  if os.path.exists(temp_yaml):
1606
1606
  os.remove(temp_yaml)
1607
1607
 
1608
- llm = byzerllm.ByzerLLM.from_default_model(
1609
- args.inference_model or args.model)
1608
+ product_mode = conf.get("product_mode", "lite")
1609
+ llm = get_single_llm(args.chat_model or args.model, product_mode=product_mode)
1610
1610
 
1611
1611
  auto_guesser = AutoGuessQuery(
1612
1612
  llm=llm, project_dir=os.getcwd(), skip_diff=True)
@@ -1,4 +1,5 @@
1
1
  import locale
2
+ from byzerllm.utils import format_str_jinja2
2
3
 
3
4
  MESSAGES = {
4
5
  "en": {
@@ -236,3 +237,7 @@ def get_system_language():
236
237
  def get_message(key):
237
238
  lang = get_system_language()
238
239
  return MESSAGES.get(lang, MESSAGES['en']).get(key, MESSAGES['en'][key])
240
+
241
+
242
+ def get_message_with_format(msg_key: str, **kwargs):
243
+ return format_str_jinja2(get_message(msg_key), **kwargs)
@@ -1,9 +1,13 @@
1
1
  import locale
2
+ from byzerllm.utils import format_str_jinja2
2
3
 
3
4
  MESSAGES = {
4
- "en": {
5
+ "en": {
6
+ "model_not_found": "Model {{model_name}} not found",
5
7
  "new_session_started": "New session started. Previous chat history has been archived.",
6
8
  "memory_save_success": "✅ Saved to your memory",
9
+ "file_decode_error": "Failed to decode file: {{file_path}}. Tried encodings: {{encodings}}",
10
+ "file_write_error": "Failed to write file: {{file_path}}. Error: {{error}}",
7
11
  "index_file_too_large": "⚠️ File {{ file_path }} is too large ({{ file_size }} > {{ max_length }}), splitting into chunks...",
8
12
  "index_update_success": "✅ Successfully updated index for {{ file_path }} (md5: {{ md5 }}) in {{ duration }}s",
9
13
  "index_build_error": "❌ Error building index for {{ file_path }}: {{ error }}",
@@ -73,8 +77,11 @@ MESSAGES = {
73
77
  "git_init_required": "⚠️ auto_merge only applies to git repositories.\n\nPlease try using git init in the source directory:\n\n```shell\ncd {{ source_dir }}\ngit init.\n```\n\nThen run auto - coder again.\nError: {{ error }}"
74
78
  },
75
79
  "zh": {
80
+ "model_not_found": "未找到模型: {{model_name}}",
76
81
  "new_session_started": "新会话已开始。之前的聊天历史已存档。",
77
82
  "memory_save_success": "✅ 已保存到您的记忆中",
83
+ "file_decode_error": "无法解码文件: {{file_path}}。尝试的编码: {{encodings}}",
84
+ "file_write_error": "无法写入文件: {{file_path}}. 错误: {{error}}",
78
85
  "index_file_too_large": "⚠️ 文件 {{ file_path }} 过大 ({{ file_size }} > {{ max_length }}), 正在分块处理...",
79
86
  "index_update_success": "✅ 成功更新 {{ file_path }} 的索引 (md5: {{ md5 }}), 耗时 {{ duration }} 秒",
80
87
  "index_build_error": "❌ 构建 {{ file_path }} 索引时出错: {{ error }}",
@@ -156,3 +163,6 @@ def get_system_language():
156
163
  def get_message(key):
157
164
  lang = get_system_language()
158
165
  return MESSAGES.get(lang, MESSAGES['en']).get(key, MESSAGES['en'][key])
166
+
167
+ def get_message_with_format(msg_key: str, **kwargs):
168
+ return format_str_jinja2(get_message(msg_key), **kwargs)
autocoder/common/files.py CHANGED
@@ -1,3 +1,6 @@
1
+ from autocoder.common.auto_coder_lang import get_message_with_format
2
+ from typing import List, Dict, Union
3
+
1
4
  def read_file(file_path):
2
5
  """Read a file with automatic encoding detection.
3
6
 
@@ -23,4 +26,33 @@ def read_file(file_path):
23
26
  except UnicodeDecodeError:
24
27
  continue
25
28
 
26
- raise ValueError(f"无法解码文件: {file_path}。尝试的编码: {', '.join(encodings)}")
29
+ raise ValueError(get_message_with_format("file_decode_error",
30
+ file_path=file_path,
31
+ encodings=", ".join(encodings)))
32
+
33
+
34
+
35
+ def save_file(file_path: str, content: Union[str, List[str]]) -> None:
36
+ """Save content to a file using UTF-8 encoding.
37
+
38
+ Args:
39
+ file_path (str): Path to the file to write
40
+ content (Union[str, List[str]]): Content to write to the file.
41
+ Can be a string or list of strings (will be joined with newlines)
42
+
43
+ Raises:
44
+ IOError: If the file cannot be written
45
+ TypeError: If content is neither str nor List[str]
46
+ """
47
+ try:
48
+ with open(file_path, 'w', encoding='utf-8') as f:
49
+ if isinstance(content, str):
50
+ f.write(content)
51
+ elif isinstance(content, list):
52
+ f.write('\n'.join(content))
53
+ else:
54
+ raise TypeError("Content must be either str or List[str]")
55
+ except IOError as e:
56
+ raise IOError(get_message_with_format("file_write_error",
57
+ file_path=file_path,
58
+ error=str(e)))
autocoder/index/index.py CHANGED
@@ -130,6 +130,7 @@ class IndexManager:
130
130
  如果有符号,按如下格式返回:
131
131
 
132
132
  ```
133
+ 用途:主要用于提供自动实现函数模板的功能。
133
134
  {符号类型}: {符号名称}, {符号名称}, ...
134
135
  ```
135
136
 
autocoder/models.py CHANGED
@@ -36,6 +36,22 @@ default_models_list = [
36
36
  }
37
37
  ]
38
38
 
39
+ def process_api_key_path(base_url: str) -> str:
40
+ """
41
+ 从 base_url 中提取 host 部分并处理特殊字符
42
+ 例如: https://api.example.com:8080/v1 -> api.example.com_8080
43
+ """
44
+ if not base_url:
45
+ return ""
46
+
47
+ parsed = urlparse(base_url)
48
+ host = parsed.netloc
49
+
50
+ # 将冒号替换为下划线
51
+ host = host.replace(":", "_")
52
+
53
+ return host
54
+
39
55
  def load_models() -> List[Dict]:
40
56
  """
41
57
  Load models from ~/.auto-coder/keys/models.json and merge with default_models_list.
@@ -73,7 +89,7 @@ def load_models() -> List[Dict]:
73
89
  api_key_file = os.path.join(api_key_dir, model["api_key_path"])
74
90
  if os.path.exists(api_key_file):
75
91
  with open(api_key_file, "r") as f:
76
- model["api_key"] = f.read()
92
+ model["api_key"] = f.read()
77
93
  return target_models
78
94
 
79
95
  def save_models(models: List[Dict]) -> None:
@@ -85,22 +101,6 @@ def save_models(models: List[Dict]) -> None:
85
101
  json.dump(models, f, indent=2, ensure_ascii=False)
86
102
 
87
103
 
88
- def process_api_key_path(base_url: str) -> str:
89
- """
90
- 从 base_url 中提取 host 部分并处理特殊字符
91
- 例如: https://api.example.com:8080/v1 -> api.example.com_8080
92
- """
93
- if not base_url:
94
- return ""
95
-
96
- parsed = urlparse(base_url)
97
- host = parsed.netloc
98
-
99
- # 将冒号替换为下划线
100
- host = host.replace(":", "_")
101
-
102
- return host
103
-
104
104
  def get_model_by_name(name: str) -> Dict:
105
105
  """
106
106
  根据模型名称查找模型
@@ -109,7 +109,8 @@ def get_model_by_name(name: str) -> Dict:
109
109
  v = [m for m in models if m["name"] == name.strip()]
110
110
 
111
111
  if len(v) == 0:
112
- raise Exception(f"Model {name} not found")
112
+ from autocoder.common.auto_coder_lang import get_message_with_format
113
+ raise Exception(get_message_with_format("model_not_found", model_name=name))
113
114
  return v[0]
114
115
 
115
116
  def update_model_with_api_key(name: str, api_key: str) -> Dict:
@@ -135,9 +136,8 @@ def update_model_with_api_key(name: str, api_key: str) -> Dict:
135
136
 
136
137
  if not found_model:
137
138
  return None
138
-
139
- # base_url 中提取并处理 host
140
- api_key_path = process_api_key_path(found_model["base_url"])
139
+
140
+ api_key_path = name
141
141
  if api_key_path:
142
142
  found_model["api_key_path"] = api_key_path
143
143
 
@@ -36,9 +36,8 @@ class SuffixProject:
36
36
  self.target_file = args.target_file
37
37
  self.project_type = args.project_type
38
38
  self.suffixs = [
39
- f".{suffix.strip()}" if not suffix.startswith(".") else suffix.strip()
40
- for suffix in self.project_type.split(",")
41
- if suffix.strip() != ""
39
+ suffix.strip() if suffix.startswith(".") else f".{suffix.strip()}"
40
+ for suffix in self.project_type.split(",") if suffix.strip()
42
41
  ]
43
42
  self.file_filter = file_filter
44
43
  self.sources = []
@@ -11,256 +11,132 @@ from autocoder.utils.request_queue import request_queue
11
11
  import time
12
12
 
13
13
  MAX_HISTORY_LINES = 40 # 最大保留历史行数
14
- LAYOUT_TYPES = Literal["vertical", "horizontal"]
15
14
 
16
- class StreamController:
17
- def __init__(self, layout_type: LAYOUT_TYPES = "vertical", console: Optional[Console] = None):
18
- self.console = console or Console(force_terminal=True, color_system="auto", height=24) # 设置默认高度
19
- self.layout = Layout()
20
- self.queue = Queue()
15
+ class StreamRenderer:
16
+ def __init__(self, title: str):
17
+ self.title = title
18
+ self.content = ""
21
19
  self.lock = Lock()
22
- self.running = True
23
- self.workers = []
24
- self.layout_type = layout_type
25
- self.stream_count = 0
26
-
27
- def _create_stream_panel(self, idx: int) -> Layout:
28
- """创建流面板布局"""
29
- # 计算安全高度
30
- current_height = self.console.height or 24 # 默认24行防止获取失败
31
- safe_height = max(min(50, current_height // 2 - 4), 5) # 限制最小高度为5行
32
-
33
- # 使用整数设置 Layout 的 size
34
- panel = Layout(name=f"stream-{idx}", size=safe_height)
35
-
36
- panel.update(
37
- Panel(
38
- Markdown(""),
39
- title=f"Stream {idx + 1}",
40
- border_style="green",
41
- height=safe_height # 确保数值有效
42
- )
43
- )
44
- return panel
45
-
46
- def prepare_layout(self, count: int):
47
- """准备动态布局结构"""
48
- self.stream_count = count
49
-
50
- # 创建一个主布局容器
51
- streams_layout = Layout(name="streams")
52
-
53
- # 创建所有流的布局
54
- stream_layouts = []
55
- for i in range(count):
56
- stream_layout = Layout(name=f"stream-{i}")
57
- panel = self._create_stream_panel(i)
58
- stream_layout.update(panel)
59
- stream_layouts.append(stream_layout)
60
-
61
- # 将所有流添加到主布局
62
- if stream_layouts:
63
- streams_layout.update(stream_layouts[0])
64
- for i in range(1, len(stream_layouts)):
65
- if self.layout_type == "vertical":
66
- streams_layout.split_column(stream_layouts[i])
67
- elif self.layout_type == "horizontal":
68
- streams_layout.split_row(stream_layouts[i])
69
- else:
70
- streams_layout.split_column(stream_layouts[i])
20
+ self.is_complete = False
71
21
 
72
- # header streams 布局分开
73
- self.layout.split(
74
- Layout(name="header", size=1),
75
- streams_layout
76
- )
77
-
78
- def update_panel(self, idx: int, content: str, final: bool = False):
79
- """线程安全的面板更新方法"""
22
+ def update(self, content: str):
80
23
  with self.lock:
81
- # 计算安全高度
82
- safe_height = min(50, self.console.height // 2 - 4)
24
+ self.content += content
83
25
 
84
- if final:
85
- new_panel = Panel(
86
- Markdown(content),
87
- title=f"Final Stream {idx+1}",
88
- border_style="blue",
89
- height=safe_height
90
- )
91
- else:
92
- new_panel = Panel(
93
- Markdown(content),
94
- title=f"Stream {idx+1}",
95
- border_style="green",
96
- height=safe_height
97
- )
26
+ def get_content(self) -> str:
27
+ with self.lock:
28
+ return self.content
29
+
30
+ def complete(self):
31
+ with self.lock:
32
+ self.is_complete = True
98
33
 
99
- panel_name = f"stream-{idx}"
100
- streams_layout = self.layout["streams"]
34
+ class MultiStreamRenderer:
35
+ def __init__(self, stream_titles: List[str], layout: str = "horizontal", console: Optional[Console] = None):
36
+ """
37
+ Initialize multi-stream renderer
38
+
39
+ Args:
40
+ stream_titles: List of titles for each stream
41
+ layout: "horizontal" or "vertical"
42
+ console: Rich console instance
43
+ """
44
+ if console is None:
45
+ console = Console(force_terminal=True, color_system="auto")
101
46
 
102
- # 递归查找目标布局
103
- def find_layout(layout, name):
104
- if layout.name == name:
105
- return layout
106
- for child in layout.children:
107
- result = find_layout(child, name)
108
- if result:
109
- return result
110
- return None
47
+ self.console = console
48
+ self.layout_type = layout
49
+ self.streams = [StreamRenderer(title) for title in stream_titles]
50
+ self.layout = Layout()
51
+
52
+ # Create named layouts for each stream
53
+ self.stream_layouts = [Layout(name=f"stream{i}") for i in range(len(stream_titles))]
54
+
55
+ # Configure layout
56
+ if layout == "horizontal":
57
+ self.layout.split_row(*self.stream_layouts)
58
+ else:
59
+ self.layout.split_column(*self.stream_layouts)
111
60
 
112
- # 查找并更新目标布局
113
- target_layout = find_layout(streams_layout, panel_name)
114
- if target_layout:
115
- target_layout.update(new_panel)
116
- else:
117
- import logging
118
- logging.warning(f"未找到布局 {panel_name},无法更新面板。")
61
+ def _process_stream(self,
62
+ stream_idx: int,
63
+ stream_generator: Generator[Tuple[str, Dict[str, Any]], None, None]):
64
+ """Process a single stream in a separate thread"""
65
+ stream = self.streams[stream_idx]
66
+ try:
67
+ for content, meta in stream_generator:
68
+ if content:
69
+ stream.update(content)
70
+ finally:
71
+ stream.complete()
119
72
 
120
- def stream_worker(
121
- idx: int,
122
- generator: Generator[Tuple[str, Dict[str, Any]], None, None],
123
- controller: StreamController,
124
- request_id: Optional[str] = None
125
- ) -> Tuple[str, Optional[Dict[str, Any]]]:
126
- """单个流处理工作线程"""
127
- lines_buffer = []
128
- current_line = ""
129
- assistant_response = ""
130
- last_meta = None
131
-
132
- try:
133
- for res in generator:
134
- content, meta = res
135
- last_meta = meta
73
+ def render_streams(self,
74
+ stream_generators: List[Generator[Tuple[str, Dict[str, Any]], None, None]]) -> List[str]:
75
+ """
76
+ Render multiple streams simultaneously
77
+
78
+ Args:
79
+ stream_generators: List of stream generators to render
136
80
 
137
- assistant_response += content
138
- display_delta = meta.reasoning_content or content
139
-
140
- parts = (current_line + display_delta).split("\n")
141
- if len(parts) > 1:
142
- lines_buffer.extend(parts[:-1])
143
- if len(lines_buffer) > MAX_HISTORY_LINES:
144
- del lines_buffer[0:len(lines_buffer) - MAX_HISTORY_LINES]
81
+ Returns:
82
+ List of final content from each stream
83
+ """
84
+ assert len(stream_generators) == len(self.streams), "Number of generators must match number of streams"
85
+
86
+ # Start processing threads
87
+ threads = []
88
+ for i, generator in enumerate(stream_generators):
89
+ thread = Thread(target=self._process_stream, args=(i, generator))
90
+ thread.daemon = True
91
+ thread.start()
92
+ threads.append(thread)
145
93
 
146
- current_line = parts[-1]
147
- display_content = "\n".join(lines_buffer[-MAX_HISTORY_LINES:] + [current_line])
94
+ try:
95
+ with Live(self.layout, console=self.console, refresh_per_second=10) as live:
96
+ while any(not stream.is_complete for stream in self.streams):
97
+ # Update all panels
98
+ for i, stream in enumerate(self.streams):
99
+ panel = Panel(
100
+ Markdown(stream.get_content() or "Waiting..."),
101
+ title=stream.title,
102
+ border_style="green" if not stream.is_complete else "blue"
103
+ )
104
+
105
+ # Update appropriate layout section
106
+ self.stream_layouts[i].update(panel)
107
+
108
+ time.sleep(0.1) # Prevent excessive CPU usage
109
+
110
+ except KeyboardInterrupt:
111
+ print("\nStopping streams...")
148
112
 
149
- controller.queue.put((idx, display_content, False))
113
+ # Wait for all threads to complete
114
+ for thread in threads:
115
+ thread.join()
150
116
 
151
- if request_id and request_queue:
152
- request_queue.add_request(
153
- request_id,
154
- RequestValue(
155
- value=StreamValue(value=[content]),
156
- status=RequestOption.RUNNING,
157
- ),
158
- )
159
-
160
- if current_line:
161
- lines_buffer.append(current_line)
162
- controller.queue.put((idx, assistant_response, True))
163
- return assistant_response, last_meta
164
-
165
- except Exception as e:
166
- error_content = f"Error: {str(e)}"
167
- controller.queue.put((idx, error_content, True))
168
- if request_id and request_queue:
169
- request_queue.add_request(
170
- request_id,
171
- RequestValue(
172
- value=StreamValue(value=[str(e)]),
173
- status=RequestOption.FAILED
174
- ),
175
- )
176
- return assistant_response, last_meta
177
- finally:
178
- if request_id and request_queue:
179
- request_queue.add_request(
180
- request_id,
181
- RequestValue(
182
- value=StreamValue(value=[""]),
183
- status=RequestOption.COMPLETED
184
- ),
185
- )
117
+ return [stream.get_content() for stream in self.streams]
186
118
 
187
119
  def multi_stream_out(
188
120
  stream_generators: List[Generator[Tuple[str, Dict[str, Any]], None, None]],
189
- request_ids: Optional[List[str]] = None,
190
- console: Optional[Console] = None,
191
- layout_type: LAYOUT_TYPES = "vertical"
192
- ) -> List[Tuple[str, Optional[Dict[str, Any]]]]:
121
+ titles: List[str],
122
+ layout: str = "horizontal",
123
+ console: Optional[Console] = None
124
+ ) -> List[str]:
193
125
  """
194
- 多流并行输出处理器
126
+ Render multiple streams with Rich
195
127
 
196
128
  Args:
197
- stream_generators: 流处理器列表
198
- request_ids: 对应请求ID列表
199
- console: Rich Console对象
200
- layout_type: 布局类型 vertical/horizontal
129
+ stream_generators: List of stream generators
130
+ titles: List of titles for each stream
131
+ layout: "horizontal" or "vertical"
132
+ console: Optional Rich console instance
201
133
 
202
134
  Returns:
203
- List[Tuple[str, Dict]]: 各流的处理结果
135
+ List of final content from each stream
204
136
  """
205
- # 确保使用统一的console实例
206
- if console is None:
207
- console = Console(force_terminal=True, color_system="auto", height=24)
208
-
209
- # 初始化控制器
210
- controller = StreamController(layout_type, console=console)
211
- stream_count = len(stream_generators)
212
- controller.prepare_layout(stream_count)
213
-
214
- # 启动工作线程
215
- results = [None] * stream_count
216
- threads = []
217
-
218
- # 创建工作线程
219
- def worker_target(idx: int, gen: Generator[Tuple[str, Dict[str, Any]], None, None]):
220
- req_id = request_ids[idx] if request_ids and idx < len(request_ids) else None
221
- results[idx] = stream_worker(idx, gen, controller, req_id)
222
-
223
- # 启动所有工作线程
224
- for idx, gen in enumerate(stream_generators):
225
- t = Thread(target=worker_target, args=(idx, gen))
226
- t.start()
227
- threads.append(t)
228
-
229
- # 主渲染线程
230
- try:
231
- with Live(
232
- controller.layout,
233
- console=console or controller.console,
234
- refresh_per_second=10,
235
- screen=True
236
- ) as live:
237
- while controller.running:
238
- updated = False
239
- try:
240
- while True: # 处理队列中的所有更新
241
- idx, content, final = controller.queue.get_nowait()
242
- controller.update_panel(idx, content, final)
243
- updated = True
244
- except Empty:
245
- pass
246
-
247
- if updated:
248
- live.refresh()
249
-
250
- # 检查线程是否全部完成
251
- if all(not t.is_alive() for t in threads):
252
- break
253
-
254
- time.sleep(0.1)
255
-
256
- finally:
257
- controller.running = False
258
- for t in threads:
259
- t.join()
137
+ renderer = MultiStreamRenderer(titles, layout, console)
138
+ return renderer.render_streams(stream_generators)
260
139
 
261
- # 确保最后一次刷新
262
- (console or controller.console).print(controller.layout)
263
- return results
264
140
 
265
141
  def stream_out(
266
142
  stream_generator: Generator[Tuple[str, Dict[str, Any]], None, None],
autocoder/utils/rest.py CHANGED
@@ -1,16 +1,22 @@
1
1
  import requests
2
2
  from bs4 import BeautifulSoup
3
- from typing import List,Dict,Type,Optional
3
+ from typing import List,Dict,Union,Optional
4
4
  from autocoder.common import SourceCode
5
5
  import byzerllm
6
- from bs4 import BeautifulSoup
7
6
  from loguru import logger
8
7
  import os
9
8
  from pathlib import Path
10
9
  from autocoder.common import files as FileUtils
10
+ import traceback
11
+ from autocoder.rag.loaders import (
12
+ extract_text_from_pdf,
13
+ extract_text_from_docx,
14
+ extract_text_from_ppt,
15
+ extract_text_from_excel
16
+ )
11
17
 
12
18
  class HttpDoc:
13
- def __init__(self, args, llm: byzerllm.ByzerLLM,urls:Optional[List[str]]=None):
19
+ def __init__(self, args, llm: Union[byzerllm.ByzerLLM, byzerllm.SimpleByzerLLM],urls:Optional[List[str]]=None):
14
20
  self.args = args
15
21
  urls_from_args = self.args.urls
16
22
  if urls_from_args:
@@ -41,104 +47,50 @@ class HttpDoc:
41
47
  {{ html }}
42
48
 
43
49
  输出的内容请以 "<MARKER></MARKER> 标签对包裹。
44
- """
45
-
46
- def is_binary_file(self,filepath):
47
- try:
48
- with open(filepath, 'rb') as file:
49
- chunk = file.read(1024*8) # Read first 1024 bytes
50
- if b'\x00' in chunk: # Binary files often contain null bytes
51
- return True
52
- # Attempt to decode as UTF-8 (or any encoding you expect your text files to be in)
53
- chunk.decode('utf-8')
54
- return False
55
- except UnicodeDecodeError:
56
- return True
50
+ """
57
51
 
58
- def get_file_extractor(self):
52
+ def _process_local_file(self, file_path: str) -> List[SourceCode]:
53
+ """统一处理本地文件,返回标准化的 SourceCode 列表"""
54
+ results = []
59
55
  try:
60
- from llama_index.core.readers.base import BaseReader
61
- from fsspec import AbstractFileSystem
62
- from llama_index.core.schema import Document
63
- from llama_index.core.readers.file.base import get_default_fs
64
- from llama_index.readers.file import (
65
- DocxReader,
66
- EpubReader,
67
- HWPReader,
68
- ImageReader,
69
- IPYNBReader,
70
- MarkdownReader,
71
- MboxReader,
72
- PandasCSVReader,
73
- PDFReader,
74
- PptxReader,
75
- VideoAudioReader,
76
- ) # pants: no-infer-dep
77
- except ImportError as e:
78
- raise ImportError(f"`llama-index-readers-file` package not found. {e}")
56
+ ext = os.path.splitext(file_path)[1].lower()
57
+
58
+ # 分发到不同 loader
59
+ if ext == '.pdf':
60
+ content = extract_text_from_pdf(file_path)
61
+ results.append(SourceCode(module_name=file_path, source_code=content))
62
+ elif ext == '.docx':
63
+ content = extract_text_from_docx(file_path)
64
+ results.append(SourceCode(module_name=file_path, source_code=content))
65
+ elif ext in ('.pptx', '.ppt'):
66
+ for slide_id, slide_content in extract_text_from_ppt(file_path):
67
+ results.append(SourceCode(module_name=f"{file_path}#{slide_id}", source_code=slide_content))
68
+ elif ext in ('.xlsx', '.xls'):
69
+ for sheet_name, sheet_content in extract_text_from_excel(file_path):
70
+ results.append(SourceCode(module_name=f"{file_path}#{sheet_name}", source_code=sheet_content))
71
+ else:
72
+ content = FileUtils.read_file(file_path)
73
+ results.append(SourceCode(module_name=file_path, source_code=content))
79
74
 
80
- default_file_reader_cls: Dict[str, BaseReader] = {
81
- ".hwp": HWPReader(),
82
- ".pdf": PDFReader(return_full_document=True),
83
- ".docx": DocxReader(),
84
- # ".pptx": PptxReader(),
85
- # ".ppt": PptxReader(),
86
- # ".pptm": PptxReader(),
87
- # ".jpg": ImageReader(),
88
- # ".png": ImageReader(),
89
- # ".jpeg": ImageReader(),
90
- # ".mp3": VideoAudioReader(),
91
- # ".mp4": VideoAudioReader(),
92
- # ".csv": PandasCSVReader(),
93
- ".epub": EpubReader(),
94
- ".mbox": MboxReader(),
95
- ".ipynb": IPYNBReader(),
96
- }
97
- return default_file_reader_cls
75
+ except Exception as e:
76
+ logger.error(f"Failed to process {file_path}: {str(e)}")
77
+ traceback.print_exc()
78
+
79
+ return results
98
80
 
99
81
  def crawl_urls(self) -> List[SourceCode]:
100
- source_codes = []
82
+ source_codes = []
101
83
  for url in self.urls:
102
- if not url.startswith("http://") and not url.startswith("https://"):
84
+ if not url.startswith(("http://", "https://")):
103
85
  try:
104
- from llama_index.core import SimpleDirectoryReader
105
- exts = self.get_file_extractor()
106
- documents = []
107
-
108
- def process_single_file(file_path: str,skip_binary_file_test:bool=False):
109
- temp_documents = []
110
- ext = os.path.splitext(file_path)[1].lower()
111
- if not skip_binary_file_test and self.is_binary_file(file_path):
112
- logger.warning(f"Skipping binary file: {file_path}")
113
- return temp_documents
114
-
115
- if ext not in exts.keys():
116
- main_content = FileUtils.read_file(file_path)
117
- source_code = SourceCode(module_name=file_path, source_code=main_content)
118
- source_codes.append(source_code)
86
+ if os.path.isdir(url):
87
+ for root, _, files in os.walk(url, followlinks=True):
88
+ for file in files:
89
+ source_codes.extend(self._process_local_file(os.path.join(root, file)))
119
90
  else:
120
- temp_documents = SimpleDirectoryReader(input_files=[url],file_extractor=exts).load_data()
121
- return temp_documents
122
-
123
- if os.path.isdir(url):
124
- for root, dirs, files in os.walk(url,followlinks=True):
125
- dirs[:] = [d for d in dirs if d not in ['.git',"node_modules"]] # Exclude .git directory
126
- for file in files:
127
- file_path = os.path.join(root, file)
128
- documents.extend(process_single_file(file_path))
129
-
130
- else:
131
- documents.extend(process_single_file(url,skip_binary_file_test=True))
132
-
133
- for document in documents:
134
- source_code = SourceCode(module_name=document.metadata["file_path"], source_code=document.get_content())
135
- source_codes.append(source_code)
136
-
137
- except ImportError as e:
138
- logger.warning(f"Failed to import llama_index. Please install it using 'pip install llama_index' {e}")
139
- main_content = FileUtils.read_file(url)
140
- source_code = SourceCode(module_name=url, source_code=main_content)
141
- source_codes.append(source_code)
91
+ source_codes.extend(self._process_local_file(url))
92
+ except Exception as e:
93
+ logger.error(f"Error accessing path {url}: {str(e)}")
142
94
  else:
143
95
  if self.args.urls_use_model:
144
96
  from autocoder.common.screenshots import gen_screenshots
autocoder/version.py CHANGED
@@ -1 +1 @@
1
- __version__ = "0.1.243"
1
+ __version__ = "0.1.244"