auto-coder-web 0.1.23__py3-none-any.whl → 0.1.25__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/auto_coder_runner_wrapper.py +7 -0
- auto_coder_web/common_router/__init__.py +0 -0
- auto_coder_web/common_router/auto_coder_conf_router.py +42 -0
- auto_coder_web/common_router/chat_list_router.py +75 -0
- auto_coder_web/common_router/completions_router.py +53 -0
- auto_coder_web/common_router/file_group_router.py +120 -0
- auto_coder_web/common_router/file_router.py +78 -0
- auto_coder_web/proxy.py +22 -472
- auto_coder_web/routers/chat_router.py +330 -0
- auto_coder_web/routers/coding_router.py +330 -0
- auto_coder_web/routers/commit_router.py +79 -21
- auto_coder_web/routers/config_router.py +199 -0
- auto_coder_web/routers/todo_router.py +463 -20
- auto_coder_web/version.py +1 -1
- auto_coder_web/web/asset-manifest.json +6 -6
- auto_coder_web/web/index.html +1 -1
- auto_coder_web/web/static/css/main.95277c5f.css +6 -0
- auto_coder_web/web/static/css/main.95277c5f.css.map +1 -0
- auto_coder_web/web/static/js/main.66c37ccf.js +3 -0
- auto_coder_web/web/static/js/{main.ce819ed7.js.LICENSE.txt → main.66c37ccf.js.LICENSE.txt} +40 -0
- auto_coder_web/web/static/js/{main.ce819ed7.js.map → main.66c37ccf.js.map} +1 -1
- {auto_coder_web-0.1.23.dist-info → auto_coder_web-0.1.25.dist-info}/METADATA +2 -2
- {auto_coder_web-0.1.23.dist-info → auto_coder_web-0.1.25.dist-info}/RECORD +28 -17
- {auto_coder_web-0.1.23.dist-info → auto_coder_web-0.1.25.dist-info}/top_level.txt +1 -0
- expert_routers/__init__.py +3 -0
- expert_routers/history_router.py +333 -0
- auto_coder_web/web/static/css/main.8a9e40fa.css +0 -6
- auto_coder_web/web/static/css/main.8a9e40fa.css.map +0 -1
- auto_coder_web/web/static/js/main.ce819ed7.js +0 -3
- {auto_coder_web-0.1.23.dist-info → auto_coder_web-0.1.25.dist-info}/WHEEL +0 -0
- {auto_coder_web-0.1.23.dist-info → auto_coder_web-0.1.25.dist-info}/entry_points.txt +0 -0
@@ -416,33 +416,47 @@ async def get_current_changes(
|
|
416
416
|
Returns:
|
417
417
|
提交哈希列表
|
418
418
|
"""
|
419
|
+
logger.info(f"开始获取当前变更 - 参数: limit={limit}, hours_ago={hours_ago}, event_file_id={event_file_id}, project_path={project_path}")
|
419
420
|
try:
|
420
421
|
repo = get_repo(project_path)
|
422
|
+
logger.info(f"成功获取Git仓库: {project_path}")
|
421
423
|
|
422
424
|
# 如果提供了事件文件ID,从事件中获取相关提交
|
423
425
|
if event_file_id:
|
426
|
+
logger.info(f"使用事件文件模式获取提交, event_file_id={event_file_id}")
|
424
427
|
try:
|
425
428
|
# 获取事件文件路径
|
426
429
|
event_file_path = get_event_file_path(event_file_id, project_path)
|
430
|
+
logger.info(f"事件文件路径: {event_file_path}")
|
427
431
|
|
428
432
|
# 获取事件管理器
|
429
433
|
event_manager = get_event_manager(event_file_path)
|
434
|
+
logger.info(f"成功获取事件管理器")
|
430
435
|
|
431
436
|
# 获取所有事件
|
432
437
|
all_events = event_manager.event_store.get_events()
|
433
|
-
logger.info(f"
|
438
|
+
logger.info(f"获取事件总数: {len(all_events)}")
|
434
439
|
|
435
440
|
# 创建ActionYmlFileManager实例
|
436
441
|
action_manager = ActionYmlFileManager(project_path)
|
442
|
+
logger.info(f"成功创建ActionYmlFileManager实例")
|
437
443
|
|
438
444
|
action_files = set()
|
439
445
|
final_action_files = []
|
440
446
|
|
441
|
-
|
447
|
+
# 记录事件中包含action_file字段的事件数量
|
448
|
+
action_file_count = 0
|
449
|
+
|
450
|
+
for i, event in enumerate(all_events):
|
451
|
+
logger.debug(f"处理事件 {i+1}/{len(all_events)}, 事件类型: {event.event_type}")
|
442
452
|
# 检查元数据中是否有action_file字段
|
443
|
-
if 'action_file' in event.metadata and event.metadata['action_file']:
|
453
|
+
if 'action_file' in event.metadata and event.metadata['action_file']:
|
454
|
+
action_file_count += 1
|
444
455
|
action_file = event.metadata['action_file']
|
445
|
-
|
456
|
+
logger.debug(f"事件 {i+1} 包含action_file: {action_file}")
|
457
|
+
|
458
|
+
if action_file in action_files:
|
459
|
+
logger.debug(f"跳过重复的action_file: {action_file}")
|
446
460
|
continue
|
447
461
|
|
448
462
|
action_files.add(action_file)
|
@@ -450,25 +464,58 @@ async def get_current_changes(
|
|
450
464
|
# action_file 这里的值是 类似这样的 actions/000000000104_chat_action.yml
|
451
465
|
if action_file.startswith("actions"):
|
452
466
|
action_file = action_file[len("actions/"):]
|
467
|
+
logger.debug(f"处理后的action_file: {action_file}")
|
453
468
|
|
454
469
|
final_action_files.append(action_file)
|
455
470
|
|
471
|
+
logger.info(f"从 {len(all_events)} 个事件中提取到 {action_file_count} 个包含action_file的事件")
|
472
|
+
logger.info(f"去重后得到 {len(action_files)} 个唯一action_file")
|
473
|
+
logger.info(f"最终处理的action_files: {final_action_files}")
|
474
|
+
|
456
475
|
commits = []
|
457
|
-
for action_file in final_action_files:
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
476
|
+
for i, action_file in enumerate(final_action_files):
|
477
|
+
logger.info(f"处理action文件 {i+1}/{len(final_action_files)}: {action_file}")
|
478
|
+
commit_ids = action_manager.get_all_commit_id_from_file(action_file)
|
479
|
+
|
480
|
+
logger.info(f"从action文件 {action_file} 获取到的提交ID列表: {commit_ids}")
|
481
|
+
|
482
|
+
if not commit_ids:
|
483
|
+
logger.warning(f"无法从action文件 {action_file} 获取提交ID")
|
484
|
+
continue
|
485
|
+
|
486
|
+
# 如果有两个提交,检查是否有一个是revert提交
|
487
|
+
if len(commit_ids) == 2:
|
488
|
+
logger.info(f"检测到两个提交ID,可能存在revert操作: {commit_ids}")
|
489
|
+
revert_commit_id = None
|
490
|
+
|
491
|
+
# 检查每个提交是否是revert提交
|
492
|
+
for cid in commit_ids:
|
493
|
+
try:
|
494
|
+
commit = repo.commit(cid)
|
495
|
+
message = commit.message.strip()
|
496
|
+
logger.info(f"检查提交 {cid[:7]} 是否为revert提交,消息: {message[:50]}...")
|
497
|
+
|
498
|
+
if message.startswith("<revert>"):
|
499
|
+
logger.info(f"找到revert提交: {cid}")
|
500
|
+
revert_commit_id = cid
|
501
|
+
break
|
502
|
+
except Exception as e:
|
503
|
+
logger.warning(f"检查提交 {cid} 时出错: {str(e)}")
|
504
|
+
|
505
|
+
# 如果找到revert提交,只处理这个提交
|
506
|
+
if revert_commit_id:
|
507
|
+
logger.info(f"将只处理revert提交: {revert_commit_id}")
|
508
|
+
commit_ids = [revert_commit_id]
|
509
|
+
|
510
|
+
# 处理所有提交ID(或者只处理revert提交)
|
511
|
+
for commit_id in commit_ids:
|
467
512
|
# 验证提交ID是否存在于仓库中
|
468
513
|
try:
|
514
|
+
logger.info(f"获取提交详情: {commit_id}")
|
469
515
|
commit = repo.commit(commit_id)
|
470
516
|
# 获取提交统计信息
|
471
517
|
stats = commit.stats.total
|
518
|
+
logger.info(f"提交统计信息: 插入={stats['insertions']}, 删除={stats['deletions']}, 文件变更={stats['files']}")
|
472
519
|
|
473
520
|
# 构建提交信息
|
474
521
|
commit_info = {
|
@@ -485,27 +532,38 @@ async def get_current_changes(
|
|
485
532
|
}
|
486
533
|
}
|
487
534
|
commits.append(commit_info)
|
488
|
-
|
489
|
-
return {"commits": commits, "total": len(list(repo.iter_commits()))}
|
535
|
+
logger.info(f"成功添加提交信息到结果列表,当前结果数: {len(commits)}")
|
490
536
|
except Exception as e:
|
491
|
-
logger.warning(f"无法获取提交 {commit_id}
|
537
|
+
logger.warning(f"无法获取提交 {commit_id} 的详情: {str(e)}")
|
538
|
+
|
539
|
+
logger.info(f"处理完所有action文件,共找到 {len(commits)} 个提交")
|
492
540
|
|
493
541
|
# 按提交时间戳排序(降序 - 最新的在前面)
|
494
|
-
commits
|
542
|
+
if commits:
|
543
|
+
commits.sort(key=lambda x: x['timestamp'], reverse=True)
|
544
|
+
logger.info("提交已按时间戳降序排序")
|
495
545
|
|
496
|
-
|
546
|
+
logger.info(f"返回结果: {len(commits)} 个提交")
|
497
547
|
return {"commits": commits, "total": len(commits)}
|
498
548
|
|
499
549
|
except Exception as e:
|
500
550
|
logger.error(f"从事件文件获取提交失败: {str(e)}")
|
501
|
-
|
551
|
+
import traceback
|
552
|
+
logger.error(f"详细错误信息: {traceback.format_exc()}")
|
502
553
|
return {"commits": [], "total": 0}
|
503
554
|
else:
|
504
555
|
# 如果没有提供事件文件ID,返回最近的提交
|
556
|
+
logger.info("未提供event_file_id,应返回最近的提交,但当前实现返回空列表")
|
557
|
+
# 这里应该调用get_recent_commits函数获取最近的提交
|
558
|
+
# recent_commits = await get_recent_commits(repo, limit, hours_ago)
|
559
|
+
# logger.info(f"获取到 {len(recent_commits.get('commit_hashes', []))} 个最近的提交")
|
560
|
+
# return recent_commits
|
505
561
|
return {"commits": [], "total": 0}
|
506
562
|
|
507
563
|
except Exception as e:
|
508
564
|
logger.error(f"获取当前变更失败: {str(e)}")
|
565
|
+
import traceback
|
566
|
+
logger.error(f"详细错误信息: {traceback.format_exc()}")
|
509
567
|
raise HTTPException(
|
510
568
|
status_code=500,
|
511
569
|
detail=f"获取当前变更失败: {str(e)}"
|
@@ -593,7 +651,7 @@ async def revert_commit(
|
|
593
651
|
repo.git.revert(commit.hexsha, no_commit=True)
|
594
652
|
|
595
653
|
# 创建带有信息的 revert 提交
|
596
|
-
revert_message = f"
|
654
|
+
revert_message = f"<revert>{commit.message.strip()}\n{commit.hexsha}"
|
597
655
|
new_commit = repo.index.commit(
|
598
656
|
revert_message,
|
599
657
|
author=repo.active_branch.commit.author,
|
@@ -0,0 +1,199 @@
|
|
1
|
+
import os
|
2
|
+
import json
|
3
|
+
import logging
|
4
|
+
import asyncio
|
5
|
+
import aiofiles
|
6
|
+
from fastapi import APIRouter, HTTPException
|
7
|
+
from pydantic import BaseModel, Field
|
8
|
+
from typing import Optional, List, Union, Dict, Any
|
9
|
+
from datetime import datetime
|
10
|
+
from pathlib import Path
|
11
|
+
|
12
|
+
router = APIRouter()
|
13
|
+
|
14
|
+
# 配置存储路径
|
15
|
+
CONFIG_FILE = Path(".auto-coder/auto-coder.web/configs/config.json")
|
16
|
+
|
17
|
+
# 确保目录存在
|
18
|
+
CONFIG_FILE.parent.mkdir(parents=True, exist_ok=True)
|
19
|
+
|
20
|
+
logger = logging.getLogger(__name__)
|
21
|
+
|
22
|
+
# UI 配置模型
|
23
|
+
class UIConfig(BaseModel):
|
24
|
+
mode: str = "agent" # agent/expert
|
25
|
+
theme: str = "dark"
|
26
|
+
language: str = "zh-CN"
|
27
|
+
|
28
|
+
# 编辑器配置模型
|
29
|
+
class EditorConfig(BaseModel):
|
30
|
+
fontSize: int = 14
|
31
|
+
tabSize: int = 2
|
32
|
+
wordWrap: str = "on"
|
33
|
+
|
34
|
+
# 功能配置模型
|
35
|
+
class FeaturesConfig(BaseModel):
|
36
|
+
autoSave: bool = True
|
37
|
+
livePreview: bool = True
|
38
|
+
|
39
|
+
# 配置设置模型
|
40
|
+
class ConfigSettings(BaseModel):
|
41
|
+
ui: UIConfig = Field(default_factory=UIConfig)
|
42
|
+
editor: EditorConfig = Field(default_factory=EditorConfig)
|
43
|
+
features: FeaturesConfig = Field(default_factory=FeaturesConfig)
|
44
|
+
updated_at: str = Field(default_factory=lambda: datetime.now().isoformat())
|
45
|
+
|
46
|
+
# 更新请求模型 - 所有字段都是可选的
|
47
|
+
class UIConfigUpdate(BaseModel):
|
48
|
+
mode: Optional[str] = None
|
49
|
+
theme: Optional[str] = None
|
50
|
+
language: Optional[str] = None
|
51
|
+
|
52
|
+
class EditorConfigUpdate(BaseModel):
|
53
|
+
fontSize: Optional[int] = None
|
54
|
+
tabSize: Optional[int] = None
|
55
|
+
wordWrap: Optional[str] = None
|
56
|
+
|
57
|
+
class FeaturesConfigUpdate(BaseModel):
|
58
|
+
autoSave: Optional[bool] = None
|
59
|
+
livePreview: Optional[bool] = None
|
60
|
+
|
61
|
+
class ConfigUpdateRequest(BaseModel):
|
62
|
+
ui: Optional[UIConfigUpdate] = None
|
63
|
+
editor: Optional[EditorConfigUpdate] = None
|
64
|
+
features: Optional[FeaturesConfigUpdate] = None
|
65
|
+
|
66
|
+
async def load_config() -> ConfigSettings:
|
67
|
+
"""异步加载配置,如果文件不存在则返回默认配置"""
|
68
|
+
if not await asyncio.to_thread(lambda: CONFIG_FILE.exists()):
|
69
|
+
return ConfigSettings()
|
70
|
+
|
71
|
+
try:
|
72
|
+
async with aiofiles.open(CONFIG_FILE, mode='r') as f:
|
73
|
+
content = await f.read()
|
74
|
+
config_data = json.loads(content)
|
75
|
+
return ConfigSettings(**config_data)
|
76
|
+
except (json.JSONDecodeError, FileNotFoundError):
|
77
|
+
logger.error("Failed to parse config.json, returning default config")
|
78
|
+
return ConfigSettings()
|
79
|
+
|
80
|
+
async def save_config(config: ConfigSettings):
|
81
|
+
"""异步保存配置"""
|
82
|
+
async with aiofiles.open(CONFIG_FILE, mode='w') as f:
|
83
|
+
await f.write(json.dumps(config.dict(), indent=2, ensure_ascii=False))
|
84
|
+
|
85
|
+
@router.get("/api/config", response_model=ConfigSettings)
|
86
|
+
async def get_config():
|
87
|
+
"""获取当前所有配置"""
|
88
|
+
return await load_config()
|
89
|
+
|
90
|
+
@router.get("/api/config/ui", response_model=UIConfig)
|
91
|
+
async def get_ui_config():
|
92
|
+
"""获取UI配置"""
|
93
|
+
config = await load_config()
|
94
|
+
return config.ui
|
95
|
+
|
96
|
+
@router.get("/api/config/editor", response_model=EditorConfig)
|
97
|
+
async def get_editor_config():
|
98
|
+
"""获取编辑器配置"""
|
99
|
+
config = await load_config()
|
100
|
+
return config.editor
|
101
|
+
|
102
|
+
@router.get("/api/config/features", response_model=FeaturesConfig)
|
103
|
+
async def get_features_config():
|
104
|
+
"""获取功能配置"""
|
105
|
+
config = await load_config()
|
106
|
+
return config.features
|
107
|
+
|
108
|
+
@router.put("/api/config", response_model=ConfigSettings)
|
109
|
+
async def update_config(request: ConfigUpdateRequest):
|
110
|
+
"""更新配置"""
|
111
|
+
config = await load_config()
|
112
|
+
|
113
|
+
# 更新UI配置
|
114
|
+
if request.ui:
|
115
|
+
ui_update = request.ui.dict(exclude_unset=True)
|
116
|
+
if ui_update:
|
117
|
+
config.ui = UIConfig(**{**config.ui.dict(), **ui_update})
|
118
|
+
|
119
|
+
# 更新编辑器配置
|
120
|
+
if request.editor:
|
121
|
+
editor_update = request.editor.dict(exclude_unset=True)
|
122
|
+
if editor_update:
|
123
|
+
config.editor = EditorConfig(**{**config.editor.dict(), **editor_update})
|
124
|
+
|
125
|
+
# 更新功能配置
|
126
|
+
if request.features:
|
127
|
+
features_update = request.features.dict(exclude_unset=True)
|
128
|
+
if features_update:
|
129
|
+
config.features = FeaturesConfig(**{**config.features.dict(), **features_update})
|
130
|
+
|
131
|
+
# 更新时间戳
|
132
|
+
config.updated_at = datetime.now().isoformat()
|
133
|
+
|
134
|
+
await save_config(config)
|
135
|
+
return config
|
136
|
+
|
137
|
+
@router.put("/api/config/ui", response_model=UIConfig)
|
138
|
+
async def update_ui_config(request: UIConfigUpdate):
|
139
|
+
"""更新UI配置"""
|
140
|
+
config = await load_config()
|
141
|
+
|
142
|
+
# 只更新提供的字段
|
143
|
+
update_data = request.dict(exclude_unset=True)
|
144
|
+
if update_data:
|
145
|
+
config.ui = UIConfig(**{**config.ui.dict(), **update_data})
|
146
|
+
config.updated_at = datetime.now().isoformat()
|
147
|
+
await save_config(config)
|
148
|
+
|
149
|
+
return config.ui
|
150
|
+
|
151
|
+
@router.put("/api/config/editor", response_model=EditorConfig)
|
152
|
+
async def update_editor_config(request: EditorConfigUpdate):
|
153
|
+
"""更新编辑器配置"""
|
154
|
+
config = await load_config()
|
155
|
+
|
156
|
+
# 只更新提供的字段
|
157
|
+
update_data = request.dict(exclude_unset=True)
|
158
|
+
if update_data:
|
159
|
+
config.editor = EditorConfig(**{**config.editor.dict(), **update_data})
|
160
|
+
config.updated_at = datetime.now().isoformat()
|
161
|
+
await save_config(config)
|
162
|
+
|
163
|
+
return config.editor
|
164
|
+
|
165
|
+
@router.put("/api/config/features", response_model=FeaturesConfig)
|
166
|
+
async def update_features_config(request: FeaturesConfigUpdate):
|
167
|
+
"""更新功能配置"""
|
168
|
+
config = await load_config()
|
169
|
+
|
170
|
+
# 只更新提供的字段
|
171
|
+
update_data = request.dict(exclude_unset=True)
|
172
|
+
if update_data:
|
173
|
+
config.features = FeaturesConfig(**{**config.features.dict(), **update_data})
|
174
|
+
config.updated_at = datetime.now().isoformat()
|
175
|
+
await save_config(config)
|
176
|
+
|
177
|
+
return config.features
|
178
|
+
|
179
|
+
# 单个配置项更新端点
|
180
|
+
@router.get("/api/config/ui/mode")
|
181
|
+
async def get_ui_mode():
|
182
|
+
"""获取UI模式配置"""
|
183
|
+
config = await load_config()
|
184
|
+
return {"mode": config.ui.mode}
|
185
|
+
|
186
|
+
@router.put("/api/config/ui/mode")
|
187
|
+
async def update_ui_mode(mode: str):
|
188
|
+
"""更新UI模式配置"""
|
189
|
+
config = await load_config()
|
190
|
+
|
191
|
+
# 验证模式值
|
192
|
+
if mode not in ["agent", "expert"]:
|
193
|
+
raise HTTPException(status_code=400, detail="Mode must be 'agent' or 'expert'")
|
194
|
+
|
195
|
+
config.ui.mode = mode
|
196
|
+
config.updated_at = datetime.now().isoformat()
|
197
|
+
await save_config(config)
|
198
|
+
|
199
|
+
return {"mode": mode}
|