auto-coder-web 0.1.24__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/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.470202a1.js.LICENSE.txt → main.66c37ccf.js.LICENSE.txt} +40 -0
- auto_coder_web/web/static/js/{main.470202a1.js.map → main.66c37ccf.js.map} +1 -1
- {auto_coder_web-0.1.24.dist-info → auto_coder_web-0.1.25.dist-info}/METADATA +1 -1
- {auto_coder_web-0.1.24.dist-info → auto_coder_web-0.1.25.dist-info}/RECORD +27 -16
- {auto_coder_web-0.1.24.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.770925e5.css +0 -6
- auto_coder_web/web/static/css/main.770925e5.css.map +0 -1
- auto_coder_web/web/static/js/main.470202a1.js +0 -3
- {auto_coder_web-0.1.24.dist-info → auto_coder_web-0.1.25.dist-info}/WHEEL +0 -0
- {auto_coder_web-0.1.24.dist-info → auto_coder_web-0.1.25.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
|
-
|
28
|
-
|
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
|
@@ -54,7 +44,9 @@ import git
|
|
54
44
|
import hashlib
|
55
45
|
from datetime import datetime
|
56
46
|
from autocoder.utils import operate_config_api
|
57
|
-
from .routers import todo_router, settings_router, auto_router, commit_router
|
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,13 +323,13 @@ class ProxyServer:
|
|
331
323
|
|
332
324
|
self.setup_static_files()
|
333
325
|
self.project_path = project_path
|
334
|
-
|
335
|
-
self.setup_routes()
|
336
|
-
self.client = httpx.AsyncClient()
|
337
326
|
|
338
327
|
self.auto_coder_runner = AutoCoderRunner(project_path, product_mode=product_mode)
|
339
328
|
self.file_group_manager = FileGroupManager(self.auto_coder_runner)
|
340
329
|
|
330
|
+
self.setup_routes()
|
331
|
+
self.client = httpx.AsyncClient()
|
332
|
+
|
341
333
|
def setup_middleware(self):
|
342
334
|
self.app.add_middleware(
|
343
335
|
CORSMiddleware,
|
@@ -365,9 +357,21 @@ class ProxyServer:
|
|
365
357
|
self.app.include_router(settings_router.router)
|
366
358
|
self.app.include_router(auto_router.router)
|
367
359
|
self.app.include_router(commit_router.router)
|
360
|
+
self.app.include_router(chat_router.router)
|
361
|
+
self.app.include_router(coding_router.router)
|
362
|
+
self.app.include_router(history_router)
|
363
|
+
self.app.include_router(completions_router.router)
|
364
|
+
self.app.include_router(file_router.router)
|
365
|
+
self.app.include_router(auto_coder_conf_router.router)
|
366
|
+
self.app.include_router(chat_list_router.router)
|
367
|
+
self.app.include_router(file_group_router.router)
|
368
368
|
|
369
369
|
# Store project_path in app state for dependency injection
|
370
370
|
self.app.state.project_path = self.project_path
|
371
|
+
# Store auto_coder_runner in app state for dependency injection
|
372
|
+
self.app.state.auto_coder_runner = self.auto_coder_runner
|
373
|
+
# Store file_group_manager in app state for dependency injection
|
374
|
+
self.app.state.file_group_manager = self.file_group_manager
|
371
375
|
|
372
376
|
@self.app.on_event("shutdown")
|
373
377
|
async def shutdown_event():
|
@@ -378,23 +382,6 @@ class ProxyServer:
|
|
378
382
|
session_id = str(uuid.uuid4())
|
379
383
|
await terminal_manager.handle_websocket(websocket, session_id)
|
380
384
|
|
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
385
|
@self.app.get("/", response_class=HTMLResponse)
|
399
386
|
async def read_root():
|
400
387
|
if os.path.exists(self.index_html_path):
|
@@ -410,61 +397,10 @@ class ProxyServer:
|
|
410
397
|
def get_project_runner(project_path: str) -> AutoCoderRunner:
|
411
398
|
return self.projects[project_path]
|
412
399
|
|
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
400
|
@self.app.get("/api/os")
|
458
401
|
async def get_os():
|
459
402
|
return {"os": os.name}
|
460
403
|
|
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
404
|
@self.app.get("/api/conf/keys")
|
469
405
|
async def get_conf_keys():
|
470
406
|
"""Get all available configuration keys from AutoCoderArgs"""
|
@@ -492,29 +428,6 @@ class ProxyServer:
|
|
492
428
|
})
|
493
429
|
return {"keys": keys}
|
494
430
|
|
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
431
|
@self.app.post("/api/revert")
|
519
432
|
async def revert():
|
520
433
|
try:
|
@@ -523,388 +436,25 @@ class ProxyServer:
|
|
523
436
|
except Exception as e:
|
524
437
|
raise HTTPException(status_code=500, detail=str(e))
|
525
438
|
|
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
439
|
@self.app.get("/api/active-files")
|
607
440
|
async def get_active_files():
|
608
441
|
"""获取当前活动文件列表"""
|
609
442
|
active_files = self.auto_coder_runner.get_active_files()
|
610
443
|
return active_files
|
611
444
|
|
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
445
|
@self.app.post("/api/commit")
|
686
446
|
async def commit():
|
687
447
|
try:
|
688
448
|
result = self.auto_coder_runner.commit()
|
689
449
|
return result
|
690
450
|
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)
|
451
|
+
raise HTTPException(status_code=500, detail=str(e))
|
696
452
|
|
697
453
|
@self.app.get("/api/last-yaml")
|
698
454
|
async def get_last_yaml():
|
699
455
|
"""Get information about the last YAML file"""
|
700
456
|
return JSONResponse(content=self.auto_coder_runner.get_last_yaml_info())
|
701
457
|
|
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
458
|
@self.app.get("/api/history/file-content/{file_number}", response_model=FileContentResponse)
|
909
459
|
async def get_file_content(file_number: int):
|
910
460
|
"""获取指定编号文件的完整内容"""
|