Spaces:
Sleeping
Sleeping
| <html lang="en"> | |
| <head> | |
| <meta charset="utf-8" /> | |
| <meta | |
| name="viewport" | |
| content="width=device-width, initial-scale=1, maximum-scale=1" | |
| /> | |
| <title>Open Voice Agent</title> | |
| <style> | |
| *, | |
| *::before, | |
| *::after { | |
| box-sizing: border-box; | |
| } | |
| :root { | |
| --bg-primary: #0b0e13; | |
| --bg-secondary: #131720; | |
| --bg-tertiary: #1a2030; | |
| --bg-card: #161c2a; | |
| --border: #252d3f; | |
| --border-light: #2e3a50; | |
| --text-primary: #eef0f6; | |
| --text-secondary: #8d95ab; | |
| --text-muted: #5c6478; | |
| --accent: #6c8fff; | |
| --accent-light: #8eaaff; | |
| --accent-glow: rgba(108, 143, 255, 0.15); | |
| --accent-gradient: linear-gradient(135deg, #6c8fff 0%, #a78bfa 100%); | |
| --user-color: #22c55e; | |
| --user-bg: rgba(34, 197, 94, 0.08); | |
| --agent-color: #6c8fff; | |
| --agent-bg: rgba(108, 143, 255, 0.08); | |
| --warning: #f59e0b; | |
| --tool-accent: #f59e0b; | |
| --post-accent: #34d399; | |
| --critical: #ef4444; | |
| --radius: 12px; | |
| --radius-sm: 8px; | |
| --radius-xs: 6px; | |
| } | |
| body { | |
| font-family: 'Inter', system-ui, -apple-system, sans-serif; | |
| margin: 0; | |
| padding: 0; | |
| background: var(--bg-primary); | |
| color: var(--text-primary); | |
| min-height: 100vh; | |
| overflow-x: hidden; | |
| } | |
| /* Header */ | |
| .header { | |
| padding: 16px 24px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| border-bottom: 1px solid var(--border); | |
| background: var(--bg-secondary); | |
| } | |
| .header-left { | |
| display: flex; | |
| align-items: center; | |
| gap: 12px; | |
| } | |
| .header-right { | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| } | |
| .logo { | |
| font-size: 18px; | |
| font-weight: 700; | |
| letter-spacing: -0.3px; | |
| background: var(--accent-gradient); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| } | |
| .status-badge { | |
| display: flex; | |
| align-items: center; | |
| gap: 6px; | |
| padding: 4px 10px; | |
| background: var(--bg-tertiary); | |
| border: 1px solid var(--border); | |
| border-radius: 20px; | |
| font-size: 12px; | |
| color: var(--text-secondary); | |
| } | |
| .meta-badge { | |
| display: flex; | |
| align-items: center; | |
| gap: 6px; | |
| padding: 4px 10px; | |
| background: var(--bg-tertiary); | |
| border: 1px solid var(--border); | |
| border-radius: 20px; | |
| font-size: 11px; | |
| color: var(--text-secondary); | |
| } | |
| .meta-label { | |
| color: var(--text-muted); | |
| font-weight: 600; | |
| } | |
| .meta-value { | |
| color: var(--text-primary); | |
| font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; | |
| font-size: 10px; | |
| } | |
| .status-dot { | |
| width: 7px; | |
| height: 7px; | |
| border-radius: 50%; | |
| background: var(--text-muted); | |
| transition: background 0.3s; | |
| } | |
| .status-dot.connected { | |
| background: #22c55e; | |
| box-shadow: 0 0 6px rgba(34, 197, 94, 0.5); | |
| } | |
| .status-dot.connecting { | |
| background: var(--warning); | |
| animation: pulse-dot 1s infinite; | |
| } | |
| @keyframes pulse-dot { | |
| 0%, 100% { opacity: 1; } | |
| 50% { opacity: 0.4; } | |
| } | |
| .controls { | |
| display: flex; | |
| gap: 6px; | |
| } | |
| .controls button { | |
| padding: 7px 14px; | |
| border-radius: 20px; | |
| border: 1px solid var(--border); | |
| background: var(--bg-tertiary); | |
| color: var(--text-primary); | |
| cursor: pointer; | |
| font-size: 12px; | |
| font-weight: 500; | |
| transition: all 0.2s; | |
| display: flex; | |
| align-items: center; | |
| gap: 5px; | |
| } | |
| .controls button:hover:not(:disabled) { | |
| background: var(--bg-card); | |
| border-color: var(--border-light); | |
| } | |
| .controls button:disabled { | |
| opacity: 0.35; | |
| cursor: not-allowed; | |
| } | |
| .controls button.btn-primary { | |
| background: var(--accent); | |
| border-color: var(--accent); | |
| color: #fff; | |
| } | |
| .controls button.btn-primary:hover:not(:disabled) { | |
| background: var(--accent-light); | |
| border-color: var(--accent-light); | |
| } | |
| .controls button.btn-danger { | |
| color: var(--critical); | |
| border-color: rgba(239, 68, 68, 0.3); | |
| } | |
| .controls button.btn-danger:hover:not(:disabled) { | |
| background: rgba(239, 68, 68, 0.1); | |
| border-color: rgba(239, 68, 68, 0.5); | |
| } | |
| .trace-dropdown { | |
| position: relative; | |
| min-width: 360px; | |
| } | |
| .trace-dropdown-toggle { | |
| width: 100%; | |
| border-radius: 10px; | |
| border: 1px solid var(--border); | |
| background: var(--bg-tertiary); | |
| color: var(--text-primary); | |
| padding: 8px 10px; | |
| font-size: 11px; | |
| font-weight: 600; | |
| line-height: 1; | |
| display: inline-flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| gap: 8px; | |
| cursor: pointer; | |
| transition: border-color 0.2s, background 0.2s; | |
| } | |
| .trace-dropdown-toggle:hover:not(:disabled) { | |
| border-color: rgba(108, 143, 255, 0.45); | |
| background: var(--bg-card); | |
| } | |
| .trace-dropdown-toggle:disabled { | |
| opacity: 0.45; | |
| cursor: not-allowed; | |
| } | |
| .trace-dropdown-label { | |
| overflow: hidden; | |
| text-overflow: ellipsis; | |
| white-space: nowrap; | |
| } | |
| .trace-dropdown-chevron { | |
| width: 14px; | |
| height: 14px; | |
| color: var(--text-secondary); | |
| flex-shrink: 0; | |
| transition: transform 0.2s ease; | |
| } | |
| .trace-dropdown-toggle[aria-expanded="true"] .trace-dropdown-chevron { | |
| transform: rotate(180deg); | |
| } | |
| .trace-dropdown-menu { | |
| position: absolute; | |
| top: calc(100% + 6px); | |
| right: 0; | |
| width: min(520px, 78vw); | |
| max-height: 320px; | |
| overflow-y: auto; | |
| border-radius: 10px; | |
| border: 1px solid var(--border-light); | |
| background: #111725; | |
| box-shadow: 0 10px 32px rgba(0, 0, 0, 0.35); | |
| z-index: 60; | |
| padding: 8px; | |
| } | |
| .trace-dropdown-empty { | |
| font-size: 11px; | |
| color: var(--text-muted); | |
| padding: 8px; | |
| } | |
| .trace-dropdown-list { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 6px; | |
| } | |
| .trace-entry { | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| gap: 8px; | |
| padding: 8px; | |
| border-radius: 8px; | |
| border: 1px solid rgba(108, 143, 255, 0.18); | |
| background: rgba(108, 143, 255, 0.05); | |
| } | |
| .trace-entry-meta { | |
| min-width: 0; | |
| display: flex; | |
| flex-direction: column; | |
| gap: 3px; | |
| } | |
| .trace-entry-id { | |
| font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; | |
| font-size: 11px; | |
| color: var(--text-primary); | |
| overflow: hidden; | |
| text-overflow: ellipsis; | |
| white-space: nowrap; | |
| } | |
| .trace-entry-time { | |
| font-size: 10px; | |
| color: var(--text-secondary); | |
| } | |
| .trace-entry-open { | |
| flex-shrink: 0; | |
| padding: 6px 9px; | |
| border-radius: 7px; | |
| border: 1px solid rgba(108, 143, 255, 0.5); | |
| color: #b8c9ff; | |
| text-decoration: none; | |
| background: rgba(108, 143, 255, 0.08); | |
| font-size: 10px; | |
| font-weight: 700; | |
| text-transform: uppercase; | |
| letter-spacing: 0.05em; | |
| } | |
| .trace-entry-open:hover { | |
| border-color: rgba(108, 143, 255, 0.7); | |
| color: #d7e1ff; | |
| } | |
| /* Main layout — single column, top to bottom */ | |
| .main { | |
| display: flex; | |
| flex-direction: column; | |
| height: calc(100vh - 57px); | |
| overflow-x: hidden; | |
| overflow-y: auto; | |
| } | |
| /* Waveform hero */ | |
| .waveform-section { | |
| flex-grow: 1; | |
| flex-shrink: 1; | |
| min-height: 200px; | |
| max-height: 60vh; | |
| padding: 20px 24px 16px; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| background: radial-gradient(ellipse at center, rgba(108, 143, 255, 0.04) 0%, transparent 70%); | |
| overflow: hidden; | |
| } | |
| canvas { | |
| width: 100%; | |
| max-width: 900px; | |
| height: auto; | |
| max-height: 100%; | |
| border-radius: var(--radius-sm); | |
| } | |
| /* Live metrics row */ | |
| .live-metrics-row { | |
| flex-shrink: 0; | |
| padding: 0 24px 12px; | |
| } | |
| .pipeline-header { | |
| display: flex; | |
| align-items: baseline; | |
| gap: 10px; | |
| margin-bottom: 10px; | |
| } | |
| .pipeline-title { | |
| font-size: 11px; | |
| font-weight: 700; | |
| text-transform: uppercase; | |
| letter-spacing: 0.08em; | |
| color: var(--text-muted); | |
| } | |
| .pipeline-subtitle { | |
| font-size: 11px; | |
| color: var(--text-muted); | |
| } | |
| .pipeline-phase-label { | |
| --phase-color: var(--accent-light); | |
| --phase-line: rgba(108, 143, 255, 0.2); | |
| color: var(--phase-color); | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| margin: 2px 0 8px; | |
| } | |
| .pipeline-phase-label::before { | |
| content: ""; | |
| width: 10px; | |
| height: 2px; | |
| border-radius: 999px; | |
| background: var(--phase-color); | |
| } | |
| .pipeline-phase-label::after { | |
| content: ""; | |
| flex: 1; | |
| height: 1px; | |
| background: var(--phase-line); | |
| } | |
| .pipeline-phase-label .text { | |
| font-size: 10px; | |
| font-weight: 700; | |
| text-transform: uppercase; | |
| letter-spacing: 0.08em; | |
| } | |
| .pipeline-phase-label.primary { | |
| --phase-color: var(--accent-light); | |
| --phase-line: rgba(108, 143, 255, 0.22); | |
| } | |
| .pipeline-phase-label.tool { | |
| --phase-color: var(--tool-accent); | |
| --phase-line: rgba(245, 158, 11, 0.22); | |
| } | |
| .pipeline-phase-label.post { | |
| --phase-color: var(--post-accent); | |
| --phase-line: rgba(52, 211, 153, 0.22); | |
| } | |
| .pipeline-stage-row { | |
| display: grid; | |
| grid-template-columns: repeat(3, minmax(0, 1fr)); | |
| gap: 10px; | |
| --connector-a: rgba(108, 143, 255, 0.18); | |
| --connector-b: rgba(108, 143, 255, 0.5); | |
| --connector-c: rgba(167, 139, 250, 0.58); | |
| --connector-shadow: rgba(108, 143, 255, 0.18); | |
| --connector-arrow: rgba(167, 139, 250, 0.72); | |
| } | |
| .pipeline-stage-row.tool-phase-row { | |
| grid-template-columns: minmax(0, 1fr); | |
| } | |
| .pipeline-stage-row.post-tool-row { | |
| grid-template-columns: repeat(2, minmax(0, 1fr)); | |
| --connector-a: rgba(52, 211, 153, 0.2); | |
| --connector-b: rgba(52, 211, 153, 0.6); | |
| --connector-c: rgba(16, 185, 129, 0.56); | |
| --connector-shadow: rgba(52, 211, 153, 0.2); | |
| --connector-arrow: rgba(52, 211, 153, 0.76); | |
| } | |
| .pipeline-stage-row.no-connectors .stage-card::after, | |
| .pipeline-stage-row.no-connectors .stage-card::before { | |
| display: none ; | |
| } | |
| .stage-card[hidden] { | |
| display: none; | |
| } | |
| .metric-card { | |
| background: var(--bg-card); | |
| border: 1px solid var(--border); | |
| border-radius: var(--radius-sm); | |
| padding: 10px 12px; | |
| } | |
| .stage-card { | |
| position: relative; | |
| } | |
| .stage-card::after { | |
| content: ""; | |
| position: absolute; | |
| top: 50%; | |
| left: calc(100% - 6px); | |
| width: 16px; | |
| height: 1px; | |
| border-radius: 999px; | |
| background: linear-gradient( | |
| 90deg, | |
| var(--connector-a) 0%, | |
| var(--connector-b) 60%, | |
| var(--connector-c) 100% | |
| ); | |
| box-shadow: 0 0 5px var(--connector-shadow); | |
| transform: translateY(-50%); | |
| pointer-events: none; | |
| z-index: 1; | |
| } | |
| .stage-card::before { | |
| content: ""; | |
| position: absolute; | |
| top: 50%; | |
| left: calc(100% + 10px); | |
| width: 6px; | |
| height: 6px; | |
| background: var(--connector-arrow); | |
| clip-path: polygon(0 0, 100% 50%, 0 100%); | |
| box-shadow: 0 0 6px var(--connector-shadow); | |
| transform: translateY(-50%); | |
| pointer-events: none; | |
| z-index: 1; | |
| } | |
| .stage-card:last-child::after { | |
| display: none; | |
| } | |
| .stage-card:last-child::before { | |
| display: none; | |
| } | |
| .metric-card-top { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| margin-bottom: 8px; | |
| } | |
| .metric-step { | |
| width: 22px; | |
| height: 22px; | |
| border-radius: 6px; | |
| display: inline-grid; | |
| place-items: center; | |
| font-size: 11px; | |
| font-weight: 700; | |
| color: var(--accent-light); | |
| background: var(--accent-glow); | |
| border: 1px solid rgba(108, 143, 255, 0.35); | |
| flex-shrink: 0; | |
| } | |
| .metric-card.theme-primary { | |
| border-color: rgba(108, 143, 255, 0.24); | |
| } | |
| .metric-card.theme-tool { | |
| background: linear-gradient(135deg, rgba(245, 158, 11, 0.08), rgba(245, 158, 11, 0.03)); | |
| border-color: rgba(245, 158, 11, 0.3); | |
| } | |
| .metric-card.theme-post { | |
| background: linear-gradient(135deg, rgba(52, 211, 153, 0.1), rgba(16, 185, 129, 0.03)); | |
| border-color: rgba(52, 211, 153, 0.3); | |
| } | |
| .metric-card.theme-tool .metric-step { | |
| color: var(--tool-accent); | |
| background: rgba(245, 158, 11, 0.18); | |
| border-color: rgba(245, 158, 11, 0.45); | |
| } | |
| .metric-card.theme-post .metric-step { | |
| color: var(--post-accent); | |
| background: rgba(52, 211, 153, 0.16); | |
| border-color: rgba(52, 211, 153, 0.45); | |
| } | |
| .metric-card.theme-tool .metric-card-value { | |
| color: var(--tool-accent); | |
| } | |
| .metric-card.theme-post .metric-card-value { | |
| color: var(--post-accent); | |
| } | |
| .metric-card.theme-tool .metric-card-fill { | |
| background: linear-gradient(135deg, rgba(245, 158, 11, 0.95), rgba(245, 158, 11, 0.5)); | |
| } | |
| .metric-card.theme-post .metric-card-fill { | |
| background: linear-gradient(135deg, rgba(52, 211, 153, 0.95), rgba(16, 185, 129, 0.5)); | |
| } | |
| .metric-tech-wrap { | |
| display: inline-flex; | |
| align-items: center; | |
| gap: 6px; | |
| } | |
| .metric-tech { | |
| font-size: 10px; | |
| color: var(--text-secondary); | |
| } | |
| .metric-card-human { | |
| font-size: 13px; | |
| font-weight: 600; | |
| margin-bottom: 2px; | |
| } | |
| .metric-card-desc { | |
| font-size: 11px; | |
| color: var(--text-muted); | |
| margin-bottom: 8px; | |
| min-height: 28px; | |
| } | |
| .metric-card-value-row { | |
| display: flex; | |
| align-items: baseline; | |
| gap: 8px; | |
| margin-bottom: 8px; | |
| } | |
| .metric-card-value { | |
| font-size: 22px; | |
| font-weight: 700; | |
| line-height: 1; | |
| font-variant-numeric: tabular-nums; | |
| color: var(--accent-light); | |
| transition: color 0.3s; | |
| } | |
| .metric-card-value.loading { | |
| font-size: 13px; | |
| font-weight: 600; | |
| line-height: 1.2; | |
| color: var(--text-muted); | |
| } | |
| .metric-card-value.warning { color: var(--warning); } | |
| .metric-card-value.critical { color: var(--critical); } | |
| .metric-card-avg { | |
| font-size: 10px; | |
| color: var(--text-secondary); | |
| } | |
| .metric-card-track { | |
| height: 4px; | |
| background: var(--border); | |
| border-radius: 999px; | |
| overflow: hidden; | |
| } | |
| .metric-card-fill { | |
| height: 100%; | |
| border-radius: 999px; | |
| background: var(--accent-gradient); | |
| transition: width 0.5s ease, background 0.3s; | |
| } | |
| .metric-card-fill.warning { background: var(--warning); } | |
| .metric-card-fill.critical { background: var(--critical); } | |
| .pipeline-total-card { | |
| margin-top: 10px; | |
| background: linear-gradient(135deg, rgba(108, 143, 255, 0.08), rgba(167, 139, 250, 0.06)); | |
| border: 1px solid rgba(108, 143, 255, 0.28); | |
| border-radius: var(--radius-sm); | |
| padding: 12px 14px; | |
| } | |
| .pipeline-total-top { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: flex-end; | |
| gap: 10px; | |
| margin-bottom: 10px; | |
| } | |
| .pipeline-total-label { | |
| font-size: 14px; | |
| font-weight: 600; | |
| } | |
| .pipeline-total-tech { | |
| font-size: 10px; | |
| color: var(--text-secondary); | |
| display: inline-flex; | |
| align-items: center; | |
| gap: 6px; | |
| } | |
| .pipeline-total-value-wrap { | |
| display: flex; | |
| flex-direction: column; | |
| align-items: flex-end; | |
| gap: 2px; | |
| } | |
| .pipeline-total-value { | |
| font-size: 28px; | |
| } | |
| .pipeline-total-avg { | |
| font-size: 11px; | |
| color: var(--text-secondary); | |
| } | |
| .pipeline-total-card.post-tool { | |
| background: linear-gradient(135deg, rgba(52, 211, 153, 0.12), rgba(16, 185, 129, 0.04)); | |
| border-color: rgba(52, 211, 153, 0.3); | |
| } | |
| .pipeline-total-card.post-tool .pipeline-total-value { | |
| color: var(--post-accent); | |
| } | |
| .pipeline-total-card.neutral { | |
| background: linear-gradient(135deg, rgba(141, 149, 171, 0.09), rgba(74, 85, 110, 0.04)); | |
| border-color: rgba(141, 149, 171, 0.3); | |
| } | |
| .pipeline-total-card.neutral .pipeline-total-value { | |
| color: var(--text-secondary); | |
| } | |
| .tool-phase-shell { | |
| margin-top: 16px; | |
| } | |
| .tool-phase-toggle { | |
| width: 100%; | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| border: 1px solid rgba(245, 158, 11, 0.24); | |
| background: rgba(245, 158, 11, 0.06); | |
| color: var(--tool-accent); | |
| border-radius: 8px; | |
| padding: 8px 10px; | |
| cursor: pointer; | |
| transition: border-color 0.2s, background 0.2s; | |
| } | |
| .tool-phase-toggle:hover { | |
| border-color: rgba(245, 158, 11, 0.42); | |
| background: rgba(245, 158, 11, 0.1); | |
| } | |
| .tool-phase-chevron { | |
| width: 14px; | |
| height: 14px; | |
| flex-shrink: 0; | |
| transition: transform 0.25s ease; | |
| } | |
| .tool-phase-toggle[aria-expanded="true"] .tool-phase-chevron { | |
| transform: rotate(180deg); | |
| } | |
| .tool-phase-title { | |
| font-size: 10px; | |
| font-weight: 700; | |
| text-transform: uppercase; | |
| letter-spacing: 0.08em; | |
| } | |
| .tool-phase-count { | |
| font-size: 11px; | |
| color: var(--text-muted); | |
| } | |
| .tool-phase-toggle-spacer { | |
| flex: 1; | |
| } | |
| .tool-phase-content { | |
| margin-top: 12px; | |
| animation: fade-in-slide 0.2s ease; | |
| } | |
| .tool-phase-content[hidden] { | |
| display: none; | |
| } | |
| .tool-list { | |
| margin: 6px 0 8px; | |
| display: flex; | |
| flex-direction: column; | |
| gap: 4px; | |
| } | |
| .tool-list-item { | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| gap: 8px; | |
| font-size: 11px; | |
| color: var(--text-secondary); | |
| } | |
| .tool-list-name { | |
| color: rgba(245, 158, 11, 0.96); | |
| } | |
| .tool-list-duration { | |
| font-variant-numeric: tabular-nums; | |
| } | |
| .tool-list-empty { | |
| font-size: 11px; | |
| color: var(--text-muted); | |
| } | |
| .tool-tag { | |
| display: inline-flex; | |
| align-items: center; | |
| border-radius: 5px; | |
| background: rgba(245, 158, 11, 0.16); | |
| color: var(--tool-accent); | |
| font-size: 9px; | |
| font-weight: 700; | |
| letter-spacing: 0.05em; | |
| padding: 2px 6px; | |
| text-transform: uppercase; | |
| } | |
| @keyframes fade-in-slide { | |
| from { | |
| opacity: 0; | |
| transform: translateY(-6px); | |
| } | |
| to { | |
| opacity: 1; | |
| transform: translateY(0); | |
| } | |
| } | |
| .tooltip { | |
| position: relative; | |
| display: inline-flex; | |
| align-items: center; | |
| } | |
| .tooltip-trigger { | |
| width: 16px; | |
| height: 16px; | |
| padding: 0; | |
| border-radius: 50%; | |
| border: 1px solid var(--border-light); | |
| background: rgba(255, 255, 255, 0.02); | |
| color: var(--text-secondary); | |
| cursor: pointer; | |
| font-size: 10px; | |
| line-height: 1; | |
| display: inline-grid; | |
| place-items: center; | |
| } | |
| .tooltip-trigger:hover { | |
| border-color: rgba(108, 143, 255, 0.4); | |
| color: var(--text-primary); | |
| } | |
| .tooltip-content { | |
| position: absolute; | |
| right: 0; | |
| bottom: calc(100% + 8px); | |
| width: 220px; | |
| padding: 8px; | |
| border-radius: 8px; | |
| border: 1px solid var(--border-light); | |
| background: #111725; | |
| color: var(--text-primary); | |
| font-size: 11px; | |
| line-height: 1.35; | |
| opacity: 0; | |
| pointer-events: none; | |
| transform: translateY(4px); | |
| transition: opacity 0.2s ease, transform 0.2s ease; | |
| z-index: 20; | |
| white-space: normal; | |
| overflow-wrap: anywhere; | |
| } | |
| .tooltip.tooltip-right .tooltip-content { | |
| left: calc(100% + 8px); | |
| right: auto; | |
| top: 50%; | |
| bottom: auto; | |
| transform: translateY(-50%) translateX(4px); | |
| } | |
| .tooltip:hover .tooltip-content, | |
| .tooltip:focus-within .tooltip-content { | |
| opacity: 1; | |
| pointer-events: auto; | |
| transform: translateY(0); | |
| } | |
| .tooltip.tooltip-right:hover .tooltip-content, | |
| .tooltip.tooltip-right:focus-within .tooltip-content { | |
| transform: translateY(-50%) translateX(0); | |
| } | |
| @media (max-width: 1100px) { | |
| .header { | |
| flex-wrap: wrap; | |
| align-items: flex-start; | |
| gap: 10px; | |
| } | |
| .header-right { | |
| width: 100%; | |
| justify-content: space-between; | |
| flex-wrap: wrap; | |
| } | |
| .trace-dropdown { | |
| flex: 1; | |
| min-width: 280px; | |
| } | |
| .pipeline-stage-row { | |
| grid-template-columns: repeat(2, minmax(0, 1fr)); | |
| } | |
| .pipeline-stage-row.tool-phase-row { | |
| grid-template-columns: 1fr; | |
| } | |
| .pipeline-stage-row.post-tool-row { | |
| grid-template-columns: repeat(2, minmax(0, 1fr)); | |
| } | |
| .stage-card::after { | |
| display: none; | |
| } | |
| .stage-card::before { | |
| display: none; | |
| } | |
| .stage-card:nth-child(odd)::after { | |
| display: block; | |
| } | |
| .stage-card:nth-child(odd)::before { | |
| display: block; | |
| } | |
| .stage-card:last-child::after { | |
| display: none; | |
| } | |
| .stage-card:last-child::before { | |
| display: none; | |
| } | |
| } | |
| @media (max-width: 700px) { | |
| .header { | |
| padding: 12px 14px; | |
| } | |
| .header-left { | |
| width: 100%; | |
| justify-content: space-between; | |
| } | |
| .header-right { | |
| width: 100%; | |
| flex-direction: column; | |
| align-items: stretch; | |
| gap: 8px; | |
| } | |
| .trace-dropdown { | |
| width: 100%; | |
| min-width: 0; | |
| } | |
| .trace-dropdown-menu { | |
| left: 0; | |
| right: 0; | |
| width: 100%; | |
| } | |
| .controls { | |
| width: 100%; | |
| } | |
| .controls button { | |
| flex: 1; | |
| justify-content: center; | |
| } | |
| .waveform-section { | |
| min-height: 160px; | |
| padding: 14px 14px 10px; | |
| } | |
| .live-metrics-row { | |
| padding: 0 14px 10px; | |
| } | |
| .pipeline-stage-row { | |
| grid-template-columns: 1fr; | |
| } | |
| .pipeline-stage-row.post-tool-row { | |
| grid-template-columns: 1fr; | |
| } | |
| .pipeline-total-top { | |
| align-items: flex-start; | |
| flex-direction: column; | |
| } | |
| .pipeline-total-value-wrap { | |
| align-items: flex-start; | |
| } | |
| .stage-card::after { | |
| display: none ; | |
| } | |
| .stage-card::before { | |
| display: none ; | |
| } | |
| .tooltip.tooltip-right .tooltip-content { | |
| left: auto; | |
| right: 0; | |
| top: auto; | |
| bottom: calc(100% + 8px); | |
| transform: translateY(4px); | |
| } | |
| .tooltip.tooltip-right:hover .tooltip-content, | |
| .tooltip.tooltip-right:focus-within .tooltip-content { | |
| transform: translateY(0); | |
| } | |
| } | |
| /* Footer note */ | |
| .footer-note { | |
| flex-shrink: 0; | |
| padding: 8px 24px; | |
| border-top: 1px solid var(--border); | |
| font-size: 10px; | |
| color: var(--text-muted); | |
| text-align: center; | |
| background: var(--bg-secondary); | |
| } | |
| .footer-note a { | |
| color: var(--accent); | |
| text-decoration: none; | |
| transition: color 0.2s; | |
| } | |
| .footer-note a:hover { | |
| color: var(--accent-light); | |
| text-decoration: underline; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <!-- Header --> | |
| <div class="header"> | |
| <div class="header-left"> | |
| <div class="logo">Open Voice Agent</div> | |
| <div class="status-badge"> | |
| <div class="status-dot" id="status-dot"></div> | |
| <span id="status">Idle</span> | |
| </div> | |
| </div> | |
| <div class="header-right"> | |
| <div class="trace-dropdown" id="trace-dropdown"> | |
| <button | |
| type="button" | |
| class="trace-dropdown-toggle" | |
| id="trace-dropdown-toggle" | |
| aria-expanded="false" | |
| aria-controls="trace-dropdown-menu" | |
| disabled | |
| > | |
| <span class="trace-dropdown-label" id="trace-dropdown-label">Connect to initialize trace links.</span> | |
| <svg class="trace-dropdown-chevron" viewBox="0 0 16 16" fill="none" aria-hidden="true"> | |
| <path d="M4 6l4 4 4-4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /> | |
| </svg> | |
| </button> | |
| <div class="trace-dropdown-menu" id="trace-dropdown-menu" hidden> | |
| <div class="trace-dropdown-empty" id="trace-dropdown-empty">No traces yet.</div> | |
| <div class="trace-dropdown-list" id="trace-dropdown-list"></div> | |
| </div> | |
| </div> | |
| <div class="controls"> | |
| <button id="connect" class="btn-primary"> | |
| <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14"/><path d="m12 5 7 7-7 7"/></svg> | |
| Connect | |
| </button> | |
| <button id="disconnect" class="btn-danger" disabled> | |
| <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/></svg> | |
| Disconnect | |
| </button> | |
| <button id="mute" disabled> | |
| <svg id="mic-icon" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z"/><path d="M19 10v2a7 7 0 0 1-14 0v-2"/><line x1="12" x2="12" y1="19" y2="22"/></svg> | |
| Mute | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Main single-column layout --> | |
| <div class="main"> | |
| <!-- Waveform hero --> | |
| <div class="waveform-section"> | |
| <canvas id="wave" width="900" height="200"></canvas> | |
| </div> | |
| <!-- Live metric cards --> | |
| <div class="live-metrics-row"> | |
| <div class="pipeline-header"> | |
| <span class="pipeline-title">Voice Agent Pipeline</span> | |
| <span class="pipeline-subtitle">Core stages from end-of-speech to first assistant audio</span> | |
| </div> | |
| <div class="pipeline-phase-label primary"> | |
| <span class="text">Initial Response</span> | |
| </div> | |
| <div class="pipeline-stage-row" id="pipeline-stage-row"> | |
| <div class="metric-card stage-card theme-primary"> | |
| <div class="metric-card-top"> | |
| <span class="metric-step">1</span> | |
| <div class="metric-tech-wrap"> | |
| <span class="metric-tech">EOU Delay</span> | |
| <div class="tooltip"> | |
| <button type="button" class="tooltip-trigger" aria-label="What is EOU Delay?">i</button> | |
| <span class="tooltip-content" role="tooltip">Seconds from detected end of speech (VAD) until your turn is finalized, including transcription delay.</span> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="metric-card-human">Turn Detection</div> | |
| <div class="metric-card-desc">Finalizing when your turn is complete</div> | |
| <div class="metric-card-value-row"> | |
| <span class="metric-card-value" id="live-eou">--</span> | |
| <span class="metric-card-avg" id="live-eou-avg"></span> | |
| </div> | |
| <div class="metric-card-track"> | |
| <div class="metric-card-fill" id="live-eou-bar" style="width: 0%"></div> | |
| </div> | |
| </div> | |
| <div class="metric-card stage-card theme-primary"> | |
| <div class="metric-card-top"> | |
| <span class="metric-step">2</span> | |
| <div class="metric-tech-wrap"> | |
| <span class="metric-tech">LLM TTFT</span> | |
| <div class="tooltip"> | |
| <button type="button" class="tooltip-trigger" aria-label="What is LLM TTFT?">i</button> | |
| <span class="tooltip-content" role="tooltip">Thinking time: from LLM request start to the first generated token.</span> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="metric-card-human">Thinking</div> | |
| <div class="metric-card-desc">Generating the first token</div> | |
| <div class="metric-card-value-row"> | |
| <span class="metric-card-value" id="live-llm-ttft">--</span> | |
| <span class="metric-card-avg" id="live-llm-ttft-avg"></span> | |
| </div> | |
| <div class="metric-card-track"> | |
| <div class="metric-card-fill" id="live-llm-ttft-bar" style="width: 0%"></div> | |
| </div> | |
| </div> | |
| <div class="metric-card stage-card theme-primary"> | |
| <div class="metric-card-top"> | |
| <span class="metric-step">3</span> | |
| <div class="metric-tech-wrap"> | |
| <span class="metric-tech">TTS TTFB</span> | |
| <div class="tooltip"> | |
| <button type="button" class="tooltip-trigger" aria-label="What is Voice Generation?">i</button> | |
| <span class="tooltip-content" role="tooltip">Voice generation startup only: time from TTS request start to the first audio chunk (TTFB).</span> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="metric-card-human">Voice Generation</div> | |
| <div class="metric-card-desc">Starting audio synthesis</div> | |
| <div class="metric-card-value-row"> | |
| <span class="metric-card-value" id="live-voice-generation">--</span> | |
| <span class="metric-card-avg" id="live-voice-generation-avg"></span> | |
| </div> | |
| <div class="metric-card-track"> | |
| <div class="metric-card-fill" id="live-voice-generation-bar" style="width: 0%"></div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="pipeline-total-card" id="first-audio-card"> | |
| <div class="pipeline-total-top"> | |
| <div> | |
| <div class="pipeline-total-label" id="live-total-label">Total Round-Trip</div> | |
| <div class="pipeline-total-tech"> | |
| <span id="live-total-tech-text">End-to-End Latency</span> | |
| <div class="tooltip tooltip-right"> | |
| <button type="button" class="tooltip-trigger" aria-label="What is End-to-End Latency?">i</button> | |
| <span class="tooltip-content" role="tooltip" id="live-total-tooltip-text">Sum of the three stages above: EOU delay + Thinking (LLM TTFT) + Voice Generation (TTS TTFB).</span> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="pipeline-total-value-wrap"> | |
| <span class="metric-card-value pipeline-total-value" id="live-total">--</span> | |
| <span class="pipeline-total-avg" id="live-total-avg"></span> | |
| </div> | |
| </div> | |
| <div class="metric-card-track"> | |
| <div class="metric-card-fill" id="live-total-bar" style="width: 0%"></div> | |
| </div> | |
| </div> | |
| <div class="tool-phase-shell" id="tool-phase-shell" hidden> | |
| <button | |
| type="button" | |
| class="tool-phase-toggle" | |
| id="tool-phase-toggle" | |
| aria-expanded="true" | |
| aria-controls="tool-phase-content" | |
| > | |
| <svg class="tool-phase-chevron" id="tool-phase-chevron" viewBox="0 0 16 16" fill="none" aria-hidden="true"> | |
| <path d="M4 6l4 4 4-4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /> | |
| </svg> | |
| <span class="tool-phase-title">Tool Execution</span> | |
| <span class="tool-phase-count" id="tool-phase-count">0 tools called</span> | |
| <span class="tool-phase-toggle-spacer"></span> | |
| </button> | |
| <div class="tool-phase-content" id="tool-phase-content"> | |
| <div class="pipeline-stage-row tool-phase-row no-connectors"> | |
| <div class="metric-card stage-card theme-tool" id="live-tool-card"> | |
| <div class="metric-card-top"> | |
| <span class="metric-step"> | |
| <svg width="12" height="12" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true"> | |
| <path d="M12.1 3.9c-.5-.5-1.2-.7-1.9-.6l-1.5-1.5c-.2-.2-.5-.2-.7 0s-.2.5 0 .7l1.3 1.3c-.3.8-.1 1.7.5 2.3.5.5 1.2.7 1.9.6l2.5 2.5c.2.2.5.2.7 0s.2-.5 0-.7l-2.5-2.5c.1-.7-.1-1.4-.6-1.9zM3.5 12.5l3-3L5 8l-3 3c-.3.3-.3.7 0 1l.5.5c.3.3.7.3 1 0zM13.4 11.6l-5-5-.7.7 5 5c.2.2.5.2.7 0s.2-.5 0-.7zM4.1 2.6L2 4.7l1.1 1.1 1.1-.6 1.1 1.1-.6 1.1 1.1 1.1 2.1-2.1-3.8-3.8z" /> | |
| </svg> | |
| </span> | |
| <div class="metric-tech-wrap"> | |
| <span class="metric-tech">Tool Calls</span> | |
| <span class="tool-tag" id="live-tool-tag">Tool Call</span> | |
| </div> | |
| </div> | |
| <div class="metric-card-human" id="live-tool-title">Tool execution</div> | |
| <div class="metric-card-desc" id="live-tool-desc">Executing external function calls</div> | |
| <div class="tool-list" id="live-tool-list"> | |
| <div class="tool-list-empty">No tools executed.</div> | |
| </div> | |
| <div class="metric-card-value-row"> | |
| <span class="metric-card-value" id="live-tool-total">--</span> | |
| </div> | |
| <div class="metric-card-track"> | |
| <div class="metric-card-fill" id="live-tool-total-bar" style="width: 0%"></div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="pipeline-phase-label post"> | |
| <span class="text">Post-Tool Response</span> | |
| </div> | |
| <div class="pipeline-stage-row post-tool-row" id="post-tool-stage-row"> | |
| <div class="metric-card stage-card theme-post"> | |
| <div class="metric-card-top"> | |
| <span class="metric-step">5</span> | |
| <div class="metric-tech-wrap"> | |
| <span class="metric-tech">LLM TTFT</span> | |
| </div> | |
| </div> | |
| <div class="metric-card-human">Thinking</div> | |
| <div class="metric-card-desc">Generating the first token after tools</div> | |
| <div class="metric-card-value-row"> | |
| <span class="metric-card-value" id="live-post-llm-ttft">--</span> | |
| </div> | |
| <div class="metric-card-track"> | |
| <div class="metric-card-fill" id="live-post-llm-ttft-bar" style="width: 0%"></div> | |
| </div> | |
| </div> | |
| <div class="metric-card stage-card theme-post"> | |
| <div class="metric-card-top"> | |
| <span class="metric-step">6</span> | |
| <div class="metric-tech-wrap"> | |
| <span class="metric-tech">TTS TTFB</span> | |
| </div> | |
| </div> | |
| <div class="metric-card-human">Voice Generation</div> | |
| <div class="metric-card-desc">Starting post-tool audio synthesis</div> | |
| <div class="metric-card-value-row"> | |
| <span class="metric-card-value" id="live-post-voice-generation">--</span> | |
| </div> | |
| <div class="metric-card-track"> | |
| <div class="metric-card-fill" id="live-post-voice-generation-bar" style="width: 0%"></div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="pipeline-total-card post-tool" id="second-audio-card"> | |
| <div class="pipeline-total-top"> | |
| <div> | |
| <div class="pipeline-total-label">Second Audio</div> | |
| <div class="pipeline-total-tech"> | |
| <span id="live-second-audio-tech-text">Tool completion to post-tool assistant audio</span> | |
| <span class="tool-tag">Tool→Audio</span> | |
| </div> | |
| </div> | |
| <div class="pipeline-total-value-wrap"> | |
| <span class="metric-card-value pipeline-total-value" id="live-second-audio">--</span> | |
| </div> | |
| </div> | |
| <div class="metric-card-track"> | |
| <div class="metric-card-fill" id="live-second-audio-bar" style="width: 0%"></div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="pipeline-total-card neutral" id="total-turn-card"> | |
| <div class="pipeline-total-top"> | |
| <div> | |
| <div class="pipeline-total-label">Total Turn Duration</div> | |
| <div class="pipeline-total-tech"> | |
| <span>Complete turn including tool execution and post-tool response</span> | |
| </div> | |
| </div> | |
| <div class="pipeline-total-value-wrap"> | |
| <span class="metric-card-value pipeline-total-value" id="live-total-turn">--</span> | |
| </div> | |
| </div> | |
| <div class="metric-card-track"> | |
| <div class="metric-card-fill" id="live-total-turn-bar" style="width: 0%"></div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="footer-note"> | |
| {{FOOTER_POWERED_BY}} | |
| </div> | |
| </div> | |
| <audio id="remote-audio" autoplay></audio> | |
| <script src="https://unpkg.com/livekit-client/dist/livekit-client.umd.js"></script> | |
| <script> | |
| </script> | |
| <script> | |
| </script> | |
| </body> | |
| </html> | |