summaryrefslogtreecommitdiff
path: root/Omni/Ava/Web/trace.html
diff options
context:
space:
mode:
authorBen Sima <ben@bensima.com>2025-12-19 21:54:54 -0500
committerBen Sima <ben@bensima.com>2025-12-19 21:54:54 -0500
commitfcb8629182fa1552e4a840ccd4ec0aa2b8042cc0 (patch)
treee389479cf9349fbdab107da739bceef11cf8e7ee /Omni/Ava/Web/trace.html
parente856c766584ed933bed0b79c7ef47b6d98b0fb7e (diff)
feat(ava): add tool trace viewer mini-app
- Add SQLite storage for tool traces (Omni/Ava/Trace.hs) - Add web server to serve trace viewer (Omni/Ava/Web.hs) - Add HTML/CSS/JS trace viewer UI (Omni/Ava/Web/trace.html) - Integrate trace storage into Engine.hs tool execution callback - Add trace links to Telegram responses when AVA_WEB_URL is set - Configure Tailscale Funnel for public access - Fix pre-push hook variable scope bug - Add direnv, bash, nix to Ava service PATH - Add mustache dep to Ava.hs for template rendering Epic: t-272
Diffstat (limited to 'Omni/Ava/Web/trace.html')
-rw-r--r--Omni/Ava/Web/trace.html71
1 files changed, 71 insertions, 0 deletions
diff --git a/Omni/Ava/Web/trace.html b/Omni/Ava/Web/trace.html
new file mode 100644
index 0000000..ce990a4
--- /dev/null
+++ b/Omni/Ava/Web/trace.html
@@ -0,0 +1,71 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+ <title>Trace: {{tool_name}}</title>
+ <style>
+ :root { --bg: #1a1a2e; --fg: #eee; --accent: #4a9eff; --code-bg: #0d0d1a; }
+ @media (prefers-color-scheme: light) {
+ :root { --bg: #fff; --fg: #222; --accent: #0066cc; --code-bg: #f5f5f5; }
+ }
+ * { box-sizing: border-box; }
+ body { font-family: system-ui, sans-serif; background: var(--bg); color: var(--fg); padding: 1rem; margin: 0; max-width: 100%; }
+ h1 { font-size: 1.25rem; margin: 0 0 0.5rem; }
+ .meta { font-size: 0.875rem; opacity: 0.7; margin-bottom: 1rem; }
+ .section { margin: 1rem 0; border: 1px solid var(--accent); border-radius: 8px; overflow: hidden; }
+ .section-header { padding: 0.75rem 1rem; cursor: pointer; display: flex; justify-content: space-between; align-items: center; background: var(--code-bg); }
+ .section-header::before { content: '▼'; margin-right: 0.5rem; transition: transform 0.2s; }
+ .section.collapsed .section-header::before { transform: rotate(-90deg); }
+ .section-content { padding: 1rem; display: block; overflow-x: auto; background: var(--code-bg); }
+ .section.collapsed .section-content { display: none; }
+ pre { margin: 0; white-space: pre-wrap; word-break: break-word; font-size: 0.875rem; font-family: ui-monospace, monospace; }
+ .copy-btn { background: var(--accent); color: white; border: none; padding: 0.25rem 0.5rem; border-radius: 4px; cursor: pointer; font-size: 0.75rem; }
+ .copy-btn:hover { opacity: 0.8; }
+ .footer { font-size: 0.75rem; opacity: 0.5; margin-top: 2rem; text-align: center; }
+ </style>
+</head>
+<body>
+ <h1>{{tool_name}}</h1>
+ <p class="meta">{{created_at}} · {{duration_ms}}ms</p>
+
+ <div class="section" id="input-section">
+ <div class="section-header">
+ Input
+ <button class="copy-btn" data-target="input-content">Copy</button>
+ </div>
+ <div class="section-content" id="input-content"><pre>{{input_json}}</pre></div>
+ </div>
+
+ <div class="section" id="output-section">
+ <div class="section-header">
+ Output
+ <button class="copy-btn" data-target="output-content">Copy</button>
+ </div>
+ <div class="section-content" id="output-content"><pre>{{output_json}}</pre></div>
+ </div>
+
+ <p class="footer">Trace ID: {{trace_id}}</p>
+
+ <script>
+ document.querySelectorAll('.section-header').forEach(header => {
+ header.addEventListener('click', (e) => {
+ if (e.target.classList.contains('copy-btn')) return;
+ header.parentElement.classList.toggle('collapsed');
+ });
+ });
+
+ document.querySelectorAll('.copy-btn').forEach(btn => {
+ btn.addEventListener('click', () => {
+ const targetId = btn.getAttribute('data-target');
+ const text = document.getElementById(targetId).querySelector('pre').textContent;
+ navigator.clipboard.writeText(text).then(() => {
+ const orig = btn.textContent;
+ btn.textContent = 'Copied!';
+ setTimeout(() => btn.textContent = orig, 1500);
+ });
+ });
+ });
+ </script>
+</body>
+</html>