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

1#!/usr/bin/env python

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

3

4 import sys

5 import json

6 from functools import wraps

7 from typing import Any, Dict, List

8 from rich.console import Console

9 from rich.text import Text

10 from rich.panel import Panel

11 from rich.syntax import Syntax

12 from rich.table import Table

13 from rich.markdown import Markdown

14

15 from aipyapp.display import RichDisplayPlugin

16 from live_display import LiveDisplay

17 from aipyapp import T

18

19 def 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

31 class 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()