auto-coder-web 0.1.61__py3-none-any.whl → 0.1.62__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/auto_coder_conf_router.py +3 -3
- auto_coder_web/common_router/completions_router.py +15 -8
- auto_coder_web/common_router/file_router.py +39 -14
- auto_coder_web/expert_routers/history_router.py +1 -1
- auto_coder_web/file_manager.py +178 -3
- auto_coder_web/proxy.py +2 -1
- auto_coder_web/routers/auto_router.py +1 -1
- auto_coder_web/routers/chat_router.py +2 -2
- auto_coder_web/routers/coding_router.py +3 -3
- auto_coder_web/routers/commit_router.py +1 -1
- auto_coder_web/routers/config_router.py +9 -4
- auto_coder_web/routers/mcp_router.py +287 -62
- auto_coder_web/version.py +1 -1
- auto_coder_web/web/HistoryPanel-WXEMtQ1V.js +1 -0
- auto_coder_web/web/{assets/cssMode-DzQL_FdL.js → cssMode-CQYz0o1d.js} +1 -1
- auto_coder_web/web/{assets/freemarker2-B6aemwaC.js → freemarker2-BJU3cSen.js} +1 -1
- auto_coder_web/web/{assets/handlebars-C2cM4xS8.js → handlebars-VxUdciXQ.js} +1 -1
- auto_coder_web/web/{assets/html-Da2X74rm.js → html-DEgskwXL.js} +1 -1
- auto_coder_web/web/{assets/htmlMode-Y3eBAl3y.js → htmlMode-58cXxJlI.js} +1 -1
- auto_coder_web/web/index.html +2 -2
- auto_coder_web/web/{assets/javascript-zkPPW3gg.js → javascript-DbZlm-ig.js} +1 -1
- auto_coder_web/web/{assets/jsonMode-Ce8jLIxT.js → jsonMode-C2HKgmE0.js} +1 -1
- auto_coder_web/web/{assets/liquid-CHrk1uPD.js → liquid-BFft-XkQ.js} +1 -1
- auto_coder_web/web/main-DxnFm18B.css +32 -0
- auto_coder_web/web/{assets/index-kyBHOjJU.js → main.js} +384 -373
- auto_coder_web/web/{assets/mdx-B7AzIQ1K.js → mdx-DZdDhrJW.js} +1 -1
- auto_coder_web/web/{assets/python-D340AvYz.js → python-DOHUGzLU.js} +1 -1
- auto_coder_web/web/{assets/razor-DfFnL0En.js → razor-Dh5mSHi2.js} +1 -1
- auto_coder_web/web/{assets/tsMode-CwjVYPI2.js → tsMode-tyI6CeIR.js} +1 -1
- auto_coder_web/web/{assets/typescript-JxFW6htN.js → typescript-DZzM_VgT.js} +1 -1
- auto_coder_web/web/{assets/xml-CY673mn0.js → xml-Dl6413Na.js} +1 -1
- auto_coder_web/web/{assets/yaml-D2wmMay7.js → yaml-DML583wh.js} +1 -1
- {auto_coder_web-0.1.61.dist-info → auto_coder_web-0.1.62.dist-info}/METADATA +2 -2
- {auto_coder_web-0.1.61.dist-info → auto_coder_web-0.1.62.dist-info}/RECORD +108 -107
- auto_coder_web/web/assets/index-CRXh98Y9.css +0 -32
- /auto_coder_web/web/{assets/abap-BrgZPUOV.js → abap-BrgZPUOV.js} +0 -0
- /auto_coder_web/web/{assets/apex-DyP6w7ZV.js → apex-DyP6w7ZV.js} +0 -0
- /auto_coder_web/web/{assets/azcli-BaLxmfj-.js → azcli-BaLxmfj-.js} +0 -0
- /auto_coder_web/web/{assets/bat-CFOPXBzS.js → bat-CFOPXBzS.js} +0 -0
- /auto_coder_web/web/{assets/bicep-BfEKNvv3.js → bicep-BfEKNvv3.js} +0 -0
- /auto_coder_web/web/{assets/cameligo-BFG1Mk7z.js → cameligo-BFG1Mk7z.js} +0 -0
- /auto_coder_web/web/{assets/clojure-DTECt2xU.js → clojure-DTECt2xU.js} +0 -0
- /auto_coder_web/web/{assets/codicon-DCmgc-ay.ttf → codicon-DCmgc-ay.ttf} +0 -0
- /auto_coder_web/web/{assets/coffee-CDGzqUPQ.js → coffee-CDGzqUPQ.js} +0 -0
- /auto_coder_web/web/{assets/cpp-CLLBncYj.js → cpp-CLLBncYj.js} +0 -0
- /auto_coder_web/web/{assets/csharp-dUCx_-0o.js → csharp-dUCx_-0o.js} +0 -0
- /auto_coder_web/web/{assets/csp-5Rap-vPy.js → csp-5Rap-vPy.js} +0 -0
- /auto_coder_web/web/{assets/css-D3h14YRZ.js → css-D3h14YRZ.js} +0 -0
- /auto_coder_web/web/{assets/cypher-DrQuvNYM.js → cypher-DrQuvNYM.js} +0 -0
- /auto_coder_web/web/{assets/dart-CFKIUWau.js → dart-CFKIUWau.js} +0 -0
- /auto_coder_web/web/{assets/dockerfile-Zznr-cwX.js → dockerfile-Zznr-cwX.js} +0 -0
- /auto_coder_web/web/{assets/ecl-Ce3n6wWz.js → ecl-Ce3n6wWz.js} +0 -0
- /auto_coder_web/web/{assets/elixir-deUWdS0T.js → elixir-deUWdS0T.js} +0 -0
- /auto_coder_web/web/{assets/flow9-i9-g7ZhI.js → flow9-i9-g7ZhI.js} +0 -0
- /auto_coder_web/web/{assets/fsharp-CzKuDChf.js → fsharp-CzKuDChf.js} +0 -0
- /auto_coder_web/web/{assets/go-Cphgjts3.js → go-Cphgjts3.js} +0 -0
- /auto_coder_web/web/{assets/graphql-Cg7bfA9N.js → graphql-Cg7bfA9N.js} +0 -0
- /auto_coder_web/web/{assets/hcl-0cvrggvQ.js → hcl-0cvrggvQ.js} +0 -0
- /auto_coder_web/web/{assets/ini-Drc7WvVn.js → ini-Drc7WvVn.js} +0 -0
- /auto_coder_web/web/{assets/java-B_fMsGYe.js → java-B_fMsGYe.js} +0 -0
- /auto_coder_web/web/{assets/julia-Bqgm2twL.js → julia-Bqgm2twL.js} +0 -0
- /auto_coder_web/web/{assets/kotlin-BSkB5QuD.js → kotlin-BSkB5QuD.js} +0 -0
- /auto_coder_web/web/{assets/less-BsTHnhdd.js → less-BsTHnhdd.js} +0 -0
- /auto_coder_web/web/{assets/lexon-YWi4-JPR.js → lexon-YWi4-JPR.js} +0 -0
- /auto_coder_web/web/{assets/lua-nf6ki56Z.js → lua-nf6ki56Z.js} +0 -0
- /auto_coder_web/web/{assets/m3-Cpb6xl2v.js → m3-Cpb6xl2v.js} +0 -0
- /auto_coder_web/web/{assets/markdown-DSZPf7rp.js → markdown-DSZPf7rp.js} +0 -0
- /auto_coder_web/web/{assets/mips-B_c3zf-v.js → mips-B_c3zf-v.js} +0 -0
- /auto_coder_web/web/{assets/msdax-rUNN04Wq.js → msdax-rUNN04Wq.js} +0 -0
- /auto_coder_web/web/{assets/mysql-DDwshQtU.js → mysql-DDwshQtU.js} +0 -0
- /auto_coder_web/web/{assets/objective-c-B5zXfXm9.js → objective-c-B5zXfXm9.js} +0 -0
- /auto_coder_web/web/{assets/pascal-CXOwvkN_.js → pascal-CXOwvkN_.js} +0 -0
- /auto_coder_web/web/{assets/pascaligo-Bc-ZgV77.js → pascaligo-Bc-ZgV77.js} +0 -0
- /auto_coder_web/web/{assets/perl-CwNk8-XU.js → perl-CwNk8-XU.js} +0 -0
- /auto_coder_web/web/{assets/pgsql-tGk8EFnU.js → pgsql-tGk8EFnU.js} +0 -0
- /auto_coder_web/web/{assets/php-CpIb_Oan.js → php-CpIb_Oan.js} +0 -0
- /auto_coder_web/web/{assets/pla-B03wrqEc.js → pla-B03wrqEc.js} +0 -0
- /auto_coder_web/web/{assets/postiats-BKlk5iyT.js → postiats-BKlk5iyT.js} +0 -0
- /auto_coder_web/web/{assets/powerquery-Bhzvs7bI.js → powerquery-Bhzvs7bI.js} +0 -0
- /auto_coder_web/web/{assets/powershell-Dd3NCNK9.js → powershell-Dd3NCNK9.js} +0 -0
- /auto_coder_web/web/{assets/protobuf-COyEY5Pt.js → protobuf-COyEY5Pt.js} +0 -0
- /auto_coder_web/web/{assets/pug-BaJupSGV.js → pug-BaJupSGV.js} +0 -0
- /auto_coder_web/web/{assets/qsharp-DXyYeYxl.js → qsharp-DXyYeYxl.js} +0 -0
- /auto_coder_web/web/{assets/r-CdQndTaG.js → r-CdQndTaG.js} +0 -0
- /auto_coder_web/web/{assets/redis-CVwtpugi.js → redis-CVwtpugi.js} +0 -0
- /auto_coder_web/web/{assets/redshift-25W9uPmb.js → redshift-25W9uPmb.js} +0 -0
- /auto_coder_web/web/{assets/restructuredtext-DfzH4Xui.js → restructuredtext-DfzH4Xui.js} +0 -0
- /auto_coder_web/web/{assets/ruby-Cp1zYvxS.js → ruby-Cp1zYvxS.js} +0 -0
- /auto_coder_web/web/{assets/rust-D5C2fndG.js → rust-D5C2fndG.js} +0 -0
- /auto_coder_web/web/{assets/sb-CDntyWJ8.js → sb-CDntyWJ8.js} +0 -0
- /auto_coder_web/web/{assets/scala-BoFRg7Ot.js → scala-BoFRg7Ot.js} +0 -0
- /auto_coder_web/web/{assets/scheme-Bio4gycK.js → scheme-Bio4gycK.js} +0 -0
- /auto_coder_web/web/{assets/scss-4Ik7cdeQ.js → scss-4Ik7cdeQ.js} +0 -0
- /auto_coder_web/web/{assets/shell-CX-rkNHf.js → shell-CX-rkNHf.js} +0 -0
- /auto_coder_web/web/{assets/solidity-Tw7wswEv.js → solidity-Tw7wswEv.js} +0 -0
- /auto_coder_web/web/{assets/sophia-C5WLch3f.js → sophia-C5WLch3f.js} +0 -0
- /auto_coder_web/web/{assets/sparql-DHaeiCBh.js → sparql-DHaeiCBh.js} +0 -0
- /auto_coder_web/web/{assets/sql-CCSDG5nI.js → sql-CCSDG5nI.js} +0 -0
- /auto_coder_web/web/{assets/st-pnP8ivHi.js → st-pnP8ivHi.js} +0 -0
- /auto_coder_web/web/{assets/swift-DwJ7jVG9.js → swift-DwJ7jVG9.js} +0 -0
- /auto_coder_web/web/{assets/systemverilog-B9Xyijhd.js → systemverilog-B9Xyijhd.js} +0 -0
- /auto_coder_web/web/{assets/tcl-DnHyzjbg.js → tcl-DnHyzjbg.js} +0 -0
- /auto_coder_web/web/{assets/twig-CPajHgWi.js → twig-CPajHgWi.js} +0 -0
- /auto_coder_web/web/{assets/typespec-D-MeaMDU.js → typespec-D-MeaMDU.js} +0 -0
- /auto_coder_web/web/{assets/vb-DgyLZaXg.js → vb-DgyLZaXg.js} +0 -0
- /auto_coder_web/web/{assets/wgsl-BIv9DU6q.js → wgsl-BIv9DU6q.js} +0 -0
- {auto_coder_web-0.1.61.dist-info → auto_coder_web-0.1.62.dist-info}/WHEEL +0 -0
- {auto_coder_web-0.1.61.dist-info → auto_coder_web-0.1.62.dist-info}/entry_points.txt +0 -0
- {auto_coder_web-0.1.61.dist-info → auto_coder_web-0.1.62.dist-info}/top_level.txt +0 -0
@@ -1,5 +1,6 @@
|
|
1
1
|
from fastapi import APIRouter, Request, HTTPException, Depends
|
2
2
|
from autocoder.auto_coder_runner import get_memory, configure
|
3
|
+
import asyncio
|
3
4
|
|
4
5
|
router = APIRouter()
|
5
6
|
|
@@ -19,19 +20,18 @@ async def config(
|
|
19
20
|
data = await request.json()
|
20
21
|
try:
|
21
22
|
for key, value in data.items():
|
22
|
-
configure
|
23
|
+
await asyncio.to_thread(configure, f"{key}:{str(value)}")
|
23
24
|
return {"status": "success"}
|
24
25
|
except Exception as e:
|
25
26
|
raise HTTPException(status_code=400, detail=str(e))
|
26
27
|
|
27
|
-
|
28
28
|
@router.delete("/api/conf/{key}")
|
29
29
|
async def delete_config(
|
30
30
|
key: str
|
31
31
|
):
|
32
32
|
"""删除配置项"""
|
33
33
|
try:
|
34
|
-
configure
|
34
|
+
await asyncio.to_thread(configure, f"/drop {key}")
|
35
35
|
return {"status": "success"}
|
36
36
|
except ValueError as e:
|
37
37
|
raise HTTPException(status_code=404, detail=str(e))
|
@@ -15,6 +15,8 @@ from autocoder.index.symbols_utils import (
|
|
15
15
|
from autocoder.auto_coder_runner import get_memory
|
16
16
|
import json
|
17
17
|
import asyncio
|
18
|
+
import aiofiles
|
19
|
+
import aiofiles.os
|
18
20
|
|
19
21
|
|
20
22
|
router = APIRouter()
|
@@ -98,14 +100,19 @@ def find_files_in_project(patterns: List[str], project_path: str) -> List[str]:
|
|
98
100
|
|
99
101
|
return list(matched_files)
|
100
102
|
|
101
|
-
def
|
103
|
+
async def get_symbol_list_async(project_path: str) -> List[SymbolItem]:
|
104
|
+
"""Asynchronously reads the index file and extracts symbols."""
|
102
105
|
list_of_symbols = []
|
103
|
-
index_file = os.path.join(
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
106
|
+
index_file = os.path.join(project_path, ".auto-coder", "index.json")
|
107
|
+
|
108
|
+
if await aiofiles.os.path.exists(index_file):
|
109
|
+
try:
|
110
|
+
async with aiofiles.open(index_file, "r", encoding='utf-8') as file:
|
111
|
+
content = await file.read()
|
112
|
+
index_data = json.loads(content)
|
113
|
+
except (IOError, json.JSONDecodeError):
|
114
|
+
# Handle file reading or JSON parsing errors
|
115
|
+
index_data = {}
|
109
116
|
else:
|
110
117
|
index_data = {}
|
111
118
|
|
@@ -169,7 +176,7 @@ async def get_symbol_completions(
|
|
169
176
|
project_path: str = Depends(get_project_path)
|
170
177
|
):
|
171
178
|
"""获取符号补全"""
|
172
|
-
symbols = await
|
179
|
+
symbols = await get_symbol_list_async(project_path)
|
173
180
|
matches = []
|
174
181
|
|
175
182
|
for symbol in symbols:
|
@@ -1,7 +1,12 @@
|
|
1
1
|
import os
|
2
2
|
import shutil
|
3
|
+
import aiofiles
|
4
|
+
import aiofiles.os
|
3
5
|
from fastapi import APIRouter, Request, HTTPException, Depends
|
4
|
-
from auto_coder_web.file_manager import
|
6
|
+
from auto_coder_web.file_manager import (
|
7
|
+
get_directory_tree_async,
|
8
|
+
read_file_content_async,
|
9
|
+
)
|
5
10
|
|
6
11
|
router = APIRouter()
|
7
12
|
|
@@ -15,16 +20,18 @@ async def get_auto_coder_runner(request: Request):
|
|
15
20
|
|
16
21
|
@router.delete("/api/files/{path:path}")
|
17
22
|
async def delete_file(
|
18
|
-
path: str,
|
23
|
+
path: str,
|
19
24
|
project_path: str = Depends(get_project_path)
|
20
25
|
):
|
21
26
|
try:
|
22
27
|
full_path = os.path.join(project_path, path)
|
23
|
-
if os.path.exists(full_path):
|
24
|
-
if os.path.isdir(full_path):
|
28
|
+
if await aiofiles.os.path.exists(full_path):
|
29
|
+
if await aiofiles.os.path.isdir(full_path):
|
30
|
+
# Use shutil.rmtree for directories as aiofiles doesn't have a recursive delete
|
31
|
+
# Consider adding a custom async recursive delete if performance is critical
|
25
32
|
shutil.rmtree(full_path)
|
26
33
|
else:
|
27
|
-
os.remove(full_path)
|
34
|
+
await aiofiles.os.remove(full_path)
|
28
35
|
return {"message": f"Successfully deleted {path}"}
|
29
36
|
else:
|
30
37
|
raise HTTPException(
|
@@ -34,11 +41,22 @@ async def delete_file(
|
|
34
41
|
|
35
42
|
@router.get("/api/files")
|
36
43
|
async def get_files(
|
44
|
+
request: Request, # Need request to access project_path if not using Depends
|
45
|
+
path: str = None, # Optional path parameter for lazy loading
|
46
|
+
lazy: bool = False, # Optional lazy parameter
|
37
47
|
project_path: str = Depends(get_project_path)
|
38
48
|
):
|
39
|
-
|
40
|
-
|
41
|
-
|
49
|
+
try:
|
50
|
+
# Pass path and lazy parameters if provided in the query
|
51
|
+
query_params = request.query_params
|
52
|
+
path_param = query_params.get("path")
|
53
|
+
lazy_param = query_params.get("lazy", "false").lower() == "true"
|
54
|
+
|
55
|
+
tree = await get_directory_tree_async(project_path, path=path_param, lazy=lazy_param)
|
56
|
+
return {"tree": tree}
|
57
|
+
except Exception as e:
|
58
|
+
# Log the error e
|
59
|
+
raise HTTPException(status_code=500, detail=f"Failed to get directory tree: {str(e)}")
|
42
60
|
|
43
61
|
@router.put("/api/file/{path:path}")
|
44
62
|
async def update_file(
|
@@ -54,15 +72,22 @@ async def update_file(
|
|
54
72
|
status_code=400, detail="Content is required")
|
55
73
|
|
56
74
|
full_path = os.path.join(project_path, path)
|
75
|
+
dir_path = os.path.dirname(full_path)
|
76
|
+
|
77
|
+
# Ensure the directory exists asynchronously
|
78
|
+
if not await aiofiles.os.path.exists(dir_path):
|
79
|
+
await aiofiles.os.makedirs(dir_path, exist_ok=True)
|
80
|
+
elif not await aiofiles.os.path.isdir(dir_path):
|
81
|
+
raise HTTPException(status_code=400, detail=f"Path conflict: {dir_path} exists but is not a directory.")
|
57
82
|
|
58
|
-
# Ensure the directory exists
|
59
|
-
os.makedirs(os.path.dirname(full_path), exist_ok=True)
|
60
83
|
|
61
|
-
# Write the file content
|
62
|
-
with open(full_path, 'w', encoding='utf-8') as f:
|
63
|
-
f.write(content)
|
84
|
+
# Write the file content asynchronously
|
85
|
+
async with aiofiles.open(full_path, 'w', encoding='utf-8') as f:
|
86
|
+
await f.write(content)
|
64
87
|
|
65
88
|
return {"message": f"Successfully updated {path}"}
|
89
|
+
except HTTPException as http_exc: # Re-raise HTTP exceptions
|
90
|
+
raise http_exc
|
66
91
|
except Exception as e:
|
67
92
|
raise HTTPException(status_code=500, detail=str(e))
|
68
93
|
|
@@ -71,7 +96,7 @@ async def get_file_content(
|
|
71
96
|
path: str,
|
72
97
|
project_path: str = Depends(get_project_path)
|
73
98
|
):
|
74
|
-
content =
|
99
|
+
content = await read_file_content_async(project_path, path)
|
75
100
|
if content is None:
|
76
101
|
raise HTTPException(
|
77
102
|
status_code=404, detail="File not found or cannot be read")
|
@@ -87,7 +87,7 @@ async def validate_and_load_history(
|
|
87
87
|
历史记录查询列表
|
88
88
|
"""
|
89
89
|
# 定义在线程中执行的函数
|
90
|
-
def load_history_task(project_path, max_history_count=
|
90
|
+
def load_history_task(project_path, max_history_count=10):
|
91
91
|
try:
|
92
92
|
# 过滤出聊天动作事件
|
93
93
|
action_manager = ActionYmlFileManager(project_path)
|
auto_coder_web/file_manager.py
CHANGED
@@ -1,5 +1,8 @@
|
|
1
1
|
import os
|
2
2
|
import json
|
3
|
+
import asyncio
|
4
|
+
import aiofiles
|
5
|
+
import aiofiles.os
|
3
6
|
from typing import List, Dict, Any, Optional
|
4
7
|
|
5
8
|
|
@@ -100,15 +103,187 @@ def get_directory_tree(root_path: str, path: str = None, lazy: bool = False) ->
|
|
100
103
|
return build_tree(target_path)
|
101
104
|
return []
|
102
105
|
|
103
|
-
# If no path provided, build tree from root
|
106
|
+
# If no path provided, build tree from root
|
107
|
+
# If lazy is True, only immediate children are returned.
|
108
|
+
# If lazy is False, the full tree is built recursively.
|
109
|
+
# If no path provided, build tree from root
|
110
|
+
# If lazy is True, only immediate children are returned.
|
111
|
+
# If lazy is False, the full tree is built recursively.
|
104
112
|
return build_tree(root_path)
|
105
113
|
|
106
114
|
|
115
|
+
async def get_directory_tree_async(root_path: str, path: str = None, lazy: bool = False) -> List[Dict[str, Any]]:
|
116
|
+
"""
|
117
|
+
Asynchronously generate a directory tree structure using aiofiles while ignoring common directories and files
|
118
|
+
that should not be included in version control or IDE specific files.
|
119
|
+
|
120
|
+
Args:
|
121
|
+
root_path: The root directory path to start traversing from
|
122
|
+
path: Optional path relative to root_path to get children for
|
123
|
+
lazy: If True, only return immediate children for directories
|
124
|
+
|
125
|
+
Returns:
|
126
|
+
A list of dictionaries representing the directory tree structure
|
127
|
+
"""
|
128
|
+
# Common directories and files to ignore (same as synchronous version)
|
129
|
+
IGNORE_PATTERNS = {
|
130
|
+
# Version control
|
131
|
+
'.git', '.svn', '.hg',
|
132
|
+
# Dependencies
|
133
|
+
'node_modules', 'venv', '.venv', 'env', '.env',
|
134
|
+
'__pycache__', '.pytest_cache',
|
135
|
+
# Build outputs
|
136
|
+
'dist', 'build', 'target',
|
137
|
+
# IDE specific
|
138
|
+
'.idea', '.vscode', '.vs',
|
139
|
+
# OS specific
|
140
|
+
'.DS_Store', 'Thumbs.db',
|
141
|
+
# Other common patterns
|
142
|
+
'coverage', '.coverage', 'htmlcov',
|
143
|
+
# Hidden directories (start with .) - Note: This logic is slightly different now
|
144
|
+
# '.hidden_file', '.hidden_dir' # Example explicit hidden items if needed
|
145
|
+
}
|
146
|
+
|
147
|
+
def should_ignore(name: str) -> bool:
|
148
|
+
"""Check if a file or directory should be ignored"""
|
149
|
+
# Ignore hidden files/directories (starting with '.')
|
150
|
+
if name.startswith('.'):
|
151
|
+
return True
|
152
|
+
# Ignore exact matches
|
153
|
+
return name in IGNORE_PATTERNS
|
154
|
+
|
155
|
+
async def build_tree(current_path: str) -> List[Dict[str, Any]]:
|
156
|
+
"""Recursively build the directory tree asynchronously using aiofiles"""
|
157
|
+
items = []
|
158
|
+
try:
|
159
|
+
# Use aiofiles.os.listdir
|
160
|
+
child_names = await aiofiles.os.listdir(current_path)
|
161
|
+
tasks = []
|
162
|
+
for name in sorted(child_names):
|
163
|
+
if should_ignore(name):
|
164
|
+
continue
|
165
|
+
tasks.append(process_item(current_path, name))
|
166
|
+
|
167
|
+
results = await asyncio.gather(*tasks)
|
168
|
+
items = [item for item in results if item is not None] # Filter out None results from ignored items or errors
|
169
|
+
|
170
|
+
except PermissionError:
|
171
|
+
# Skip directories we don't have permission to read
|
172
|
+
pass
|
173
|
+
except FileNotFoundError:
|
174
|
+
# Handle case where directory doesn't exist during processing
|
175
|
+
pass
|
176
|
+
|
177
|
+
return items
|
178
|
+
|
179
|
+
async def process_item(current_path: str, name: str) -> Optional[Dict[str, Any]]:
|
180
|
+
"""Process a single directory item asynchronously"""
|
181
|
+
try:
|
182
|
+
full_path = os.path.join(current_path, name)
|
183
|
+
relative_path = os.path.relpath(full_path, root_path)
|
184
|
+
|
185
|
+
# Use aiofiles.os.path.isdir
|
186
|
+
is_dir = await aiofiles.os.path.isdir(full_path)
|
187
|
+
|
188
|
+
if is_dir:
|
189
|
+
if lazy:
|
190
|
+
# For lazy loading, check if directory has any visible children asynchronously
|
191
|
+
has_children = False
|
192
|
+
try:
|
193
|
+
# Use aiofiles.os.listdir
|
194
|
+
for child_name in await aiofiles.os.listdir(full_path):
|
195
|
+
if not should_ignore(child_name):
|
196
|
+
has_children = True
|
197
|
+
break
|
198
|
+
except (PermissionError, FileNotFoundError):
|
199
|
+
pass # Ignore errors checking for children, assume no visible children
|
200
|
+
|
201
|
+
return {
|
202
|
+
'title': name,
|
203
|
+
'key': relative_path,
|
204
|
+
'children': [], # Empty children array for lazy loading
|
205
|
+
'isLeaf': False,
|
206
|
+
'hasChildren': has_children
|
207
|
+
}
|
208
|
+
else:
|
209
|
+
children = await build_tree(full_path)
|
210
|
+
if children: # Only add non-empty directories
|
211
|
+
return {
|
212
|
+
'title': name,
|
213
|
+
'key': relative_path,
|
214
|
+
'children': children,
|
215
|
+
'isLeaf': False,
|
216
|
+
'hasChildren': True
|
217
|
+
}
|
218
|
+
else: # Represent empty directories as leaves if not lazy loading
|
219
|
+
return {
|
220
|
+
'title': name,
|
221
|
+
'key': relative_path,
|
222
|
+
'isLeaf': True,
|
223
|
+
'hasChildren': False
|
224
|
+
}
|
225
|
+
else:
|
226
|
+
return {
|
227
|
+
'title': name,
|
228
|
+
'key': relative_path,
|
229
|
+
'isLeaf': True,
|
230
|
+
'hasChildren': False
|
231
|
+
}
|
232
|
+
except (PermissionError, FileNotFoundError):
|
233
|
+
# Skip items we can't process
|
234
|
+
return None
|
235
|
+
|
236
|
+
|
237
|
+
target_path = root_path
|
238
|
+
if path:
|
239
|
+
# If path is provided, get children of that specific directory
|
240
|
+
potential_target_path = os.path.join(root_path, path)
|
241
|
+
# Use aiofiles.os.path.isdir for the check
|
242
|
+
if await aiofiles.os.path.isdir(potential_target_path):
|
243
|
+
target_path = potential_target_path
|
244
|
+
else:
|
245
|
+
return [] # Path does not point to a valid directory
|
246
|
+
|
247
|
+
return await build_tree(target_path)
|
248
|
+
|
249
|
+
|
107
250
|
def read_file_content(project_path: str, file_path: str) -> str:
|
108
251
|
"""Read the content of a file"""
|
109
252
|
try:
|
110
253
|
full_path = os.path.join(project_path, file_path)
|
111
|
-
|
254
|
+
# Check if the path exists and is a file before attempting to open
|
255
|
+
if not os.path.exists(full_path) or not os.path.isfile(full_path):
|
256
|
+
return None # Or raise a specific error like FileNotFoundError
|
257
|
+
with open(full_path, 'r', encoding='utf-8', errors='ignore') as f: # Added errors='ignore' for robustness
|
112
258
|
return f.read()
|
113
|
-
except
|
259
|
+
except IOError: # Catching only IOError, as UnicodeDecodeError is handled by errors='ignore'
|
260
|
+
# Log the error here if needed
|
261
|
+
return None
|
262
|
+
except Exception as e:
|
263
|
+
# Catch any other unexpected error
|
264
|
+
# Log e
|
265
|
+
return None
|
266
|
+
|
267
|
+
|
268
|
+
async def read_file_content_async(project_path: str, file_path: str) -> Optional[str]:
|
269
|
+
"""Asynchronously read the content of a file using aiofiles"""
|
270
|
+
full_path = os.path.join(project_path, file_path)
|
271
|
+
try:
|
272
|
+
# Check if the path exists and is a file before attempting to open using aiofiles.os
|
273
|
+
path_exists = await aiofiles.os.path.exists(full_path)
|
274
|
+
is_file = await aiofiles.os.path.isfile(full_path)
|
275
|
+
|
276
|
+
if not path_exists or not is_file:
|
277
|
+
return None # Or raise a specific error like FileNotFoundError
|
278
|
+
|
279
|
+
# Use aiofiles for asynchronous file reading
|
280
|
+
async with aiofiles.open(full_path, mode='r', encoding='utf-8', errors='ignore') as f:
|
281
|
+
content = await f.read()
|
282
|
+
return content
|
283
|
+
except (IOError, FileNotFoundError): # Catch file-related errors
|
284
|
+
# Log the error here if needed
|
285
|
+
return None
|
286
|
+
except Exception as e:
|
287
|
+
# Catch any other unexpected error during path checks or reading
|
288
|
+
# Log e
|
114
289
|
return None
|
auto_coder_web/proxy.py
CHANGED
@@ -19,7 +19,7 @@ import sys
|
|
19
19
|
from auto_coder_web.terminal import terminal_manager
|
20
20
|
from autocoder.common import AutoCoderArgs
|
21
21
|
from auto_coder_web.auto_coder_runner_wrapper import AutoCoderRunnerWrapper
|
22
|
-
from auto_coder_web.routers import todo_router, settings_router, auto_router, commit_router, chat_router, coding_router, index_router, config_router, upload_router, rag_router, editable_preview_router
|
22
|
+
from auto_coder_web.routers import todo_router, settings_router, auto_router, commit_router, chat_router, coding_router, index_router, config_router, upload_router, rag_router, editable_preview_router,mcp_router
|
23
23
|
from auto_coder_web.expert_routers import history_router
|
24
24
|
from auto_coder_web.common_router import completions_router, file_router, auto_coder_conf_router, chat_list_router, file_group_router, model_router, compiler_router
|
25
25
|
from rich.console import Console
|
@@ -101,6 +101,7 @@ class ProxyServer:
|
|
101
101
|
self.app.include_router(upload_router.router)
|
102
102
|
self.app.include_router(rag_router.router)
|
103
103
|
self.app.include_router(editable_preview_router.router)
|
104
|
+
self.app.include_router(mcp_router.router)
|
104
105
|
# self.app.include_router(rag_router.router)
|
105
106
|
|
106
107
|
@self.app.on_event("shutdown")
|
@@ -87,7 +87,7 @@ async def auto_command(request: AutoCommandRequest, project_path: str = Depends(
|
|
87
87
|
except Exception as e:
|
88
88
|
logger.error(f"Error executing auto command {file_id}: {str(e)}")
|
89
89
|
get_event_manager(event_file).write_error(
|
90
|
-
EventContentCreator.create_error("500",
|
90
|
+
EventContentCreator.create_error(error_code="500", error_message=str(e), details={}).to_dict()
|
91
91
|
)
|
92
92
|
|
93
93
|
# 创建并启动线程
|
@@ -80,7 +80,7 @@ async def chat_command(request: ChatCommandRequest, project_path: str = Depends(
|
|
80
80
|
except Exception as e:
|
81
81
|
logger.error(f"Error executing chat command {file_id}: {str(e)}")
|
82
82
|
get_event_manager(event_file).write_error(
|
83
|
-
EventContentCreator.create_error("500",
|
83
|
+
EventContentCreator.create_error(error_code="500", error_message=str(e), details={}).to_dict()
|
84
84
|
)
|
85
85
|
|
86
86
|
# 创建并启动线程
|
@@ -313,7 +313,7 @@ async def cancel_task(request: CancelTaskRequest, project_path: str = Depends(ge
|
|
313
313
|
# 写入取消事件
|
314
314
|
event_manager.write_error(
|
315
315
|
EventContentCreator.create_error(
|
316
|
-
"499", "cancelled",
|
316
|
+
error_code="499", error_message="cancelled", details={}).to_dict()
|
317
317
|
)
|
318
318
|
|
319
319
|
logger.info(f"Task {request.event_file_id} cancelled successfully")
|
@@ -146,7 +146,7 @@ async def coding_command(request: CodingCommandRequest, project_path: str = Depe
|
|
146
146
|
except Exception as e:
|
147
147
|
logger.error(f"Error executing coding command {file_id}: {str(e)}")
|
148
148
|
get_event_manager(event_file).write_error(
|
149
|
-
EventContentCreator.create_error("500",
|
149
|
+
EventContentCreator.create_error(error_code="500", error_message=str(e), details={}).to_dict()
|
150
150
|
)
|
151
151
|
|
152
152
|
# 创建并启动线程
|
@@ -371,7 +371,7 @@ async def cancel_task(request: CancelTaskRequest, project_path: str = Depends(ge
|
|
371
371
|
def cancel_in_thread():
|
372
372
|
try:
|
373
373
|
# 设置全局取消标志
|
374
|
-
global_cancel.
|
374
|
+
global_cancel.set()
|
375
375
|
|
376
376
|
# 获取事件管理器
|
377
377
|
event_manager = get_event_manager(event_file)
|
@@ -379,7 +379,7 @@ async def cancel_task(request: CancelTaskRequest, project_path: str = Depends(ge
|
|
379
379
|
# 写入取消事件
|
380
380
|
event_manager.write_error(
|
381
381
|
EventContentCreator.create_error(
|
382
|
-
"499", "cancelled",
|
382
|
+
error_code="499", error_message="cancelled", details={}).to_dict()
|
383
383
|
)
|
384
384
|
|
385
385
|
logger.info(f"Task {request.event_file_id} cancelled successfully")
|
@@ -1,5 +1,6 @@
|
|
1
1
|
import os
|
2
2
|
import json
|
3
|
+
import aiofiles
|
3
4
|
from fastapi import APIRouter, HTTPException, Request, Depends
|
4
5
|
from pydantic import BaseModel
|
5
6
|
from pathlib import Path
|
@@ -27,16 +28,20 @@ async def load_config(config_path: Path) -> UIConfig:
|
|
27
28
|
return UIConfig()
|
28
29
|
|
29
30
|
try:
|
30
|
-
with open(config_path, 'r') as f:
|
31
|
-
|
31
|
+
async with aiofiles.open(config_path, mode='r', encoding='utf-8') as f:
|
32
|
+
content = await f.read()
|
33
|
+
config_data = json.loads(content)
|
32
34
|
return UIConfig(**config_data)
|
35
|
+
except FileNotFoundError:
|
36
|
+
return UIConfig()
|
33
37
|
except json.JSONDecodeError:
|
38
|
+
# Handle cases where the file is corrupted or empty
|
34
39
|
return UIConfig()
|
35
40
|
|
36
41
|
async def save_config(config: UIConfig, config_path: Path):
|
37
42
|
"""保存配置"""
|
38
|
-
with open(config_path, 'w') as f:
|
39
|
-
json.
|
43
|
+
async with aiofiles.open(config_path, mode='w', encoding='utf-8') as f:
|
44
|
+
await f.write(json.dumps(config.dict()))
|
40
45
|
|
41
46
|
@router.get("/api/config/ui/mode")
|
42
47
|
async def get_ui_mode(request: Request):
|