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.
Files changed (31) hide show
  1. auto_coder_web/auto_coder_runner_wrapper.py +7 -0
  2. auto_coder_web/common_router/__init__.py +0 -0
  3. auto_coder_web/common_router/auto_coder_conf_router.py +42 -0
  4. auto_coder_web/common_router/chat_list_router.py +75 -0
  5. auto_coder_web/common_router/completions_router.py +53 -0
  6. auto_coder_web/common_router/file_group_router.py +120 -0
  7. auto_coder_web/common_router/file_router.py +78 -0
  8. auto_coder_web/proxy.py +22 -472
  9. auto_coder_web/routers/chat_router.py +330 -0
  10. auto_coder_web/routers/coding_router.py +330 -0
  11. auto_coder_web/routers/commit_router.py +79 -21
  12. auto_coder_web/routers/config_router.py +199 -0
  13. auto_coder_web/routers/todo_router.py +463 -20
  14. auto_coder_web/version.py +1 -1
  15. auto_coder_web/web/asset-manifest.json +6 -6
  16. auto_coder_web/web/index.html +1 -1
  17. auto_coder_web/web/static/css/main.95277c5f.css +6 -0
  18. auto_coder_web/web/static/css/main.95277c5f.css.map +1 -0
  19. auto_coder_web/web/static/js/main.66c37ccf.js +3 -0
  20. auto_coder_web/web/static/js/{main.ce819ed7.js.LICENSE.txt → main.66c37ccf.js.LICENSE.txt} +40 -0
  21. auto_coder_web/web/static/js/{main.ce819ed7.js.map → main.66c37ccf.js.map} +1 -1
  22. {auto_coder_web-0.1.23.dist-info → auto_coder_web-0.1.25.dist-info}/METADATA +2 -2
  23. {auto_coder_web-0.1.23.dist-info → auto_coder_web-0.1.25.dist-info}/RECORD +28 -17
  24. {auto_coder_web-0.1.23.dist-info → auto_coder_web-0.1.25.dist-info}/top_level.txt +1 -0
  25. expert_routers/__init__.py +3 -0
  26. expert_routers/history_router.py +333 -0
  27. auto_coder_web/web/static/css/main.8a9e40fa.css +0 -6
  28. auto_coder_web/web/static/css/main.8a9e40fa.css.map +0 -1
  29. auto_coder_web/web/static/js/main.ce819ed7.js +0 -3
  30. {auto_coder_web-0.1.23.dist-info → auto_coder_web-0.1.25.dist-info}/WHEEL +0 -0
  31. {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"获取事件: {len(all_events)}")
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
- for event in all_events:
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
- if action_file in action_files:
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
- commit_msg_part = action_manager.get_commit_id_from_file(action_file)
459
- commit_id = None
460
- logger.info(f"获取Action文件提交ID: {commit_msg_part}")
461
- for commit in repo.iter_commits():
462
- if commit_msg_part in commit.message:
463
- commit_id = commit.hexsha
464
- break
465
-
466
- if commit_id:
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}: {str(e)}")
537
+ logger.warning(f"无法获取提交 {commit_id} 的详情: {str(e)}")
538
+
539
+ logger.info(f"处理完所有action文件,共找到 {len(commits)} 个提交")
492
540
 
493
541
  # 按提交时间戳排序(降序 - 最新的在前面)
494
- commits.sort(key=lambda x: x['timestamp'], reverse=True)
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"Revert \"{commit.message.strip()}\"\n\nThis reverts commit {commit.hexsha}"
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}