auto-coder-web 0.1.40__py3-none-any.whl → 0.1.41__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.
- auto_coder_web/common_router/model_router.py +4 -4
- auto_coder_web/proxy.py +0 -4
- auto_coder_web/version.py +1 -1
- auto_coder_web/web/asset-manifest.json +3 -3
- auto_coder_web/web/index.html +1 -1
- auto_coder_web/web/static/js/{main.8d7d4613.js → main.0fc8391d.js} +3 -3
- auto_coder_web/web/static/js/{main.8d7d4613.js.map → main.0fc8391d.js.map} +1 -1
- {auto_coder_web-0.1.40.dist-info → auto_coder_web-0.1.41.dist-info}/METADATA +1 -1
- {auto_coder_web-0.1.40.dist-info → auto_coder_web-0.1.41.dist-info}/RECORD +13 -14
- auto_coder_web/auto_coder_runner.py +0 -836
- /auto_coder_web/web/static/js/{main.8d7d4613.js.LICENSE.txt → main.0fc8391d.js.LICENSE.txt} +0 -0
- {auto_coder_web-0.1.40.dist-info → auto_coder_web-0.1.41.dist-info}/WHEEL +0 -0
- {auto_coder_web-0.1.40.dist-info → auto_coder_web-0.1.41.dist-info}/entry_points.txt +0 -0
- {auto_coder_web-0.1.40.dist-info → auto_coder_web-0.1.41.dist-info}/top_level.txt +0 -0
@@ -1,836 +0,0 @@
|
|
1
|
-
from fastapi import HTTPException, BackgroundTasks
|
2
|
-
from typing import List, Dict, Any, Optional
|
3
|
-
import os
|
4
|
-
import yaml
|
5
|
-
import json
|
6
|
-
import uuid
|
7
|
-
import glob
|
8
|
-
from pydantic import BaseModel
|
9
|
-
import sys
|
10
|
-
import io
|
11
|
-
import subprocess
|
12
|
-
import byzerllm
|
13
|
-
import hashlib
|
14
|
-
from contextlib import contextmanager
|
15
|
-
from byzerllm.utils.langutil import asyncfy_with_semaphore
|
16
|
-
from autocoder.common import AutoCoderArgs
|
17
|
-
from byzerllm.utils.nontext import Image
|
18
|
-
from autocoder.auto_coder import main as auto_coder_main
|
19
|
-
from autocoder.utils import get_last_yaml_file
|
20
|
-
from autocoder.utils.request_queue import (
|
21
|
-
request_queue,
|
22
|
-
RequestValue,
|
23
|
-
StreamValue,
|
24
|
-
DefaultValue,
|
25
|
-
RequestOption,
|
26
|
-
)
|
27
|
-
from autocoder.utils.queue_communicate import (
|
28
|
-
queue_communicate,
|
29
|
-
CommunicateEvent,
|
30
|
-
CommunicateEventType,
|
31
|
-
)
|
32
|
-
from autocoder.utils.log_capture import LogCapture
|
33
|
-
from autocoder.common import git_utils
|
34
|
-
from threading import Thread
|
35
|
-
from byzerllm.utils import format_str_jinja2
|
36
|
-
from autocoder.index.symbols_utils import (
|
37
|
-
extract_symbols,
|
38
|
-
symbols_info_to_str,
|
39
|
-
SymbolsInfo,
|
40
|
-
SymbolType,
|
41
|
-
)
|
42
|
-
from autocoder.utils.llms import get_single_llm
|
43
|
-
|
44
|
-
|
45
|
-
class SymbolItem(BaseModel):
|
46
|
-
symbol_name: str
|
47
|
-
symbol_type: SymbolType
|
48
|
-
file_name: str
|
49
|
-
|
50
|
-
|
51
|
-
class AutoCoderRunner:
|
52
|
-
def __init__(self, project_path: str, product_mode: str = "pro"):
|
53
|
-
self.project_path = project_path
|
54
|
-
self.base_persist_dir = os.path.join(
|
55
|
-
project_path, ".auto-coder", "plugins", "chat-auto-coder")
|
56
|
-
self.default_exclude_dirs = [
|
57
|
-
".git", "node_modules", "dist", "build", "__pycache__"]
|
58
|
-
self.memory = {
|
59
|
-
"conversation": [],
|
60
|
-
"current_files": {"files": []},
|
61
|
-
"conf": {},
|
62
|
-
"exclude_dirs": [],
|
63
|
-
}
|
64
|
-
self.load_memory()
|
65
|
-
# Configure product mode
|
66
|
-
self.configure("product_mode", product_mode)
|
67
|
-
|
68
|
-
@contextmanager
|
69
|
-
def redirect_stdout(self):
|
70
|
-
original_stdout = sys.stdout
|
71
|
-
sys.stdout = f = io.StringIO()
|
72
|
-
try:
|
73
|
-
yield f
|
74
|
-
finally:
|
75
|
-
sys.stdout = original_stdout
|
76
|
-
|
77
|
-
def save_memory(self):
|
78
|
-
os.makedirs(self.base_persist_dir, exist_ok=True)
|
79
|
-
with open(os.path.join(self.base_persist_dir, "memory.json"), "w") as f:
|
80
|
-
json.dump(self.memory, f, indent=2, ensure_ascii=False)
|
81
|
-
|
82
|
-
def load_memory(self):
|
83
|
-
memory_path = os.path.join(self.base_persist_dir, "memory.json")
|
84
|
-
if os.path.exists(memory_path):
|
85
|
-
with open(memory_path, "r") as f:
|
86
|
-
self.memory = json.load(f)
|
87
|
-
|
88
|
-
def add_group(self, group_name: str, description: str) -> Dict[str, str]:
|
89
|
-
|
90
|
-
if group_name in self.memory["current_files"]["groups"]:
|
91
|
-
return None
|
92
|
-
|
93
|
-
self.memory["current_files"]["groups"][group_name] = []
|
94
|
-
|
95
|
-
if "groups_info" not in self.memory["current_files"]:
|
96
|
-
self.memory["current_files"]["groups_info"] = {}
|
97
|
-
|
98
|
-
self.memory["current_files"]["groups_info"][group_name] = {
|
99
|
-
"query_prefix": description
|
100
|
-
}
|
101
|
-
self.save_memory()
|
102
|
-
return {"message": f"Added group: {group_name}"}
|
103
|
-
|
104
|
-
def remove_group(self, group_name: str) -> Dict[str, str]:
|
105
|
-
if group_name not in self.memory["current_files"]["groups"]:
|
106
|
-
return None
|
107
|
-
del self.memory["current_files"]["groups"][group_name]
|
108
|
-
if group_name in self.memory["current_files"]["groups_info"]:
|
109
|
-
del self.memory["current_files"]["groups_info"][group_name]
|
110
|
-
self.save_memory()
|
111
|
-
return {"message": f"Removed group: {group_name}"}
|
112
|
-
|
113
|
-
def switch_groups(self, group_names: List[str]) -> Dict[str, str]:
|
114
|
-
new_files = []
|
115
|
-
for group_name in group_names:
|
116
|
-
files = self.memory["current_files"]["groups"][group_name]
|
117
|
-
new_files.extend(files)
|
118
|
-
self.memory["current_files"]["files"] = new_files
|
119
|
-
self.memory["current_files"]["current_groups"] = group_names
|
120
|
-
self.save_memory()
|
121
|
-
return {"message": f"Switched to groups: {group_names}"}
|
122
|
-
|
123
|
-
def add_files_to_group(self, group_name: str, files: List[str]) -> Dict[str, Any]:
|
124
|
-
for file in files:
|
125
|
-
if file:
|
126
|
-
self.memory["current_files"]["groups"][group_name].append(
|
127
|
-
os.path.join(self.project_path, file))
|
128
|
-
self.save_memory()
|
129
|
-
return {
|
130
|
-
"message": f"Added files to group: {group_name}"
|
131
|
-
}
|
132
|
-
|
133
|
-
def remove_files_from_group(self, group_name: str, files: List[str]) -> Dict[str, Any]:
|
134
|
-
existing_files = self.memory["current_files"]["groups"][group_name]
|
135
|
-
for file in files:
|
136
|
-
target_file = os.path.join(self.project_path, file)
|
137
|
-
if target_file in existing_files:
|
138
|
-
existing_files.remove(target_file)
|
139
|
-
self.save_memory()
|
140
|
-
return {
|
141
|
-
"message": f"Removed files from group: {group_name}"
|
142
|
-
}
|
143
|
-
|
144
|
-
def get_groups(self) -> Dict[str, List[str]]:
|
145
|
-
if "groups" not in self.memory["current_files"]:
|
146
|
-
return {"groups": []}
|
147
|
-
return {"groups": list(self.memory["current_files"]["groups"].keys())}
|
148
|
-
|
149
|
-
def get_files_in_group(self, group_name: str) -> Dict[str, List[str]]:
|
150
|
-
files = self.memory["current_files"]["groups"][group_name]
|
151
|
-
files = [os.path.relpath(f, self.project_path) for f in files]
|
152
|
-
return {"files": files}
|
153
|
-
|
154
|
-
def get_group_description(self, group_name: str) -> str:
|
155
|
-
if group_name not in self.memory["current_files"]["groups_info"]:
|
156
|
-
return ""
|
157
|
-
return self.memory["current_files"]["groups_info"][group_name].get("query_prefix", "")
|
158
|
-
|
159
|
-
def get_active_files(self) -> Dict[str, List[str]]:
|
160
|
-
return {"files": self.memory["current_files"]["files"]}
|
161
|
-
|
162
|
-
def get_exclude_dirs(self) -> Dict[str, List[str]]:
|
163
|
-
return {"files": self.memory.get("exclude_dirs", [])}
|
164
|
-
|
165
|
-
def find_files_in_project(self, patterns: List[str]) -> List[str]:
|
166
|
-
|
167
|
-
project_root = self.project_path
|
168
|
-
matched_files = []
|
169
|
-
|
170
|
-
active_files = self.get_active_files()
|
171
|
-
active_file_list = active_files.get("files", [])
|
172
|
-
if len(patterns) == 1 and patterns[0] == "":
|
173
|
-
return active_file_list
|
174
|
-
|
175
|
-
for pattern in patterns:
|
176
|
-
for file_path in active_file_list:
|
177
|
-
if pattern in os.path.basename(file_path):
|
178
|
-
matched_files.append(file_path)
|
179
|
-
|
180
|
-
final_exclude_dirs = self.default_exclude_dirs + \
|
181
|
-
self.memory.get("exclude_dirs", [])
|
182
|
-
|
183
|
-
for pattern in patterns:
|
184
|
-
if "*" in pattern or "?" in pattern:
|
185
|
-
for file_path in glob.glob(pattern, recursive=True):
|
186
|
-
if os.path.isfile(file_path):
|
187
|
-
abs_path = os.path.abspath(file_path)
|
188
|
-
if not any(
|
189
|
-
exclude_dir in abs_path.split(os.sep)
|
190
|
-
for exclude_dir in final_exclude_dirs
|
191
|
-
):
|
192
|
-
matched_files.append(abs_path)
|
193
|
-
else:
|
194
|
-
is_added = False
|
195
|
-
for root, dirs, files in os.walk(project_root, followlinks=True):
|
196
|
-
dirs[:] = [d for d in dirs if d not in final_exclude_dirs]
|
197
|
-
if pattern in files:
|
198
|
-
matched_files.append(os.path.join(root, pattern))
|
199
|
-
is_added = True
|
200
|
-
else:
|
201
|
-
for file in files:
|
202
|
-
if pattern in os.path.join(root, file):
|
203
|
-
matched_files.append(os.path.join(root, file))
|
204
|
-
is_added = True
|
205
|
-
if not is_added:
|
206
|
-
matched_files.append(pattern)
|
207
|
-
|
208
|
-
return list(set(matched_files))
|
209
|
-
|
210
|
-
def get_symbol_list(self) -> List[SymbolItem]:
|
211
|
-
list_of_symbols = []
|
212
|
-
index_file = os.path.join(
|
213
|
-
self.project_path, ".auto-coder", "index.json")
|
214
|
-
|
215
|
-
if os.path.exists(index_file):
|
216
|
-
with open(index_file, "r") as file:
|
217
|
-
index_data = json.load(file)
|
218
|
-
else:
|
219
|
-
index_data = {}
|
220
|
-
|
221
|
-
for item in index_data.values():
|
222
|
-
symbols_str = item["symbols"]
|
223
|
-
module_name = item["module_name"]
|
224
|
-
info1 = extract_symbols(symbols_str)
|
225
|
-
for name in info1.classes:
|
226
|
-
list_of_symbols.append(
|
227
|
-
SymbolItem(
|
228
|
-
symbol_name=name,
|
229
|
-
symbol_type=SymbolType.CLASSES,
|
230
|
-
file_name=module_name,
|
231
|
-
)
|
232
|
-
)
|
233
|
-
for name in info1.functions:
|
234
|
-
list_of_symbols.append(
|
235
|
-
SymbolItem(
|
236
|
-
symbol_name=name,
|
237
|
-
symbol_type=SymbolType.FUNCTIONS,
|
238
|
-
file_name=module_name,
|
239
|
-
)
|
240
|
-
)
|
241
|
-
for name in info1.variables:
|
242
|
-
list_of_symbols.append(
|
243
|
-
SymbolItem(
|
244
|
-
symbol_name=name,
|
245
|
-
symbol_type=SymbolType.VARIABLES,
|
246
|
-
file_name=module_name,
|
247
|
-
)
|
248
|
-
)
|
249
|
-
return list_of_symbols
|
250
|
-
|
251
|
-
def convert_config_value(self, key: str, value: str) -> Any:
|
252
|
-
field_info = AutoCoderArgs.model_fields.get(key)
|
253
|
-
if field_info:
|
254
|
-
if value.strip() == "":
|
255
|
-
return value
|
256
|
-
if value.lower() in ["true", "false"]:
|
257
|
-
return value.lower() == "true"
|
258
|
-
elif "int" in str(field_info.annotation):
|
259
|
-
return int(value)
|
260
|
-
elif "float" in str(field_info.annotation):
|
261
|
-
return float(value)
|
262
|
-
else:
|
263
|
-
return value
|
264
|
-
else:
|
265
|
-
return None
|
266
|
-
|
267
|
-
def convert_yaml_config_to_str(self, yaml_config: Dict) -> str:
|
268
|
-
return yaml.safe_dump(
|
269
|
-
yaml_config,
|
270
|
-
allow_unicode=True,
|
271
|
-
default_flow_style=False,
|
272
|
-
default_style=None,
|
273
|
-
)
|
274
|
-
|
275
|
-
def add_files(self, files: List[str]) -> Dict[str, Any]:
|
276
|
-
project_root = os.getcwd()
|
277
|
-
existing_files = self.memory["current_files"]["files"]
|
278
|
-
matched_files = self.find_files_in_project(files)
|
279
|
-
|
280
|
-
files_to_add = [f for f in matched_files if f not in existing_files]
|
281
|
-
if files_to_add:
|
282
|
-
self.memory["current_files"]["files"].extend(files_to_add)
|
283
|
-
self.save_memory()
|
284
|
-
return {
|
285
|
-
"message": f"Added files: {[os.path.relpath(f, project_root) for f in files_to_add]}"
|
286
|
-
}
|
287
|
-
else:
|
288
|
-
return {
|
289
|
-
"message": "All specified files are already in the current session or no matches found."
|
290
|
-
}
|
291
|
-
|
292
|
-
def remove_files(self, files: List[str]) -> Dict[str, str]:
|
293
|
-
if "/all" in files:
|
294
|
-
self.memory["current_files"]["files"] = []
|
295
|
-
self.save_memory()
|
296
|
-
return {"message": "Removed all files."}
|
297
|
-
else:
|
298
|
-
removed_files = []
|
299
|
-
for file in self.memory["current_files"]["files"]:
|
300
|
-
if os.path.basename(file) in files or file in files:
|
301
|
-
removed_files.append(file)
|
302
|
-
for file in removed_files:
|
303
|
-
self.memory["current_files"]["files"].remove(file)
|
304
|
-
self.save_memory()
|
305
|
-
return {
|
306
|
-
"message": f"Removed files: {[os.path.basename(f) for f in removed_files]}"
|
307
|
-
}
|
308
|
-
|
309
|
-
def list_files(self) -> Dict[str, List[str]]:
|
310
|
-
return {"files": self.memory["current_files"]["files"]}
|
311
|
-
|
312
|
-
def configure(self, key: str, value: str) -> Dict[str, str]:
|
313
|
-
self.memory["conf"][key] = value
|
314
|
-
self.save_memory()
|
315
|
-
return {"message": f"Set {key} to {value}"}
|
316
|
-
|
317
|
-
def drop_config(self, key: str) -> Dict[str, str]:
|
318
|
-
if key in self.memory["conf"]:
|
319
|
-
del self.memory["conf"][key]
|
320
|
-
self.save_memory()
|
321
|
-
return {"message": f"Deleted configuration: {key}"}
|
322
|
-
|
323
|
-
def convert_yaml_to_config(self, yaml_file: str):
|
324
|
-
"""Convert YAML file to AutoCoderArgs configuration"""
|
325
|
-
from autocoder.commmon import AutoCoderArgs, load_include_files, Template
|
326
|
-
args = AutoCoderArgs()
|
327
|
-
with open(yaml_file, "r") as f:
|
328
|
-
config = yaml.safe_load(f)
|
329
|
-
config = load_include_files(config, yaml_file)
|
330
|
-
for key, value in config.items():
|
331
|
-
if key != "file": # 排除 --file 参数本身
|
332
|
-
# key: ENV {{VARIABLE_NAME}}
|
333
|
-
if isinstance(value, str) and value.startswith("ENV"):
|
334
|
-
template = Template(value.removeprefix("ENV").strip())
|
335
|
-
value = template.render(os.environ)
|
336
|
-
setattr(args, key, value)
|
337
|
-
return args
|
338
|
-
|
339
|
-
def commit(self):
|
340
|
-
"""Commit changes using git"""
|
341
|
-
try:
|
342
|
-
def prepare_commit_yaml():
|
343
|
-
auto_coder_main(["next", "chat_action"])
|
344
|
-
|
345
|
-
prepare_commit_yaml()
|
346
|
-
|
347
|
-
latest_yaml_file = get_last_yaml_file("actions")
|
348
|
-
|
349
|
-
if latest_yaml_file:
|
350
|
-
try:
|
351
|
-
execute_file = os.path.join("actions", latest_yaml_file)
|
352
|
-
yaml_config = {
|
353
|
-
"include_file": ["./base/base.yml"],
|
354
|
-
"auto_merge": self.memory.get("conf", {}).get("auto_merge", "editblock"),
|
355
|
-
"human_as_model": self.memory.get("conf", {}).get("human_as_model", "false") == "true",
|
356
|
-
"skip_build_index": self.memory.get("conf", {}).get("skip_build_index", "true") == "true",
|
357
|
-
"skip_confirm": self.memory.get("conf", {}).get("skip_confirm", "true") == "true",
|
358
|
-
"silence": self.memory.get("conf", {}).get("silence", "true") == "true",
|
359
|
-
"include_project_structure": self.memory.get("conf", {}).get("include_project_structure", "true") == "true",
|
360
|
-
}
|
361
|
-
|
362
|
-
conf = self.memory.get("conf", {})
|
363
|
-
for key, value in conf.items():
|
364
|
-
converted_value = self.convert_config_value(key, value)
|
365
|
-
if converted_value is not None:
|
366
|
-
yaml_config[key] = converted_value
|
367
|
-
|
368
|
-
yaml_config["urls"] = self.memory["current_files"]["files"] + self.get_llm_friendly_package_docs(
|
369
|
-
return_paths=True
|
370
|
-
)
|
371
|
-
args = self.convert_yaml_to_config(execute_file)
|
372
|
-
product_mode = conf.get("product_mode", "pro")
|
373
|
-
target_model = args.code_model or args.model
|
374
|
-
llm = get_single_llm(target_model, product_mode)
|
375
|
-
uncommitted_changes = git_utils.get_uncommitted_changes(".")
|
376
|
-
commit_message = git_utils.generate_commit_message.with_llm(
|
377
|
-
llm).run(uncommitted_changes)
|
378
|
-
|
379
|
-
yaml_config["query"] = commit_message
|
380
|
-
yaml_content = self.convert_yaml_config_to_str(yaml_config=yaml_config)
|
381
|
-
with open(execute_file, "w") as f:
|
382
|
-
f.write(yaml_content)
|
383
|
-
|
384
|
-
file_content = open(execute_file).read()
|
385
|
-
md5 = hashlib.md5(file_content.encode('utf-8')).hexdigest()
|
386
|
-
file_name = os.path.basename(execute_file)
|
387
|
-
commit_result = git_utils.commit_changes(".", f"auto_coder_{file_name}_{md5}")
|
388
|
-
|
389
|
-
return {"status": True, "message": "Changes committed successfully", "commit_info": commit_result}
|
390
|
-
except Exception as e:
|
391
|
-
if execute_file:
|
392
|
-
os.remove(execute_file)
|
393
|
-
return {"status": False, "message": f"Failed to commit: {str(e)}"}
|
394
|
-
else:
|
395
|
-
return {"status": False, "message": "No changes to commit"}
|
396
|
-
except Exception as e:
|
397
|
-
return {"status": False, "message": f"Failed to commit: {str(e)}"}
|
398
|
-
|
399
|
-
def get_config(self) -> Dict[str, str]:
|
400
|
-
"""Get current configuration
|
401
|
-
|
402
|
-
Returns:
|
403
|
-
Dict with current config values
|
404
|
-
"""
|
405
|
-
return self.memory.get("conf", {})
|
406
|
-
|
407
|
-
def delete_config(self, key: str) -> Dict[str, str]:
|
408
|
-
if key in self.memory["conf"]:
|
409
|
-
del self.memory["conf"][key]
|
410
|
-
self.save_memory()
|
411
|
-
return {"message": f"Deleted configuration: {key}"}
|
412
|
-
else:
|
413
|
-
raise ValueError(f"Configuration not found: {key}")
|
414
|
-
|
415
|
-
def get_event(self, request_id: str) -> Dict:
|
416
|
-
if not request_id:
|
417
|
-
raise ValueError("request_id is required")
|
418
|
-
return queue_communicate.get_event(request_id)
|
419
|
-
|
420
|
-
def clear_events(self):
|
421
|
-
queue_communicate.request_queues.clear()
|
422
|
-
queue_communicate.response_queues.clear()
|
423
|
-
return {"message": "Event queue cleared successfully"}
|
424
|
-
|
425
|
-
def response_event(self, request_id: str, event: CommunicateEvent, response: str):
|
426
|
-
if not request_id:
|
427
|
-
raise ValueError("request_id is required")
|
428
|
-
|
429
|
-
event = CommunicateEvent(**event)
|
430
|
-
queue_communicate.response_event(request_id, event, response=response)
|
431
|
-
return {"message": "success"}
|
432
|
-
|
433
|
-
async def get_result(self, request_id: str) -> Dict[str, Any]:
|
434
|
-
result = request_queue.get_request(request_id)
|
435
|
-
return result
|
436
|
-
|
437
|
-
async def coding(self, query: str) -> Dict[str, str]:
|
438
|
-
self.memory["conversation"].append({"role": "user", "content": query})
|
439
|
-
conf = self.memory.get("conf", {})
|
440
|
-
current_files = self.memory["current_files"]["files"]
|
441
|
-
current_groups = self.memory["current_files"].get("current_groups", [])
|
442
|
-
groups = self.memory["current_files"].get("groups", {})
|
443
|
-
groups_info = self.memory["current_files"].get("groups_info", {})
|
444
|
-
request_id = str(uuid.uuid4())
|
445
|
-
|
446
|
-
def process():
|
447
|
-
def prepare_chat_yaml():
|
448
|
-
auto_coder_main(["next", "chat_action"])
|
449
|
-
|
450
|
-
prepare_chat_yaml()
|
451
|
-
|
452
|
-
latest_yaml_file = get_last_yaml_file("actions")
|
453
|
-
|
454
|
-
if latest_yaml_file:
|
455
|
-
yaml_config = {
|
456
|
-
"include_file": ["./base/base.yml"],
|
457
|
-
"auto_merge": conf.get("auto_merge", "editblock"),
|
458
|
-
"human_as_model": conf.get("human_as_model", "false") == "true",
|
459
|
-
"skip_build_index": conf.get("skip_build_index", "true") == "true",
|
460
|
-
"skip_confirm": conf.get("skip_confirm", "true") == "true",
|
461
|
-
"silence": conf.get("silence", "false") == "true",
|
462
|
-
"urls": current_files,
|
463
|
-
"query": query,
|
464
|
-
}
|
465
|
-
|
466
|
-
for key, value in conf.items():
|
467
|
-
converted_value = self.convert_config_value(key, value)
|
468
|
-
if converted_value is not None:
|
469
|
-
yaml_config[key] = converted_value
|
470
|
-
|
471
|
-
if current_groups:
|
472
|
-
active_groups_context = "下面是对上面文件按分组给到的一些描述,当用户的需求正好匹配描述的时候,参考描述来做修改:\n"
|
473
|
-
for group in current_groups:
|
474
|
-
group_files = groups.get(group, [])
|
475
|
-
query_prefix = groups_info.get(group, {}).get("query_prefix", "")
|
476
|
-
active_groups_context += f"组名: {group}\n"
|
477
|
-
active_groups_context += f"文件列表:\n"
|
478
|
-
for file in group_files:
|
479
|
-
active_groups_context += f"- {file}\n"
|
480
|
-
active_groups_context += f"组描述: {query_prefix}\n\n"
|
481
|
-
|
482
|
-
yaml_config["context"] = active_groups_context + "\n"
|
483
|
-
|
484
|
-
yaml_content = self.convert_yaml_config_to_str(yaml_config)
|
485
|
-
execute_file = os.path.join("actions", latest_yaml_file)
|
486
|
-
with open(execute_file, "w") as f:
|
487
|
-
f.write(yaml_content)
|
488
|
-
|
489
|
-
try:
|
490
|
-
log_capture = LogCapture(request_id=request_id)
|
491
|
-
with log_capture.capture() as log_queue:
|
492
|
-
auto_coder_main(
|
493
|
-
["--file", execute_file, "--request_id", request_id])
|
494
|
-
except Exception as e:
|
495
|
-
_ = queue_communicate.send_event_no_wait(
|
496
|
-
request_id=request_id,
|
497
|
-
event=CommunicateEvent(
|
498
|
-
event_type=CommunicateEventType.CODE_ERROR.value, data=str(
|
499
|
-
e)
|
500
|
-
),
|
501
|
-
)
|
502
|
-
raise e
|
503
|
-
|
504
|
-
_ = queue_communicate.send_event_no_wait(
|
505
|
-
request_id=request_id,
|
506
|
-
event=CommunicateEvent(
|
507
|
-
event_type=CommunicateEventType.CODE_END.value, data=""
|
508
|
-
),
|
509
|
-
)
|
510
|
-
|
511
|
-
_ = queue_communicate.send_event_no_wait(
|
512
|
-
request_id=request_id,
|
513
|
-
event=CommunicateEvent(
|
514
|
-
event_type=CommunicateEventType.CODE_START.value, data=query
|
515
|
-
),
|
516
|
-
)
|
517
|
-
Thread(target=process).start()
|
518
|
-
return {"request_id": request_id}
|
519
|
-
|
520
|
-
@byzerllm.prompt()
|
521
|
-
def code_review(self, query: str) -> str:
|
522
|
-
"""
|
523
|
-
对代码进行review,参考如下检查点。
|
524
|
-
1. 有没有调用不符合方法,类的签名的调用
|
525
|
-
2. 有没有未声明直接使用的变量,方法,类
|
526
|
-
3. 有没有明显的语法错误
|
527
|
-
4. 如果是python代码,检查有没有缩进方面的错误
|
528
|
-
5. 如果是python代码,检查是否 try 后面缺少 except 或者 finally
|
529
|
-
{% if query %}
|
530
|
-
6. 用户的额外的检查需求:{{ query }}
|
531
|
-
{% endif %}
|
532
|
-
|
533
|
-
如果用户的需求包含了@一个文件名 或者 @@符号, 那么重点关注这些文件或者符号(函数,类)进行上述的review。
|
534
|
-
review 过程中严格遵循上述的检查点,不要遗漏,没有发现异常的点直接跳过,只对发现的异常点,给出具体的修改后的代码。
|
535
|
-
"""
|
536
|
-
|
537
|
-
def convert_yaml_config_to_str(self, yaml_config):
|
538
|
-
yaml_content = yaml.safe_dump(
|
539
|
-
yaml_config,
|
540
|
-
allow_unicode=True,
|
541
|
-
default_flow_style=False,
|
542
|
-
default_style=None,
|
543
|
-
)
|
544
|
-
return yaml_content
|
545
|
-
|
546
|
-
async def chat(self, query: str) -> Dict[str, str]:
|
547
|
-
request_id = str(uuid.uuid4())
|
548
|
-
|
549
|
-
def process_chat():
|
550
|
-
nonlocal query
|
551
|
-
nonlocal request_id
|
552
|
-
conf = self.memory.get("conf", {})
|
553
|
-
|
554
|
-
yaml_config = {
|
555
|
-
"include_file": ["./base/base.yml"],
|
556
|
-
"include_project_structure": conf.get("include_project_structure", "true") in ["true", "True"],
|
557
|
-
"human_as_model": conf.get("human_as_model", "false") == "true",
|
558
|
-
"skip_build_index": conf.get("skip_build_index", "true") == "true",
|
559
|
-
"skip_confirm": conf.get("skip_confirm", "true") == "true",
|
560
|
-
"silence": conf.get("silence", "true") == "true",
|
561
|
-
}
|
562
|
-
|
563
|
-
current_files = self.memory["current_files"]["files"] + self.get_llm_friendly_package_docs(
|
564
|
-
return_paths=True
|
565
|
-
)
|
566
|
-
|
567
|
-
yaml_config["urls"] = current_files
|
568
|
-
|
569
|
-
if "emb_model" in conf:
|
570
|
-
yaml_config["emb_model"] = conf["emb_model"]
|
571
|
-
|
572
|
-
is_new = query.strip().startswith("/new")
|
573
|
-
if is_new:
|
574
|
-
query = query.replace("/new", "", 1).strip()
|
575
|
-
|
576
|
-
is_review = query.strip().startswith("/review")
|
577
|
-
if is_review:
|
578
|
-
query = query.replace("/review", "", 1).strip()
|
579
|
-
if "prompt_review" in conf:
|
580
|
-
query = format_str_jinja2(
|
581
|
-
conf["prompt_review"], query=query)
|
582
|
-
else:
|
583
|
-
query = self.code_review.prompt(query)
|
584
|
-
|
585
|
-
is_no_context = query.strip().startswith("/no_context")
|
586
|
-
if is_no_context:
|
587
|
-
query = query.replace("/no_context", "", 1).strip()
|
588
|
-
|
589
|
-
for key, value in conf.items():
|
590
|
-
converted_value = self.convert_config_value(key, value)
|
591
|
-
if converted_value is not None:
|
592
|
-
yaml_config[key] = converted_value
|
593
|
-
|
594
|
-
query = Image.convert_image_paths_from(query)
|
595
|
-
|
596
|
-
yaml_config["query"] = query
|
597
|
-
|
598
|
-
yaml_content = self.convert_yaml_config_to_str(
|
599
|
-
yaml_config=yaml_config)
|
600
|
-
|
601
|
-
execute_file = os.path.join("actions", f"{uuid.uuid4()}.yml")
|
602
|
-
|
603
|
-
with open(os.path.join(execute_file), "w") as f:
|
604
|
-
f.write(yaml_content)
|
605
|
-
|
606
|
-
def execute_ask():
|
607
|
-
cmd = ["agent", "chat", "--file",
|
608
|
-
execute_file, "--request_id", request_id, "--skip_events"]
|
609
|
-
if is_new:
|
610
|
-
cmd.append("--new_session")
|
611
|
-
auto_coder_main(cmd)
|
612
|
-
|
613
|
-
try:
|
614
|
-
execute_ask()
|
615
|
-
finally:
|
616
|
-
os.remove(execute_file)
|
617
|
-
|
618
|
-
Thread(target=process_chat).start()
|
619
|
-
request_queue.add_request(
|
620
|
-
request_id,
|
621
|
-
RequestValue(value=StreamValue(
|
622
|
-
value=[""]), status=RequestOption.RUNNING),
|
623
|
-
)
|
624
|
-
return {"request_id": request_id}
|
625
|
-
|
626
|
-
async def ask(self, query: str) -> Dict[str, str]:
|
627
|
-
conf = self.memory.get("conf", {})
|
628
|
-
request_id = str(uuid.uuid4())
|
629
|
-
|
630
|
-
def process():
|
631
|
-
yaml_config = {"include_file": ["./base/base.yml"], "query": query}
|
632
|
-
|
633
|
-
if "project_type" in conf:
|
634
|
-
yaml_config["project_type"] = conf["project_type"]
|
635
|
-
|
636
|
-
for model_type in ["model", "index_model", "vl_model", "code_model"]:
|
637
|
-
if model_type in conf:
|
638
|
-
yaml_config[model_type] = conf[model_type]
|
639
|
-
|
640
|
-
yaml_content = self.convert_yaml_config_to_str(yaml_config)
|
641
|
-
execute_file = os.path.join("actions", f"{uuid.uuid4()}.yml")
|
642
|
-
|
643
|
-
with open(execute_file, "w") as f:
|
644
|
-
f.write(yaml_content)
|
645
|
-
|
646
|
-
try:
|
647
|
-
auto_coder_main(
|
648
|
-
[
|
649
|
-
"agent",
|
650
|
-
"project_reader",
|
651
|
-
"--file",
|
652
|
-
execute_file,
|
653
|
-
"--request_id",
|
654
|
-
request_id,
|
655
|
-
]
|
656
|
-
)
|
657
|
-
finally:
|
658
|
-
os.remove(execute_file)
|
659
|
-
|
660
|
-
request_queue.add_request(
|
661
|
-
request_id,
|
662
|
-
RequestValue(value=DefaultValue(value=""),
|
663
|
-
status=RequestOption.RUNNING),
|
664
|
-
)
|
665
|
-
return {"request_id": request_id}
|
666
|
-
|
667
|
-
def revert(self) -> Dict[str, str]:
|
668
|
-
last_yaml_file = get_last_yaml_file("actions")
|
669
|
-
if last_yaml_file:
|
670
|
-
file_path = os.path.join("actions", last_yaml_file)
|
671
|
-
with self.redirect_stdout() as output:
|
672
|
-
auto_coder_main(["revert", "--file", file_path])
|
673
|
-
result = output.getvalue()
|
674
|
-
|
675
|
-
if "Successfully reverted changes" in result:
|
676
|
-
os.remove(file_path)
|
677
|
-
return {"message": "Reverted the last chat action successfully", "status": True}
|
678
|
-
else:
|
679
|
-
return {"message": result, "status": False}
|
680
|
-
else:
|
681
|
-
return {"message": "No previous chat action found to revert.", "status": False}
|
682
|
-
|
683
|
-
async def index_build(self) -> Dict[str, str]:
|
684
|
-
request_id = str(uuid.uuid4())
|
685
|
-
yaml_file = os.path.join("actions", f"{uuid.uuid4()}.yml")
|
686
|
-
yaml_content = """
|
687
|
-
include_file:
|
688
|
-
- ./base/base.yml
|
689
|
-
"""
|
690
|
-
with open(yaml_file, "w") as f:
|
691
|
-
f.write(yaml_content)
|
692
|
-
|
693
|
-
log_capture = LogCapture(request_id=request_id)
|
694
|
-
with log_capture.capture() as log_queue:
|
695
|
-
try:
|
696
|
-
auto_coder_main(
|
697
|
-
["index", "--file", yaml_file, "--request_id", request_id]
|
698
|
-
)
|
699
|
-
finally:
|
700
|
-
os.remove(yaml_file)
|
701
|
-
|
702
|
-
return {"request_id": request_id}
|
703
|
-
|
704
|
-
async def index_query(self, query: str) -> Dict[str, str]:
|
705
|
-
request_id = str(uuid.uuid4())
|
706
|
-
yaml_file = os.path.join("actions", f"{uuid.uuid4()}.yml")
|
707
|
-
yaml_content = f"""
|
708
|
-
include_file:
|
709
|
-
- ./base/base.yml
|
710
|
-
query: |
|
711
|
-
{query}
|
712
|
-
"""
|
713
|
-
with open(yaml_file, "w") as f:
|
714
|
-
f.write(yaml_content)
|
715
|
-
|
716
|
-
try:
|
717
|
-
auto_coder_main(
|
718
|
-
["index-query", "--file", yaml_file, "--request_id", request_id]
|
719
|
-
)
|
720
|
-
finally:
|
721
|
-
os.remove(yaml_file)
|
722
|
-
|
723
|
-
request_queue.add_request(
|
724
|
-
request_id,
|
725
|
-
RequestValue(value=DefaultValue(value=""),
|
726
|
-
status=RequestOption.RUNNING),
|
727
|
-
)
|
728
|
-
|
729
|
-
v: RequestValue = await asyncfy_with_semaphore(request_queue.get_request_block)(
|
730
|
-
request_id, timeout=60
|
731
|
-
)
|
732
|
-
return {"message": v.value.value}
|
733
|
-
|
734
|
-
def exclude_dirs(self, dirs: List[str]) -> Dict[str, str]:
|
735
|
-
existing_dirs = self.memory.get("exclude_dirs", [])
|
736
|
-
dirs_to_add = [d for d in dirs if d not in existing_dirs]
|
737
|
-
if dirs_to_add:
|
738
|
-
existing_dirs.extend(dirs_to_add)
|
739
|
-
self.memory["exclude_dirs"] = existing_dirs
|
740
|
-
self.save_memory()
|
741
|
-
return {"message": f"Added exclude dirs: {dirs_to_add}"}
|
742
|
-
else:
|
743
|
-
return {"message": "All specified dirs are already in the exclude list."}
|
744
|
-
|
745
|
-
def execute_shell(self, command: str) -> Dict[str, str]:
|
746
|
-
try:
|
747
|
-
result = subprocess.run(
|
748
|
-
command, shell=True, capture_output=True, text=True)
|
749
|
-
if result.returncode == 0:
|
750
|
-
return {"output": result.stdout}
|
751
|
-
else:
|
752
|
-
return {"error": result.stderr}
|
753
|
-
except Exception as e:
|
754
|
-
raise Exception(str(e))
|
755
|
-
|
756
|
-
def find_files_by_query(self, query: str) -> Dict[str, List[str]]:
|
757
|
-
matched_files = self.find_files_in_project([query])
|
758
|
-
return {"files": matched_files}
|
759
|
-
|
760
|
-
def get_logs(self, request_id: str) -> Dict[str, List[str]]:
|
761
|
-
v = LogCapture.get_log_capture(request_id)
|
762
|
-
logs = v.get_captured_logs() if v else []
|
763
|
-
return {"logs": logs}
|
764
|
-
|
765
|
-
def get_llm_friendly_package_docs(self,
|
766
|
-
package_name: Optional[str] = None, return_paths: bool = False
|
767
|
-
) -> List[str]:
|
768
|
-
lib_dir = os.path.join(".auto-coder", "libs")
|
769
|
-
llm_friendly_packages_dir = os.path.join(
|
770
|
-
lib_dir, "llm_friendly_packages")
|
771
|
-
docs = []
|
772
|
-
|
773
|
-
if not os.path.exists(llm_friendly_packages_dir):
|
774
|
-
print("llm_friendly_packages directory not found.")
|
775
|
-
return docs
|
776
|
-
|
777
|
-
libs = list(self.memory.get("libs", {}).keys())
|
778
|
-
|
779
|
-
for domain in os.listdir(llm_friendly_packages_dir):
|
780
|
-
domain_path = os.path.join(llm_friendly_packages_dir, domain)
|
781
|
-
if os.path.isdir(domain_path):
|
782
|
-
for username in os.listdir(domain_path):
|
783
|
-
username_path = os.path.join(domain_path, username)
|
784
|
-
if os.path.isdir(username_path):
|
785
|
-
for lib_name in os.listdir(username_path):
|
786
|
-
lib_path = os.path.join(username_path, lib_name)
|
787
|
-
if (
|
788
|
-
os.path.isdir(lib_path)
|
789
|
-
and (
|
790
|
-
package_name is None
|
791
|
-
or lib_name == package_name
|
792
|
-
or package_name == os.path.join(username, lib_name)
|
793
|
-
)
|
794
|
-
and lib_name in libs
|
795
|
-
):
|
796
|
-
for root, _, files in os.walk(lib_path):
|
797
|
-
for file in files:
|
798
|
-
if file.endswith(".md"):
|
799
|
-
file_path = os.path.join(
|
800
|
-
root, file)
|
801
|
-
if return_paths:
|
802
|
-
docs.append(file_path)
|
803
|
-
else:
|
804
|
-
with open(file_path, "r") as f:
|
805
|
-
docs.append(f.read())
|
806
|
-
|
807
|
-
return docs
|
808
|
-
|
809
|
-
def get_last_yaml_info(self) -> Dict[str, Any]:
|
810
|
-
last_yaml_file = get_last_yaml_file("actions")
|
811
|
-
if last_yaml_file:
|
812
|
-
file_path = os.path.join("actions", last_yaml_file)
|
813
|
-
try:
|
814
|
-
with open(file_path, 'r') as f:
|
815
|
-
yaml_content = yaml.safe_load(f)
|
816
|
-
return {
|
817
|
-
"status": True,
|
818
|
-
"file_name": last_yaml_file,
|
819
|
-
"content": yaml_content
|
820
|
-
}
|
821
|
-
except yaml.YAMLError as e:
|
822
|
-
return {
|
823
|
-
"status": False,
|
824
|
-
"message": f"Error parsing YAML file: {str(e)}"
|
825
|
-
}
|
826
|
-
except Exception as e:
|
827
|
-
return {
|
828
|
-
"status": False,
|
829
|
-
"message": f"Error reading YAML file: {str(e)}"
|
830
|
-
}
|
831
|
-
else:
|
832
|
-
return {
|
833
|
-
"status": False,
|
834
|
-
"message": "No previous chat action found."
|
835
|
-
}
|
836
|
-
|