|
1 | | -import json |
2 | 1 | from typing import Dict, List, Optional |
3 | 2 |
|
4 | 3 | from textual.app import ComposeResult |
@@ -29,14 +28,7 @@ def show_span_details(self, trace_msg: TraceMessage): |
29 | 28 | details_log = self.query_one("#span-details", RichLog) |
30 | 29 | details_log.clear() |
31 | 30 |
|
32 | | - # Format span details |
33 | 31 | details_log.write(f"[bold cyan]Span: {trace_msg.span_name}[/bold cyan]") |
34 | | - details_log.write(f"[dim]Trace ID: {trace_msg.trace_id}[/dim]") |
35 | | - details_log.write(f"[dim]Span ID: {trace_msg.span_id}[/dim]") |
36 | | - details_log.write(f"[dim]Run ID: {trace_msg.run_id}[/dim]") |
37 | | - |
38 | | - if trace_msg.parent_span_id: |
39 | | - details_log.write(f"[dim]Parent Span: {trace_msg.parent_span_id}[/dim]") |
40 | 32 |
|
41 | 33 | details_log.write("") # Empty line |
42 | 34 |
|
@@ -68,6 +60,16 @@ def show_span_details(self, trace_msg: TraceMessage): |
68 | 60 | for key, value in trace_msg.attributes.items(): |
69 | 61 | details_log.write(f" {key}: {value}") |
70 | 62 |
|
| 63 | + details_log.write("") # Empty line |
| 64 | + |
| 65 | + # Format span details |
| 66 | + details_log.write(f"[dim]Trace ID: {trace_msg.trace_id}[/dim]") |
| 67 | + details_log.write(f"[dim]Span ID: {trace_msg.span_id}[/dim]") |
| 68 | + details_log.write(f"[dim]Run ID: {trace_msg.run_id}[/dim]") |
| 69 | + |
| 70 | + if trace_msg.parent_span_id: |
| 71 | + details_log.write(f"[dim]Parent Span: {trace_msg.parent_span_id}[/dim]") |
| 72 | + |
71 | 73 |
|
72 | 74 | class RunDetailsPanel(Container): |
73 | 75 | """Panel showing traces and logs for selected run with tabbed interface.""" |
@@ -144,10 +146,65 @@ def show_run(self, run: ExecutionRun): |
144 | 146 | # Clear and rebuild traces tree using TraceMessage objects |
145 | 147 | self._rebuild_spans_tree() |
146 | 148 |
|
| 149 | + def switch_tab(self, tab_id: str) -> None: |
| 150 | + """Switch to a specific tab by id (e.g. 'run-tab', 'traces-tab').""" |
| 151 | + tabbed = self.query_one(TabbedContent) |
| 152 | + tabbed.active = tab_id |
| 153 | + |
147 | 154 | def _update_resume_tab(self, run: ExecutionRun) -> None: |
148 | 155 | resume_panel = self.query_one("#resume-panel", ResumePanel) |
149 | 156 | resume_panel.display = run.status == "suspended" |
150 | 157 |
|
| 158 | + def _flatten_values(self, value: object, prefix: str = "") -> list[str]: |
| 159 | + """Flatten nested dict/list structures into dot-notation paths.""" |
| 160 | + lines: list[str] = [] |
| 161 | + |
| 162 | + if value is None: |
| 163 | + lines.append(f"{prefix}: [dim]—[/dim]" if prefix else "[dim]—[/dim]") |
| 164 | + |
| 165 | + elif isinstance(value, dict): |
| 166 | + if not value: |
| 167 | + lines.append(f"{prefix}: {{}}" if prefix else "{}") |
| 168 | + else: |
| 169 | + for k, v in value.items(): |
| 170 | + new_prefix = f"{prefix}.{k}" if prefix else k |
| 171 | + lines.extend(self._flatten_values(v, new_prefix)) |
| 172 | + |
| 173 | + elif isinstance(value, list): |
| 174 | + if not value: |
| 175 | + lines.append(f"{prefix}: []" if prefix else "[]") |
| 176 | + else: |
| 177 | + for i, item in enumerate(value): |
| 178 | + new_prefix = f"{prefix}[{i}]" |
| 179 | + lines.extend(self._flatten_values(item, new_prefix)) |
| 180 | + |
| 181 | + elif isinstance(value, str): |
| 182 | + if prefix: |
| 183 | + for line in value.splitlines(): |
| 184 | + lines.append(f"{prefix}: {line}") |
| 185 | + else: |
| 186 | + lines.extend(value.splitlines()) |
| 187 | + |
| 188 | + else: |
| 189 | + if prefix: |
| 190 | + lines.append(f"{prefix}: {value}") |
| 191 | + else: |
| 192 | + lines.append(str(value)) |
| 193 | + |
| 194 | + return lines |
| 195 | + |
| 196 | + def _write_block( |
| 197 | + self, log: RichLog, title: str, data: object, style: str = "white" |
| 198 | + ) -> None: |
| 199 | + """Pretty-print a block with flattened dot-notation paths.""" |
| 200 | + log.write(f"[bold {style}]{title.upper()}:[/bold {style}]") |
| 201 | + log.write("[dim]" + "=" * 50 + "[/dim]") |
| 202 | + |
| 203 | + for line in self._flatten_values(data): |
| 204 | + log.write(line) |
| 205 | + |
| 206 | + log.write("") |
| 207 | + |
151 | 208 | def _show_run_details(self, run: ExecutionRun): |
152 | 209 | """Display detailed information about the run in the Details tab.""" |
153 | 210 | self._update_resume_tab(run) |
@@ -202,35 +259,16 @@ def _show_run_details(self, run: ExecutionRun): |
202 | 259 |
|
203 | 260 | run_details_log.write("") |
204 | 261 |
|
205 | | - # Input section |
206 | | - if hasattr(run, "input_data") and run.input_data is not None: |
207 | | - run_details_log.write("[bold green]INPUT:[/bold green]") |
208 | | - run_details_log.write("[dim]" + "=" * 50 + "[/dim]") |
209 | | - |
210 | | - # Handle different input types |
211 | | - if isinstance(run.input_data, str): |
212 | | - run_details_log.write(run.input_data) |
213 | | - elif isinstance(run.input_data, dict): |
214 | | - run_details_log.write(json.dumps(run.input_data, indent=2)) |
215 | | - else: |
216 | | - run_details_log.write(str(run.input_data)) |
217 | | - |
218 | | - run_details_log.write("") |
| 262 | + if hasattr(run, "input_data"): |
| 263 | + self._write_block(run_details_log, "Input", run.input_data, style="green") |
219 | 264 |
|
220 | | - # Output section |
221 | | - if hasattr(run, "output_data") and run.output_data is not None: |
222 | | - run_details_log.write("[bold magenta]OUTPUT:[/bold magenta]") |
223 | | - run_details_log.write("[dim]" + "=" * 50 + "[/dim]") |
224 | | - |
225 | | - # Handle different output types |
226 | | - if isinstance(run.output_data, str): |
227 | | - run_details_log.write(run.output_data) |
228 | | - elif isinstance(run.output_data, dict): |
229 | | - run_details_log.write(json.dumps(run.output_data, indent=2)) |
230 | | - else: |
231 | | - run_details_log.write(str(run.output_data)) |
| 265 | + if hasattr(run, "resume_data") and run.resume_data: |
| 266 | + self._write_block(run_details_log, "Resume", run.resume_data, style="green") |
232 | 267 |
|
233 | | - run_details_log.write("") |
| 268 | + if hasattr(run, "output_data"): |
| 269 | + self._write_block( |
| 270 | + run_details_log, "Output", run.output_data, style="magenta" |
| 271 | + ) |
234 | 272 |
|
235 | 273 | # Error section (if applicable) |
236 | 274 | if hasattr(run, "error") and run.error: |
|
0 commit comments