auto-coder-web 0.1.24__py3-none-any.whl → 0.1.26__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 (30) 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 +39 -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 +192 -0
  7. auto_coder_web/common_router/file_router.py +79 -0
  8. auto_coder_web/proxy.py +22 -477
  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/config_router.py +199 -0
  12. auto_coder_web/routers/todo_router.py +463 -20
  13. auto_coder_web/version.py +1 -1
  14. auto_coder_web/web/asset-manifest.json +6 -6
  15. auto_coder_web/web/index.html +1 -1
  16. auto_coder_web/web/static/css/main.b9764291.css +6 -0
  17. auto_coder_web/web/static/css/main.b9764291.css.map +1 -0
  18. auto_coder_web/web/static/js/main.a707a18c.js +3 -0
  19. auto_coder_web/web/static/js/{main.470202a1.js.LICENSE.txt → main.a707a18c.js.LICENSE.txt} +43 -1
  20. auto_coder_web/web/static/js/{main.470202a1.js.map → main.a707a18c.js.map} +1 -1
  21. {auto_coder_web-0.1.24.dist-info → auto_coder_web-0.1.26.dist-info}/METADATA +1 -1
  22. {auto_coder_web-0.1.24.dist-info → auto_coder_web-0.1.26.dist-info}/RECORD +27 -16
  23. {auto_coder_web-0.1.24.dist-info → auto_coder_web-0.1.26.dist-info}/top_level.txt +1 -0
  24. expert_routers/__init__.py +3 -0
  25. expert_routers/history_router.py +333 -0
  26. auto_coder_web/web/static/css/main.770925e5.css +0 -6
  27. auto_coder_web/web/static/css/main.770925e5.css.map +0 -1
  28. auto_coder_web/web/static/js/main.470202a1.js +0 -3
  29. {auto_coder_web-0.1.24.dist-info → auto_coder_web-0.1.26.dist-info}/WHEEL +0 -0
  30. {auto_coder_web-0.1.24.dist-info → auto_coder_web-0.1.26.dist-info}/entry_points.txt +0 -0
auto_coder_web/proxy.py CHANGED
@@ -23,19 +23,9 @@ from auto_coder_web.file_group import FileGroupManager
23
23
  from auto_coder_web.file_manager import get_directory_tree
24
24
  from auto_coder_web.auto_coder_runner import AutoCoderRunner
25
25
  from autocoder.agent.auto_filegroup import AutoFileGroup
26
- from .types import (
27
- EventGetRequest,
28
- EventResponseRequest,
29
- CompletionItem,
30
- CompletionResponse,
31
- ChatList,
32
- HistoryQuery,
33
- ValidationResponse,
34
- QueryWithFileNumber,
35
- ValidationResponseWithFileNumbers,
36
- FileContentResponse,
37
- FileChange,
38
- CommitDiffResponse,
26
+ from .types import (
27
+ ChatList,
28
+ FileContentResponse,
39
29
  )
40
30
 
41
31
  from rich.console import Console
@@ -53,8 +43,10 @@ import yaml
53
43
  import git
54
44
  import hashlib
55
45
  from datetime import datetime
56
- from autocoder.utils import operate_config_api
57
- from .routers import todo_router, settings_router, auto_router, commit_router
46
+ from auto_coder_web.auto_coder_runner_wrapper import AutoCoderRunnerWrapper
47
+ from .routers import todo_router, settings_router, auto_router, commit_router, chat_router, coding_router
48
+ from expert_routers import history_router
49
+ from .common_router import completions_router, file_router, auto_coder_conf_router, chat_list_router, file_group_router
58
50
 
59
51
 
60
52
 
@@ -331,12 +323,9 @@ class ProxyServer:
331
323
 
332
324
  self.setup_static_files()
333
325
  self.project_path = project_path
334
-
326
+ self.auto_coder_runner = AutoCoderRunnerWrapper(project_path, product_mode=product_mode)
335
327
  self.setup_routes()
336
328
  self.client = httpx.AsyncClient()
337
-
338
- self.auto_coder_runner = AutoCoderRunner(project_path, product_mode=product_mode)
339
- self.file_group_manager = FileGroupManager(self.auto_coder_runner)
340
329
 
341
330
  def setup_middleware(self):
342
331
  self.app.add_middleware(
@@ -361,13 +350,23 @@ class ProxyServer:
361
350
 
362
351
  def setup_routes(self):
363
352
 
353
+ # Store project_path in app state for dependency injection
354
+ self.app.state.project_path = self.project_path
355
+ # Store auto_coder_runner in app state for dependency injection
356
+ self.app.state.auto_coder_runner = self.auto_coder_runner
357
+
364
358
  self.app.include_router(todo_router.router)
365
359
  self.app.include_router(settings_router.router)
366
360
  self.app.include_router(auto_router.router)
367
361
  self.app.include_router(commit_router.router)
368
-
369
- # Store project_path in app state for dependency injection
370
- self.app.state.project_path = self.project_path
362
+ self.app.include_router(chat_router.router)
363
+ self.app.include_router(coding_router.router)
364
+ self.app.include_router(history_router)
365
+ self.app.include_router(completions_router.router)
366
+ self.app.include_router(file_router.router)
367
+ self.app.include_router(auto_coder_conf_router.router)
368
+ self.app.include_router(chat_list_router.router)
369
+ self.app.include_router(file_group_router.router)
371
370
 
372
371
  @self.app.on_event("shutdown")
373
372
  async def shutdown_event():
@@ -378,23 +377,6 @@ class ProxyServer:
378
377
  session_id = str(uuid.uuid4())
379
378
  await terminal_manager.handle_websocket(websocket, session_id)
380
379
 
381
- @self.app.delete("/api/files/{path:path}")
382
- async def delete_file(path: str):
383
- try:
384
- full_path = os.path.join(self.project_path, path)
385
- if os.path.exists(full_path):
386
- if os.path.isdir(full_path):
387
- import shutil
388
- shutil.rmtree(full_path)
389
- else:
390
- os.remove(full_path)
391
- return {"message": f"Successfully deleted {path}"}
392
- else:
393
- raise HTTPException(
394
- status_code=404, detail="File not found")
395
- except Exception as e:
396
- raise HTTPException(status_code=500, detail=str(e))
397
-
398
380
  @self.app.get("/", response_class=HTMLResponse)
399
381
  async def read_root():
400
382
  if os.path.exists(self.index_html_path):
@@ -410,61 +392,10 @@ class ProxyServer:
410
392
  def get_project_runner(project_path: str) -> AutoCoderRunner:
411
393
  return self.projects[project_path]
412
394
 
413
- @self.app.post("/api/file-groups")
414
- async def create_file_group(request: Request):
415
- data = await request.json()
416
- name = data.get("name")
417
- description = data.get("description", "")
418
- group = await self.file_group_manager.create_group(name, description)
419
- return group
420
-
421
- @self.app.post("/api/file-groups/auto")
422
- async def auto_create_groups(request: Request):
423
- try:
424
- data = await request.json()
425
- file_size_limit = data.get("file_size_limit", 100)
426
- skip_diff = data.get("skip_diff", False)
427
- group_num_limit = data.get("group_num_limit", 10)
428
-
429
- # Create AutoFileGroup instance
430
- auto_grouper = AutoFileGroup(
431
- operate_config_api.get_llm(self.auto_coder_runner.memory),
432
- self.project_path,
433
- skip_diff=skip_diff,
434
- file_size_limit=file_size_limit,
435
- group_num_limit=group_num_limit
436
- )
437
-
438
- # Get groups
439
- groups = auto_grouper.group_files()
440
-
441
- # Create groups using file_group_manager
442
- for group in groups:
443
- await self.file_group_manager.create_group(
444
- name=group.name,
445
- description=group.description
446
- )
447
- # Add files to the group
448
- await self.file_group_manager.add_files_to_group(
449
- group.name,
450
- group.urls
451
- )
452
-
453
- return {"status": "success", "message": f"Created {len(groups)} groups"}
454
- except Exception as e:
455
- raise HTTPException(status_code=500, detail=str(e))
456
-
457
395
  @self.app.get("/api/os")
458
396
  async def get_os():
459
397
  return {"os": os.name}
460
398
 
461
- @self.app.post("/api/file-groups/switch")
462
- async def switch_file_groups(request: Request):
463
- data = await request.json()
464
- group_names = data.get("group_names", [])
465
- result = await self.file_group_manager.switch_groups(group_names)
466
- return result
467
-
468
399
  @self.app.get("/api/conf/keys")
469
400
  async def get_conf_keys():
470
401
  """Get all available configuration keys from AutoCoderArgs"""
@@ -492,29 +423,6 @@ class ProxyServer:
492
423
  })
493
424
  return {"keys": keys}
494
425
 
495
- @self.app.delete("/api/file-groups/{name}")
496
- async def delete_file_group(name: str):
497
- await self.file_group_manager.delete_group(name)
498
- return {"status": "success"}
499
-
500
- @self.app.post("/api/file-groups/{name}/files")
501
- async def add_files_to_group(name: str, request: Request):
502
- data = await request.json()
503
- files = data.get("files", [])
504
- description = data.get("description")
505
- if description is not None:
506
- group = await self.file_group_manager.update_group_description(name, description)
507
- else:
508
- group = await self.file_group_manager.add_files_to_group(name, files)
509
- return group
510
-
511
- @self.app.delete("/api/file-groups/{name}/files")
512
- async def remove_files_from_group(name: str, request: Request):
513
- data = await request.json()
514
- files = data.get("files", [])
515
- group = await self.file_group_manager.remove_files_from_group(name, files)
516
- return group
517
-
518
426
  @self.app.post("/api/revert")
519
427
  async def revert():
520
428
  try:
@@ -523,388 +431,25 @@ class ProxyServer:
523
431
  except Exception as e:
524
432
  raise HTTPException(status_code=500, detail=str(e))
525
433
 
526
- @self.app.get("/api/file-groups")
527
- async def get_file_groups():
528
- groups = await self.file_group_manager.get_groups()
529
- return {"groups": groups}
530
-
531
- @self.app.get("/api/files")
532
- async def get_files():
533
- tree = get_directory_tree(self.project_path)
534
- return {"tree": tree}
535
-
536
- @self.app.get("/api/completions/files")
537
- async def get_file_completions(name: str = Query(...)):
538
- """获取文件名补全"""
539
- matches = self.auto_coder_runner.find_files_in_project([name])
540
- completions = []
541
- project_root = self.auto_coder_runner.project_path
542
- for file_name in matches:
543
- path_parts = file_name.split(os.sep)
544
- # 只显示最后三层路径,让显示更简洁
545
- display_name = os.sep.join(
546
- path_parts[-3:]) if len(path_parts) > 3 else file_name
547
- relative_path = os.path.relpath(file_name, project_root)
548
-
549
- completions.append(CompletionItem(
550
- name=relative_path, # 给补全项一个唯一标识
551
- path=relative_path, # 实际用于替换的路径
552
- display=display_name, # 显示的简短路径
553
- location=relative_path # 完整的相对路径信息
554
- ))
555
- return CompletionResponse(completions=completions)
556
-
557
- @self.app.get("/api/completions/symbols")
558
- async def get_symbol_completions(name: str = Query(...)):
559
- """获取符号补全"""
560
- symbols = self.auto_coder_runner.get_symbol_list()
561
- matches = []
562
-
563
- for symbol in symbols:
564
- if name.lower() in symbol.symbol_name.lower():
565
- relative_path = os.path.relpath(
566
- symbol.file_name, self.project_path)
567
- matches.append(CompletionItem(
568
- name=symbol.symbol_name,
569
- path=f"{symbol.symbol_name} ({relative_path}/{symbol.symbol_type.value})",
570
- display=f"{symbol.symbol_name}(location: {relative_path})"
571
- ))
572
- return CompletionResponse(completions=matches)
573
-
574
- @self.app.put("/api/file/{path:path}")
575
- async def update_file(path: str, request: Request):
576
- try:
577
- data = await request.json()
578
- content = data.get("content")
579
- if content is None:
580
- raise HTTPException(
581
- status_code=400, detail="Content is required")
582
-
583
- full_path = os.path.join(self.project_path, path)
584
-
585
- # Ensure the directory exists
586
- os.makedirs(os.path.dirname(full_path), exist_ok=True)
587
-
588
- # Write the file content
589
- with open(full_path, 'w', encoding='utf-8') as f:
590
- f.write(content)
591
-
592
- return {"message": f"Successfully updated {path}"}
593
- except Exception as e:
594
- raise HTTPException(status_code=500, detail=str(e))
595
-
596
- @self.app.get("/api/file/{path:path}")
597
- async def get_file_content(path: str):
598
- from .file_manager import read_file_content
599
- content = read_file_content(self.project_path, path)
600
- if content is None:
601
- raise HTTPException(
602
- status_code=404, detail="File not found or cannot be read")
603
-
604
- return {"content": content}
605
-
606
434
  @self.app.get("/api/active-files")
607
435
  async def get_active_files():
608
436
  """获取当前活动文件列表"""
609
437
  active_files = self.auto_coder_runner.get_active_files()
610
438
  return active_files
611
439
 
612
- @self.app.get("/api/conf")
613
- async def get_conf():
614
- return {"conf": self.auto_coder_runner.get_config()}
615
-
616
- @self.app.post("/api/conf")
617
- async def config(request: Request):
618
- data = await request.json()
619
- try:
620
- for key, value in data.items():
621
- self.auto_coder_runner.configure(key, str(value))
622
- return {"status": "success"}
623
- except Exception as e:
624
- raise HTTPException(status_code=400, detail=str(e))
625
-
626
- @self.app.delete("/api/conf/{key}")
627
- async def delete_config(key: str):
628
- try:
629
- result = self.auto_coder_runner.drop_config(key)
630
- return result
631
- except ValueError as e:
632
- raise HTTPException(status_code=404, detail=str(e))
633
- except Exception as e:
634
- raise HTTPException(status_code=400, detail=str(e))
635
-
636
- @self.app.post("/api/coding")
637
- async def coding(request: Request):
638
- data = await request.json()
639
- query = data.get("query", "")
640
- if not query:
641
- raise HTTPException(
642
- status_code=400, detail="Query is required")
643
- return await self.auto_coder_runner.coding(query)
644
-
645
- @self.app.post("/api/chat")
646
- async def chat(request: Request):
647
- data = await request.json()
648
- query = data.get("query", "")
649
- if not query:
650
- raise HTTPException(
651
- status_code=400, detail="Query is required")
652
- return await self.auto_coder_runner.chat(query)
653
-
654
- @self.app.get("/api/result/{request_id}")
655
- async def get_result(request_id: str):
656
- result = await self.auto_coder_runner.get_result(request_id)
657
- if result is None:
658
- raise HTTPException(
659
- status_code=404, detail="Result not found or not ready yet")
660
-
661
- v = {"result": result.value, "status": result.status.value}
662
- return v
663
-
664
- @self.app.post("/api/event/get")
665
- async def get_event(request: EventGetRequest):
666
- request_id = request.request_id
667
- if not request_id:
668
- raise HTTPException(
669
- status_code=400, detail="request_id is required")
670
-
671
- v = self.auto_coder_runner.get_event(request_id)
672
- return v
673
-
674
- @self.app.post("/api/event/response")
675
- async def response_event(request: EventResponseRequest):
676
- request_id = request.request_id
677
- if not request_id:
678
- raise HTTPException(
679
- status_code=400, detail="request_id is required")
680
-
681
- self.auto_coder_runner.response_event(
682
- request_id, request.event, request.response)
683
- return {"message": "success"}
684
-
685
440
  @self.app.post("/api/commit")
686
441
  async def commit():
687
442
  try:
688
443
  result = self.auto_coder_runner.commit()
689
444
  return result
690
445
  except Exception as e:
691
- raise HTTPException(status_code=500, detail=str(e))
692
-
693
- @self.app.get("/api/output/{request_id}")
694
- async def get_terminal_logs(request_id: str):
695
- return self.auto_coder_runner.get_logs(request_id)
446
+ raise HTTPException(status_code=500, detail=str(e))
696
447
 
697
448
  @self.app.get("/api/last-yaml")
698
449
  async def get_last_yaml():
699
450
  """Get information about the last YAML file"""
700
451
  return JSONResponse(content=self.auto_coder_runner.get_last_yaml_info())
701
452
 
702
- @self.app.post("/api/chat-lists/save")
703
- async def save_chat_list(chat_list: ChatList):
704
- try:
705
- chat_lists_dir = os.path.join(
706
- ".auto-coder", "auto-coder.web", "chat-lists")
707
- os.makedirs(chat_lists_dir, exist_ok=True)
708
-
709
- file_path = os.path.join(
710
- chat_lists_dir, f"{chat_list.name}.json")
711
- async with aiofiles.open(file_path, 'w') as f:
712
- await f.write(json.dumps({"messages": chat_list.messages}, indent=2))
713
- return {"status": "success", "message": f"Chat list {chat_list.name} saved successfully"}
714
- except Exception as e:
715
- raise HTTPException(status_code=500, detail=str(e))
716
-
717
- @self.app.get("/api/chat-lists")
718
- async def get_chat_lists():
719
- try:
720
- chat_lists_dir = os.path.join(
721
- ".auto-coder", "auto-coder.web", "chat-lists")
722
- os.makedirs(chat_lists_dir, exist_ok=True)
723
-
724
- # Get files with their modification times
725
- chat_lists = []
726
- for file in os.listdir(chat_lists_dir):
727
- if file.endswith('.json'):
728
- file_path = os.path.join(chat_lists_dir, file)
729
- mod_time = os.path.getmtime(file_path)
730
- # Store tuple of (name, mod_time)
731
- chat_lists.append((file[:-5], mod_time))
732
-
733
- # Sort by modification time (newest first)
734
- chat_lists.sort(key=lambda x: x[1], reverse=True)
735
-
736
- # Return only the chat list names
737
- return {"chat_lists": [name for name, _ in chat_lists]}
738
- except Exception as e:
739
- raise HTTPException(status_code=500, detail=str(e))
740
-
741
- @self.app.get("/api/chat-lists/{name}")
742
- async def get_chat_list(name: str):
743
- try:
744
- file_path = os.path.join(
745
- ".auto-coder", "auto-coder.web", "chat-lists", f"{name}.json")
746
- if not os.path.exists(file_path):
747
- raise HTTPException(
748
- status_code=404, detail=f"Chat list {name} not found")
749
-
750
- async with aiofiles.open(file_path, 'r') as f:
751
- content = await f.read()
752
- return json.loads(content)
753
- except Exception as e:
754
- raise HTTPException(status_code=500, detail=str(e))
755
-
756
- @self.app.delete("/api/chat-lists/{name}")
757
- async def delete_chat_list(name: str):
758
- try:
759
- file_path = os.path.join(
760
- ".auto-coder", "auto-coder.web", "chat-lists", f"{name}.json")
761
- if not os.path.exists(file_path):
762
- raise HTTPException(
763
- status_code=404, detail=f"Chat list {name} not found")
764
-
765
- os.remove(file_path)
766
- return {"status": "success", "message": f"Chat list {name} deleted successfully"}
767
- except Exception as e:
768
- raise HTTPException(status_code=500, detail=str(e))
769
-
770
- @self.app.post("/api/event/clear")
771
- async def clear_events():
772
- """Clear all pending events in the event queue"""
773
- try:
774
- self.auto_coder_runner.clear_events()
775
- return {"status": "success", "message": "Event queue cleared successfully"}
776
- except Exception as e:
777
- raise HTTPException(status_code=500, detail=str(e))
778
-
779
-
780
- @self.app.get("/api/history/validate-and-load", response_model=ValidationResponseWithFileNumbers)
781
- async def validate_and_load_queries():
782
- try:
783
- # 检查必要的目录
784
- if not os.path.exists("actions") or not os.path.exists(".auto-coder"):
785
- return ValidationResponseWithFileNumbers(
786
- success=False,
787
- message="无效的 auto-coder.chat 项目:缺少 actions 或 .auto-coder 目录"
788
- )
789
-
790
- queries = []
791
- auto_coder_dir = "actions"
792
-
793
- # 遍历actions目录下的所有yaml文件
794
- for root, _, files in os.walk(auto_coder_dir):
795
- for file in files:
796
- if file.endswith('chat_action.yml'):
797
- file_path = os.path.join(root, file)
798
- match = re.match(r'(\d+)_chat_action\.yml', file)
799
- if match:
800
- file_number = int(match.group(1))
801
- with open(file_path, 'r', encoding='utf-8') as f:
802
- try:
803
- yaml_content = yaml.safe_load(f)
804
- if isinstance(yaml_content, dict) and 'query' in yaml_content:
805
- timestamp = datetime.fromtimestamp(
806
- os.path.getmtime(file_path)
807
- ).strftime('%Y-%m-%d %H:%M:%S')
808
-
809
- file_md5 = hashlib.md5(open(file_path, 'rb').read()).hexdigest()
810
- response_str = f"auto_coder_{file}_{file_md5}"
811
-
812
- urls = yaml_content.get('urls', [])
813
-
814
- queries.append(QueryWithFileNumber(
815
- query=yaml_content['query'],
816
- timestamp=timestamp,
817
- file_number=file_number,
818
- response=response_str,
819
- urls=urls
820
- ))
821
- except yaml.YAMLError:
822
- continue
823
-
824
- # 按时间戳排序
825
- queries.sort(key=lambda x: x.timestamp or '', reverse=True)
826
-
827
- return ValidationResponseWithFileNumbers(
828
- success=True,
829
- queries=queries
830
- )
831
-
832
- except Exception as e:
833
- return ValidationResponseWithFileNumbers(
834
- success=False,
835
- message=f"读取项目文件时出错: {str(e)}"
836
- )
837
-
838
- @self.app.get("/api/history/commit-diff/{response_id}", response_model=CommitDiffResponse)
839
- async def get_commit_diff(response_id: str):
840
- """根据response_id获取对应的git commit diff"""
841
- try:
842
- repo = git.Repo(self.project_path)
843
-
844
- # 查找包含特定response message的commit
845
- search_pattern = f"{response_id}"
846
-
847
- matching_commits = []
848
- for commit in repo.iter_commits():
849
- if search_pattern in commit.message:
850
- matching_commits.append(commit)
851
-
852
- if not matching_commits:
853
- return CommitDiffResponse(
854
- success=False,
855
- message=f"找不到对应的commit: {response_id}"
856
- )
857
-
858
- # 使用第一个匹配的commit
859
- target_commit = matching_commits[0]
860
-
861
- file_changes = []
862
- if target_commit.parents:
863
- parent = target_commit.parents[0]
864
- diff = repo.git.diff(parent.hexsha, target_commit.hexsha)
865
-
866
- # 获取变更的文件
867
- diff_index = parent.diff(target_commit)
868
-
869
- for diff_item in diff_index:
870
- if diff_item.new_file:
871
- file_changes.append(FileChange(
872
- path=diff_item.b_path,
873
- change_type="added"
874
- ))
875
- else:
876
- file_changes.append(FileChange(
877
- path=diff_item.b_path,
878
- change_type="modified"
879
- ))
880
- else:
881
- diff = repo.git.show(target_commit.hexsha)
882
-
883
- # 对于初始commit,所有文件都是新增的
884
- for item in target_commit.tree.traverse():
885
- if item.type == 'blob': # 只处理文件,不处理目录
886
- file_changes.append(FileChange(
887
- path=item.path,
888
- change_type="added"
889
- ))
890
-
891
- return CommitDiffResponse(
892
- success=True,
893
- diff=diff,
894
- file_changes=file_changes
895
- )
896
-
897
- except git.exc.GitCommandError as e:
898
- return CommitDiffResponse(
899
- success=False,
900
- message=f"Git命令执行错误: {str(e)}"
901
- )
902
- except Exception as e:
903
- return CommitDiffResponse(
904
- success=False,
905
- message=f"获取commit diff时出错: {str(e)}"
906
- )
907
-
908
453
  @self.app.get("/api/history/file-content/{file_number}", response_model=FileContentResponse)
909
454
  async def get_file_content(file_number: int):
910
455
  """获取指定编号文件的完整内容"""