Coverage for aipyapp/plugins/p_style_modern.py: 0%

363 statements  

« prev     ^ index     » next       coverage.py v7.10.3, created at 2025-08-11 12:02 +0200

1#!/usr/bin/env python 

2# -*- coding: utf-8 -*- 

3 

4import sys 

5import json 

6from functools import wraps 

7from typing import Any, Dict, List 

8from rich.console import Console 

9from rich.text import Text 

10from rich.panel import Panel 

11from rich.syntax import Syntax 

12from rich.table import Table 

13from rich.markdown import Markdown 

14 

15from aipyapp.display import RichDisplayPlugin 

16from live_display import LiveDisplay 

17from aipyapp import T 

18 

19def restore_output(func): 

20 @wraps(func) 

21 def wrapper(self, *args, **kwargs): 

22 old_stdout, old_stderr = sys.stdout, sys.stderr 

23 sys.stdout, sys.stderr = sys.__stdout__, sys.__stderr__ 

24 

25 try: 

26 return func(self, *args, **kwargs) 

27 finally: 

28 sys.stdout, sys.stderr = old_stdout, old_stderr 

29 return wrapper 

30 

31class DisplayModern(RichDisplayPlugin): 

32 """Modern display style""" 

33 name = "modern" 

34 version = "1.0.0" 

35 description = "Modern display style" 

36 author = "AiPy Team" 

37 

38 def __init__(self, console: Console, quiet: bool = False): 

39 super().__init__(console, quiet) 

40 self.current_block = None 

41 self.execution_status = {} 

42 self.live_display = None 

43 self.stream_buffer = "" 

44 self.thinking_buffer = "" 

45 self.is_thinking = False 

46 

47 def on_task_start(self, event): 

48 """任务开始事件处理""" 

49 data = event.data 

50 instruction = data.get('instruction', '') 

51 user_prompt = data.get('user_prompt', '') 

52 

53 # 显示任务开始信息 

54 title = Text("🚀 任务开始", style="bold blue") 

55 content = Text(instruction, style="white") 

56 panel = Panel(content, title=title, border_style="blue") 

57 self.console.print(panel) 

58 self.console.print() 

59 

60 def on_round_start(self, event): 

61 """回合开始事件处理""" 

62 data = event.data 

63 instruction = data.get('instruction', '') 

64 

65 # 显示回合开始信息 

66 title = Text("🔄 回合开始", style="bold yellow") 

67 content = Text(instruction, style="white") 

68 panel = Panel(content, title=title, border_style="yellow") 

69 self.console.print(panel) 

70 self.console.print() 

71 

72 def on_query_start(self, event): 

73 """查询开始事件处理""" 

74 self.console.print(f"📤 {T('Sending message to LLM')}...", style="dim cyan") 

75 

76 def on_stream_start(self, event): 

77 """流式开始事件处理""" 

78 if not self.quiet: 

79 self.live_display = LiveDisplay() 

80 self.live_display.__enter__() 

81 self.console.print(f"📥 {T('Streaming started')}...", style="dim cyan") 

82 

83 def on_stream_end(self, event): 

84 """流式结束事件处理""" 

85 if self.live_display: 

86 self.live_display.__exit__(None, None, None) 

87 self.live_display = None 

88 self.console.print() 

89 

90 def on_stream(self, event): 

91 """LLM 流式响应事件处理""" 

92 response = event.data 

93 lines = response.get('lines', []) 

94 reason = response.get('reason', False) 

95 

96 if self.live_display: 

97 self.live_display.update_display(lines, reason=reason) 

98 

99 def on_response_complete(self, event): 

100 """LLM 响应完成事件处理""" 

101 data = event.data 

102 llm = data.get('llm', '') 

103 msg = data.get('msg') 

104 

105 if not msg: 

106 self.console.print(f"{T('LLM response is empty')}", style="red") 

107 return 

108 

109 if msg.role == 'error': 

110 self.console.print(f"{msg.content}", style="red") 

111 return 

112 

113 # 处理响应内容 

114 if msg.reason: 

115 content = f"{msg.reason}\n\n-----\n\n{msg.content}" 

116 else: 

117 content = msg.content 

118 

119 # 智能解析和显示内容 

120 self._parse_and_display_content(content, llm) 

121 

122 def on_parse_reply(self, event): 

123 """消息解析结果事件处理""" 

124 ret = event.data.get('result') 

125 if ret: 

126 # 显示解析结果摘要 

127 if 'commands' in ret: 

128 commands = ret['commands'] 

129 if commands: 

130 # 统计不同类型的指令 

131 exec_count = sum(1 for cmd in commands if cmd['type'] == 'exec') 

132 edit_count = sum(1 for cmd in commands if cmd['type'] == 'edit') 

133 

134 summary_parts = [] 

135 if exec_count > 0: 

136 summary_parts.append(f"{exec_count} exec") 

137 if edit_count > 0: 

138 summary_parts.append(f"{edit_count} edit") 

139 

140 if summary_parts: 

141 summary = ', '.join(summary_parts) 

142 self.console.print(f"🎯 {T('Found commands')}: {summary}", style="dim green") 

143 elif 'call_tool' in ret: 

144 self.console.print(f"🔧 {T('Tool call detected')}", style="dim blue") 

145 elif 'blocks' in ret and ret['blocks']: 

146 block_count = len(ret['blocks']) 

147 self.console.print(f"📝 {T('Found {} code blocks')}.format({block_count})", style="dim green") 

148 

149 def on_exec(self, event): 

150 """代码执行开始事件处理""" 

151 block = event.data.get('block') 

152 if not block: 

153 return 

154 

155 block_name = getattr(block, 'name', 'Unknown') 

156 self.current_block = block_name 

157 self.execution_status[block_name] = 'running' 

158 

159 # 显示代码块 

160 self._show_code_block(block) 

161 

162 # 显示执行状态 

163 self.console.print(f"{T('Executing')}...", style="yellow") 

164 

165 def on_exec_result(self, event): 

166 """代码执行结果事件处理""" 

167 data = event.data 

168 result = data.get('result') 

169 block = data.get('block') 

170 

171 if block and hasattr(block, 'name'): 

172 self.current_block = block.name 

173 self.execution_status[block.name] = 'success' 

174 

175 # 显示执行结果 

176 self._show_execution_result(result) 

177 

178 def on_edit_start(self, event): 

179 """代码编辑开始事件处理""" 

180 instruction = event.data.get('instruction', {}) 

181 block_name = instruction.get('name', 'Unknown') 

182 old_str = instruction.get('old', '') 

183 new_str = instruction.get('new', '') 

184 

185 # 显示编辑操作信息 

186 title = Text(f"✏️ 编辑代码块: {block_name}", style="bold yellow") 

187 

188 # 创建编辑预览内容 

189 content_lines = [] 

190 if old_str: 

191 old_preview = old_str[:50] + '...' if len(old_str) > 50 else old_str 

192 content_lines.append(Text(f"替换: {repr(old_preview)}", style="red")) 

193 if new_str: 

194 new_preview = new_str[:50] + '...' if len(new_str) > 50 else new_str 

195 content_lines.append(Text(f"为: {repr(new_preview)}", style="green")) 

196 

197 from rich.console import Group 

198 content = Group(*content_lines) if content_lines else Text("编辑操作", style="white") 

199 panel = Panel(content, title=title, border_style="yellow") 

200 self.console.print(panel) 

201 

202 def on_edit_result(self, event): 

203 """代码编辑结果事件处理""" 

204 data = event.data 

205 result = data.get('result', {}) 

206 

207 success = result.get('success', False) 

208 message = result.get('message', '') 

209 block_name = result.get('block_name', 'Unknown') 

210 new_version = result.get('new_version') 

211 

212 if success: 

213 title = Text(f"✅ 编辑成功: {block_name}", style="bold green") 

214 content_lines = [] 

215 

216 if message: 

217 content_lines.append(Text(message, style="white")) 

218 if new_version: 

219 content_lines.append(Text(f"新版本: v{new_version}", style="cyan")) 

220 

221 from rich.console import Group 

222 content = Group(*content_lines) if content_lines else Text("编辑完成", style="white") 

223 panel = Panel(content, title=title, border_style="green") 

224 else: 

225 title = Text(f"❌ 编辑失败: {block_name}", style="bold red") 

226 content = Text(message or "编辑操作失败", style="red") 

227 panel = Panel(content, title=title, border_style="red") 

228 

229 self.console.print(panel) 

230 

231 def on_mcp_call(self, event): 

232 """MCP 工具调用事件处理""" 

233 block = event.data.get('block') 

234 if block and hasattr(block, 'content'): 

235 # 显示工具调用内容 

236 title = Text("🔧 MCP 工具调用", style="bold blue") 

237 content = Syntax(block.content, 'json', line_numbers=False, word_wrap=True) 

238 panel = Panel(content, title=title, border_style="blue") 

239 self.console.print(panel) 

240 else: 

241 self.console.print(f"🔧 {T('Calling MCP tool')}...", style="dim blue") 

242 

243 def on_mcp_result(self, event): 

244 """MCP 工具调用结果事件处理""" 

245 data = event.data 

246 result = data.get('result') 

247 block = data.get('block') 

248 

249 # 显示工具调用结果 

250 title = Text("🔧 MCP 工具结果", style="bold green") 

251 if isinstance(result, dict): 

252 content = Syntax(json.dumps(result, ensure_ascii=False, indent=2), 'json', line_numbers=False, word_wrap=True) 

253 else: 

254 content = Text(str(result), style="white") 

255 panel = Panel(content, title=title, border_style="green") 

256 self.console.print(panel) 

257 

258 def on_round_end(self, event): 

259 """回合结束事件处理""" 

260 data = event.data 

261 summary = data.get('summary', {}) 

262 response = data.get('response', '') 

263 

264 # 显示统计信息 

265 if 'usages' in summary and summary['usages']: 

266 self._show_usage_table(summary['usages']) 

267 

268 # 显示总结信息 

269 summary_text = summary.get('summary', '') 

270 if summary_text: 

271 title = Text("📊 执行统计", style="bold cyan") 

272 content = Text(summary_text, style="white") 

273 panel = Panel(content, title=title, border_style="cyan") 

274 self.console.print(panel) 

275 

276 # 显示最终响应 

277 if response: 

278 self.console.print() 

279 self._parse_and_display_content(response, "Final Response") 

280 

281 def on_task_end(self, event): 

282 """任务结束事件处理""" 

283 path = event.data.get('path', '') 

284 title = Text("✅ 任务完成", style="bold green") 

285 content = Text(f"结果已保存到: {path}", style="white") 

286 panel = Panel(content, title=title, border_style="green") 

287 self.console.print(panel) 

288 

289 def on_upload_result(self, event): 

290 """云端上传结果事件处理""" 

291 data = event.data 

292 status_code = data.get('status_code', 0) 

293 url = data.get('url', '') 

294 

295 if url: 

296 title = Text("☁️ 上传成功", style="bold green") 

297 content = Text(f"链接: {url}", style="white") 

298 panel = Panel(content, title=title, border_style="green") 

299 self.console.print(panel) 

300 else: 

301 title = Text("❌ 上传失败", style="bold red") 

302 content = Text(f"状态码: {status_code}", style="white") 

303 panel = Panel(content, title=title, border_style="red") 

304 self.console.print(panel) 

305 

306 def on_exception(self, event): 

307 """异常事件处理""" 

308 import traceback 

309 data = event.data 

310 msg = data.get('msg', '') 

311 exception = data.get('exception') 

312 traceback_str = data.get('traceback') 

313 

314 title = Text("💥 异常", style="bold red") 

315 if traceback_str: 

316 content = Syntax(traceback_str, 'python', line_numbers=True, word_wrap=True) 

317 elif exception: 

318 try: 

319 tb_lines = traceback.format_exception(type(exception), exception, exception.__traceback__) 

320 tb_str = ''.join(tb_lines) 

321 content = Syntax(tb_str, 'python', line_numbers=True, word_wrap=True) 

322 except: 

323 content = Text(f"{msg}: {exception}", style="red") 

324 else: 

325 content = Text(msg, style="red") 

326 

327 panel = Panel(content, title=title, border_style="red") 

328 self.console.print(panel) 

329 

330 def on_runtime_message(self, event): 

331 """Runtime消息事件处理""" 

332 data = event.data 

333 message = data.get('message', '') 

334 if message: 

335 self.console.print(message, style="dim white") 

336 

337 def on_runtime_input(self, event): 

338 """Runtime输入事件处理""" 

339 # 输入事件通常不需要特殊处理,因为input_prompt已经处理了 

340 pass 

341 

342 @restore_output 

343 def on_call_function(self, event): 

344 """函数调用事件处理""" 

345 data = event.data 

346 funcname = data.get('funcname') 

347 title = Text(f"🔧 {T('Start calling function {}')}".format(funcname), style="bold blue") 

348 panel = Panel(Text(funcname, style="white"), title=title, border_style="blue") 

349 self.console.print(panel) 

350 

351 @restore_output 

352 def on_call_function_result(self, event): 

353 """函数调用结果事件处理""" 

354 data = event.data 

355 funcname = data.get('funcname') 

356 success = data.get('success', False) 

357 result = data.get('result') 

358 error = data.get('error') 

359 

360 if success: 

361 title = Text(f"{T('Function call result {}')}".format(funcname), style="bold green") 

362 

363 if result is not None: 

364 # 格式化并显示结果 

365 if isinstance(result, (dict, list)): 

366 content = Syntax(json.dumps(result, ensure_ascii=False, indent=2, default=str), 'json', line_numbers=False, word_wrap=True) 

367 else: 

368 content = Text(str(result), style="white") 

369 else: 

370 content = Text(T("No return value"), style="dim white") 

371 

372 panel = Panel(content, title=title, border_style="green") 

373 self.console.print(panel) 

374 else: 

375 title = Text(f"{T('Function call failed {}')}".format(funcname), style="bold red") 

376 content = Text(error if error else T("Unknown error"), style="red") 

377 panel = Panel(content, title=title, border_style="red") 

378 self.console.print(panel) 

379 

380 def _parse_and_display_content(self, content: str, llm: str = ""): 

381 """智能解析并显示内容""" 

382 if not content: 

383 return 

384 

385 # 检测是否包含代码块 

386 if '```' in content: 

387 self._show_content_with_code_blocks(content, llm) 

388 else: 

389 self._show_text_content(content, llm) 

390 

391 def _show_content_with_code_blocks(self, content: str, llm: str = ""): 

392 """显示包含代码块的内容""" 

393 lines = content.split('\n') 

394 in_code_block = False 

395 code_lang = "" 

396 code_content = [] 

397 text_content = [] 

398 

399 for line in lines: 

400 if line.startswith('```'): 

401 if in_code_block: 

402 # 结束代码块 

403 if code_content: 

404 self._show_code_block_content(code_lang, '\n'.join(code_content)) 

405 in_code_block = False 

406 code_content = [] 

407 else: 

408 # 开始代码块 

409 in_code_block = True 

410 code_lang = line[3:].strip() 

411 elif in_code_block: 

412 code_content.append(line) 

413 else: 

414 # 普通文本行 

415 text_content.append(line) 

416 

417 # 显示文本内容 

418 if text_content: 

419 text = '\n'.join(text_content).strip() 

420 if text: 

421 self._show_text_content(text, llm) 

422 

423 def _show_text_content(self, content: str, llm: str = ""): 

424 """显示纯文本内容""" 

425 if not content.strip(): 

426 return 

427 

428 # 使用 Markdown 渲染文本内容 

429 try: 

430 markdown = Markdown(content) 

431 if llm: 

432 title = Text(f"🤖 {llm}", style="bold cyan") 

433 panel = Panel(markdown, title=title, border_style="cyan") 

434 else: 

435 panel = Panel(markdown, border_style="white") 

436 self.console.print(panel) 

437 except: 

438 # 如果 Markdown 渲染失败,直接显示文本 

439 if llm: 

440 self.console.print(f"🤖 {llm}:", style="bold cyan") 

441 self.console.print(content) 

442 

443 def _show_code_block(self, block: Any): 

444 """显示代码块""" 

445 if hasattr(block, 'code') and hasattr(block, 'lang'): 

446 self._show_code_block_content(block.lang, block.code, block.name) 

447 else: 

448 # 兼容其他格式 

449 self.console.print(f"📝 {T('Code block')}", style="dim white") 

450 

451 def _show_code_block_content(self, lang: str, code: str, name: str = None): 

452 """显示代码块内容""" 

453 if not code.strip(): 

454 return 

455 

456 title = f"📝 {name or T('Code')} ({lang})" 

457 

458 # 使用语法高亮显示代码 

459 syntax = Syntax(code, lang, line_numbers=True, word_wrap=True) 

460 panel = Panel(syntax, title=title, border_style="blue") 

461 self.console.print(panel) 

462 

463 def _show_execution_result(self, result: Any): 

464 """显示执行结果""" 

465 if isinstance(result, dict): 

466 self._show_structured_result(result) 

467 else: 

468 self._show_simple_result(result) 

469 

470 def _show_structured_result(self, result: Dict[str, Any]): 

471 """显示结构化结果""" 

472 # 检查是否有错误 

473 if 'traceback' in result or 'error' in result: 

474 title = Text("❌ 执行失败", style="bold red") 

475 if 'traceback' in result: 

476 content = Syntax(result['traceback'], 'python', line_numbers=True, word_wrap=True) 

477 else: 

478 content = Text(str(result.get('error', 'Unknown error')), style="red") 

479 panel = Panel(content, title=title, border_style="red") 

480 self.console.print(panel) 

481 else: 

482 # 显示成功结果 

483 title = Text("✅ 执行成功", style="bold green") 

484 output_parts = [] 

485 

486 # 收集输出信息 

487 if 'output' in result and result['output']: 

488 output_parts.append(f"📤 {T('Output')}: {result['output']}") 

489 if 'stdout' in result and result['stdout']: 

490 output_parts.append(f"📤 {T('Stdout')}: {result['stdout']}") 

491 if 'stderr' in result and result['stderr']: 

492 output_parts.append(f"⚠️ {T('Stderr')}: {result['stderr']}") 

493 

494 if output_parts: 

495 content = Text('\n'.join(output_parts), style="white") 

496 panel = Panel(content, title=title, border_style="green") 

497 self.console.print(panel) 

498 else: 

499 self.console.print("✅ 执行成功", style="green") 

500 

501 def _show_simple_result(self, result: Any): 

502 """显示简单结果""" 

503 if result is None: 

504 self.console.print("✅ 执行完成", style="green") 

505 else: 

506 title = Text("✅ 执行结果", style="bold green") 

507 content = Text(str(result), style="white") 

508 panel = Panel(content, title=title, border_style="green") 

509 self.console.print(panel) 

510 

511 def _show_usage_table(self, usages: List[Dict[str, Any]]): 

512 """显示使用统计表格""" 

513 if not usages: 

514 return 

515 

516 table = Table(title=T("执行统计"), show_lines=True) 

517 

518 table.add_column(T("回合"), justify="center", style="bold cyan", no_wrap=True) 

519 table.add_column(T("时间(s)"), justify="right") 

520 table.add_column(T("输入Token"), justify="right") 

521 table.add_column(T("输出Token"), justify="right") 

522 table.add_column(T("总计Token"), justify="right", style="bold magenta") 

523 

524 for i, usage in enumerate(usages, 1): 

525 table.add_row( 

526 str(i), 

527 str(usage.get("time", 0)), 

528 str(usage.get("input_tokens", 0)), 

529 str(usage.get("output_tokens", 0)), 

530 str(usage.get("total_tokens", 0)), 

531 ) 

532 

533 self.console.print(table) 

534 self.console.print()