auto-coder-web 0.1.90__py3-none-any.whl → 0.1.91__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,6 +1,7 @@
1
1
  import os
2
2
  import glob
3
3
  import json
4
+ import fnmatch
4
5
  from typing import List
5
6
  from pydantic import BaseModel
6
7
  from fastapi import APIRouter, Query, Request, Depends
@@ -13,12 +14,15 @@ from autocoder.index.symbols_utils import (
13
14
  )
14
15
 
15
16
  from autocoder.auto_coder_runner import get_memory
17
+ from autocoder.common.ignorefiles.ignore_file_utils import should_ignore
18
+ from autocoder.common.directory_cache.cache import DirectoryCache, initialize_cache
16
19
  import json
17
20
  import asyncio
18
21
  import aiofiles
19
22
  import aiofiles.os
23
+ import logging
20
24
 
21
-
25
+ logger = logging.getLogger(__name__)
22
26
  router = APIRouter()
23
27
 
24
28
  class SymbolItem(BaseModel):
@@ -35,67 +39,57 @@ async def get_project_path(request: Request):
35
39
  """获取项目路径作为依赖"""
36
40
  return request.app.state.project_path
37
41
 
38
- def find_files_in_project(patterns: List[str], project_path: str) -> List[str]:
42
+ async def find_files_in_project(patterns: List[str], project_path: str) -> List[str]:
39
43
  memory = get_memory()
40
- default_exclude_dirs = [".git", "node_modules", "dist", "build", "__pycache__", ".venv", ".auto-coder"]
41
44
  active_file_list = memory["current_files"]["files"]
42
- final_exclude_dirs = default_exclude_dirs + memory.get("exclude_dirs", [])
43
45
  project_root = project_path
44
46
 
45
- def should_exclude_path(path: str) -> bool:
46
- """检查路径是否应该被排除(路径中包含排除目录或以.开头的目录/文件)"""
47
- # 处理相对/绝对路径
48
- rel_path = path
49
- if os.path.isabs(path):
50
- try:
51
- rel_path = os.path.relpath(path, project_root)
52
- except ValueError:
53
- rel_path = path
54
-
55
- # 检查文件或目录本身是否以.开头
56
- if os.path.basename(rel_path).startswith('.'):
57
- return True
58
-
59
- # 检查路径中是否包含排除目录
60
- path_parts = rel_path.split(os.sep)
61
- return any(part in final_exclude_dirs or part.startswith('.') for part in path_parts)
62
-
47
+ # 确保目录缓存已初始化
48
+ try:
49
+ cache = DirectoryCache.get_instance(project_root)
50
+ except ValueError:
51
+ # 如果缓存未初始化,则初始化它
52
+ initialize_cache(project_root)
53
+ cache = DirectoryCache.get_instance()
54
+
63
55
  # 如果没有提供有效模式,返回过滤后的活动文件列表
64
56
  if not patterns or (len(patterns) == 1 and patterns[0] == ""):
65
- return [f for f in active_file_list if not should_exclude_path(f)]
57
+ # 使用缓存中的所有文件
58
+ all_files = await cache.query([])
59
+ # 合并活动文件列表和缓存文件
60
+ combined_files = set(all_files)
61
+ combined_files.update([f for f in active_file_list if not should_ignore(f)])
62
+ return list(combined_files)
66
63
 
67
64
  matched_files = set() # 使用集合避免重复
68
-
65
+
66
+ # 1. 首先从活动文件列表中匹配,这通常是最近编辑的文件
69
67
  for pattern in patterns:
70
- # 1. 从活动文件列表中匹配
71
68
  for file_path in active_file_list:
72
- if not should_exclude_path(file_path) and pattern in os.path.basename(file_path):
69
+ if not should_ignore(file_path) and pattern.lower() in os.path.basename(file_path).lower():
73
70
  matched_files.add(file_path)
74
-
75
- # 2. 如果是通配符模式,使用glob
71
+
72
+ # 2. 使用DirectoryCache进行高效查询
73
+ cache_patterns = []
74
+ for pattern in patterns:
75
+ # 处理通配符模式
76
76
  if "*" in pattern or "?" in pattern:
77
- for file_path in glob.glob(pattern, recursive=True):
78
- if os.path.isfile(file_path) and not should_exclude_path(file_path):
79
- matched_files.add(os.path.abspath(file_path))
80
- continue
81
-
82
- # 3. 使用os.walk在文件系统中查找
83
- for root, dirs, files in os.walk(project_root, followlinks=True):
84
- # 过滤不需要遍历的目录
85
- dirs[:] = [d for d in dirs if d not in final_exclude_dirs and not d.startswith('.')]
86
-
87
- if should_exclude_path(root):
88
- continue
89
-
90
- # 查找匹配文件
91
- for file in files:
92
- if pattern in file:
93
- file_path = os.path.join(root, file)
94
- if not should_exclude_path(file_path):
95
- matched_files.add(file_path)
96
-
97
- # 4. 如果pattern本身是文件路径
98
- if os.path.exists(pattern) and os.path.isfile(pattern) and not should_exclude_path(pattern):
77
+ cache_patterns.append(pattern)
78
+ else:
79
+ # 对于非通配符模式,我们添加一个通配符以进行部分匹配
80
+ cache_patterns.append(f"*{pattern}*")
81
+
82
+ # 执行缓存查询
83
+ if cache_patterns:
84
+ try:
85
+ cache_results = await cache.query(cache_patterns)
86
+ matched_files.update(cache_results)
87
+ except Exception as e:
88
+ logger.error(f"Error querying directory cache: {e}", exc_info=True)
89
+
90
+ # 3. 如果pattern本身是文件路径,直接添加
91
+ for pattern in patterns:
92
+ if os.path.exists(pattern) and os.path.isfile(pattern) and not should_ignore(pattern):
99
93
  matched_files.add(os.path.abspath(pattern))
100
94
 
101
95
  return list(matched_files)
@@ -153,11 +147,11 @@ async def get_file_completions(
153
147
  ):
154
148
  """获取文件名补全"""
155
149
  patterns = [name]
156
- matches = await asyncio.to_thread(find_files_in_project, patterns,project_path)
150
+ # 直接调用异步函数,不需要使用asyncio.to_thread
151
+ matches = await find_files_in_project(patterns, project_path)
157
152
  completions = []
158
153
  project_root = project_path
159
154
  for file_name in matches:
160
- # path_parts = file_name.split(os.sep)
161
155
  # 只显示最后三层路径,让显示更简洁
162
156
  display_name = os.path.basename(file_name)
163
157
  relative_path = os.path.relpath(file_name, project_root)
@@ -14,6 +14,9 @@ import pathspec
14
14
 
15
15
  router = APIRouter()
16
16
 
17
+ class CreateFileRequest(BaseModel):
18
+ content: str = ""
19
+
17
20
  DEFAULT_IGNORED_DIRS = ['.git', '.auto-coder', 'node_modules', '.mvn', '.idea', '__pycache__', '.venv', 'venv', 'dist', 'build', '.gradle']
18
21
 
19
22
  def load_ignore_spec(source_dir: str) -> Optional[pathspec.PathSpec]:
@@ -196,6 +199,69 @@ async def list_files_in_directory(
196
199
  return result
197
200
 
198
201
 
202
+ @router.post("/api/file/{path:path}")
203
+ async def create_file(
204
+ path: str,
205
+ request: Request,
206
+ project_path: str = Depends(get_project_path)
207
+ ):
208
+ """
209
+ Create a new file at the specified path with optional initial content.
210
+ """
211
+ try:
212
+ data = await request.json()
213
+ content = data.get("content", "")
214
+
215
+ full_path = os.path.join(project_path, path)
216
+ dir_path = os.path.dirname(full_path)
217
+
218
+ # Check if file already exists
219
+ if await aiofiles.os.path.exists(full_path):
220
+ raise HTTPException(status_code=409, detail=f"File already exists at {path}")
221
+
222
+ # Ensure the directory exists asynchronously
223
+ if not await aiofiles.os.path.exists(dir_path):
224
+ await aiofiles.os.makedirs(dir_path, exist_ok=True)
225
+ elif not await aiofiles.os.path.isdir(dir_path):
226
+ raise HTTPException(status_code=400, detail=f"Path conflict: {dir_path} exists but is not a directory.")
227
+
228
+ # Write the file content asynchronously
229
+ async with aiofiles.open(full_path, 'w', encoding='utf-8') as f:
230
+ await f.write(content)
231
+
232
+ return {"message": f"Successfully created {path}"}
233
+ except HTTPException as http_exc: # Re-raise HTTP exceptions
234
+ raise http_exc
235
+ except Exception as e:
236
+ raise HTTPException(status_code=500, detail=str(e))
237
+
238
+ @router.post("/api/directory/{path:path}")
239
+ async def create_directory(
240
+ path: str,
241
+ project_path: str = Depends(get_project_path)
242
+ ):
243
+ """
244
+ Create a new directory at the specified path.
245
+ """
246
+ try:
247
+ full_path = os.path.join(project_path, path)
248
+
249
+ # Check if directory already exists
250
+ if await aiofiles.os.path.exists(full_path):
251
+ if await aiofiles.os.path.isdir(full_path):
252
+ return {"message": f"Directory already exists at {path}"}
253
+ else:
254
+ raise HTTPException(status_code=409, detail=f"A file already exists at {path}")
255
+
256
+ # Create the directory and any necessary parent directories
257
+ await aiofiles.os.makedirs(full_path, exist_ok=True)
258
+
259
+ return {"message": f"Successfully created directory {path}"}
260
+ except HTTPException as http_exc: # Re-raise HTTP exceptions
261
+ raise http_exc
262
+ except Exception as e:
263
+ raise HTTPException(status_code=500, detail=str(e))
264
+
199
265
  @router.get("/api/search-in-files")
200
266
  async def search_in_files(
201
267
  query: str = Query(..., description="Search text"),
auto_coder_web/version.py CHANGED
@@ -1 +1 @@
1
- __version__ = "0.1.90"
1
+ __version__ = "0.1.91"