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
@@ -0,0 +1,330 @@
1
+ import asyncio
2
+ import json
3
+ import os
4
+ from contextlib import contextmanager
5
+ from threading import Thread
6
+ from concurrent.futures import ThreadPoolExecutor
7
+ from fastapi import APIRouter, HTTPException, Request, Depends
8
+ from fastapi.responses import StreamingResponse
9
+ from pydantic import BaseModel
10
+ from typing import Dict, Any, Optional, List
11
+ from auto_coder_web.auto_coder_runner_wrapper import AutoCoderRunnerWrapper
12
+ from autocoder.events.event_manager_singleton import get_event_manager, gengerate_event_file_path, get_event_file_path
13
+ from autocoder.events import event_content as EventContentCreator
14
+ from autocoder.events.event_types import EventType
15
+ from byzerllm.utils.langutil import asyncfy_with_semaphore
16
+ from autocoder.common.global_cancel import global_cancel, CancelRequestedException
17
+ from loguru import logger
18
+
19
+ router = APIRouter()
20
+
21
+ # 创建线程池
22
+ cancel_thread_pool = ThreadPoolExecutor(max_workers=5)
23
+
24
+ class ChatCommandRequest(BaseModel):
25
+ command: str
26
+
27
+ class EventPollRequest(BaseModel):
28
+ event_file_id: str
29
+
30
+ class UserResponseRequest(BaseModel):
31
+ event_id: str
32
+ event_file_id: str
33
+ response: str
34
+
35
+ class TaskHistoryRequest(BaseModel):
36
+ query: str
37
+ event_file_id: str
38
+ messages: List[Dict[str, Any]]
39
+ status: str
40
+ timestamp: int
41
+
42
+ class CancelTaskRequest(BaseModel):
43
+ event_file_id: str
44
+
45
+ async def get_project_path(request: Request) -> str:
46
+ """
47
+ 从FastAPI请求上下文中获取项目路径
48
+ """
49
+ return request.app.state.project_path
50
+
51
+ def ensure_task_dir(project_path: str) -> str:
52
+ """确保任务历史目录存在"""
53
+ task_dir = os.path.join(project_path, ".auto-coder", "auto-coder.web", "tasks")
54
+ os.makedirs(task_dir, exist_ok=True)
55
+ return task_dir
56
+
57
+ @router.post("/api/chat-command")
58
+ async def chat_command(request: ChatCommandRequest, project_path: str = Depends(get_project_path)):
59
+ """
60
+ 执行chat命令
61
+
62
+ 通过AutoCoderRunnerWrapper调用chat方法,执行指定的命令
63
+ 在单独的线程中运行,并返回一个唯一的UUID
64
+ """
65
+ event_file, file_id = gengerate_event_file_path()
66
+ # 定义在线程中运行的函数
67
+ def run_command_in_thread():
68
+ try:
69
+ # 创建AutoCoderRunnerWrapper实例,使用从应用上下文获取的项目路径
70
+ wrapper = AutoCoderRunnerWrapper(project_path)
71
+ wrapper.configure_wrapper(f"event_file:{event_file}")
72
+
73
+ # 调用chat方法
74
+ result = wrapper.chat_wrapper(request.command)
75
+ get_event_manager(event_file).write_completion(
76
+ EventContentCreator.create_completion(
77
+ "200", "completed", result).to_dict()
78
+ )
79
+ logger.info(f"Event file id: {file_id} completed successfully")
80
+ except Exception as e:
81
+ logger.error(f"Error executing chat command {file_id}: {str(e)}")
82
+ get_event_manager(event_file).write_error(
83
+ EventContentCreator.create_error("500", "error", str(e)).to_dict()
84
+ )
85
+
86
+ # 创建并启动线程
87
+ thread = Thread(target=run_command_in_thread)
88
+ thread.daemon = True # 设置为守护线程,这样当主程序退出时,线程也会退出
89
+ thread.start()
90
+
91
+ logger.info(f"Started chat command {file_id} in background thread")
92
+ return {"event_file_id": file_id}
93
+
94
+ @router.get("/api/chat-command/events")
95
+ async def poll_chat_command_events(event_file_id: str, project_path: str = Depends(get_project_path)):
96
+ async def event_stream():
97
+ event_file = get_event_file_path(event_file_id, project_path)
98
+ event_manager = get_event_manager(event_file)
99
+ while True:
100
+ try:
101
+ events = await asyncio.to_thread(event_manager.read_events, block=False)
102
+
103
+ if not events:
104
+ await asyncio.sleep(0.1) # 减少休眠时间,更频繁地检查
105
+ continue
106
+
107
+ current_event = None
108
+ for event in events:
109
+ current_event = event
110
+ # Convert event to JSON string
111
+ event_json = event.to_json()
112
+ # Format as SSE
113
+ yield f"data: {event_json}\n\n"
114
+
115
+ # 防止current_event为None导致的错误
116
+ if current_event is not None:
117
+ if current_event.event_type == EventType.ERROR:
118
+ logger.info("Breaking loop due to ERROR event")
119
+ break
120
+
121
+ if current_event.event_type == EventType.COMPLETION:
122
+ logger.info("Breaking loop due to COMPLETION event")
123
+ break
124
+ except Exception as e:
125
+ logger.error(f"Error in SSE stream: {str(e)}")
126
+ yield f"data: {{\"error\": \"{str(e)}\"}}\n\n"
127
+ break
128
+
129
+ return StreamingResponse(
130
+ event_stream(),
131
+ media_type="text/event-stream",
132
+ headers={
133
+ "Cache-Control": "no-cache, no-transform",
134
+ "Connection": "keep-alive",
135
+ "Content-Type": "text/event-stream",
136
+ "X-Accel-Buffering": "no",
137
+ "Transfer-Encoding": "chunked",
138
+ },
139
+ )
140
+
141
+ @router.post("/api/chat-command/response")
142
+ async def response_user(request: UserResponseRequest, project_path: str = Depends(get_project_path)):
143
+ """
144
+ 响应用户询问
145
+
146
+ 接收用户对ASK_USER事件的回复,并将其传递给事件管理器
147
+
148
+ Args:
149
+ request: 包含event_id和response的请求对象
150
+ project_path: 项目路径
151
+
152
+ Returns:
153
+ 响应结果
154
+ """
155
+ try:
156
+ # 获取事件管理器
157
+ event_file = get_event_file_path(file_id=request.event_file_id, project_path=project_path)
158
+ event_manager = get_event_manager(event_file)
159
+
160
+ # 调用respond_to_user方法发送用户响应
161
+ response_event = event_manager.respond_to_user(
162
+ request.event_id, request.response)
163
+
164
+ # 返回成功响应
165
+ return {
166
+ "status": "success",
167
+ "message": "Response sent successfully",
168
+ "event_id": response_event.event_id
169
+ }
170
+ except Exception as e:
171
+ logger.error(f"Error sending user response: {str(e)}")
172
+ raise HTTPException(
173
+ status_code=500, detail=f"Failed to send user response: {str(e)}")
174
+
175
+ @router.post("/api/chat-command/save-history")
176
+ async def save_task_history(request: TaskHistoryRequest, project_path: str = Depends(get_project_path)):
177
+ """
178
+ 保存任务历史
179
+
180
+ 将任务的查询、消息历史和事件文件ID保存到本地文件系统
181
+
182
+ Args:
183
+ request: 包含任务信息的请求对象
184
+ project_path: 项目路径
185
+
186
+ Returns:
187
+ 保存结果
188
+ """
189
+ try:
190
+ task_dir = ensure_task_dir(project_path)
191
+ task_file = os.path.join(task_dir, f"{request.event_file_id}.json")
192
+
193
+ # 过滤掉系统消息和空消息
194
+ filtered_messages = []
195
+ for msg in request.messages:
196
+ # 跳过系统消息和空消息
197
+ if msg.get("type") == "SYSTEM" or not msg.get("content"):
198
+ continue
199
+ # 跳过token统计消息
200
+ if msg.get("type") == "TOKEN_STAT":
201
+ continue
202
+ filtered_messages.append(msg)
203
+
204
+ task_data = {
205
+ "query": request.query,
206
+ "event_file_id": request.event_file_id,
207
+ "messages": filtered_messages,
208
+ "status": request.status,
209
+ "timestamp": request.timestamp,
210
+ "type": "chat" # 添加类型标识
211
+ }
212
+
213
+ with open(task_file, "w", encoding="utf-8") as f:
214
+ json.dump(task_data, f, ensure_ascii=False, indent=2)
215
+
216
+ return {"status": "success", "message": "Task history saved successfully"}
217
+ except Exception as e:
218
+ logger.error(f"Error saving task history: {str(e)}")
219
+ raise HTTPException(
220
+ status_code=500, detail=f"Failed to save task history: {str(e)}")
221
+
222
+ @router.get("/api/chat-command/history")
223
+ async def get_task_history(project_path: str = Depends(get_project_path)):
224
+ """
225
+ 获取任务历史列表
226
+
227
+ 从本地文件系统读取所有保存的任务历史,返回原始JSON文件内容
228
+
229
+ Args:
230
+ project_path: 项目路径
231
+
232
+ Returns:
233
+ 任务历史列表,包含完整的原始数据
234
+ """
235
+ try:
236
+ task_dir = ensure_task_dir(project_path)
237
+ task_files = [f for f in os.listdir(task_dir) if f.endswith(".json")]
238
+
239
+ tasks = []
240
+ for file_name in task_files:
241
+ file_path = os.path.join(task_dir, file_name)
242
+ try:
243
+ with open(file_path, "r", encoding="utf-8") as f:
244
+ task_data = json.load(f)
245
+ # 只包含类型为chat的任务
246
+ if task_data.get("type") == "chat":
247
+ tasks.append(task_data)
248
+ except Exception as e:
249
+ logger.error(f"Error reading task file {file_name}: {str(e)}")
250
+
251
+ # 按时间戳排序,最新的在前面
252
+ tasks.sort(key=lambda x: x.get("timestamp", 0), reverse=True)
253
+
254
+ return {"tasks": tasks}
255
+ except Exception as e:
256
+ logger.error(f"Error getting task history: {str(e)}")
257
+ raise HTTPException(
258
+ status_code=500, detail=f"Failed to get task history: {str(e)}")
259
+
260
+ @router.get("/api/chat-command/task/{task_id}")
261
+ async def get_task_detail(task_id: str, project_path: str = Depends(get_project_path)):
262
+ """
263
+ 获取特定任务的详细信息
264
+
265
+ Args:
266
+ task_id: 任务ID (event_file_id)
267
+ project_path: 项目路径
268
+
269
+ Returns:
270
+ 任务详细信息
271
+ """
272
+ try:
273
+ task_dir = ensure_task_dir(project_path)
274
+ task_file = os.path.join(task_dir, f"{task_id}.json")
275
+
276
+ if not os.path.exists(task_file):
277
+ raise HTTPException(status_code=404, detail="Task not found")
278
+
279
+ with open(task_file, "r", encoding="utf-8") as f:
280
+ task_data = json.load(f)
281
+
282
+ return task_data
283
+ except HTTPException:
284
+ raise
285
+ except Exception as e:
286
+ logger.error(f"Error getting task detail: {str(e)}")
287
+ raise HTTPException(
288
+ status_code=500, detail=f"Failed to get task detail: {str(e)}")
289
+
290
+ @router.post("/api/chat-command/cancel")
291
+ async def cancel_task(request: CancelTaskRequest, project_path: str = Depends(get_project_path)):
292
+ """
293
+ 取消正在运行的任务
294
+
295
+ Args:
296
+ request: 包含event_file_id的请求对象
297
+ project_path: 项目路径
298
+
299
+ Returns:
300
+ 取消操作的结果
301
+ """
302
+ try:
303
+ event_file = get_event_file_path(file_id=request.event_file_id, project_path=project_path)
304
+
305
+ def cancel_in_thread():
306
+ try:
307
+ # 设置全局取消标志
308
+ global_cancel.set_cancel(request.event_file_id)
309
+
310
+ # 获取事件管理器
311
+ event_manager = get_event_manager(event_file)
312
+
313
+ # 写入取消事件
314
+ event_manager.write_error(
315
+ EventContentCreator.create_error(
316
+ "499", "cancelled", "Task cancelled by user").to_dict()
317
+ )
318
+
319
+ logger.info(f"Task {request.event_file_id} cancelled successfully")
320
+ except Exception as e:
321
+ logger.error(f"Error cancelling task: {str(e)}")
322
+
323
+ # 在线程池中执行取消操作
324
+ cancel_thread_pool.submit(cancel_in_thread)
325
+
326
+ return {"status": "success", "message": "Cancel request sent"}
327
+ except Exception as e:
328
+ logger.error(f"Error sending cancel request: {str(e)}")
329
+ raise HTTPException(
330
+ status_code=500, detail=f"Failed to send cancel request: {str(e)}")
@@ -0,0 +1,330 @@
1
+ import asyncio
2
+ import json
3
+ import os
4
+ from contextlib import contextmanager
5
+ from threading import Thread
6
+ from concurrent.futures import ThreadPoolExecutor
7
+ from fastapi import APIRouter, HTTPException, Request, Depends
8
+ from fastapi.responses import StreamingResponse
9
+ from pydantic import BaseModel
10
+ from typing import Dict, Any, Optional, List
11
+ from auto_coder_web.auto_coder_runner_wrapper import AutoCoderRunnerWrapper
12
+ from autocoder.events.event_manager_singleton import get_event_manager, gengerate_event_file_path, get_event_file_path
13
+ from autocoder.events import event_content as EventContentCreator
14
+ from autocoder.events.event_types import EventType
15
+ from byzerllm.utils.langutil import asyncfy_with_semaphore
16
+ from autocoder.common.global_cancel import global_cancel, CancelRequestedException
17
+ from loguru import logger
18
+
19
+ router = APIRouter()
20
+
21
+ # 创建线程池
22
+ cancel_thread_pool = ThreadPoolExecutor(max_workers=5)
23
+
24
+ class CodingCommandRequest(BaseModel):
25
+ command: str
26
+
27
+ class EventPollRequest(BaseModel):
28
+ event_file_id: str
29
+
30
+ class UserResponseRequest(BaseModel):
31
+ event_id: str
32
+ event_file_id: str
33
+ response: str
34
+
35
+ class TaskHistoryRequest(BaseModel):
36
+ query: str
37
+ event_file_id: str
38
+ messages: List[Dict[str, Any]]
39
+ status: str
40
+ timestamp: int
41
+
42
+ class CancelTaskRequest(BaseModel):
43
+ event_file_id: str
44
+
45
+ async def get_project_path(request: Request) -> str:
46
+ """
47
+ 从FastAPI请求上下文中获取项目路径
48
+ """
49
+ return request.app.state.project_path
50
+
51
+ def ensure_task_dir(project_path: str) -> str:
52
+ """确保任务历史目录存在"""
53
+ task_dir = os.path.join(project_path, ".auto-coder", "auto-coder.web", "tasks")
54
+ os.makedirs(task_dir, exist_ok=True)
55
+ return task_dir
56
+
57
+ @router.post("/api/coding-command")
58
+ async def coding_command(request: CodingCommandRequest, project_path: str = Depends(get_project_path)):
59
+ """
60
+ 执行coding命令
61
+
62
+ 通过AutoCoderRunnerWrapper调用coding方法,执行指定的命令
63
+ 在单独的线程中运行,并返回一个唯一的UUID
64
+ """
65
+ event_file, file_id = gengerate_event_file_path()
66
+ # 定义在线程中运行的函数
67
+ def run_command_in_thread():
68
+ try:
69
+ # 创建AutoCoderRunnerWrapper实例,使用从应用上下文获取的项目路径
70
+ wrapper = AutoCoderRunnerWrapper(project_path)
71
+ wrapper.configure_wrapper(f"event_file:{event_file}")
72
+
73
+ # 调用coding方法
74
+ result = wrapper.coding_wapper(request.command)
75
+ get_event_manager(event_file).write_completion(
76
+ EventContentCreator.create_completion(
77
+ "200", "completed", result).to_dict()
78
+ )
79
+ logger.info(f"Event file id: {file_id} completed successfully")
80
+ except Exception as e:
81
+ logger.error(f"Error executing coding command {file_id}: {str(e)}")
82
+ get_event_manager(event_file).write_error(
83
+ EventContentCreator.create_error("500", "error", str(e)).to_dict()
84
+ )
85
+
86
+ # 创建并启动线程
87
+ thread = Thread(target=run_command_in_thread)
88
+ thread.daemon = True # 设置为守护线程,这样当主程序退出时,线程也会退出
89
+ thread.start()
90
+
91
+ logger.info(f"Started coding command {file_id} in background thread")
92
+ return {"event_file_id": file_id}
93
+
94
+ @router.get("/api/coding-command/events")
95
+ async def poll_coding_command_events(event_file_id: str, project_path: str = Depends(get_project_path)):
96
+ async def event_stream():
97
+ event_file = get_event_file_path(event_file_id, project_path)
98
+ event_manager = get_event_manager(event_file)
99
+ while True:
100
+ try:
101
+ events = await asyncio.to_thread(event_manager.read_events, block=False)
102
+
103
+ if not events:
104
+ await asyncio.sleep(0.1) # 减少休眠时间,更频繁地检查
105
+ continue
106
+
107
+ current_event = None
108
+ for event in events:
109
+ current_event = event
110
+ # Convert event to JSON string
111
+ event_json = event.to_json()
112
+ # Format as SSE
113
+ yield f"data: {event_json}\n\n"
114
+
115
+ # 防止current_event为None导致的错误
116
+ if current_event is not None:
117
+ if current_event.event_type == EventType.ERROR:
118
+ logger.info("Breaking loop due to ERROR event")
119
+ break
120
+
121
+ if current_event.event_type == EventType.COMPLETION:
122
+ logger.info("Breaking loop due to COMPLETION event")
123
+ break
124
+ except Exception as e:
125
+ logger.error(f"Error in SSE stream: {str(e)}")
126
+ yield f"data: {{\"error\": \"{str(e)}\"}}\n\n"
127
+ break
128
+
129
+ return StreamingResponse(
130
+ event_stream(),
131
+ media_type="text/event-stream",
132
+ headers={
133
+ "Cache-Control": "no-cache, no-transform",
134
+ "Connection": "keep-alive",
135
+ "Content-Type": "text/event-stream",
136
+ "X-Accel-Buffering": "no",
137
+ "Transfer-Encoding": "chunked",
138
+ },
139
+ )
140
+
141
+ @router.post("/api/coding-command/response")
142
+ async def response_user(request: UserResponseRequest, project_path: str = Depends(get_project_path)):
143
+ """
144
+ 响应用户询问
145
+
146
+ 接收用户对ASK_USER事件的回复,并将其传递给事件管理器
147
+
148
+ Args:
149
+ request: 包含event_id和response的请求对象
150
+ project_path: 项目路径
151
+
152
+ Returns:
153
+ 响应结果
154
+ """
155
+ try:
156
+ # 获取事件管理器
157
+ event_file = get_event_file_path(file_id=request.event_file_id, project_path=project_path)
158
+ event_manager = get_event_manager(event_file)
159
+
160
+ # 调用respond_to_user方法发送用户响应
161
+ response_event = event_manager.respond_to_user(
162
+ request.event_id, request.response)
163
+
164
+ # 返回成功响应
165
+ return {
166
+ "status": "success",
167
+ "message": "Response sent successfully",
168
+ "event_id": response_event.event_id
169
+ }
170
+ except Exception as e:
171
+ logger.error(f"Error sending user response: {str(e)}")
172
+ raise HTTPException(
173
+ status_code=500, detail=f"Failed to send user response: {str(e)}")
174
+
175
+ @router.post("/api/coding-command/save-history")
176
+ async def save_task_history(request: TaskHistoryRequest, project_path: str = Depends(get_project_path)):
177
+ """
178
+ 保存任务历史
179
+
180
+ 将任务的查询、消息历史和事件文件ID保存到本地文件系统
181
+
182
+ Args:
183
+ request: 包含任务信息的请求对象
184
+ project_path: 项目路径
185
+
186
+ Returns:
187
+ 保存结果
188
+ """
189
+ try:
190
+ task_dir = ensure_task_dir(project_path)
191
+ task_file = os.path.join(task_dir, f"{request.event_file_id}.json")
192
+
193
+ # 过滤掉系统消息和空消息
194
+ filtered_messages = []
195
+ for msg in request.messages:
196
+ # 跳过系统消息和空消息
197
+ if msg.get("type") == "SYSTEM" or not msg.get("content"):
198
+ continue
199
+ # 跳过token统计消息
200
+ if msg.get("type") == "TOKEN_STAT":
201
+ continue
202
+ filtered_messages.append(msg)
203
+
204
+ task_data = {
205
+ "query": request.query,
206
+ "event_file_id": request.event_file_id,
207
+ "messages": filtered_messages,
208
+ "status": request.status,
209
+ "timestamp": request.timestamp,
210
+ "type": "coding" # 添加类型标识
211
+ }
212
+
213
+ with open(task_file, "w", encoding="utf-8") as f:
214
+ json.dump(task_data, f, ensure_ascii=False, indent=2)
215
+
216
+ return {"status": "success", "message": "Task history saved successfully"}
217
+ except Exception as e:
218
+ logger.error(f"Error saving task history: {str(e)}")
219
+ raise HTTPException(
220
+ status_code=500, detail=f"Failed to save task history: {str(e)}")
221
+
222
+ @router.get("/api/coding-command/history")
223
+ async def get_task_history(project_path: str = Depends(get_project_path)):
224
+ """
225
+ 获取任务历史列表
226
+
227
+ 从本地文件系统读取所有保存的任务历史,返回原始JSON文件内容
228
+
229
+ Args:
230
+ project_path: 项目路径
231
+
232
+ Returns:
233
+ 任务历史列表,包含完整的原始数据
234
+ """
235
+ try:
236
+ task_dir = ensure_task_dir(project_path)
237
+ task_files = [f for f in os.listdir(task_dir) if f.endswith(".json")]
238
+
239
+ tasks = []
240
+ for file_name in task_files:
241
+ file_path = os.path.join(task_dir, file_name)
242
+ try:
243
+ with open(file_path, "r", encoding="utf-8") as f:
244
+ task_data = json.load(f)
245
+ # 只包含类型为coding的任务
246
+ if task_data.get("type") == "coding":
247
+ tasks.append(task_data)
248
+ except Exception as e:
249
+ logger.error(f"Error reading task file {file_name}: {str(e)}")
250
+
251
+ # 按时间戳排序,最新的在前面
252
+ tasks.sort(key=lambda x: x.get("timestamp", 0), reverse=True)
253
+
254
+ return {"tasks": tasks}
255
+ except Exception as e:
256
+ logger.error(f"Error getting task history: {str(e)}")
257
+ raise HTTPException(
258
+ status_code=500, detail=f"Failed to get task history: {str(e)}")
259
+
260
+ @router.get("/api/coding-command/task/{task_id}")
261
+ async def get_task_detail(task_id: str, project_path: str = Depends(get_project_path)):
262
+ """
263
+ 获取特定任务的详细信息
264
+
265
+ Args:
266
+ task_id: 任务ID (event_file_id)
267
+ project_path: 项目路径
268
+
269
+ Returns:
270
+ 任务详细信息
271
+ """
272
+ try:
273
+ task_dir = ensure_task_dir(project_path)
274
+ task_file = os.path.join(task_dir, f"{task_id}.json")
275
+
276
+ if not os.path.exists(task_file):
277
+ raise HTTPException(status_code=404, detail="Task not found")
278
+
279
+ with open(task_file, "r", encoding="utf-8") as f:
280
+ task_data = json.load(f)
281
+
282
+ return task_data
283
+ except HTTPException:
284
+ raise
285
+ except Exception as e:
286
+ logger.error(f"Error getting task detail: {str(e)}")
287
+ raise HTTPException(
288
+ status_code=500, detail=f"Failed to get task detail: {str(e)}")
289
+
290
+ @router.post("/api/coding-command/cancel")
291
+ async def cancel_task(request: CancelTaskRequest, project_path: str = Depends(get_project_path)):
292
+ """
293
+ 取消正在运行的任务
294
+
295
+ Args:
296
+ request: 包含event_file_id的请求对象
297
+ project_path: 项目路径
298
+
299
+ Returns:
300
+ 取消操作的结果
301
+ """
302
+ try:
303
+ event_file = get_event_file_path(file_id=request.event_file_id, project_path=project_path)
304
+
305
+ def cancel_in_thread():
306
+ try:
307
+ # 设置全局取消标志
308
+ global_cancel.set_cancel(request.event_file_id)
309
+
310
+ # 获取事件管理器
311
+ event_manager = get_event_manager(event_file)
312
+
313
+ # 写入取消事件
314
+ event_manager.write_error(
315
+ EventContentCreator.create_error(
316
+ "499", "cancelled", "Task cancelled by user").to_dict()
317
+ )
318
+
319
+ logger.info(f"Task {request.event_file_id} cancelled successfully")
320
+ except Exception as e:
321
+ logger.error(f"Error cancelling task: {str(e)}")
322
+
323
+ # 在线程池中执行取消操作
324
+ cancel_thread_pool.submit(cancel_in_thread)
325
+
326
+ return {"status": "success", "message": "Cancel request sent"}
327
+ except Exception as e:
328
+ logger.error(f"Error sending cancel request: {str(e)}")
329
+ raise HTTPException(
330
+ status_code=500, detail=f"Failed to send cancel request: {str(e)}")