auto-coder-web 0.1.0__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.
@@ -0,0 +1,532 @@
1
+ from fastapi import FastAPI, Request, HTTPException, Response, Query
2
+ from fastapi.responses import HTMLResponse, JSONResponse
3
+ from fastapi import WebSocket
4
+ from fastapi.middleware.cors import CORSMiddleware
5
+ from fastapi.staticfiles import StaticFiles
6
+ import uvicorn
7
+ import httpx
8
+ import uuid
9
+ from typing import Optional, Dict, List
10
+ import os
11
+ import argparse
12
+ import aiofiles
13
+ import pkg_resources
14
+ from .file_group import FileGroupManager
15
+ from .file_manager import get_directory_tree
16
+ from .auto_coder_runner import AutoCoderRunner
17
+
18
+ from rich.console import Console
19
+ from prompt_toolkit.shortcuts import radiolist_dialog
20
+ from prompt_toolkit.formatted_text import HTML
21
+ import subprocess
22
+ from prompt_toolkit import prompt
23
+ from pydantic import BaseModel
24
+ from autocoder.utils.log_capture import LogCapture
25
+ from typing import Optional, Dict, List, Any
26
+ from .terminal import terminal_manager
27
+
28
+
29
+ class EventGetRequest(BaseModel):
30
+ request_id: str
31
+
32
+
33
+ class EventResponseRequest(BaseModel):
34
+ request_id: str
35
+ event: Dict[str, str]
36
+ response: str
37
+
38
+
39
+ class CompletionItem(BaseModel):
40
+ name: str
41
+ path: str
42
+ display: str
43
+ location: Optional[str] = None
44
+
45
+
46
+ class CompletionResponse(BaseModel):
47
+ completions: List[CompletionItem]
48
+
49
+
50
+ def check_environment():
51
+ """Check and initialize the required environment"""
52
+ console = Console()
53
+ console.print("\n[blue]Initializing the environment...[/blue]")
54
+
55
+ def check_project():
56
+ """Check if the current directory is initialized as an auto-coder project"""
57
+ def print_status(message, status):
58
+ if status == "success":
59
+ console.print(f"✓ {message}", style="green")
60
+ elif status == "warning":
61
+ console.print(f"! {message}", style="yellow")
62
+ elif status == "error":
63
+ console.print(f"✗ {message}", style="red")
64
+ else:
65
+ console.print(f" {message}")
66
+
67
+ first_time = False
68
+ if not os.path.exists("actions") or not os.path.exists(".auto-coder"):
69
+ first_time = True
70
+ print_status("Project not initialized", "warning")
71
+ init_choice = input(
72
+ " Do you want to initialize the project? (y/n): ").strip().lower()
73
+ if init_choice == "y":
74
+ try:
75
+ if not os.path.exists("actions"):
76
+ os.makedirs("actions", exist_ok=True)
77
+ print_status("Created actions directory", "success")
78
+
79
+ if not os.path.exists(".auto-coder"):
80
+ os.makedirs(".auto-coder", exist_ok=True)
81
+ print_status(
82
+ "Created .auto-coder directory", "success")
83
+
84
+ subprocess.run(
85
+ ["auto-coder", "init", "--source_dir", "."], check=True)
86
+ print_status("Project initialized successfully", "success")
87
+ except subprocess.CalledProcessError:
88
+ print_status("Failed to initialize project", "error")
89
+ print_status(
90
+ "Please try to initialize manually: auto-coder init --source_dir .", "warning")
91
+ return False
92
+ else:
93
+ print_status("Exiting due to no initialization", "warning")
94
+ return False
95
+
96
+ print_status("Project initialization check complete", "success")
97
+ return True
98
+
99
+ if not check_project():
100
+ return False
101
+
102
+ def print_status(message, status):
103
+ if status == "success":
104
+ console.print(f"✓ {message}", style="green")
105
+ elif status == "warning":
106
+ console.print(f"! {message}", style="yellow")
107
+ elif status == "error":
108
+ console.print(f"✗ {message}", style="red")
109
+ else:
110
+ console.print(f" {message}")
111
+
112
+ # Check if Ray is running
113
+ print_status("Checking Ray", "")
114
+ ray_status = subprocess.run(
115
+ ["ray", "status"], capture_output=True, text=True)
116
+ if ray_status.returncode != 0:
117
+ print_status("Ray is not running", "warning")
118
+ try:
119
+ subprocess.run(["ray", "start", "--head"], check=True)
120
+ print_status("Ray started successfully", "success")
121
+ except subprocess.CalledProcessError:
122
+ print_status("Failed to start Ray", "error")
123
+ return False
124
+
125
+ # Check if deepseek_chat model is available
126
+ print_status("Checking deepseek_chat model", "")
127
+ try:
128
+ result = subprocess.run(
129
+ ["easy-byzerllm", "chat", "deepseek_chat", "你好"],
130
+ capture_output=True,
131
+ text=True,
132
+ timeout=30,
133
+ )
134
+ if result.returncode == 0:
135
+ print_status("deepseek_chat model is available", "success")
136
+ print_status("Environment check complete", "success")
137
+ return True
138
+ except subprocess.TimeoutExpired:
139
+ print_status("Model check timeout", "error")
140
+ except subprocess.CalledProcessError:
141
+ print_status("Model check error", "error")
142
+ except Exception as e:
143
+ print_status(f"Unexpected error: {str(e)}", "error")
144
+
145
+ print_status("deepseek_chat model is not available", "warning")
146
+
147
+ # If deepseek_chat is not available, prompt user to choose a provider
148
+ choice = radiolist_dialog(
149
+ title="Select Provider",
150
+ text="Please select a provider for deepseek_chat model:",
151
+ values=[
152
+ ("1", "硅基流动(https://siliconflow.cn)"),
153
+ ("2", "Deepseek官方(https://www.deepseek.com/)"),
154
+ ],
155
+ ).run()
156
+
157
+ if choice is None:
158
+ print_status("No provider selected", "error")
159
+ return False
160
+
161
+ api_key = prompt(HTML("<b>Please enter your API key: </b>"))
162
+
163
+ if choice == "1":
164
+ print_status("Deploying model with 硅基流动", "")
165
+ deploy_cmd = [
166
+ "easy-byzerllm",
167
+ "deploy",
168
+ "deepseek-ai/deepseek-v2-chat",
169
+ "--token",
170
+ api_key,
171
+ "--alias",
172
+ "deepseek_chat",
173
+ ]
174
+ else:
175
+ print_status("Deploying model with Deepseek官方", "")
176
+ deploy_cmd = [
177
+ "byzerllm",
178
+ "deploy",
179
+ "--pretrained_model_type",
180
+ "saas/openai",
181
+ "--cpus_per_worker",
182
+ "0.001",
183
+ "--gpus_per_worker",
184
+ "0",
185
+ "--worker_concurrency",
186
+ "1000",
187
+ "--num_workers",
188
+ "1",
189
+ "--infer_params",
190
+ f"saas.base_url=https://api.deepseek.com/v1 saas.api_key={api_key} saas.model=deepseek-chat",
191
+ "--model",
192
+ "deepseek_chat",
193
+ ]
194
+
195
+ try:
196
+ subprocess.run(deploy_cmd, check=True)
197
+ print_status("Model deployed successfully", "success")
198
+ except subprocess.CalledProcessError:
199
+ print_status("Failed to deploy model", "error")
200
+ return False
201
+
202
+ # Validate the deployment
203
+ print_status("Validating model deployment", "")
204
+ try:
205
+ validation_result = subprocess.run(
206
+ ["easy-byzerllm", "chat", "deepseek_chat", "你好"],
207
+ capture_output=True,
208
+ text=True,
209
+ timeout=30,
210
+ check=True,
211
+ )
212
+ print_status("Model validation successful", "success")
213
+ except (subprocess.TimeoutExpired, subprocess.CalledProcessError):
214
+ print_status("Model validation failed", "error")
215
+ print_status(
216
+ "You may need to try manually: easy-byzerllm chat deepseek_chat 你好", "warning")
217
+ return False
218
+
219
+ print_status("Environment initialization complete", "success")
220
+ return True
221
+
222
+
223
+ class ProxyServer:
224
+ def __init__(self, project_path: str, quick: bool = False):
225
+ self.app = FastAPI()
226
+
227
+ if not quick:
228
+ # Check the environment if not in quick mode
229
+ if not check_environment():
230
+ print(
231
+ "\033[31mEnvironment check failed. Some features may not work properly.\033[0m")
232
+ self.setup_middleware()
233
+
234
+ self.setup_static_files()
235
+
236
+ self.setup_routes()
237
+ self.client = httpx.AsyncClient()
238
+ self.project_path = project_path
239
+ self.auto_coder_runner = AutoCoderRunner(project_path)
240
+ self.file_group_manager = FileGroupManager(self.auto_coder_runner)
241
+
242
+ def setup_middleware(self):
243
+ self.app.add_middleware(
244
+ CORSMiddleware,
245
+ allow_origins=["*"],
246
+ allow_credentials=True,
247
+ allow_methods=["*"],
248
+ allow_headers=["*"],
249
+ )
250
+
251
+ def setup_static_files(self):
252
+ self.index_html_path = pkg_resources.resource_filename(
253
+ "auto_coder_web", "web/index.html")
254
+ self.resource_dir = os.path.dirname(self.index_html_path)
255
+ self.static_dir = os.path.join(self.resource_dir, "static")
256
+ self.app.mount(
257
+ "/static", StaticFiles(directory=self.static_dir), name="static")
258
+
259
+ def setup_routes(self):
260
+ @self.app.on_event("shutdown")
261
+ async def shutdown_event():
262
+ await self.client.aclose()
263
+
264
+ @self.app.websocket("/ws/terminal")
265
+ async def terminal_websocket(websocket: WebSocket):
266
+ session_id = str(uuid.uuid4())
267
+ await terminal_manager.handle_websocket(websocket, session_id)
268
+
269
+
270
+ @self.app.delete("/api/files/{path:path}")
271
+ async def delete_file(path: str):
272
+ try:
273
+ full_path = os.path.join(self.project_path, path)
274
+ if os.path.exists(full_path):
275
+ if os.path.isdir(full_path):
276
+ import shutil
277
+ shutil.rmtree(full_path)
278
+ else:
279
+ os.remove(full_path)
280
+ return {"message": f"Successfully deleted {path}"}
281
+ else:
282
+ raise HTTPException(
283
+ status_code=404, detail="File not found")
284
+ except Exception as e:
285
+ raise HTTPException(status_code=500, detail=str(e))
286
+
287
+ @self.app.get("/", response_class=HTMLResponse)
288
+ async def read_root():
289
+ if os.path.exists(self.index_html_path):
290
+ async with aiofiles.open(self.index_html_path, "r") as f:
291
+ content = await f.read()
292
+ return HTMLResponse(content=content)
293
+ return HTMLResponse(content="<h1>Welcome to Proxy Server</h1>")
294
+
295
+ @self.app.get("/api/project-path")
296
+ async def get_project_path():
297
+ return {"project_path": self.project_path}
298
+
299
+ def get_project_runner(project_path: str) -> AutoCoderRunner:
300
+ return self.projects[project_path]
301
+
302
+ @self.app.post("/api/file-groups")
303
+ async def create_file_group(request: Request):
304
+ data = await request.json()
305
+ name = data.get("name")
306
+ description = data.get("description", "")
307
+ group = await self.file_group_manager.create_group(name, description)
308
+ return group
309
+
310
+ @self.app.get("/api/os")
311
+ async def get_os():
312
+ return {"os": os.name}
313
+
314
+ @self.app.post("/api/file-groups/switch")
315
+ async def switch_file_groups(request: Request):
316
+ data = await request.json()
317
+ group_names = data.get("group_names", [])
318
+ result = await self.file_group_manager.switch_groups(group_names)
319
+ return result
320
+
321
+ @self.app.delete("/api/file-groups/{name}")
322
+ async def delete_file_group(name: str):
323
+ await self.file_group_manager.delete_group(name)
324
+ return {"status": "success"}
325
+
326
+ @self.app.post("/api/file-groups/{name}/files")
327
+ async def add_files_to_group(name: str, request: Request):
328
+ data = await request.json()
329
+ files = data.get("files", [])
330
+ description = data.get("description")
331
+ if description is not None:
332
+ group = await self.file_group_manager.update_group_description(name, description)
333
+ else:
334
+ group = await self.file_group_manager.add_files_to_group(name, files)
335
+ return group
336
+
337
+ @self.app.delete("/api/file-groups/{name}/files")
338
+ async def remove_files_from_group(name: str, request: Request):
339
+ data = await request.json()
340
+ files = data.get("files", [])
341
+ group = await self.file_group_manager.remove_files_from_group(name, files)
342
+ return group
343
+
344
+ @self.app.post("/api/revert")
345
+ async def revert():
346
+ try:
347
+ result = self.auto_coder_runner.revert()
348
+ return result
349
+ except Exception as e:
350
+ raise HTTPException(status_code=500, detail=str(e))
351
+
352
+ @self.app.get("/api/file-groups")
353
+ async def get_file_groups():
354
+ groups = await self.file_group_manager.get_groups()
355
+ return {"groups": groups}
356
+
357
+ @self.app.get("/api/files")
358
+ async def get_files():
359
+ tree = get_directory_tree(self.project_path)
360
+ return {"tree": tree}
361
+
362
+ @self.app.get("/api/completions/files")
363
+ async def get_file_completions(name: str = Query(...)):
364
+ """获取文件名补全"""
365
+ matches = self.auto_coder_runner.find_files_in_project([name])
366
+ completions = []
367
+ project_root = self.auto_coder_runner.project_path
368
+ for file_name in matches:
369
+ path_parts = file_name.split(os.sep)
370
+ # 只显示最后三层路径,让显示更简洁
371
+ display_name = os.sep.join(
372
+ path_parts[-3:]) if len(path_parts) > 3 else file_name
373
+ relative_path = os.path.relpath(file_name, project_root)
374
+
375
+ completions.append(CompletionItem(
376
+ name=relative_path, # 给补全项一个唯一标识
377
+ path=relative_path, # 实际用于替换的路径
378
+ display=display_name, # 显示的简短路径
379
+ location=relative_path # 完整的相对路径信息
380
+ ))
381
+ return CompletionResponse(completions=completions)
382
+
383
+ @self.app.get("/api/completions/symbols")
384
+ async def get_symbol_completions(name: str = Query(...)):
385
+ """获取符号补全"""
386
+ symbols = self.auto_coder_runner.get_symbol_list()
387
+ matches = []
388
+
389
+ for symbol in symbols:
390
+ if name.lower() in symbol.symbol_name.lower():
391
+ relative_path = os.path.relpath(
392
+ symbol.file_name, self.project_path)
393
+ matches.append(CompletionItem(
394
+ name=symbol.symbol_name,
395
+ path=f"{symbol.symbol_name} ({relative_path}/{symbol.symbol_type.value})",
396
+ display=f"{symbol.symbol_name}(location: {relative_path})"
397
+ ))
398
+ return CompletionResponse(completions=matches)
399
+
400
+ @self.app.put("/api/file/{path:path}")
401
+ async def update_file(path: str, request: Request):
402
+ try:
403
+ data = await request.json()
404
+ content = data.get("content")
405
+ if content is None:
406
+ raise HTTPException(
407
+ status_code=400, detail="Content is required")
408
+
409
+ full_path = os.path.join(self.project_path, path)
410
+
411
+ # Ensure the directory exists
412
+ os.makedirs(os.path.dirname(full_path), exist_ok=True)
413
+
414
+ # Write the file content
415
+ with open(full_path, 'w', encoding='utf-8') as f:
416
+ f.write(content)
417
+
418
+ return {"message": f"Successfully updated {path}"}
419
+ except Exception as e:
420
+ raise HTTPException(status_code=500, detail=str(e))
421
+
422
+ @self.app.get("/api/file/{path:path}")
423
+ async def get_file_content(path: str):
424
+ from .file_manager import read_file_content
425
+ content = read_file_content(self.project_path, path)
426
+ if content is None:
427
+ raise HTTPException(
428
+ status_code=404, detail="File not found or cannot be read")
429
+
430
+ return {"content": content}
431
+
432
+ @self.app.get("/api/active-files")
433
+ async def get_active_files():
434
+ """获取当前活动文件列表"""
435
+ active_files = self.auto_coder_runner.get_active_files()
436
+ return active_files
437
+
438
+ @self.app.get("/api/conf")
439
+ async def get_conf():
440
+ return {"conf": self.auto_coder_runner.get_config()}
441
+
442
+ @self.app.post("/api/conf")
443
+ async def config(request: Request):
444
+ data = await request.json()
445
+ try:
446
+ for key, value in data.items():
447
+ self.auto_coder_runner.configure(key, str(value))
448
+ return {"status": "success"}
449
+ except Exception as e:
450
+ raise HTTPException(status_code=400, detail=str(e))
451
+
452
+ @self.app.post("/api/coding")
453
+ async def coding(request: Request):
454
+ data = await request.json()
455
+ query = data.get("query", "")
456
+ if not query:
457
+ raise HTTPException(
458
+ status_code=400, detail="Query is required")
459
+ return await self.auto_coder_runner.coding(query)
460
+
461
+ @self.app.post("/api/chat")
462
+ async def chat(request: Request):
463
+ data = await request.json()
464
+ query = data.get("query", "")
465
+ if not query:
466
+ raise HTTPException(
467
+ status_code=400, detail="Query is required")
468
+ return await self.auto_coder_runner.chat(query)
469
+
470
+ @self.app.get("/api/result/{request_id}")
471
+ async def get_result(request_id: str):
472
+ result = await self.auto_coder_runner.get_result(request_id)
473
+ if result is None:
474
+ raise HTTPException(
475
+ status_code=404, detail="Result not found or not ready yet")
476
+
477
+ v = {"result": result.value, "status": result.status.value}
478
+ return v
479
+
480
+ @self.app.post("/api/event/get")
481
+ async def get_event(request: EventGetRequest):
482
+ request_id = request.request_id
483
+ if not request_id:
484
+ raise HTTPException(
485
+ status_code=400, detail="request_id is required")
486
+
487
+ v = self.auto_coder_runner.get_event(request_id)
488
+ return v
489
+
490
+ @self.app.post("/api/event/response")
491
+ async def response_event(request: EventResponseRequest):
492
+ request_id = request.request_id
493
+ if not request_id:
494
+ raise HTTPException(
495
+ status_code=400, detail="request_id is required")
496
+
497
+ self.auto_coder_runner.response_event(
498
+ request_id, request.event, request.response)
499
+ return {"message": "success"}
500
+
501
+ @self.app.get("/api/output/{request_id}")
502
+ async def get_terminal_logs(request_id: str):
503
+ return self.auto_coder_runner.get_logs(request_id)
504
+
505
+
506
+ def main():
507
+ parser = argparse.ArgumentParser(description="Proxy Server")
508
+ parser.add_argument(
509
+ "--port",
510
+ type=int,
511
+ default=8007,
512
+ help="Port to run the proxy server on (default: 8007)",
513
+ )
514
+ parser.add_argument(
515
+ "--host",
516
+ type=str,
517
+ default="0.0.0.0",
518
+ help="Host to run the proxy server on (default: 0.0.0.0)",
519
+ )
520
+ parser.add_argument(
521
+ "--quick",
522
+ action="store_true",
523
+ help="Skip environment check",
524
+ )
525
+ args = parser.parse_args()
526
+
527
+ proxy_server = ProxyServer(quick=args.quick, project_path=os.getcwd())
528
+ uvicorn.run(proxy_server.app, host=args.host, port=args.port)
529
+
530
+
531
+ if __name__ == "__main__":
532
+ main()