auto-coder-web 0.1.40__py3-none-any.whl → 0.1.42__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.
@@ -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
-