diff --git a/Web/apps/hyperloom/.env.development b/Web/apps/hyperloom/.env.development new file mode 100644 index 000000000..4ece81022 --- /dev/null +++ b/Web/apps/hyperloom/.env.development @@ -0,0 +1,3 @@ +VITE_CLAW_BASE_URL=/claw-api/v1 +VITE_MCP_TRACELENS_URL=/mcp/tracelens +VITE_MCP_GEAK_URL=/mcp/geak diff --git a/Web/apps/hyperloom/.env.example b/Web/apps/hyperloom/.env.example new file mode 100644 index 000000000..c98ee2ff4 --- /dev/null +++ b/Web/apps/hyperloom/.env.example @@ -0,0 +1,16 @@ +# HyperLoom Environment Variables +# Copy to .env.development.local for local overrides + +# Proxy targets (Vite dev server, not exposed to browser) +PROXY_API_TARGET=https://oci-slc.primus-safe.amd.com +PROXY_MCP_TARGET=https://oci-slc.primus-safe.amd.com + +# Frontend: Claw Service +VITE_CLAW_BASE_URL=/claw-api/v1 + +# Frontend: MCP Agents +VITE_MCP_TRACELENS_URL=/mcp/tracelens +VITE_MCP_GEAK_URL=/mcp/geak + +# GEAK Agent requires Bearer token (apply from SaFE platform) +VITE_MCP_GEAK_API_KEY= diff --git a/Web/apps/hyperloom/Dockerfile b/Web/apps/hyperloom/Dockerfile new file mode 100644 index 000000000..12d0a118c --- /dev/null +++ b/Web/apps/hyperloom/Dockerfile @@ -0,0 +1,12 @@ +FROM node:22-alpine AS builder +WORKDIR /app +COPY . . +RUN npm config set registry https://registry.npmjs.org/ \ + && npm install \ + && npm run build + +FROM nginx:1.25-alpine AS runtime +COPY --from=builder /app/dist /app/dist/ +COPY nginx.conf /etc/nginx/nginx.conf +EXPOSE 80 +CMD ["nginx"] diff --git a/Web/apps/hyperloom/index.html b/Web/apps/hyperloom/index.html new file mode 100644 index 000000000..3ede13350 --- /dev/null +++ b/Web/apps/hyperloom/index.html @@ -0,0 +1,14 @@ + + + + + + + HyperLoom — GPU Performance Intelligence + + + +
+ + + diff --git a/Web/apps/hyperloom/nginx.conf b/Web/apps/hyperloom/nginx.conf new file mode 100644 index 000000000..28a16d772 --- /dev/null +++ b/Web/apps/hyperloom/nginx.conf @@ -0,0 +1,105 @@ +daemon off; +worker_processes 1; + +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + sendfile on; + keepalive_timeout 65; + + large_client_header_buffers 4 32k; + + gzip on; + gzip_types text/plain application/javascript application/x-javascript text/css application/json; + gzip_min_length 1024; + + map $http_upgrade $connection_upgrade { + default upgrade; + '' close; + } + + server { + listen 80; + server_name _; + + root /app/dist/; + index index.html; + + # PrimusClaw SSE streaming + location ~* ^/claw-api/v1/chat/sessions/.+/messages { + proxy_pass http://primus-safe-apiserver.primus-safe:8088; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + proxy_buffering off; + proxy_cache off; + gzip off; + add_header Cache-Control "no-cache"; + add_header X-Accel-Buffering "no"; + chunked_transfer_encoding on; + + proxy_connect_timeout 75s; + proxy_read_timeout 3600s; + proxy_send_timeout 3600s; + } + + # PrimusClaw REST + location ^~ /claw-api/ { + proxy_pass http://primus-safe-apiserver.primus-safe:8088; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_connect_timeout 30s; + proxy_read_timeout 300s; + proxy_send_timeout 300s; + } + + # Tools service + location ^~ /tools/ { + proxy_pass http://primus-safe-apiserver.primus-safe:8088; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + # SaFE API for auth verification + location /api/v1/ { + proxy_pass http://primus-safe-apiserver.primus-safe:8088; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Cookie "Token=$cookie_Token; userId=$cookie_userId; userType=$cookie_userType"; + proxy_connect_timeout 30s; + proxy_read_timeout 60s; + proxy_send_timeout 60s; + } + + # HyperLoom SPA + location /hyperloom { + alias /app/dist; + try_files $uri $uri/ /hyperloom/index.html; + + location ~ \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot|mp4|webm)$ { + expires 7d; + access_log off; + } + } + + location = / { + return 301 /hyperloom/; + } + } +} diff --git a/Web/apps/hyperloom/package.json b/Web/apps/hyperloom/package.json new file mode 100644 index 000000000..b78f6277c --- /dev/null +++ b/Web/apps/hyperloom/package.json @@ -0,0 +1,36 @@ +{ + "name": "@primus/hyperloom", + "version": "0.0.1", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview", + "test": "vitest run", + "test:watch": "vitest" + }, + "dependencies": { + "@element-plus/icons-vue": "^2.3.2", + "axios": "^1.10.0", + "chart.js": "^4.4.9", + "element-plus": "^2.10.4", + "marked": "^17.0.5", + "pinia": "^3.0.4", + "pinia-plugin-persistedstate": "^4.7.1", + "vue": "^3.5.17", + "vue-router": "^4.5.1" + }, + "devDependencies": { + "@iconify-json/ep": "^1.2.2", + "@types/node": "^22.15.32", + "@unocss/preset-icons": "^66.3.3", + "@unocss/preset-uno": "^66.3.3", + "@vitejs/plugin-vue": "^5.0.4", + "sass": "^1.89.2", + "typescript": "^5.8.3", + "unocss": "^66.3.3", + "vite": "^5.2.0", + "vitest": "^3.2.1" + } +} diff --git a/Web/apps/hyperloom/public/legacy/app.js b/Web/apps/hyperloom/public/legacy/app.js new file mode 100644 index 000000000..9311c3606 --- /dev/null +++ b/Web/apps/hyperloom/public/legacy/app.js @@ -0,0 +1,3972 @@ +/* ══════════════════════════════════════════════════════ + AMD PRISM — App Controller + ══════════════════════════════════════════════════════ */ + +(function initPRISM() { + + /* ──── Phase Tabs (old static — kept for non-analysis pages) ──── */ + document.querySelectorAll('.phase-tab:not([data-phase])').forEach(tab => { + tab.addEventListener('click', () => { + tab.parentElement.querySelectorAll('.phase-tab').forEach(t => t.classList.remove('active')); + tab.classList.add('active'); + }); + }); + + /* ──── Detail Tabs → Panel Switching ──── */ + document.querySelectorAll('.detail-tab').forEach(tab => { + tab.addEventListener('click', () => { + const tabsContainer = tab.parentElement; + tabsContainer.querySelectorAll('.detail-tab').forEach(t => t.classList.remove('active')); + tab.classList.add('active'); + + // Find the panels container (next sibling element) + const panelsContainer = tabsContainer.nextElementSibling; + if (panelsContainer && panelsContainer.classList.contains('detail-tab-panels')) { + panelsContainer.querySelectorAll('.detail-panel').forEach(p => p.classList.remove('active')); + const targetPanel = panelsContainer.querySelector(`[data-panel="${tab.textContent.trim()}"]`); + if (targetPanel) targetPanel.classList.add('active'); + } + }); + }); + + /* ──── Kernel Chips → Update Detail Content ──── */ + const KERNEL_DETAIL_DATA = { + 'fp4_moe_gemm_kernel': { + pre: { bw: '2487 GB/s', compute: '1200 TFLOPS', bwUtil: '31.0%', computeUtil: '12.0%', bottleneck: '🔴 Mem Bound' }, + post: { bw: '5134 GB/s', compute: '2487 TFLOPS', bwUtil: '64.2%', bwDelta: '+33.2%', computeUtil: '24.7%', computeDelta: '+12.7%', speedup: '2.4×' }, + roofline: { name: 'fp4_moe_gemm_kernel', peakBW: '8000 GB/s', peakCompute: '10040 TFLOPS', preOI: '2.8', postOI: '4.8', preBW: '31.0%', postBW: '64.2%', preTFLOPS: '1200', postTFLOPS: '2487' }, + trace: { before: '714.8ms', after: '543.3ms', hotspot: 'buffer_load_dword', delta: '-171.5ms (24.0%)' } + }, + 'fp4_dequant_gemm': { + pre: { bw: '2240 GB/s', compute: '2100 TFLOPS', bwUtil: '28.0%', computeUtil: '20.9%', bottleneck: '🔴 Mem Bound' }, + post: { bw: '4200 GB/s', compute: '4834 TFLOPS', bwUtil: '52.5%', bwDelta: '+24.5%', computeUtil: '48.2%', computeDelta: '+27.3%', speedup: '1.8×' }, + roofline: { name: 'fp4_dequant_gemm', peakBW: '8000 GB/s', peakCompute: '10040 TFLOPS', preOI: '2.2', postOI: '3.5', preBW: '28.0%', postBW: '52.5%', preTFLOPS: '2100', postTFLOPS: '4834' }, + trace: { before: '543.3ms', after: '517.3ms', hotspot: 'shared_mem_load', delta: '-26.0ms (4.8%)' } + }, + 'flash_attn_fwd_v2': { + pre: { bw: '2800 GB/s', compute: '2600 TFLOPS', bwUtil: '35.0%', computeUtil: '25.9%', bottleneck: '🔴 Mem Bound' }, + post: { bw: '3900 GB/s', compute: '5236 TFLOPS', bwUtil: '48.8%', bwDelta: '+13.8%', computeUtil: '52.1%', computeDelta: '+26.2%', speedup: '1.35×' }, + roofline: { name: 'flash_attn_fwd_v2', peakBW: '8000 GB/s', peakCompute: '10040 TFLOPS', preOI: '3.0', postOI: '4.2', preBW: '35.0%', postBW: '48.8%', preTFLOPS: '2600', postTFLOPS: '5236' }, + trace: { before: '517.3ms', after: '498.2ms', hotspot: 'qk_matmul', delta: '-19.1ms (3.7%)' } + } + }; + + function updateKernelDetail(kernelName) { + const data = KERNEL_DETAIL_DATA[kernelName]; + if (!data) return; + + // Update Pre-opt metrics + const preMetrics = document.querySelectorAll('#page-optimization .metrics-row.compact')[0]; + if (preMetrics) { + const cards = preMetrics.querySelectorAll('.metric-card'); + if (cards[0]) cards[0].querySelector('.metric-value').textContent = data.pre.bw; + if (cards[1]) cards[1].querySelector('.metric-value').textContent = data.pre.compute; + if (cards[2]) cards[2].querySelector('.metric-value').textContent = data.pre.bwUtil; + if (cards[3]) cards[3].querySelector('.metric-value').textContent = data.pre.computeUtil; + if (cards[4]) cards[4].querySelector('.metric-value').innerHTML = data.pre.bottleneck; + } + + // Update Post-opt metrics + const postMetrics = document.querySelectorAll('#page-optimization .metrics-row.compact')[1]; + if (postMetrics) { + const cards = postMetrics.querySelectorAll('.metric-card'); + if (cards[0]) cards[0].querySelector('.metric-value').textContent = data.post.bw; + if (cards[1]) cards[1].querySelector('.metric-value').textContent = data.post.compute; + if (cards[2]) cards[2].querySelector('.metric-value').innerHTML = `${data.post.bwUtil} ${data.post.bwDelta}`; + if (cards[3]) cards[3].querySelector('.metric-value').innerHTML = `${data.post.computeUtil} ${data.post.computeDelta}`; + if (cards[4]) cards[4].querySelector('.metric-value').textContent = data.post.speedup; + } + + // Update Roofline SVG texts + const rooflineSvg = document.querySelector('.roofline-svg'); + if (rooflineSvg) { + const rf = data.roofline; + const titleSpan = document.querySelector('.roofline-title'); + if (titleSpan) titleSpan.textContent = `Roofline Model — ${rf.name}`; + const subSpan = document.querySelector('.roofline-sub'); + if (subSpan) subSpan.textContent = `MI355X · Peak BW: ${rf.peakBW} · Peak Compute: ${rf.peakCompute}`; + + // Update SVG text elements (pre-opt and post-opt labels) + const textEls = rooflineSvg.querySelectorAll('text'); + textEls.forEach(t => { + if (t.textContent.includes('Before')) { + t.textContent = `Before (BW ${rf.preBW})`; + } else if (t.textContent.includes('After')) { + t.textContent = `After (BW ${rf.postBW})`; + } else if (t.textContent.includes('TFLOPS') && t.textContent.includes('OI=') && t.getAttribute('fill') === '#6b7280') { + if (t.previousElementSibling && t.previousElementSibling.getAttribute('fill') === '#e4002b') { + t.textContent = `${rf.preTFLOPS} TFLOPS · OI=${rf.preOI}`; + } else if (t.previousElementSibling && t.previousElementSibling.getAttribute('fill') === '#22c55e') { + t.textContent = `${rf.postTFLOPS} TFLOPS · OI=${rf.postOI}`; + } + } + }); + } + + // Update TraceDiff + const traceBefore = document.querySelector('.trace-label.before'); + const traceAfter = document.querySelector('.trace-label.after'); + const traceTimes = document.querySelectorAll('.trace-time'); + if (traceBefore && traceTimes[0]) { + traceTimes[0].textContent = data.trace.before; + } + if (traceAfter && traceTimes[1]) { + traceTimes[1].textContent = data.trace.after; + } + const traceDiffTitle = document.querySelector('.tracediff-title'); + if (traceDiffTitle) { + traceDiffTitle.textContent = `Trace Diff — ${kernelName}`; + } + const savingLegend = document.querySelector('.td-legend-item.saving'); + if (savingLegend) { + savingLegend.textContent = `Δ ${data.trace.delta}`; + } + const hotspotLegend = document.querySelector('.td-legend-item:first-child'); + if (hotspotLegend) { + const dot = hotspotLegend.querySelector('.td-dot'); + hotspotLegend.textContent = ''; + if (dot) hotspotLegend.appendChild(dot); + hotspotLegend.append(` Hotspot region (${data.trace.hotspot})`); + } + } + + document.querySelectorAll('.kernel-chip').forEach(chip => { + chip.addEventListener('click', () => { + chip.parentElement.querySelectorAll('.kernel-chip').forEach(c => c.classList.remove('active')); + chip.classList.add('active'); + updateKernelDetail(chip.textContent.trim()); + }); + }); + + /* ──── Collapsible Sections (GEAK Status) ──── */ + document.querySelectorAll('.collapsible-header').forEach(header => { + header.addEventListener('click', () => { + const section = header.closest('.collapsible-section'); + section.classList.toggle('collapsed'); + }); + }); + + /* ──── (Strategy cards now inline under each pipeline item — see inline-strategy-toggle-btn) ──── */ + + /* ══════ BENCHMARK DATA ══════ */ + // Realistic benchmark data based on InferenceX configs + // AMD MI355X data as baseline, B200 as reference, plus optimized curves + const BENCH_DATA = { + 'gpt-oss 120B': { + arch: { type: 'MoE', attention: 'Sink/Full GQA', size: '120B' }, + '1K / 1K': { + FP4: { + 'MI355X': { + SGLang: { + interactivity: [ + { x: 60, y: 9800 }, { x: 90, y: 7200 }, { x: 120, y: 5100 }, + { x: 150, y: 3500 }, { x: 180, y: 2400 }, { x: 220, y: 1500 }, + { x: 260, y: 900 }, { x: 300, y: 450 } + ], + latency: [ + { x: 3.2, y: 450 }, { x: 4.0, y: 900 }, { x: 5.0, y: 1500 }, + { x: 6.5, y: 2400 }, { x: 8.5, y: 3500 }, { x: 11, y: 5100 }, + { x: 14, y: 7200 }, { x: 18, y: 9800 } + ] + }, + vLLM: { + interactivity: [ + { x: 55, y: 8500 }, { x: 85, y: 6200 }, { x: 115, y: 4400 }, + { x: 145, y: 3000 }, { x: 175, y: 2100 }, { x: 210, y: 1300 }, + { x: 250, y: 750 }, { x: 290, y: 380 } + ], + latency: [ + { x: 3.5, y: 380 }, { x: 4.5, y: 750 }, { x: 5.5, y: 1300 }, + { x: 7.0, y: 2100 }, { x: 9.0, y: 3000 }, { x: 12, y: 4400 }, + { x: 15, y: 6200 }, { x: 19, y: 8500 } + ] + } + }, + 'MI355X_OPT': { + SGLang: { + interactivity: [ + { x: 65, y: 12800 }, { x: 95, y: 9500 }, { x: 125, y: 6800 }, + { x: 155, y: 4700 }, { x: 185, y: 3200 }, { x: 225, y: 2000 }, + { x: 265, y: 1200 }, { x: 305, y: 600 } + ], + latency: [ + { x: 2.8, y: 600 }, { x: 3.5, y: 1200 }, { x: 4.5, y: 2000 }, + { x: 5.8, y: 3200 }, { x: 7.5, y: 4700 }, { x: 10, y: 6800 }, + { x: 12.5, y: 9500 }, { x: 16, y: 12800 } + ] + }, + vLLM: { + interactivity: [ + { x: 60, y: 11200 }, { x: 90, y: 8300 }, { x: 120, y: 5900 }, + { x: 150, y: 4100 }, { x: 180, y: 2800 }, { x: 215, y: 1750 }, + { x: 255, y: 1050 }, { x: 295, y: 520 } + ], + latency: [ + { x: 3.0, y: 520 }, { x: 3.8, y: 1050 }, { x: 4.8, y: 1750 }, + { x: 6.2, y: 2800 }, { x: 8.0, y: 4100 }, { x: 10.5, y: 5900 }, + { x: 13.5, y: 8300 }, { x: 17, y: 11200 } + ] + } + }, + 'B200': { + SGLang: { + interactivity: [ + { x: 70, y: 14200 }, { x: 100, y: 10500 }, { x: 130, y: 7500 }, + { x: 160, y: 5200 }, { x: 190, y: 3500 }, { x: 230, y: 2200 }, + { x: 270, y: 1350 }, { x: 310, y: 680 } + ], + latency: [ + { x: 2.5, y: 680 }, { x: 3.2, y: 1350 }, { x: 4.0, y: 2200 }, + { x: 5.2, y: 3500 }, { x: 6.8, y: 5200 }, { x: 9.0, y: 7500 }, + { x: 11.5, y: 10500 }, { x: 15, y: 14200 } + ] + }, + vLLM: { + interactivity: [ + { x: 65, y: 13000 }, { x: 95, y: 9600 }, { x: 125, y: 6900 }, + { x: 155, y: 4800 }, { x: 185, y: 3200 }, { x: 220, y: 2000 }, + { x: 260, y: 1200 }, { x: 300, y: 600 } + ], + latency: [ + { x: 2.7, y: 600 }, { x: 3.5, y: 1200 }, { x: 4.3, y: 2000 }, + { x: 5.5, y: 3200 }, { x: 7.2, y: 4800 }, { x: 9.5, y: 6900 }, + { x: 12, y: 9600 }, { x: 15.5, y: 13000 } + ] + }, + TRT_LLM: { + interactivity: [ + { x: 75, y: 15800 }, { x: 105, y: 11600 }, { x: 135, y: 8300 }, + { x: 165, y: 5700 }, { x: 195, y: 3900 }, { x: 235, y: 2500 }, + { x: 275, y: 1500 }, { x: 315, y: 750 } + ], + latency: [ + { x: 2.2, y: 750 }, { x: 2.9, y: 1500 }, { x: 3.6, y: 2500 }, + { x: 4.8, y: 3900 }, { x: 6.2, y: 5700 }, { x: 8.5, y: 8300 }, + { x: 10.5, y: 11600 }, { x: 14, y: 15800 } + ] + } + } + }, + FP8: { + 'MI355X': { + SGLang: { + interactivity: [ + { x: 55, y: 10600 }, { x: 85, y: 7800 }, { x: 115, y: 5500 }, + { x: 145, y: 3800 }, { x: 175, y: 2600 }, { x: 215, y: 1650 }, + { x: 255, y: 980 }, { x: 295, y: 490 } + ], + latency: [ + { x: 3.0, y: 490 }, { x: 3.8, y: 980 }, { x: 4.7, y: 1650 }, + { x: 6.0, y: 2600 }, { x: 8.0, y: 3800 }, { x: 10.5, y: 5500 }, + { x: 13.5, y: 7800 }, { x: 17, y: 10600 } + ] + }, + vLLM: { + interactivity: [ + { x: 50, y: 9200 }, { x: 80, y: 6700 }, { x: 110, y: 4800 }, + { x: 140, y: 3300 }, { x: 170, y: 2300 }, { x: 205, y: 1420 }, + { x: 245, y: 820 }, { x: 285, y: 410 } + ], + latency: [ + { x: 3.3, y: 410 }, { x: 4.2, y: 820 }, { x: 5.2, y: 1420 }, + { x: 6.6, y: 2300 }, { x: 8.5, y: 3300 }, { x: 11.5, y: 4800 }, + { x: 14.5, y: 6700 }, { x: 18, y: 9200 } + ] + } + }, + 'MI355X_OPT': { + SGLang: { + interactivity: [ + { x: 60, y: 13800 }, { x: 90, y: 10200 }, { x: 120, y: 7300 }, + { x: 150, y: 5100 }, { x: 180, y: 3500 }, { x: 220, y: 2200 }, + { x: 260, y: 1300 }, { x: 300, y: 650 } + ], + latency: [ + { x: 2.6, y: 650 }, { x: 3.3, y: 1300 }, { x: 4.2, y: 2200 }, + { x: 5.4, y: 3500 }, { x: 7.0, y: 5100 }, { x: 9.5, y: 7300 }, + { x: 12, y: 10200 }, { x: 15, y: 13800 } + ] + }, + vLLM: { + interactivity: [ + { x: 55, y: 12100 }, { x: 85, y: 9000 }, { x: 115, y: 6400 }, + { x: 145, y: 4500 }, { x: 175, y: 3050 }, { x: 210, y: 1900 }, + { x: 250, y: 1140 }, { x: 290, y: 570 } + ], + latency: [ + { x: 2.8, y: 570 }, { x: 3.5, y: 1140 }, { x: 4.5, y: 1900 }, + { x: 5.8, y: 3050 }, { x: 7.5, y: 4500 }, { x: 10, y: 6400 }, + { x: 13, y: 9000 }, { x: 16, y: 12100 } + ] + } + }, + 'B200': { + SGLang: { + interactivity: [ + { x: 70, y: 14200 }, { x: 100, y: 10500 }, { x: 130, y: 7500 }, + { x: 160, y: 5200 }, { x: 190, y: 3500 }, { x: 230, y: 2200 }, + { x: 270, y: 1350 }, { x: 310, y: 680 } + ], + latency: [ + { x: 2.5, y: 680 }, { x: 3.2, y: 1350 }, { x: 4.0, y: 2200 }, + { x: 5.2, y: 3500 }, { x: 6.8, y: 5200 }, { x: 9.0, y: 7500 }, + { x: 11.5, y: 10500 }, { x: 15, y: 14200 } + ] + }, + vLLM: { + interactivity: [ + { x: 65, y: 13000 }, { x: 95, y: 9600 }, { x: 125, y: 6900 }, + { x: 155, y: 4800 }, { x: 185, y: 3200 }, { x: 220, y: 2000 }, + { x: 260, y: 1200 }, { x: 300, y: 600 } + ], + latency: [ + { x: 2.7, y: 600 }, { x: 3.5, y: 1200 }, { x: 4.3, y: 2000 }, + { x: 5.5, y: 3200 }, { x: 7.2, y: 4800 }, { x: 9.5, y: 6900 }, + { x: 12, y: 9600 }, { x: 15.5, y: 13000 } + ] + }, + TRT_LLM: { + interactivity: [ + { x: 75, y: 15800 }, { x: 105, y: 11600 }, { x: 135, y: 8300 }, + { x: 165, y: 5700 }, { x: 195, y: 3900 }, { x: 235, y: 2500 }, + { x: 275, y: 1500 }, { x: 315, y: 750 } + ], + latency: [ + { x: 2.2, y: 750 }, { x: 2.9, y: 1500 }, { x: 3.6, y: 2500 }, + { x: 4.8, y: 3900 }, { x: 6.2, y: 5700 }, { x: 8.5, y: 8300 }, + { x: 10.5, y: 11600 }, { x: 14, y: 15800 } + ] + } + } + } + }, + '8K / 1K': { + FP4: { + 'MI355X': { + SGLang: { + interactivity: [ + { x: 40, y: 6200 }, { x: 60, y: 4500 }, { x: 80, y: 3200 }, + { x: 100, y: 2200 }, { x: 130, y: 1500 }, { x: 160, y: 900 }, + { x: 190, y: 500 }, { x: 220, y: 250 } + ], + latency: [ + { x: 5.0, y: 250 }, { x: 6.5, y: 500 }, { x: 8.5, y: 900 }, + { x: 11, y: 1500 }, { x: 14, y: 2200 }, { x: 18, y: 3200 }, + { x: 22, y: 4500 }, { x: 28, y: 6200 } + ] + }, + vLLM: { + interactivity: [ + { x: 35, y: 5500 }, { x: 55, y: 4000 }, { x: 75, y: 2800 }, + { x: 95, y: 1950 }, { x: 125, y: 1300 }, { x: 155, y: 780 }, + { x: 185, y: 430 }, { x: 215, y: 220 } + ], + latency: [ + { x: 5.5, y: 220 }, { x: 7.0, y: 430 }, { x: 9.0, y: 780 }, + { x: 12, y: 1300 }, { x: 15, y: 1950 }, { x: 19, y: 2800 }, + { x: 23, y: 4000 }, { x: 30, y: 5500 } + ] + } + }, + 'MI355X_OPT': { + SGLang: { + interactivity: [ + { x: 45, y: 8100 }, { x: 65, y: 5900 }, { x: 85, y: 4200 }, + { x: 105, y: 2900 }, { x: 135, y: 2000 }, { x: 165, y: 1200 }, + { x: 195, y: 680 }, { x: 225, y: 340 } + ], + latency: [ + { x: 4.3, y: 340 }, { x: 5.5, y: 680 }, { x: 7.2, y: 1200 }, + { x: 9.5, y: 2000 }, { x: 12, y: 2900 }, { x: 16, y: 4200 }, + { x: 20, y: 5900 }, { x: 25, y: 8100 } + ] + }, + vLLM: { + interactivity: [ + { x: 40, y: 7200 }, { x: 60, y: 5200 }, { x: 80, y: 3700 }, + { x: 100, y: 2600 }, { x: 130, y: 1750 }, { x: 160, y: 1050 }, + { x: 190, y: 580 }, { x: 220, y: 290 } + ], + latency: [ + { x: 4.8, y: 290 }, { x: 6.0, y: 580 }, { x: 8.0, y: 1050 }, + { x: 10.5, y: 1750 }, { x: 13, y: 2600 }, { x: 17, y: 3700 }, + { x: 21, y: 5200 }, { x: 27, y: 7200 } + ] + } + }, + 'B200': { + SGLang: { + interactivity: [ + { x: 50, y: 9000 }, { x: 70, y: 6600 }, { x: 90, y: 4700 }, + { x: 110, y: 3300 }, { x: 140, y: 2200 }, { x: 170, y: 1350 }, + { x: 200, y: 760 }, { x: 230, y: 380 } + ], + latency: [ + { x: 3.8, y: 380 }, { x: 5.0, y: 760 }, { x: 6.5, y: 1350 }, + { x: 8.5, y: 2200 }, { x: 11, y: 3300 }, { x: 14, y: 4700 }, + { x: 18, y: 6600 }, { x: 23, y: 9000 } + ] + }, + vLLM: { + interactivity: [ + { x: 45, y: 8200 }, { x: 65, y: 6000 }, { x: 85, y: 4300 }, + { x: 105, y: 3000 }, { x: 135, y: 2000 }, { x: 165, y: 1200 }, + { x: 195, y: 680 }, { x: 225, y: 340 } + ], + latency: [ + { x: 4.2, y: 340 }, { x: 5.5, y: 680 }, { x: 7.2, y: 1200 }, + { x: 9.0, y: 2000 }, { x: 11.5, y: 3000 }, { x: 15, y: 4300 }, + { x: 19, y: 6000 }, { x: 24, y: 8200 } + ] + } + } + }, + FP8: { + 'MI355X': { + SGLang: { + interactivity: [ + { x: 38, y: 6700 }, { x: 58, y: 4900 }, { x: 78, y: 3500 }, + { x: 98, y: 2400 }, { x: 128, y: 1650 }, { x: 158, y: 990 }, + { x: 188, y: 550 }, { x: 218, y: 275 } + ], + latency: [ + { x: 4.7, y: 275 }, { x: 6.0, y: 550 }, { x: 8.0, y: 990 }, + { x: 10.5, y: 1650 }, { x: 13, y: 2400 }, { x: 17, y: 3500 }, + { x: 21, y: 4900 }, { x: 27, y: 6700 } + ] + }, + vLLM: { + interactivity: [ + { x: 33, y: 5900 }, { x: 53, y: 4300 }, { x: 73, y: 3050 }, + { x: 93, y: 2100 }, { x: 123, y: 1420 }, { x: 153, y: 860 }, + { x: 183, y: 470 }, { x: 213, y: 240 } + ], + latency: [ + { x: 5.2, y: 240 }, { x: 6.5, y: 470 }, { x: 8.5, y: 860 }, + { x: 11, y: 1420 }, { x: 14, y: 2100 }, { x: 18, y: 3050 }, + { x: 22, y: 4300 }, { x: 28, y: 5900 } + ] + } + }, + 'MI355X_OPT': { + SGLang: { + interactivity: [ + { x: 42, y: 8800 }, { x: 62, y: 6400 }, { x: 82, y: 4600 }, + { x: 102, y: 3200 }, { x: 132, y: 2200 }, { x: 162, y: 1320 }, + { x: 192, y: 740 }, { x: 222, y: 370 } + ], + latency: [ + { x: 4.0, y: 370 }, { x: 5.2, y: 740 }, { x: 6.8, y: 1320 }, + { x: 9.0, y: 2200 }, { x: 11.5, y: 3200 }, { x: 15, y: 4600 }, + { x: 19, y: 6400 }, { x: 24, y: 8800 } + ] + }, + vLLM: { + interactivity: [ + { x: 38, y: 7800 }, { x: 58, y: 5700 }, { x: 78, y: 4000 }, + { x: 98, y: 2800 }, { x: 128, y: 1900 }, { x: 158, y: 1150 }, + { x: 188, y: 640 }, { x: 218, y: 320 } + ], + latency: [ + { x: 4.5, y: 320 }, { x: 5.6, y: 640 }, { x: 7.5, y: 1150 }, + { x: 10, y: 1900 }, { x: 12.5, y: 2800 }, { x: 16, y: 4000 }, + { x: 20, y: 5700 }, { x: 25, y: 7800 } + ] + } + }, + 'B200': { + SGLang: { + interactivity: [ + { x: 50, y: 9000 }, { x: 70, y: 6600 }, { x: 90, y: 4700 }, + { x: 110, y: 3300 }, { x: 140, y: 2200 }, { x: 170, y: 1350 }, + { x: 200, y: 760 }, { x: 230, y: 380 } + ], + latency: [ + { x: 3.8, y: 380 }, { x: 5.0, y: 760 }, { x: 6.5, y: 1350 }, + { x: 8.5, y: 2200 }, { x: 11, y: 3300 }, { x: 14, y: 4700 }, + { x: 18, y: 6600 }, { x: 23, y: 9000 } + ] + }, + vLLM: { + interactivity: [ + { x: 45, y: 8200 }, { x: 65, y: 6000 }, { x: 85, y: 4300 }, + { x: 105, y: 3000 }, { x: 135, y: 2000 }, { x: 165, y: 1200 }, + { x: 195, y: 680 }, { x: 225, y: 340 } + ], + latency: [ + { x: 4.2, y: 340 }, { x: 5.5, y: 680 }, { x: 7.2, y: 1200 }, + { x: 9.0, y: 2000 }, { x: 11.5, y: 3000 }, { x: 15, y: 4300 }, + { x: 19, y: 6000 }, { x: 24, y: 8200 } + ] + } + } + } + }, + '1K / 8K': { + FP4: { + 'MI355X': { + SGLang: { + interactivity: [ + { x: 15, y: 5500 }, { x: 22, y: 4000 }, { x: 30, y: 2800 }, + { x: 38, y: 1900 }, { x: 48, y: 1200 }, { x: 60, y: 750 }, + { x: 75, y: 400 }, { x: 90, y: 200 } + ], + latency: [ + { x: 12, y: 200 }, { x: 16, y: 400 }, { x: 20, y: 750 }, + { x: 26, y: 1200 }, { x: 33, y: 1900 }, { x: 42, y: 2800 }, + { x: 55, y: 4000 }, { x: 70, y: 5500 } + ] + }, + vLLM: { + interactivity: [ + { x: 13, y: 4800 }, { x: 20, y: 3500 }, { x: 28, y: 2500 }, + { x: 35, y: 1700 }, { x: 45, y: 1050 }, { x: 55, y: 650 }, + { x: 70, y: 350 }, { x: 85, y: 175 } + ], + latency: [ + { x: 13, y: 175 }, { x: 17, y: 350 }, { x: 22, y: 650 }, + { x: 28, y: 1050 }, { x: 35, y: 1700 }, { x: 45, y: 2500 }, + { x: 58, y: 3500 }, { x: 75, y: 4800 } + ] + } + }, + 'MI355X_OPT': { + SGLang: { + interactivity: [ + { x: 17, y: 7200 }, { x: 25, y: 5200 }, { x: 33, y: 3700 }, + { x: 42, y: 2500 }, { x: 52, y: 1600 }, { x: 65, y: 1000 }, + { x: 80, y: 550 }, { x: 95, y: 280 } + ], + latency: [ + { x: 10, y: 280 }, { x: 14, y: 550 }, { x: 17, y: 1000 }, + { x: 22, y: 1600 }, { x: 29, y: 2500 }, { x: 37, y: 3700 }, + { x: 48, y: 5200 }, { x: 62, y: 7200 } + ] + }, + vLLM: { + interactivity: [ + { x: 15, y: 6300 }, { x: 23, y: 4600 }, { x: 30, y: 3200 }, + { x: 38, y: 2200 }, { x: 48, y: 1400 }, { x: 60, y: 880 }, + { x: 75, y: 480 }, { x: 90, y: 240 } + ], + latency: [ + { x: 11, y: 240 }, { x: 15, y: 480 }, { x: 19, y: 880 }, + { x: 24, y: 1400 }, { x: 31, y: 2200 }, { x: 40, y: 3200 }, + { x: 52, y: 4600 }, { x: 67, y: 6300 } + ] + } + }, + 'B200': { + SGLang: { + interactivity: [ + { x: 18, y: 7800 }, { x: 26, y: 5700 }, { x: 35, y: 4000 }, + { x: 44, y: 2800 }, { x: 55, y: 1800 }, { x: 68, y: 1100 }, + { x: 82, y: 600 }, { x: 98, y: 300 } + ], + latency: [ + { x: 9.5, y: 300 }, { x: 13, y: 600 }, { x: 16, y: 1100 }, + { x: 21, y: 1800 }, { x: 27, y: 2800 }, { x: 35, y: 4000 }, + { x: 45, y: 5700 }, { x: 58, y: 7800 } + ] + }, + vLLM: { + interactivity: [ + { x: 16, y: 7100 }, { x: 24, y: 5200 }, { x: 32, y: 3600 }, + { x: 40, y: 2500 }, { x: 50, y: 1600 }, { x: 63, y: 1000 }, + { x: 78, y: 540 }, { x: 94, y: 270 } + ], + latency: [ + { x: 10, y: 270 }, { x: 14, y: 540 }, { x: 17.5, y: 1000 }, + { x: 22.5, y: 1600 }, { x: 29, y: 2500 }, { x: 38, y: 3600 }, + { x: 48, y: 5200 }, { x: 62, y: 7100 } + ] + } + } + }, + FP8: { + 'MI355X': { + SGLang: { + interactivity: [ + { x: 14, y: 5900 }, { x: 21, y: 4300 }, { x: 29, y: 3050 }, + { x: 37, y: 2080 }, { x: 46, y: 1320 }, { x: 58, y: 820 }, + { x: 73, y: 440 }, { x: 88, y: 220 } + ], + latency: [ + { x: 11.5, y: 220 }, { x: 15, y: 440 }, { x: 19, y: 820 }, + { x: 25, y: 1320 }, { x: 32, y: 2080 }, { x: 40, y: 3050 }, + { x: 53, y: 4300 }, { x: 68, y: 5900 } + ] + }, + vLLM: { + interactivity: [ + { x: 12, y: 5200 }, { x: 19, y: 3800 }, { x: 27, y: 2700 }, + { x: 34, y: 1850 }, { x: 43, y: 1150 }, { x: 54, y: 710 }, + { x: 68, y: 380 }, { x: 83, y: 190 } + ], + latency: [ + { x: 12.5, y: 190 }, { x: 16, y: 380 }, { x: 21, y: 710 }, + { x: 27, y: 1150 }, { x: 34, y: 1850 }, { x: 43, y: 2700 }, + { x: 56, y: 3800 }, { x: 72, y: 5200 } + ] + } + }, + 'MI355X_OPT': { + SGLang: { + interactivity: [ + { x: 16, y: 7800 }, { x: 24, y: 5700 }, { x: 32, y: 4000 }, + { x: 40, y: 2750 }, { x: 50, y: 1750 }, { x: 63, y: 1100 }, + { x: 78, y: 600 }, { x: 93, y: 300 } + ], + latency: [ + { x: 9.5, y: 300 }, { x: 13, y: 600 }, { x: 16.5, y: 1100 }, + { x: 21.5, y: 1750 }, { x: 28, y: 2750 }, { x: 36, y: 4000 }, + { x: 46, y: 5700 }, { x: 60, y: 7800 } + ] + }, + vLLM: { + interactivity: [ + { x: 14, y: 6800 }, { x: 22, y: 5000 }, { x: 29, y: 3500 }, + { x: 37, y: 2400 }, { x: 46, y: 1520 }, { x: 58, y: 960 }, + { x: 73, y: 520 }, { x: 88, y: 260 } + ], + latency: [ + { x: 10.5, y: 260 }, { x: 14, y: 520 }, { x: 18, y: 960 }, + { x: 23, y: 1520 }, { x: 30, y: 2400 }, { x: 39, y: 3500 }, + { x: 50, y: 5000 }, { x: 65, y: 6800 } + ] + } + }, + 'B200': { + SGLang: { + interactivity: [ + { x: 18, y: 7800 }, { x: 26, y: 5700 }, { x: 35, y: 4000 }, + { x: 44, y: 2800 }, { x: 55, y: 1800 }, { x: 68, y: 1100 }, + { x: 82, y: 600 }, { x: 98, y: 300 } + ], + latency: [ + { x: 9.5, y: 300 }, { x: 13, y: 600 }, { x: 16, y: 1100 }, + { x: 21, y: 1800 }, { x: 27, y: 2800 }, { x: 35, y: 4000 }, + { x: 45, y: 5700 }, { x: 58, y: 7800 } + ] + }, + vLLM: { + interactivity: [ + { x: 16, y: 7100 }, { x: 24, y: 5200 }, { x: 32, y: 3600 }, + { x: 40, y: 2500 }, { x: 50, y: 1600 }, { x: 63, y: 1000 }, + { x: 78, y: 540 }, { x: 94, y: 270 } + ], + latency: [ + { x: 10, y: 270 }, { x: 14, y: 540 }, { x: 17.5, y: 1000 }, + { x: 22.5, y: 1600 }, { x: 29, y: 2500 }, { x: 38, y: 3600 }, + { x: 48, y: 5200 }, { x: 62, y: 7100 } + ] + } + } + } + } + }, + 'DeepSeek R1 0528': { + arch: { type: 'MoE', attention: 'MLA', size: '671B' }, + '1K / 1K': { + FP4: { + 'MI355X': { + SGLang: { + interactivity: [ + { x: 35, y: 5200 }, { x: 55, y: 3800 }, { x: 75, y: 2700 }, + { x: 95, y: 1850 }, { x: 120, y: 1200 }, { x: 150, y: 750 }, + { x: 180, y: 420 }, { x: 210, y: 210 } + ], + latency: [ + { x: 5.5, y: 210 }, { x: 7.0, y: 420 }, { x: 9.0, y: 750 }, + { x: 12, y: 1200 }, { x: 15, y: 1850 }, { x: 19, y: 2700 }, + { x: 24, y: 3800 }, { x: 30, y: 5200 } + ] + }, + vLLM: { + interactivity: [ + { x: 30, y: 4500 }, { x: 50, y: 3300 }, { x: 70, y: 2350 }, + { x: 90, y: 1600 }, { x: 115, y: 1050 }, { x: 145, y: 650 }, + { x: 175, y: 360 }, { x: 205, y: 180 } + ], + latency: [ + { x: 6.0, y: 180 }, { x: 7.5, y: 360 }, { x: 10, y: 650 }, + { x: 13, y: 1050 }, { x: 16, y: 1600 }, { x: 20, y: 2350 }, + { x: 25, y: 3300 }, { x: 32, y: 4500 } + ] + } + }, + 'MI355X_OPT': { + SGLang: { + interactivity: [ + { x: 40, y: 6800 }, { x: 60, y: 5000 }, { x: 80, y: 3600 }, + { x: 100, y: 2500 }, { x: 125, y: 1650 }, { x: 155, y: 1000 }, + { x: 185, y: 570 }, { x: 215, y: 285 } + ], + latency: [ + { x: 4.8, y: 285 }, { x: 6.0, y: 570 }, { x: 7.8, y: 1000 }, + { x: 10, y: 1650 }, { x: 13, y: 2500 }, { x: 17, y: 3600 }, + { x: 21, y: 5000 }, { x: 27, y: 6800 } + ] + }, + vLLM: { + interactivity: [ + { x: 35, y: 5900 }, { x: 55, y: 4300 }, { x: 75, y: 3100 }, + { x: 95, y: 2150 }, { x: 120, y: 1400 }, { x: 150, y: 880 }, + { x: 180, y: 490 }, { x: 210, y: 245 } + ], + latency: [ + { x: 5.2, y: 245 }, { x: 6.5, y: 490 }, { x: 8.5, y: 880 }, + { x: 11, y: 1400 }, { x: 14, y: 2150 }, { x: 18, y: 3100 }, + { x: 22.5, y: 4300 }, { x: 29, y: 5900 } + ] + } + }, + 'B200': { + SGLang: { + interactivity: [ + { x: 45, y: 7600 }, { x: 65, y: 5500 }, { x: 85, y: 3900 }, + { x: 105, y: 2700 }, { x: 130, y: 1800 }, { x: 160, y: 1100 }, + { x: 190, y: 620 }, { x: 220, y: 310 } + ], + latency: [ + { x: 4.2, y: 310 }, { x: 5.5, y: 620 }, { x: 7.0, y: 1100 }, + { x: 9.0, y: 1800 }, { x: 12, y: 2700 }, { x: 15, y: 3900 }, + { x: 19, y: 5500 }, { x: 25, y: 7600 } + ] + }, + vLLM: { + interactivity: [ + { x: 40, y: 6800 }, { x: 60, y: 5000 }, { x: 80, y: 3500 }, + { x: 100, y: 2400 }, { x: 125, y: 1600 }, { x: 155, y: 980 }, + { x: 185, y: 550 }, { x: 215, y: 275 } + ], + latency: [ + { x: 4.5, y: 275 }, { x: 5.8, y: 550 }, { x: 7.5, y: 980 }, + { x: 10, y: 1600 }, { x: 13, y: 2400 }, { x: 16, y: 3500 }, + { x: 20, y: 5000 }, { x: 26, y: 6800 } + ] + } + } + }, + FP8: { + 'MI355X': { + SGLang: { + interactivity: [ + { x: 30, y: 4200 }, { x: 48, y: 3100 }, { x: 65, y: 2200 }, + { x: 85, y: 1500 }, { x: 110, y: 980 }, { x: 140, y: 600 }, + { x: 170, y: 340 }, { x: 200, y: 170 } + ], + latency: [ + { x: 6.5, y: 170 }, { x: 8.5, y: 340 }, { x: 11, y: 600 }, + { x: 14, y: 980 }, { x: 18, y: 1500 }, { x: 22, y: 2200 }, + { x: 28, y: 3100 }, { x: 35, y: 4200 } + ] + }, + vLLM: { + interactivity: [ + { x: 27, y: 3800 }, { x: 44, y: 2800 }, { x: 60, y: 2000 }, + { x: 80, y: 1350 }, { x: 105, y: 880 }, { x: 135, y: 540 }, + { x: 165, y: 300 }, { x: 195, y: 150 } + ], + latency: [ + { x: 7.0, y: 150 }, { x: 9.0, y: 300 }, { x: 12, y: 540 }, + { x: 15, y: 880 }, { x: 19, y: 1350 }, { x: 24, y: 2000 }, + { x: 30, y: 2800 }, { x: 37, y: 3800 } + ] + } + }, + 'MI355X_OPT': { + SGLang: { + interactivity: [ + { x: 35, y: 5500 }, { x: 53, y: 4000 }, { x: 70, y: 2900 }, + { x: 90, y: 2000 }, { x: 115, y: 1300 }, { x: 145, y: 800 }, + { x: 175, y: 450 }, { x: 205, y: 230 } + ], + latency: [ + { x: 5.5, y: 230 }, { x: 7.2, y: 450 }, { x: 9.5, y: 800 }, + { x: 12, y: 1300 }, { x: 15.5, y: 2000 }, { x: 19, y: 2900 }, + { x: 24, y: 4000 }, { x: 30, y: 5500 } + ] + }, + vLLM: { + interactivity: [ + { x: 32, y: 4900 }, { x: 49, y: 3600 }, { x: 65, y: 2600 }, + { x: 85, y: 1750 }, { x: 110, y: 1150 }, { x: 140, y: 700 }, + { x: 170, y: 390 }, { x: 200, y: 195 } + ], + latency: [ + { x: 6.0, y: 195 }, { x: 7.8, y: 390 }, { x: 10, y: 700 }, + { x: 13, y: 1150 }, { x: 16.5, y: 1750 }, { x: 21, y: 2600 }, + { x: 26, y: 3600 }, { x: 33, y: 4900 } + ] + } + }, + 'B200': { + SGLang: { + interactivity: [ + { x: 38, y: 6000 }, { x: 58, y: 4400 }, { x: 78, y: 3100 }, + { x: 98, y: 2200 }, { x: 123, y: 1450 }, { x: 153, y: 880 }, + { x: 183, y: 500 }, { x: 213, y: 250 } + ], + latency: [ + { x: 5.0, y: 250 }, { x: 6.5, y: 500 }, { x: 8.5, y: 880 }, + { x: 11, y: 1450 }, { x: 14, y: 2200 }, { x: 17.5, y: 3100 }, + { x: 22, y: 4400 }, { x: 28, y: 6000 } + ] + }, + vLLM: { + interactivity: [ + { x: 35, y: 5400 }, { x: 53, y: 4000 }, { x: 72, y: 2800 }, + { x: 92, y: 1950 }, { x: 118, y: 1300 }, { x: 148, y: 790 }, + { x: 178, y: 440 }, { x: 208, y: 220 } + ], + latency: [ + { x: 5.5, y: 220 }, { x: 7.0, y: 440 }, { x: 9.0, y: 790 }, + { x: 12, y: 1300 }, { x: 15, y: 1950 }, { x: 19, y: 2800 }, + { x: 24, y: 4000 }, { x: 30, y: 5400 } + ] + } + } + } + }, + '8K / 1K': { + FP4: { + 'MI355X': { + SGLang: { + interactivity: [ + { x: 22, y: 3200 }, { x: 35, y: 2300 }, { x: 48, y: 1650 }, + { x: 62, y: 1100 }, { x: 80, y: 720 }, { x: 100, y: 440 }, + { x: 125, y: 250 }, { x: 150, y: 125 } + ], + latency: [ + { x: 8.0, y: 125 }, { x: 10.5, y: 250 }, { x: 14, y: 440 }, + { x: 18, y: 720 }, { x: 23, y: 1100 }, { x: 28, y: 1650 }, + { x: 35, y: 2300 }, { x: 45, y: 3200 } + ] + } + }, + 'MI355X_OPT': { + SGLang: { + interactivity: [ + { x: 26, y: 4200 }, { x: 40, y: 3000 }, { x: 54, y: 2150 }, + { x: 68, y: 1450 }, { x: 85, y: 950 }, { x: 105, y: 580 }, + { x: 130, y: 330 }, { x: 155, y: 170 } + ], + latency: [ + { x: 6.8, y: 170 }, { x: 9.0, y: 330 }, { x: 12, y: 580 }, + { x: 15, y: 950 }, { x: 20, y: 1450 }, { x: 25, y: 2150 }, + { x: 31, y: 3000 }, { x: 40, y: 4200 } + ] + } + }, + 'B200': { + SGLang: { + interactivity: [ + { x: 30, y: 4600 }, { x: 45, y: 3400 }, { x: 60, y: 2400 }, + { x: 75, y: 1650 }, { x: 92, y: 1050 }, { x: 112, y: 650 }, + { x: 135, y: 370 }, { x: 160, y: 185 } + ], + latency: [ + { x: 6.0, y: 185 }, { x: 8.0, y: 370 }, { x: 10.5, y: 650 }, + { x: 13.5, y: 1050 }, { x: 17, y: 1650 }, { x: 22, y: 2400 }, + { x: 28, y: 3400 }, { x: 36, y: 4600 } + ] + } + } + }, + FP8: { + 'MI355X': { + SGLang: { + interactivity: [ + { x: 18, y: 2600 }, { x: 30, y: 1900 }, { x: 42, y: 1350 }, + { x: 55, y: 900 }, { x: 72, y: 580 }, { x: 90, y: 360 }, + { x: 112, y: 200 }, { x: 135, y: 100 } + ], + latency: [ + { x: 9.5, y: 100 }, { x: 12.5, y: 200 }, { x: 16, y: 360 }, + { x: 20, y: 580 }, { x: 26, y: 900 }, { x: 32, y: 1350 }, + { x: 40, y: 1900 }, { x: 50, y: 2600 } + ] + } + }, + 'MI355X_OPT': { + SGLang: { + interactivity: [ + { x: 22, y: 3400 }, { x: 35, y: 2500 }, { x: 47, y: 1800 }, + { x: 60, y: 1200 }, { x: 78, y: 780 }, { x: 96, y: 480 }, + { x: 118, y: 270 }, { x: 140, y: 135 } + ], + latency: [ + { x: 8.0, y: 135 }, { x: 10.5, y: 270 }, { x: 14, y: 480 }, + { x: 17, y: 780 }, { x: 22, y: 1200 }, { x: 28, y: 1800 }, + { x: 35, y: 2500 }, { x: 45, y: 3400 } + ] + } + }, + 'B200': { + SGLang: { + interactivity: [ + { x: 25, y: 3800 }, { x: 38, y: 2800 }, { x: 52, y: 2000 }, + { x: 66, y: 1350 }, { x: 83, y: 880 }, { x: 102, y: 540 }, + { x: 125, y: 300 }, { x: 148, y: 150 } + ], + latency: [ + { x: 7.5, y: 150 }, { x: 10, y: 300 }, { x: 13, y: 540 }, + { x: 16.5, y: 880 }, { x: 21, y: 1350 }, { x: 27, y: 2000 }, + { x: 33, y: 2800 }, { x: 42, y: 3800 } + ] + } + } + } + }, + '1K / 8K': { + FP4: { + 'MI355X': { + SGLang: { + interactivity: [ + { x: 10, y: 2800 }, { x: 15, y: 2050 }, { x: 20, y: 1450 }, + { x: 26, y: 1000 }, { x: 34, y: 640 }, { x: 42, y: 390 }, + { x: 52, y: 220 }, { x: 62, y: 110 } + ], + latency: [ + { x: 18, y: 110 }, { x: 22, y: 220 }, { x: 28, y: 390 }, + { x: 35, y: 640 }, { x: 44, y: 1000 }, { x: 55, y: 1450 }, + { x: 68, y: 2050 }, { x: 85, y: 2800 } + ] + } + }, + 'MI355X_OPT': { + SGLang: { + interactivity: [ + { x: 12, y: 3700 }, { x: 18, y: 2700 }, { x: 24, y: 1900 }, + { x: 30, y: 1300 }, { x: 38, y: 850 }, { x: 47, y: 520 }, + { x: 58, y: 290 }, { x: 68, y: 145 } + ], + latency: [ + { x: 15, y: 145 }, { x: 19, y: 290 }, { x: 24, y: 520 }, + { x: 30, y: 850 }, { x: 38, y: 1300 }, { x: 48, y: 1900 }, + { x: 60, y: 2700 }, { x: 75, y: 3700 } + ] + } + }, + 'B200': { + SGLang: { + interactivity: [ + { x: 14, y: 4100 }, { x: 20, y: 3000 }, { x: 27, y: 2100 }, + { x: 34, y: 1450 }, { x: 42, y: 950 }, { x: 52, y: 580 }, + { x: 64, y: 320 }, { x: 75, y: 160 } + ], + latency: [ + { x: 14, y: 160 }, { x: 18, y: 320 }, { x: 22, y: 580 }, + { x: 28, y: 950 }, { x: 35, y: 1450 }, { x: 44, y: 2100 }, + { x: 56, y: 3000 }, { x: 70, y: 4100 } + ] + } + } + }, + FP8: { + 'MI355X': { + SGLang: { + interactivity: [ + { x: 40, y: 250 }, { x: 55, y: 175 }, { x: 68, y: 120 }, + { x: 80, y: 85 }, { x: 92, y: 62 }, { x: 105, y: 48 }, + { x: 118, y: 38 }, { x: 130, y: 30 } + ], + latency: [ + { x: 70, y: 30 }, { x: 82, y: 38 }, { x: 95, y: 48 }, + { x: 108, y: 62 }, { x: 120, y: 85 }, { x: 135, y: 120 }, + { x: 150, y: 175 }, { x: 168, y: 250 } + ] + } + }, + 'MI355X_OPT': { + SGLang: { + interactivity: [ + { x: 44, y: 325 }, { x: 60, y: 228 }, { x: 74, y: 156 }, + { x: 87, y: 110 }, { x: 100, y: 80 }, { x: 112, y: 62 }, + { x: 124, y: 49 }, { x: 136, y: 39 } + ], + latency: [ + { x: 60, y: 39 }, { x: 72, y: 49 }, { x: 84, y: 62 }, + { x: 97, y: 80 }, { x: 110, y: 110 }, { x: 125, y: 156 }, + { x: 140, y: 228 }, { x: 158, y: 325 } + ] + } + }, + 'B200': { + SGLang: { + interactivity: [ + { x: 40, y: 370 }, { x: 55, y: 260 }, { x: 68, y: 180 }, + { x: 80, y: 128 }, { x: 95, y: 88 }, { x: 108, y: 65 }, + { x: 120, y: 48 }, { x: 135, y: 35 } + ], + latency: [ + { x: 60, y: 35 }, { x: 70, y: 48 }, { x: 82, y: 65 }, + { x: 95, y: 88 }, { x: 108, y: 128 }, { x: 122, y: 180 }, + { x: 140, y: 260 }, { x: 162, y: 370 } + ] + } + } + } + } + } + }; + + /* ══════ Generate MI355X_ROOFLINE data from B200 reference ══════ */ + // MI355X Roofline = theoretical peak based on hardware roofline model. + // Slightly better than B200 (~15-20% higher throughput, ~10% better latency/interactivity). + (function generateMI355XRoofline() { + const THROUGHPUT_SCALE = 1.18; // 18% higher throughput (y) + const INTERACTIVITY_SCALE = 1.05; // 5% better interactivity (higher x) + const LATENCY_SCALE = 0.88; // 12% lower latency (lower x = better) + + function scaleData(points, xScale, yScale) { + return points.map(p => ({ + x: Math.round(p.x * xScale * 10) / 10, + y: Math.round(p.y * yScale) + })); + } + + Object.keys(BENCH_DATA).forEach(model => { + const modelData = BENCH_DATA[model]; + Object.keys(modelData).forEach(islOsl => { + if (islOsl === 'arch') return; + const islData = modelData[islOsl]; + Object.keys(islData).forEach(precision => { + const precData = islData[precision]; + const b200 = precData['B200']; + if (!b200) return; + + const rooflineEntry = {}; + Object.keys(b200).forEach(fw => { + const fwData = b200[fw]; + rooflineEntry[fw] = {}; + if (fwData.interactivity) { + rooflineEntry[fw].interactivity = scaleData(fwData.interactivity, INTERACTIVITY_SCALE, THROUGHPUT_SCALE); + } + if (fwData.latency) { + rooflineEntry[fw].latency = scaleData(fwData.latency, LATENCY_SCALE, THROUGHPUT_SCALE); + } + }); + + precData['MI355X_ROOFLINE'] = rooflineEntry; + }); + }); + }); + })(); + + /* ══════ DROPDOWN MENUS ══════ */ + document.querySelectorAll('.dropdown-trigger').forEach(trigger => { + trigger.addEventListener('click', e => { + e.stopPropagation(); + const wrap = trigger.closest('.dropdown-wrap'); + // close all other dropdowns + document.querySelectorAll('.dropdown-wrap.open').forEach(w => { + if (w !== wrap) w.classList.remove('open'); + }); + wrap.classList.toggle('open'); + }); + }); + + // Close dropdowns on outside click + document.addEventListener('click', () => { + document.querySelectorAll('.dropdown-wrap.open').forEach(w => w.classList.remove('open')); + }); + + // Dropdown item selection + document.querySelectorAll('.dropdown-wrap').forEach(wrap => { + wrap.querySelectorAll('.dd-item').forEach(item => { + item.addEventListener('click', e => { + e.stopPropagation(); + const val = item.dataset.val; + const ddValue = wrap.querySelector('.dd-value'); + // Update active state + wrap.querySelectorAll('.dd-item').forEach(i => { + i.classList.remove('active'); + const chk = i.querySelector('.dd-check'); + if (chk) chk.remove(); + }); + item.classList.add('active'); + // Update display value + if (ddValue) { + ddValue.textContent = val || item.textContent.trim(); + // Style NV reference "None" differently + if (wrap.id === 'rpt-dd-nv') { + ddValue.classList.toggle('nv-none', !val); + } + } + // Close dropdown + wrap.classList.remove('open'); + + // Handle Y-Axis metric change + if (wrap.id === 'dd-yaxis') { + handleYAxisChange(val); + } + + // Handle model change → update architecture diagram + precision + kernels + if (wrap.id === 'dd-model') { + updateArchDiagram(val); + updatePrecisionDropdown(val); + } + + // Handle precision change → update chip style + kernel charts + if (wrap.id === 'dd-precision') { + const chipEl = wrap.querySelector('.precision-chip'); + if (chipEl) chipEl.textContent = val; + } + + // Update chart subtitle and re-render charts + updateChartSubtitles(); + updateCharts(); + updateKernelCharts(); + }); + }); + }); + + /* ══════ GPU CONFIG CHIP HANDLING ══════ */ + const gpuChipAmd = document.getElementById('gpu-chip-amd'); + const gpuChipNvidia = document.getElementById('gpu-chip-nvidia'); + + // Handle GPU config dropdown item clicks (combined HW+FW format) + function setupGpuConfigDropdown(ddId, chipEl) { + document.querySelectorAll(`#${ddId} .dd-item`).forEach(item => { + item.addEventListener('click', e => { + e.stopPropagation(); + if (!chipEl) return; + chipEl.dataset.hw = item.dataset.hw; + chipEl.dataset.fw = item.dataset.fw; + chipEl.querySelector('.gpu-chip-text').textContent = item.dataset.val; + // Mark active + document.querySelectorAll(`#${ddId} .dd-item`).forEach(i => i.classList.remove('active')); + item.classList.add('active'); + document.getElementById(ddId).classList.remove('open'); + updateChartSubtitles(); + updateCharts(); + updateKernelCharts(); + }); + }); + } + + setupGpuConfigDropdown('dd-amd-gpu', gpuChipAmd); + setupGpuConfigDropdown('dd-nvidia-gpu', gpuChipNvidia); + + /* ══════ GET CURRENT SELECTIONS ══════ */ + function getSelections() { + const amdChip = document.getElementById('gpu-chip-amd'); + const nvChip = document.getElementById('gpu-chip-nvidia'); + return { + model: document.querySelector('#dd-model .dd-value')?.textContent || 'gpt-oss 120B', + isl_osl: document.querySelector('#dd-isl-osl .dd-value')?.textContent || '1K / 1K', + precision: document.querySelector('#dd-precision .dd-value')?.textContent || 'FP4', + yaxis: document.querySelector('#dd-yaxis .dd-value')?.textContent || 'Token Throughput per GPU', + hardware: amdChip?.dataset.hw || 'MI355X', + framework: amdChip?.dataset.fw || 'SGLang', + refHardware: nvChip?.dataset.hw || 'B200', + refFramework: nvChip?.dataset.fw || 'SGLang' + }; + } + + /* ══════ DYNAMIC PRECISION DROPDOWN ══════ */ + // Available precisions per model + const MODEL_PRECISIONS = { + 'gpt-oss 120B': ['FP4', 'FP8'], + 'DeepSeek R1 0528': ['FP4', 'FP8'] + }; + + function updatePrecisionDropdown(model) { + const precisions = MODEL_PRECISIONS[model] || ['FP4']; + const ddPrecision = document.querySelector('#dd-precision'); + if (!ddPrecision) return; + + const menu = ddPrecision.querySelector('.dropdown-menu'); + if (!menu) return; + + // Rebuild dropdown items + menu.innerHTML = ''; + precisions.forEach((p, i) => { + const item = document.createElement('div'); + item.className = 'dd-item' + (i === 0 ? ' active' : ''); + item.dataset.val = p; + const icon = p === 'FP4' ? '●' : '■'; + item.textContent = icon + ' ' + p; + + item.addEventListener('click', e => { + e.stopPropagation(); + menu.querySelectorAll('.dd-item').forEach(it => it.classList.remove('active')); + item.classList.add('active'); + const ddValue = ddPrecision.querySelector('.dd-value'); + if (ddValue) ddValue.textContent = p; + const chipEl = ddPrecision.querySelector('.precision-chip'); + if (chipEl) chipEl.textContent = p; + ddPrecision.classList.remove('open'); + updateChartSubtitles(); + updateCharts(); + updateKernelCharts(); + }); + + menu.appendChild(item); + }); + + // Reset to first precision + const ddValue = ddPrecision.querySelector('.dd-value'); + if (ddValue) ddValue.textContent = precisions[0]; + const chipEl = ddPrecision.querySelector('.precision-chip'); + if (chipEl) chipEl.textContent = precisions[0]; + } + + /* ══════ KERNEL DATA PER MODEL/PRECISION ══════ */ + // KERNEL_DATA: model → precision → ISL/OSL + const KERNEL_DATA = { + 'gpt-oss 120B': { + FP4: { + '1K / 1K': { + subtitle: 'gpt-oss 120B · FP4 · ISL 1K / OSL 1K', + stack: { + labels: ['B200\n(Reference)', 'MI355X\n(Before)', 'MI355X\n(Roofline)', 'MI355X\n(After Opt)'], + ATTN: [34, 48, 28, 34], + GEMM_MOE: [314, 519, 295, 385], + AR_NORM: [44, 72, 38, 52], + QUANT: [10, 26, 9, 17], + TOPK: [8, 14, 7, 10], + ACT: [6, 0, 0, 0], + CACHE: [6, 0, 0, 0], + other: [35, 83, 30, 42] + }, + sideBySide: { + labels: ['ATTN', 'GEMM/MOE', 'AR/NORM', 'QUANT', 'TOPK', 'ACT', 'CACHE', 'other'], + before: [12, 158, 38, 28, 12, 8, 34, 40], + roofline: [ 7, 108, 17, 7, 5, 3, 8, 10], + after: [10, 145, 28, 18, 8, 6, 24, 28], + reference: [20, 120, 20, 8, 6, 4, 10, 12] + } + }, + '8K / 1K': { + subtitle: 'gpt-oss 120B · FP4 · ISL 8K / OSL 1K', + stack: { + labels: ['B200\n(Reference)', 'MI355X\n(Before)', 'MI355X\n(Roofline)', 'MI355X\n(After Opt)'], + ATTN: [120, 185, 105, 130], + GEMM_MOE: [280, 470, 260, 350], + AR_NORM: [38, 62, 34, 45], + QUANT: [14, 32, 12, 22], + TOPK: [7, 12, 6, 9], + ACT: [5, 0, 0, 0], + CACHE: [18, 0, 0, 0], + other: [30, 72, 26, 38] + }, + sideBySide: { + labels: ['ATTN', 'GEMM/MOE', 'AR/NORM', 'QUANT', 'TOPK', 'ACT', 'CACHE', 'other'], + before: [52, 142, 32, 30, 10, 7, 28, 35], + roofline: [32, 96, 15, 9, 4, 3, 11, 8], + after: [38, 128, 24, 20, 7, 5, 20, 24], + reference: [42, 108, 18, 10, 5, 4, 14, 10] + } + }, + '1K / 8K': { + subtitle: 'gpt-oss 120B · FP4 · ISL 1K / OSL 8K', + stack: { + labels: ['B200\n(Reference)', 'MI355X\n(Before)', 'MI355X\n(Roofline)', 'MI355X\n(After Opt)'], + ATTN: [42, 65, 36, 45], + GEMM_MOE: [340, 560, 310, 420], + AR_NORM: [55, 90, 48, 65], + QUANT: [12, 28, 10, 18], + TOPK: [9, 16, 8, 11], + ACT: [7, 0, 0, 0], + CACHE: [22, 0, 0, 0], + other: [45, 100, 38, 52] + }, + sideBySide: { + labels: ['ATTN', 'GEMM/MOE', 'AR/NORM', 'QUANT', 'TOPK', 'ACT', 'CACHE', 'other'], + before: [18, 170, 44, 30, 14, 9, 38, 45], + roofline: [10, 115, 18, 7, 5, 4, 13, 11], + after: [14, 155, 32, 20, 10, 7, 28, 32], + reference: [22, 130, 22, 9, 7, 5, 16, 14] + } + } + }, + FP8: { + '1K / 1K': { + subtitle: 'gpt-oss 120B · FP8 · ISL 1K / OSL 1K', + stack: { + labels: ['B200\n(Reference)', 'MI355X\n(Before)', 'MI355X\n(Roofline)', 'MI355X\n(After Opt)'], + ATTN: [30, 44, 26, 32], + GEMM_MOE: [290, 480, 270, 355], + AR_NORM: [40, 66, 35, 48], + QUANT: [8, 20, 7, 13], + TOPK: [7, 12, 6, 9], + ACT: [5, 0, 0, 0], + CACHE: [5, 0, 0, 0], + other: [32, 76, 27, 38] + }, + sideBySide: { + labels: ['ATTN', 'GEMM/MOE', 'AR/NORM', 'QUANT', 'TOPK', 'ACT', 'CACHE', 'other'], + before: [10, 146, 34, 22, 10, 7, 30, 36], + roofline: [ 6, 100, 15, 6, 4, 3, 7, 9], + after: [ 8, 132, 26, 14, 7, 5, 20, 25], + reference: [18, 110, 18, 7, 5, 4, 9, 11] + } + }, + '8K / 1K': { + subtitle: 'gpt-oss 120B · FP8 · ISL 8K / OSL 1K', + stack: { + labels: ['B200\n(Reference)', 'MI355X\n(Before)', 'MI355X\n(Roofline)', 'MI355X\n(After Opt)'], + ATTN: [110, 170, 96, 120], + GEMM_MOE: [260, 435, 240, 325], + AR_NORM: [35, 57, 30, 42], + QUANT: [12, 28, 10, 19], + TOPK: [6, 11, 5, 8], + ACT: [4, 0, 0, 0], + CACHE: [16, 0, 0, 0], + other: [28, 66, 24, 35] + }, + sideBySide: { + labels: ['ATTN', 'GEMM/MOE', 'AR/NORM', 'QUANT', 'TOPK', 'ACT', 'CACHE', 'other'], + before: [48, 132, 30, 26, 9, 6, 26, 32], + roofline: [29, 88, 13, 8, 3, 3, 10, 7], + after: [35, 118, 22, 17, 6, 4, 18, 22], + reference: [39, 100, 16, 9, 4, 3, 12, 9] + } + }, + '1K / 8K': { + subtitle: 'gpt-oss 120B · FP8 · ISL 1K / OSL 8K', + stack: { + labels: ['B200\n(Reference)', 'MI355X\n(Before)', 'MI355X\n(Roofline)', 'MI355X\n(After Opt)'], + ATTN: [38, 60, 33, 42], + GEMM_MOE: [315, 520, 285, 390], + AR_NORM: [50, 82, 44, 60], + QUANT: [10, 24, 8, 15], + TOPK: [8, 14, 7, 10], + ACT: [6, 0, 0, 0], + CACHE: [20, 0, 0, 0], + other: [40, 92, 34, 48] + }, + sideBySide: { + labels: ['ATTN', 'GEMM/MOE', 'AR/NORM', 'QUANT', 'TOPK', 'ACT', 'CACHE', 'other'], + before: [16, 158, 40, 26, 12, 8, 34, 42], + roofline: [ 9, 106, 16, 6, 4, 3, 11, 10], + after: [12, 142, 30, 17, 9, 6, 25, 29], + reference: [20, 120, 20, 8, 6, 5, 14, 13] + } + } + } + }, + 'DeepSeek R1 0528': { + FP4: { + '1K / 1K': { + subtitle: 'DeepSeek R1 0528 · FP4 · ISL 1K / OSL 1K', + stack: { + labels: ['B200\n(Reference)', 'MI355X\n(Before)', 'MI355X\n(Roofline)', 'MI355X\n(After Opt)'], + ATTN: [28, 42, 24, 30], + GEMM_MOE: [380, 620, 350, 460], + AR_NORM: [52, 85, 45, 62], + QUANT: [8, 22, 7, 14], + TOPK: [10, 18, 8, 12], + ACT: [5, 0, 0, 0], + CACHE: [8, 0, 0, 0], + other: [40, 95, 34, 50] + }, + sideBySide: { + labels: ['MLA', 'MOE GEMM', 'AR/NORM', 'QUANT', 'TOPK', 'ACT', 'CACHE', 'other'], + before: [18, 185, 42, 24, 14, 10, 38, 45], + roofline: [10, 125, 20, 8, 6, 4, 10, 12], + after: [14, 165, 32, 16, 10, 8, 28, 32], + reference: [24, 140, 24, 10, 8, 5, 12, 15] + } + }, + '8K / 1K': { + subtitle: 'DeepSeek R1 0528 · FP4 · ISL 8K / OSL 1K', + stack: { + labels: ['B200\n(Reference)', 'MI355X\n(Before)', 'MI355X\n(Roofline)', 'MI355X\n(After Opt)'], + ATTN: [95, 155, 82, 105], + GEMM_MOE: [340, 560, 310, 415], + AR_NORM: [45, 74, 38, 54], + QUANT: [10, 26, 9, 16], + TOPK: [9, 15, 7, 11], + ACT: [5, 0, 0, 0], + CACHE: [20, 0, 0, 0], + other: [35, 82, 30, 44] + }, + sideBySide: { + labels: ['MLA', 'MOE GEMM', 'AR/NORM', 'QUANT', 'TOPK', 'ACT', 'CACHE', 'other'], + before: [45, 168, 38, 26, 12, 9, 32, 40], + roofline: [26, 112, 16, 8, 5, 4, 11, 9], + after: [32, 150, 28, 17, 9, 7, 24, 28], + reference: [38, 125, 20, 10, 7, 5, 14, 12] + } + }, + '1K / 8K': { + subtitle: 'DeepSeek R1 0528 · FP4 · ISL 1K / OSL 8K', + stack: { + labels: ['B200\n(Reference)', 'MI355X\n(Before)', 'MI355X\n(Roofline)', 'MI355X\n(After Opt)'], + ATTN: [35, 55, 30, 38], + GEMM_MOE: [410, 670, 375, 500], + AR_NORM: [65, 105, 56, 76], + QUANT: [10, 25, 8, 16], + TOPK: [11, 20, 9, 14], + ACT: [6, 0, 0, 0], + CACHE: [25, 0, 0, 0], + other: [48, 110, 40, 58] + }, + sideBySide: { + labels: ['MLA', 'MOE GEMM', 'AR/NORM', 'QUANT', 'TOPK', 'ACT', 'CACHE', 'other'], + before: [22, 200, 50, 28, 16, 11, 42, 50], + roofline: [12, 135, 22, 9, 7, 4, 14, 12], + after: [16, 180, 38, 18, 12, 9, 32, 36], + reference: [26, 152, 28, 11, 9, 6, 18, 16] + } + } + }, + FP8: { + '1K / 1K': { + subtitle: 'DeepSeek R1 0528 · FP8 · ISL 1K / OSL 1K', + stack: { + labels: ['B200\n(Reference)', 'MI355X\n(Before)', 'MI355X\n(Roofline)', 'MI355X\n(After Opt)'], + ATTN: [32, 48, 28, 35], + GEMM_MOE: [350, 580, 320, 430], + AR_NORM: [48, 78, 42, 56], + QUANT: [12, 30, 10, 20], + TOPK: [9, 16, 8, 11], + ACT: [6, 0, 0, 0], + CACHE: [7, 0, 0, 0], + other: [38, 88, 32, 48] + }, + sideBySide: { + labels: ['MLA', 'MOE GEMM', 'AR/NORM', 'QUANT', 'TOPK', 'ACT', 'CACHE', 'other'], + before: [20, 175, 40, 28, 16, 10, 36, 42], + roofline: [12, 118, 18, 8, 6, 4, 9, 11], + after: [16, 155, 30, 18, 12, 8, 26, 30], + reference: [26, 132, 22, 10, 8, 5, 11, 14] + } + }, + '8K / 1K': { + subtitle: 'DeepSeek R1 0528 · FP8 · ISL 8K / OSL 1K', + stack: { + labels: ['B200\n(Reference)', 'MI355X\n(Before)', 'MI355X\n(Roofline)', 'MI355X\n(After Opt)'], + ATTN: [105, 168, 92, 115], + GEMM_MOE: [315, 520, 290, 390], + AR_NORM: [42, 68, 36, 50], + QUANT: [14, 32, 12, 22], + TOPK: [8, 14, 7, 10], + ACT: [5, 0, 0, 0], + CACHE: [18, 0, 0, 0], + other: [34, 78, 29, 42] + }, + sideBySide: { + labels: ['MLA', 'MOE GEMM', 'AR/NORM', 'QUANT', 'TOPK', 'ACT', 'CACHE', 'other'], + before: [48, 158, 36, 30, 14, 9, 30, 38], + roofline: [28, 105, 14, 10, 5, 4, 10, 9], + after: [34, 140, 26, 20, 10, 7, 22, 26], + reference: [40, 118, 18, 12, 7, 5, 13, 12] + } + }, + '1K / 8K': { + subtitle: 'DeepSeek R1 0528 · FP8 · ISL 1K / OSL 8K', + stack: { + labels: ['B200\n(Reference)', 'MI355X\n(Before)', 'MI355X\n(Roofline)', 'MI355X\n(After Opt)'], + ATTN: [40, 62, 34, 44], + GEMM_MOE: [380, 625, 345, 465], + AR_NORM: [58, 95, 50, 68], + QUANT: [14, 34, 12, 22], + TOPK: [10, 18, 8, 13], + ACT: [6, 0, 0, 0], + CACHE: [22, 0, 0, 0], + other: [42, 98, 36, 52] + }, + sideBySide: { + labels: ['MLA', 'MOE GEMM', 'AR/NORM', 'QUANT', 'TOPK', 'ACT', 'CACHE', 'other'], + before: [24, 190, 46, 32, 18, 11, 40, 46], + roofline: [14, 126, 20, 10, 7, 4, 12, 11], + after: [18, 170, 34, 22, 13, 9, 30, 33], + reference: [28, 142, 25, 12, 9, 6, 16, 15] + } + } + } + } + }; + + /* ══════ ARCHITECTURE DIAGRAM DATA ══════ */ + const ARCH_DATA = { + 'gpt-oss 120B': { + org: 'OpenAI', + params: '120B total · 5B active · 131K context', + embedSub: 'd = 2,880 · vocab = 201,088', + block1Title: 'Sliding Attention + Sink', + block1Sub: '×18 layers · Top-4/128 MoE', + showAlternating: true, + alternatingText: '↕ alternating every layer', + block2Title: 'Causal Grouped Query Attention', + block2Sub: '×18 layers · Top-4/128 MoE', + outputSub: 'vocab = 201,088', + chipType: 'MoE', chipAttn: 'Sink/Full GQA', chipSize: '120B', + sumType: 'MoE', sumLayers: '36', sumAttn: 'Sink/Full GQA', sumCtx: '131K', sumExperts: '4/128', + features: ['Alternating Sliding/Full Attention', 'Attention Sink Tokens', 'YaRN RoPE (factor=32)', 'MXFP4 Quantization'], + release: 'Released by OpenAI on Jun 13, 2025' + }, + 'DeepSeek R1 0528': { + org: 'DeepSeek', + params: '671B total · 37B active · 128K context', + embedSub: 'd = 7,168 · vocab = 129,280', + block1Title: 'Dense Transformer Block', + block1Sub: '×3 dense layers · FFN = 18,432', + showAlternating: false, + alternatingText: '', + block2Title: 'MoE Transformer Block', + block2Sub: '×58 MoE layers · Multi-head Latent Attention · Top-8/257', + outputSub: 'vocab = 129,280', + chipType: 'MoE', chipAttn: 'MLA', chipSize: '671B', + sumType: 'MoE', sumLayers: '3D + 58M', sumAttn: 'MLA', sumCtx: '128K', sumExperts: '8/257', + features: ['Multi-head Latent Attention', 'Auxiliary-loss-free Load Balancing', 'Multi-Token Prediction'], + release: 'Released by DeepSeek on May 28, 2025' + } + }; + + /* ══════ ARCHITECTURE DIAGRAM UPDATE ══════ */ + function updateArchDiagram(model) { + const data = ARCH_DATA[model]; + if (!data) return; + + // Update chips in header + const chipType = document.getElementById('arch-chip-type'); + const chipAttn = document.getElementById('arch-chip-attn'); + const chipSize = document.getElementById('arch-chip-size'); + if (chipType) chipType.textContent = data.chipType; + if (chipAttn) chipAttn.textContent = data.chipAttn; + if (chipSize) chipSize.textContent = data.chipSize; + + // Update diagram content + const setTextById = (id, val) => { const el = document.getElementById(id); if (el) el.textContent = val; }; + setTextById('arch-org', data.org); + setTextById('arch-params', data.params); + setTextById('arch-embed-sub', data.embedSub); + setTextById('arch-block-1-title', data.block1Title); + setTextById('arch-block-1-sub', data.block1Sub); + setTextById('arch-block-2-title', data.block2Title); + setTextById('arch-block-2-sub', data.block2Sub); + setTextById('arch-output-sub', data.outputSub); + + // Alternating label + const altEl = document.getElementById('arch-alternating'); + if (altEl) { + altEl.textContent = data.alternatingText; + altEl.style.display = data.showAlternating ? '' : 'none'; + } + + // Summary table + setTextById('as-type', data.sumType); + setTextById('as-layers', data.sumLayers); + setTextById('as-attn', data.sumAttn); + setTextById('as-ctx', data.sumCtx); + setTextById('as-experts', data.sumExperts); + + // Features + const featureIds = ['af-1', 'af-2', 'af-3', 'af-4']; + featureIds.forEach((id, i) => { + const el = document.getElementById(id); + if (el) { + if (i < data.features.length) { + el.textContent = data.features[i]; + el.style.display = ''; + } else { + el.style.display = 'none'; + } + } + }); + + // Release + setTextById('arch-release', data.release); + } + + /* ══════ ARCH SECTION TOGGLE ══════ */ + const archHeader = document.getElementById('arch-header'); + const archSection = document.getElementById('arch-section'); + if (archHeader && archSection) { + archHeader.addEventListener('click', () => { + archSection.classList.toggle('collapsed'); + }); + } + + /* ══════ Y-AXIS METRIC — "Building" ══════ */ + function handleYAxisChange(metric) { + const isDefault = (metric === 'Token Throughput per GPU'); + document.querySelectorAll('.chart-card').forEach(card => { + const chartContainer = card.querySelector('.chart-container'); + const building = card.querySelector('.chart-building'); + if (!chartContainer || !building) return; + if (isDefault) { + chartContainer.style.display = ''; + building.style.display = 'none'; + } else { + chartContainer.style.display = 'none'; + building.style.display = ''; + building.querySelector('.building-title').textContent = 'Building…'; + building.querySelector('.building-sub').textContent = `"${metric}" — data will be available soon.`; + } + }); + } + + /* ══════ CHART SUBTITLES UPDATE ══════ */ + function updateChartSubtitles() { + const sel = getSelections(); + const sub = `${sel.model} · ${sel.precision} · ${sel.isl_osl} · Source: SemiAnalysis InferenceX™ · Updated: 03/04/2026`; + document.querySelectorAll('.chart-subtitle-text').forEach(el => { + el.textContent = sub; + }); + } + + /* ══════ CHART ZOOM (EXPAND / COLLAPSE) ══════ */ + document.querySelectorAll('.chart-zoom-btn').forEach(btn => { + btn.addEventListener('click', e => { + e.stopPropagation(); + const targetId = btn.dataset.target; + const card = document.getElementById(targetId); + if (!card) return; + + const isMaximized = card.classList.contains('maximized'); + + document.querySelectorAll('.chart-card.maximized').forEach(c => { + c.classList.remove('maximized'); + }); + + if (!isMaximized) { + card.classList.add('maximized'); + btn.textContent = '⤡'; + const row = card.closest('.card-row'); + if (row) { + row.querySelectorAll('.chart-card').forEach(sibling => { + if (sibling !== card) sibling.style.display = 'none'; + }); + } + } else { + btn.textContent = '⤢'; + const row = card.closest('.card-row'); + if (row) { + row.querySelectorAll('.chart-card').forEach(sibling => { + sibling.style.display = ''; + }); + } + } + + setTimeout(() => { + window.dispatchEvent(new Event('resize')); + }, 100); + }); + }); + + /* ──── Bridge Plan Table Row Toggle ──── */ + document.querySelectorAll('.bt-row').forEach(row => { + row.addEventListener('click', (e) => { + // Don't toggle if clicking the checkbox + if (e.target.classList.contains('bt-check')) return; + const group = row.closest('.bt-row-group'); + group.classList.toggle('expanded'); + const toggle = row.querySelector('.bt-toggle'); + toggle.textContent = group.classList.contains('expanded') ? '▾' : '▸'; + }); + }); + + /* ──── Bridge Plan Checkbox Toggle ──── */ + document.querySelectorAll('.bt-check').forEach(check => { + check.addEventListener('click', (e) => { + e.stopPropagation(); + const row = check.closest('.bt-row'); + const isSelected = row.classList.contains('selected'); + row.classList.toggle('selected', !isSelected); + check.textContent = isSelected ? '☐' : '☑'; + check.classList.toggle('unchecked', isSelected); + + // Also update kernel selections inside + const group = row.closest('.bt-row-group'); + group.querySelectorAll('.kernel-row').forEach(kr => { + kr.classList.toggle('selected', !isSelected); + const kc = kr.querySelector('.kernel-check'); + kc.textContent = isSelected ? '☐' : '☑'; + kc.classList.toggle('unchecked', isSelected); + kr.querySelector('.kernel-name').classList.toggle('muted', isSelected); + }); + + updateBridgePlanSummary(); + }); + }); + + /* ──── Kernel Row Checkbox Toggle ──── */ + document.querySelectorAll('.bt-kernels .kernel-row').forEach(kr => { + kr.addEventListener('click', () => { + const isSelected = kr.classList.contains('selected'); + kr.classList.toggle('selected', !isSelected); + const kc = kr.querySelector('.kernel-check'); + kc.textContent = isSelected ? '☐' : '☑'; + kc.classList.toggle('unchecked', isSelected); + kr.querySelector('.kernel-name').classList.toggle('muted', isSelected); + updateBridgePlanSummary(); + }); + }); + + /* ──── Saving data per plan ──── */ + const PLAN_SAVING = { p1: 171.5, p2: 26.0, p3: 19.1, p4: 10.6, p5: 4.7 }; + const E2E_BASELINE = 714.8; + + function updateBridgePlanSummary() { + const selectedKernels = []; + let totalSaving = 0; + const seenPlans = new Set(); + document.querySelectorAll('.bt-kernels .kernel-row.selected').forEach(kr => { + const name = kr.querySelector('.kernel-name').textContent.trim(); + selectedKernels.push(name); + const plan = kr.closest('.bt-row-group').dataset.plan; + if (!seenPlans.has(plan)) { + seenPlans.add(plan); + totalSaving += PLAN_SAVING[plan] || 0; + } + }); + const planCount = seenPlans.size; + const pctSaving = ((totalSaving / E2E_BASELINE) * 100).toFixed(1); + const afterTime = (E2E_BASELINE - totalSaving).toFixed(1); + + const summaryLine = document.querySelector('.plan-summary .summary-line'); + const summaryDetail = document.querySelector('.plan-summary .summary-detail'); + if (summaryLine) { + summaryLine.textContent = `💡 ${selectedKernels.length} kernels selected from ${planCount} plans · Total est. saving: -${totalSaving.toFixed(1)}ms (${pctSaving}%)`; + } + if (summaryDetail) { + summaryDetail.textContent = selectedKernels.length > 0 + ? `Selected → ${selectedKernels.join(', ')}` + : 'No kernels selected'; + } + + // Update E2E comparison row + const beforeEl = document.getElementById('e2e-before-val'); + const afterEl = document.getElementById('e2e-after-val'); + const deltaEl = document.getElementById('e2e-delta-val'); + if (beforeEl) beforeEl.textContent = `${E2E_BASELINE}ms`; + if (afterEl) afterEl.textContent = `${afterTime}ms`; + if (deltaEl) deltaEl.textContent = `-${totalSaving.toFixed(1)}ms (${pctSaving}%)`; + + // Update kernel dropdown options to match selected kernels + updateKernelDropdownOptions(selectedKernels); + } + + function updateKernelDropdownOptions(selectedKernels) { + const menu = document.getElementById('opt-kernel-menu'); + const valSpan = document.querySelector('#opt-dd-kernel .dd-value'); + if (!menu || !valSpan) return; + + if (selectedKernels.length === 0) { + menu.innerHTML = '
No kernels selected
'; + valSpan.textContent = 'No kernels selected'; + return; + } + + menu.innerHTML = selectedKernels.map((name, i) => + `
${name}
` + ).join(''); + + // If current value not in list, select first + if (!selectedKernels.includes(valSpan.textContent.trim())) { + valSpan.textContent = selectedKernels[0]; + updateKernelDetail(selectedKernels[0]); + } + + // Rebind click events + menu.querySelectorAll('.dd-item').forEach(item => { + item.addEventListener('click', () => { + menu.querySelectorAll('.dd-item').forEach(i => i.classList.remove('active')); + item.classList.add('active'); + valSpan.textContent = item.dataset.val; + menu.classList.remove('open'); + updateKernelDetail(item.dataset.val); + }); + }); + } + + /* ──── Start GEAK Agent Button (opens settings first) ──── */ + const btnGeakFull = document.querySelector('#opt-bridge-plan-card .btn-full, #page-optimization .btn-full'); + const geakOverlay = document.getElementById('geak-settings-overlay'); + const geakApplyBtn = document.getElementById('geak-settings-apply'); + const geakCancelBtn = document.getElementById('geak-settings-cancel'); + const geakCloseBtn = document.getElementById('geak-settings-close'); + + function closeGeakSettings() { + if (geakOverlay) geakOverlay.style.display = 'none'; + } + + if (btnGeakFull && geakOverlay) { + btnGeakFull.addEventListener('click', () => { + // Open settings modal instead of directly starting + geakOverlay.style.display = ''; + }); + } + + // Cancel / Close + if (geakCancelBtn) geakCancelBtn.addEventListener('click', closeGeakSettings); + if (geakCloseBtn) geakCloseBtn.addEventListener('click', closeGeakSettings); + if (geakOverlay) { + geakOverlay.addEventListener('click', e => { + if (e.target === geakOverlay) closeGeakSettings(); + }); + } + + /* ──── GEAK Model Dropdown ──── */ + const geakModelTrigger = document.getElementById('geak-model-trigger'); + const geakModelDropdown = document.getElementById('geak-model-dropdown'); + const geakModelValue = document.getElementById('geak-model-value'); + if (geakModelTrigger && geakModelDropdown) { + + geakModelTrigger.addEventListener('click', e => { + e.stopPropagation(); + const isOpen = geakModelDropdown.classList.toggle('open'); + geakModelTrigger.classList.toggle('open', isOpen); + }); + + geakModelDropdown.addEventListener('click', e => { + e.stopPropagation(); + const opt = e.target.closest('.geak-model-option'); + if (!opt) return; + geakModelDropdown.querySelectorAll('.geak-model-option').forEach(o => o.classList.remove('active')); + opt.classList.add('active'); + if (geakModelValue) geakModelValue.textContent = opt.dataset.model; + geakModelDropdown.classList.remove('open'); + geakModelTrigger.classList.remove('open'); + }); + + document.addEventListener('click', e => { + if (geakModelTrigger.contains(e.target) || geakModelDropdown.contains(e.target)) return; + geakModelDropdown.classList.remove('open'); + geakModelTrigger.classList.remove('open'); + }); + } + + // Apply & Start + if (geakApplyBtn) { + geakApplyBtn.addEventListener('click', () => { + closeGeakSettings(); + + // Read parallelism setting + const parallelVal = parseInt(document.getElementById('geak-set-parallel')?.value || '1', 10); + + // Show all GEAK agent cards + document.querySelectorAll('.geak-agent-card').forEach(card => { + card.classList.remove('opt-hidden'); + card.classList.add('opt-visible'); + }); + + // Apply parallelism-dependent state to kernel 2 (fp4_dequant_gemm) + applyParallelismState(parallelVal); + + // Update status items to match selected kernels + updateStatusVisibility(); + + // Visual feedback + if (btnGeakFull) { + btnGeakFull.textContent = '✅ GEAK Agent Started!'; + btnGeakFull.classList.add('btn-success'); + setTimeout(() => { + btnGeakFull.textContent = '🚀 Start GEAK Agent'; + btnGeakFull.classList.remove('btn-success'); + }, 2000); + } + }); + } + + /* ──── Parallelism: control kernel 2 state ──── */ + function applyParallelismState(parallelVal) { + const geakItems = document.querySelectorAll('#geak-merged-status-body .geak-expand-item'); + const item2 = geakItems[1]; // fp4_dequant_gemm + if (!item2) return; + + const statusLabel = item2.querySelector('.geak-status-label'); + const subSpan = item2.querySelector('.geak-item-sub'); + const detailChips = item2.querySelector('.geak-expand-detail'); + const pipeSteps = item2.querySelectorAll('.pipe-step'); + const pipeConnectors = item2.querySelectorAll('.pipe-step-connector'); + + // Header subtitle for GEAK Strategies card + const cardSub = document.querySelector('#geak-strategies-merged .card-subtitle'); + + if (parallelVal >= 2) { + // ── Parallelism ≥ 2: fp4_dequant_gemm → Validated ── + item2.classList.remove('processing'); + item2.classList.add('validated'); + + if (statusLabel) { + statusLabel.className = 'geak-status-label validated'; + statusLabel.textContent = 'Validated ✅'; + } + if (subSpan) subSpan.textContent = 'MoE GEMM Opt. · 2m 08s'; + + // Pipeline: Ready ✓ → Sent ✓ → Processing ✓ → Validated ✓ → Merged ○ + const stepData = [ + { state: 'done', icon: '✓', name: 'Ready', time: '00:00' }, + { state: 'done', icon: '✓', name: 'Sent', time: '00:08' }, + { state: 'done', icon: '✓', name: 'Processing', time: '01:20' }, + { state: 'done', icon: '✓', name: 'Validated', time: '02:08' }, + { state: 'pending', icon: '○', name: 'Merged', time: '—' } + ]; + pipeSteps.forEach((step, i) => { + if (!stepData[i]) return; + const d = stepData[i]; + step.className = `pipe-step ${d.state}`; + const iconEl = step.querySelector('.pipe-step-icon'); + if (iconEl) { + iconEl.className = `pipe-step-icon ${d.state}`; + iconEl.classList.remove('pulsing'); + iconEl.textContent = d.icon; + } + const nameEl = step.querySelector('.pipe-step-name'); + if (nameEl) nameEl.textContent = d.name; + const timeEl = step.querySelector('.pipe-step-time'); + if (timeEl) timeEl.textContent = d.time; + }); + pipeConnectors.forEach((conn, i) => { + conn.className = i < 3 ? 'pipe-step-connector done' : 'pipe-step-connector pending'; + }); + + // Detail chips: show validated results + if (detailChips) { + detailChips.innerHTML = ` + BW: 28% (pre-opt) + Est. -26.0ms + Speedup: 1.8× + E2E: 7.2% ▲ + `; + } + + // Update card subtitle + if (cardSub) cardSub.textContent = '3 kernels · 2 validated · 0 processing · 1 sent'; + + } else { + // ── Parallelism = 1: fp4_dequant_gemm stays Processing ── + item2.classList.remove('validated'); + item2.classList.add('processing'); + + if (statusLabel) { + statusLabel.className = 'geak-status-label processing'; + statusLabel.textContent = 'Processing 🔄'; + } + if (subSpan) subSpan.textContent = 'MoE GEMM Opt. · 1m 12s'; + + // Pipeline: Ready ✓ → Sent ✓ → Processing ⟳ → Validated ○ → Merged ○ + const stepData = [ + { state: 'done', icon: '✓', name: 'Ready', time: '00:00' }, + { state: 'done', icon: '✓', name: 'Sent', time: '00:08' }, + { state: 'active', icon: '⟳', name: 'Processing', time: '01:12…', pulsing: true }, + { state: 'pending', icon: '○', name: 'Validated', time: '—' }, + { state: 'pending', icon: '○', name: 'Merged', time: '—' } + ]; + pipeSteps.forEach((step, i) => { + if (!stepData[i]) return; + const d = stepData[i]; + step.className = `pipe-step ${d.state}`; + const iconEl = step.querySelector('.pipe-step-icon'); + if (iconEl) { + iconEl.className = `pipe-step-icon ${d.state}`; + if (d.pulsing) iconEl.classList.add('pulsing'); + iconEl.textContent = d.icon; + } + const nameEl = step.querySelector('.pipe-step-name'); + if (nameEl) nameEl.textContent = d.name; + const timeEl = step.querySelector('.pipe-step-time'); + if (timeEl) timeEl.textContent = d.time; + }); + pipeConnectors.forEach((conn, i) => { + if (i < 1) conn.className = 'pipe-step-connector done'; + else if (i === 1) conn.className = 'pipe-step-connector active'; + else conn.className = 'pipe-step-connector pending'; + }); + + // Detail chips: show in-progress state + if (detailChips) { + detailChips.innerHTML = ` + Fuse dequant + GEMM + Est. -26.0ms + Iter 3/8 + `; + } + + // Update card subtitle + if (cardSub) cardSub.textContent = '3 kernels · 1 validated · 1 processing · 1 sent'; + } + } + + function updateStatusVisibility() { + const selectedKernels = []; + document.querySelectorAll('.bt-kernels .kernel-row.selected').forEach(kr => { + selectedKernels.push(kr.querySelector('.kernel-name').textContent.trim()); + }); + + // Show/hide GEAK status items based on selection + document.querySelectorAll('.geak-expand-item').forEach(item => { + const name = item.querySelector('.geak-item-name').textContent.trim(); + if (selectedKernels.includes(name)) { + item.style.display = ''; + } else { + item.style.display = 'none'; + } + }); + + // Update kernel dropdown + updateKernelDropdownOptions(selectedKernels); + } + + /* ──── Optimization Model Search Dropdown ──── */ + const optModelDD = document.querySelector('#opt-dd-model'); + if (optModelDD) { + const trigger = optModelDD.querySelector('.dropdown-trigger'); + const menu = optModelDD.querySelector('.dropdown-menu'); + const searchInput = menu ? menu.querySelector('.search-dropdown-input') : null; + const searchList = menu ? menu.querySelector('.search-dropdown-list') : null; + + if (trigger && menu) { + trigger.addEventListener('click', e => { + e.stopPropagation(); + menu.classList.toggle('open'); + if (menu.classList.contains('open') && searchInput) { + searchInput.focus(); + searchInput.value = ''; + // Show all items + if (searchList) searchList.querySelectorAll('.dd-item').forEach(i => i.style.display = ''); + } + }); + + // Search filtering + if (searchInput && searchList) { + searchInput.addEventListener('input', () => { + const query = searchInput.value.toLowerCase(); + searchList.querySelectorAll('.dd-item').forEach(item => { + const text = item.textContent.toLowerCase(); + item.style.display = text.includes(query) ? '' : 'none'; + }); + }); + searchInput.addEventListener('click', e => e.stopPropagation()); + } + + // Item selection + if (searchList) { + searchList.querySelectorAll('.dd-item').forEach(item => { + item.addEventListener('click', () => { + searchList.querySelectorAll('.dd-item').forEach(i => i.classList.remove('active')); + item.classList.add('active'); + const ddValue = optModelDD.querySelector('.dd-value'); + if (ddValue) { + ddValue.textContent = item.dataset.val; + ddValue.classList.remove('dd-placeholder'); + } + menu.classList.remove('open'); + + // Show version info + const versionEl = document.getElementById('opt-bridge-version'); + if (versionEl) { + const fw = item.dataset.fw || 'SGLang'; + const hw = item.dataset.hw || 'MI355X'; + const prec = item.dataset.prec || 'FP4'; + versionEl.textContent = `${fw} · ${hw} · ${prec} — 2026-03-04`; + versionEl.style.display = ''; + } + + // Update Bridge Plan title and show content + const bpTitle = document.getElementById('opt-bridge-plan-title'); + if (bpTitle) bpTitle.textContent = `Optimization Plan — ${item.dataset.val}`; + const bpSub = document.getElementById('opt-bridge-plan-sub'); + const bpEmpty = document.getElementById('bridge-plan-empty'); + if (bpEmpty) bpEmpty.style.display = 'none'; + const bpContent = document.getElementById('bridge-plan-content'); + const bpBuilding = document.getElementById('bridge-plan-building'); + + const isGptOss = item.dataset.val.toLowerCase().includes('gpt-oss'); + if (isGptOss) { + // Show building state for gpt-oss + if (bpContent) bpContent.style.display = 'none'; + if (bpBuilding) bpBuilding.style.display = ''; + if (bpSub) bpSub.textContent = 'Plan is being built — kernel profiling in progress'; + // Hide GEAK agent cards since plan is building + document.querySelectorAll('.geak-agent-card').forEach(card => { + card.classList.add('opt-hidden'); + card.classList.remove('opt-visible'); + }); + } else { + // Show normal plan content + if (bpContent) bpContent.style.display = ''; + if (bpBuilding) bpBuilding.style.display = 'none'; + if (bpSub) bpSub.textContent = '5 optimization items · Click row to expand kernel details'; + } + + // Update E2E card subtitle to match selected model + const e2eSub = document.getElementById('opt-e2e-subtitle'); + if (e2eSub) { + const fw = item.dataset.fw || 'SGLang'; + const hw = item.dataset.hw || 'MI355X'; + const prec = item.dataset.prec || 'FP4'; + e2eSub.textContent = `${item.dataset.val} · ${fw} · ${hw} · ${prec} · 1k/1k · conc=4`; + } + }); + }); + } + } + } + + /* ──── Kernel Detail Dropdown ──── */ + const optKernelDD = document.querySelector('#opt-dd-kernel'); + if (optKernelDD) { + const trigger = optKernelDD.querySelector('.dropdown-trigger'); + const menu = optKernelDD.querySelector('.dropdown-menu'); + if (trigger && menu) { + trigger.addEventListener('click', e => { + e.stopPropagation(); + menu.classList.toggle('open'); + }); + menu.querySelectorAll('.dd-item').forEach(item => { + item.addEventListener('click', () => { + menu.querySelectorAll('.dd-item').forEach(i => i.classList.remove('active')); + item.classList.add('active'); + optKernelDD.querySelector('.dd-value').textContent = item.dataset.val; + menu.classList.remove('open'); + updateKernelDetail(item.dataset.val); + }); + }); + } + } + + /* Close opt dropdowns on outside click */ + document.addEventListener('click', () => { + document.querySelectorAll('#opt-dd-model .dropdown-menu.open, #opt-dd-kernel .dropdown-menu.open').forEach(m => m.classList.remove('open')); + }); + + /* ──── GEAK Status Expand/Collapse ──── */ + document.querySelectorAll('.geak-expand-header').forEach(header => { + header.addEventListener('click', () => { + const item = header.closest('.geak-expand-item'); + const isExpanded = item.classList.contains('expanded'); + item.classList.toggle('expanded', !isExpanded); + const toggle = header.querySelector('.geak-expand-toggle'); + toggle.textContent = isExpanded ? '▸' : '▾'; + }); + }); + + /* ──── Inline Strategy Toggle (under each pipeline item) ──── */ + document.querySelectorAll('.inline-strategy-toggle-btn').forEach(btn => { + btn.addEventListener('click', e => { + e.stopPropagation(); + const targetId = btn.dataset.target; + const body = document.getElementById(targetId); + const icon = btn.querySelector('.inline-strategy-toggle-icon'); + if (body) { + const isHidden = body.style.display === 'none'; + body.style.display = isHidden ? '' : 'none'; + if (icon) icon.textContent = isHidden ? '▾' : '▸'; + } + }); + }); + + /* ──── Report Model Expand/Collapse ──── */ + document.querySelectorAll('.report-model-header').forEach(header => { + header.addEventListener('click', () => { + const model = header.closest('.report-model'); + const isExpanded = model.classList.contains('expanded'); + model.classList.toggle('expanded', !isExpanded); + model.classList.toggle('collapsed', isExpanded); + header.classList.toggle('active', !isExpanded); + const toggle = header.querySelector('.report-toggle'); + toggle.textContent = model.classList.contains('expanded') ? '▾' : '▸'; + const body = model.querySelector('.report-model-body'); + if (body) body.style.display = model.classList.contains('expanded') ? '' : 'none'; + }); + }); + + /* ──── Report Model Checkbox (batch select) ──── */ + function updateBatchCount() { + const checked = document.querySelectorAll('.rpt-model-cb:checked'); + const batchActions = document.getElementById('report-batch-actions'); + const batchCount = document.getElementById('batch-count'); + if (batchActions) { + batchActions.style.display = checked.length > 0 ? '' : 'none'; + } + if (batchCount) { + batchCount.textContent = `${checked.length} selected`; + } + } + + document.querySelectorAll('.rpt-model-cb').forEach(cb => { + cb.addEventListener('change', updateBatchCount); + }); + + const btnBatchExport = document.getElementById('btn-batch-export'); + if (btnBatchExport) { + btnBatchExport.addEventListener('click', () => { + const checked = document.querySelectorAll('.rpt-model-cb:checked'); + const names = []; + checked.forEach(cb => { + const header = cb.closest('.report-model-header'); + const nameEl = header ? header.querySelector('.report-model-name') : null; + if (nameEl) names.push(nameEl.textContent.trim()); + }); + if (names.length === 0) return; + // Visual feedback + btnBatchExport.textContent = `✅ Exporting ${names.length} report(s)…`; + btnBatchExport.disabled = true; + setTimeout(() => { + alert('Batch export initiated for:\\n\\n' + names.join('\\n')); + btnBatchExport.textContent = '📄 Export Selected Reports (PDF)'; + btnBatchExport.disabled = false; + }, 800); + }); + } + + /* ──── Report Subsection Expand/Collapse (Level 2) ──── */ + document.querySelectorAll('.report-subsection-header[data-sub-toggle]').forEach(header => { + header.addEventListener('click', e => { + e.stopPropagation(); + const sub = header.closest('.report-subsection'); + const isExpanded = sub.classList.contains('expanded'); + sub.classList.toggle('expanded', !isExpanded); + sub.classList.toggle('collapsed', isExpanded); + const toggle = header.querySelector('.report-sub-toggle'); + if (toggle) toggle.textContent = sub.classList.contains('expanded') ? '▾' : '▸'; + const body = sub.querySelector('.report-subsection-body'); + if (body) body.style.display = sub.classList.contains('expanded') ? '' : 'none'; + }); + }); + + /* ══════ REPORT PAGE — EXPOSE LOGIC ══════ */ + + // Data availability registry: which model+precision+ISL/OSL combos have data + const REPORT_DATA_REGISTRY = { + 'gpt-oss 120B': { + FP4: { + '1K / 1K': { gap: '32%→~27% gap', gapPct: 68, rooflineGap: '19%→15%', rooflinePct: 81, status: 'in_progress', + profiling: 'Top3: Attn FP8 32.7%, GEMM 18%, Norm 12%', + kernelOpt: 'Attn +15%, 62.7% WL covered', + expectedE2E: '~8% E2E uplift', + e2ePerf: '', color: 'red' }, + '8K / 1K': { gap: '34%→~31% gap', gapPct: 66, rooflineGap: '22%', rooflinePct: 78, status: 'in_progress', + profiling: 'Top3: Attn FP8 35.1%, GEMM 16%, KV Cache 11%', + kernelOpt: 'Attn +12%, 62.1% WL covered', + expectedE2E: '~5% estimated', + e2ePerf: '', color: 'red' }, + '1K / 8K': { gap: '35% gap', gapPct: 65, rooflineGap: '26%', rooflinePct: 74, status: 'in_progress', + profiling: 'Top3: Attn FP8 30.4%, GEMM 19%, Decode 14%', + kernelOpt: '2/5 kernels, 30.4% WL', + expectedE2E: '—', + e2ePerf: '', color: 'red' } + }, + FP8: { + '1K / 1K': { gap: '26%→~17% gap', gapPct: 74, rooflineGap: '13%→9%', rooflinePct: 87, status: 'done', + profiling: 'Top3: Attn FP8 34.2%, GEMM 17%, Norm 11%', + kernelOpt: 'Attn +12%, 62.2% WL covered', + expectedE2E: '~12% E2E uplift', + e2ePerf: '~12%', color: 'yellow' }, + '8K / 1K': { gap: '28%→~22% gap', gapPct: 72, rooflineGap: '17%', rooflinePct: 83, status: 'in_progress', + profiling: 'Top3: Attn FP8 36.0%, GEMM 15%, KV Cache 10%', + kernelOpt: 'Attn +10%, 61.0% WL covered', + expectedE2E: '~8% estimated', + e2ePerf: '', color: 'red' }, + '1K / 8K': { gap: '29% gap', gapPct: 71, rooflineGap: '21%', rooflinePct: 79, status: 'in_progress', + profiling: 'Top3: Attn FP8 31.8%, GEMM 18%, Decode 13%', + kernelOpt: '2/5 kernels, 31.8% WL', + expectedE2E: '—', + e2ePerf: '', color: 'red' } + } + }, + 'DeepSeek R1 0528': { + FP4: { + '1K / 1K': { gap: '22%→19% gap', gapPct: 78, rooflineGap: '22%→18%', rooflinePct: 78, status: 'done', + profiling: 'Top3: MoE 31%, MLA Attn 13%, Attn GEMMs 13%', + kernelOpt: 'MoE +66%, MLA Attn, 5 items, 64.9% WL', + expectedE2E: '+3.2% validated', + e2ePerf: '+3.2%', color: 'yellow' }, + '8K / 1K': { gap: '27% gap', gapPct: 73, rooflineGap: '26%', rooflinePct: 74, status: 'in_progress', + profiling: 'Top3: MoE 28%, MLA Attn 18%, Attn GEMMs 15%', + kernelOpt: '2/5 items, MoE+MLA, 46% WL', + expectedE2E: '~2% estimated', + e2ePerf: '', color: 'red' }, + '1K / 8K': { gap: '25% gap', gapPct: 75, rooflineGap: '29%', rooflinePct: 71, status: 'in_progress', + profiling: 'Top3: MoE 34%, MLA Attn 22%, Comm 15%', + kernelOpt: '1/5 items, MoE, 34% WL', + expectedE2E: '—', + e2ePerf: '', color: 'red' } + }, + FP8: { + '1K / 1K': { gap: '17% gap', gapPct: 83, rooflineGap: '17%', rooflinePct: 83, status: 'in_progress', + profiling: 'Top3: MoE 28%, MLA Attn 14%, Comm 18%', + kernelOpt: '0/5 items, paused', + expectedE2E: '~2% estimated', + e2ePerf: '', color: 'green' }, + '8K / 1K': { gap: '23% gap', gapPct: 77, rooflineGap: '20%', rooflinePct: 80, status: 'in_progress', + profiling: 'Top3: MoE 29%, MLA Attn 16%, Attn GEMMs 14%', + kernelOpt: '1/5 items, MoE, 29% WL', + expectedE2E: '—', + e2ePerf: '', color: 'yellow' }, + '1K / 8K': { gap: '21% gap', gapPct: 79, rooflineGap: '23%', rooflinePct: 77, status: 'in_progress', + profiling: 'Top3: MoE 32%, MLA Attn 20%, Comm 17%', + kernelOpt: '1/5 items, MoE, 32% WL', + expectedE2E: '—', + e2ePerf: '', color: 'yellow' } + } + } + }; + + // NV reference data availability + const NV_REF_DATA = { + 'gpt-oss 120B': { + FP4: { '1K / 1K': true, '8K / 1K': true, '1K / 8K': true }, + FP8: { '1K / 1K': true, '8K / 1K': true, '1K / 8K': true } + }, + 'DeepSeek R1 0528': { + FP4: { '1K / 1K': true, '8K / 1K': true, '1K / 8K': true }, + FP8: { '1K / 1K': true, '8K / 1K': true, '1K / 8K': true } + } + }; + + let exposeHistory = []; + let exposeIdCounter = 0; + + // Report dropdown: update precision based on model (like overview page) + const rptModelDD = document.querySelector('#rpt-dd-model'); + const rptPrecDD = document.querySelector('#rpt-dd-precision'); + + function updateReportPrecision() { + if (!rptModelDD || !rptPrecDD) return; + const model = rptModelDD.querySelector('.dd-value').textContent.trim(); + const menu = rptPrecDD.querySelector('.dropdown-menu'); + const valSpan = rptPrecDD.querySelector('.dd-value'); + + // Both models support FP4 and FP8 + menu.innerHTML = '
● FP4
■ FP8
'; + if (valSpan.textContent !== 'FP4' && valSpan.textContent !== 'FP8') { + valSpan.textContent = 'FP4'; + } + // Re-bind click events for new items + menu.querySelectorAll('.dd-item').forEach(item => { + item.addEventListener('click', () => { + menu.querySelectorAll('.dd-item').forEach(i => i.classList.remove('active')); + item.classList.add('active'); + valSpan.textContent = item.dataset.val; + rptPrecDD.querySelector('.dropdown-menu').style.display = 'none'; + }); + }); + } + + if (rptModelDD) { + rptModelDD.querySelector('.dropdown-menu').addEventListener('click', () => { + setTimeout(updateReportPrecision, 50); + }); + updateReportPrecision(); + } + + function getPhaseChip(value) { + // Legacy status codes + const legacyMap = { + done: '✅ Done', + wip: '🔄 WIP', + paused: '⏸ Paused', + na: '' + }; + if (legacyMap[value]) return legacyMap[value]; + // Numeric / descriptive string — highlight key numbers + if (!value || value === '—') return ''; + // Bold numbers/percentages and key terms for readability + const formatted = value + .replace(/(\d+\.?\d*%)/g, '$1') + .replace(/(\d+\.?\d*×)/g, '$1') + .replace(/(\+\d+\.?\d*%)/g, '$1') + .replace(/(Top3:)/g, '$1') + .replace(/(\d+\/\d+ kernels)/g, '$1') + .replace(/(validated|uplift|covered|estimated)/gi, '$1'); + return `${formatted}`; + } + + function getE2eChip(value) { + if (!value || value === '—') return ''; + // Positive % results → green pill + if (/^[+~]?\d/.test(value) || value.includes('%')) { + return `${value}`; + } + // In Progress / Started → amber pill + if (/progress|started/i.test(value)) { + return `${value}`; + } + // Not started → gray + if (/not started/i.test(value)) { + return `${value}`; + } + return `${value}`; + } + + function getGapColor(pct) { + if (pct >= 100) return 'green'; + if (pct >= 75) return 'yellow'; + return 'red'; + } + + // Track which NV ref columns are visible + let hasAnyNvRef = false; + + function updateNvRefColumns() { + // Check if any exposed item has NV ref + hasAnyNvRef = exposeHistory.some(e => e.nvRef && e.nvRef !== '— None —' && e.nvRef !== ''); + const nvHeader = document.getElementById('st-nvref-header'); + const gapHeader = document.getElementById('st-gap-header'); + if (nvHeader) nvHeader.style.display = hasAnyNvRef ? '' : 'none'; + if (gapHeader) gapHeader.style.display = hasAnyNvRef ? '' : 'none'; + + // Also show/hide per-row NV ref + gap cells + document.querySelectorAll('.st-col.st-nvref').forEach(el => { + el.style.display = hasAnyNvRef ? '' : 'none'; + }); + document.querySelectorAll('.st-col.st-gap').forEach(el => { + el.style.display = hasAnyNvRef ? '' : 'none'; + }); + } + + function addStatusRow(entry) { + const body = document.getElementById('status-body'); + const empty = document.getElementById('status-empty'); + if (empty) empty.style.display = 'none'; + + const idx = body.querySelectorAll('.status-row').length; + const altClass = idx % 2 === 0 ? ' alt' : ''; + const color = entry.color || getGapColor(entry.gapPct || 0); + + const row = document.createElement('div'); + row.className = `status-row${altClass}`; + row.dataset.exposeId = entry.id; + + // NV Reference cell (beside AMD data) + const hasNv = entry.nvRef && entry.nvRef !== '— None —' && entry.nvRef !== ''; + const nvRefCell = hasNv + ? (entry.nvHasData + ? `
${entry.nvRef}Reference
` + : `
${entry.nvRef}Not Found
`) + : `
`; + + // Gap cell (only meaningful when NV ref exists) + const gapContent = entry.notFound + ? 'Not Found' + : entry.gapPct >= 100 + ? `${entry.gap}` + : `${entry.gap}
`; + + // Roofline gap cell + const rooflineColor = entry.rooflinePct >= 90 ? 'green' : entry.rooflinePct >= 75 ? 'yellow' : 'red'; + const rooflineContent = entry.notFound + ? '' + : `${entry.rooflineGap}
`; + + // Conditional: in_progress → show Expected E2E, hide E2E Perf; done → show E2E Perf, hide Expected E2E + const isDone = entry.status === 'done'; + const expectedE2ECell = entry.notFound + ? '' + : isDone + ? '' + : getPhaseChip(entry.expectedE2E); + const e2ePerfCell = entry.notFound + ? 'Not Found' + : isDone + ? getE2eChip(entry.e2ePerf) + : ''; + + row.innerHTML = ` +
+
+
+
${entry.modelName}
+
${entry.configSub}
+
+
+ ${nvRefCell} +
${gapContent}
+
${rooflineContent}
+
${entry.notFound ? '' : getPhaseChip(entry.profiling)}
+
${entry.notFound ? '' : getPhaseChip(entry.kernelOpt)}
+
${expectedE2ECell}
+
${e2ePerfCell}
+ `; + + body.appendChild(row); + updateNvRefColumns(); + } + + function addExposeHistoryItem(entry) { + const list = document.getElementById('expose-history-list'); + const empty = document.getElementById('expose-empty'); + if (empty) empty.style.display = 'none'; + + const statusClass = entry.notFound ? 'not-found' : 'found'; + const statusText = entry.notFound ? 'Not Found' : 'Data Found'; + const nvLabel = (entry.nvRef && entry.nvRef !== '— None —' && entry.nvRef !== '') + ? ` | NV: ${entry.nvRef}` : ''; + + const item = document.createElement('div'); + item.className = 'expose-item'; + item.dataset.exposeId = entry.id; + item.innerHTML = ` + ${entry.modelName} + ${entry.configSub}${nvLabel} + ${statusText} + + `; + + item.querySelector('.ei-remove').addEventListener('click', () => { + removeExposeEntry(entry.id); + }); + + list.appendChild(item); + } + + function removeExposeEntry(id) { + exposeHistory = exposeHistory.filter(e => e.id !== id); + + const histItem = document.querySelector(`.expose-item[data-expose-id="${id}"]`); + if (histItem) histItem.remove(); + + const statusRow = document.querySelector(`.status-row[data-expose-id="${id}"]`); + if (statusRow) statusRow.remove(); + + if (exposeHistory.length === 0) { + const empty = document.getElementById('expose-empty'); + if (empty) empty.style.display = ''; + const statusEmpty = document.getElementById('status-empty'); + if (statusEmpty) statusEmpty.style.display = ''; + // Hide detail reports + const detailCard = document.getElementById('detail-reports-card'); + if (detailCard) detailCard.style.display = 'none'; + } + + updateNvRefColumns(); + updateExposeCounts(); + updateBatchCount(); + } + + function updateExposeCounts() { + const countEl = document.getElementById('expose-count'); + if (countEl) countEl.textContent = `${exposeHistory.length} items`; + + const statusSub = document.getElementById('status-overview-sub'); + if (statusSub) { + statusSub.textContent = exposeHistory.length > 0 + ? `${exposeHistory.length} items exposed · Gap = perf gap vs Nvidia B200 (lower is better)` + : 'No items exposed · Click Expose above to add entries'; + } + } + + // Expose button handler + const btnExpose = document.getElementById('btn-expose'); + if (btnExpose) { + btnExpose.addEventListener('click', () => { + const model = document.querySelector('#rpt-dd-model .dd-value')?.textContent?.trim() || 'gpt-oss 120B'; + const precision = document.querySelector('#rpt-dd-precision .dd-value')?.textContent?.trim() || 'FP4'; + const islOsl = document.querySelector('#rpt-dd-isl-osl .dd-value')?.textContent?.trim() || '1K / 1K'; + const amdConfig = document.querySelector('#rpt-dd-amd .dd-value')?.textContent?.trim() || 'MI355X (SGLang)'; + const nvRefRaw = document.querySelector('#rpt-dd-nv .dd-value')?.textContent?.trim() || ''; + const nvRef = (nvRefRaw === '— None —' || nvRefRaw === '') ? '' : nvRefRaw; + + // Look up data + const registry = REPORT_DATA_REGISTRY[model]; + const precData = registry?.[precision]; + const islData = precData?.[islOsl]; + + const id = ++exposeIdCounter; + const modelName = `${model} (${precision})`; + const configSub = `${islOsl} · ${amdConfig}`; + + // Check NV ref data availability + const nvHasData = nvRef ? (NV_REF_DATA[model]?.[precision]?.[islOsl] || false) : false; + + const entry = { + id, + modelName, + configSub, + nvRef: nvRef, + nvHasData: nvHasData, + notFound: !islData, + gap: islData?.gap || 'Not Found', + gapPct: islData?.gapPct || 0, + rooflineGap: islData?.rooflineGap || '—', + rooflinePct: islData?.rooflinePct || 0, + status: islData?.status || 'in_progress', + profiling: islData?.profiling || 'na', + kernelOpt: islData?.kernelOpt || 'na', + expectedE2E: islData?.expectedE2E || 'na', + e2ePerf: islData?.e2ePerf || '', + color: islData?.color || 'red' + }; + + exposeHistory.push(entry); + addExposeHistoryItem(entry); + addStatusRow(entry); + updateExposeCounts(); + + // Show the matching Per-Model detail section if it exists + const reportModelKey = `${model}||${precision}`; + const reportModelEl = document.querySelector(`.report-model[data-report-model="${reportModelKey}"]`); + if (reportModelEl) { + reportModelEl.style.display = ''; + } + + // Show detail reports card if any Per-Model section is now visible + const detailCard = document.getElementById('detail-reports-card'); + if (detailCard) { + const anyVisible = detailCard.querySelector('.report-model[style=""]') || + detailCard.querySelector('.report-model:not([style*="display:none"])') || + reportModelEl; + if (anyVisible) detailCard.style.display = ''; + } + + // Flash the expose button + btnExpose.textContent = '✅ Exposed!'; + btnExpose.style.background = 'var(--green)'; + setTimeout(() => { + btnExpose.textContent = '🚀 Expose'; + btnExpose.style.background = ''; + }, 1200); + }); + } + + /* ══════════════════════════════════════════════════════ + CHARTS (Chart.js) + ══════════════════════════════════════════════════════ */ + + const AMD_RED = '#e4002b'; + const AMD_RED_LIGHT = 'rgba(228,0,43,0.35)'; + const GREEN = '#22c55e'; + const BLUE = '#3b82f6'; + const GRID_COLOR = 'rgba(243,244,246,1)'; + const TICK_COLOR = '#9ca3af'; + + const commonScaleOpts = { + grid: { color: GRID_COLOR, drawBorder: true, borderColor: '#e5e7eb' }, + ticks: { color: TICK_COLOR, font: { size: 10 } } + }; + + // Store chart instances for later update + let chart1Instance = null; + let chart2Instance = null; + + function getDataForSelection() { + const sel = getSelections(); + const modelData = BENCH_DATA[sel.model]; + if (!modelData) return null; + + const islOslData = modelData[sel.isl_osl]; + if (!islOslData) return null; + + const precisionData = islOslData[sel.precision]; + if (!precisionData) return null; + + // Normalize framework name (TRT-LLM → TRT_LLM) + const fwKey = sel.framework.replace('-', '_'); + const refFwKey = sel.refFramework.replace('-', '_'); + + // Get baseline (AMD), optimized, roofline, and reference data + const hwData = precisionData[sel.hardware]; + const hwOptData = precisionData[sel.hardware + '_OPT']; + const hwRooflineData = precisionData[sel.hardware + '_ROOFLINE']; + const refData = precisionData[sel.refHardware]; + + const baseline = hwData?.[fwKey] || hwData?.[sel.framework] || null; + const optimized = hwOptData?.[fwKey] || hwOptData?.[sel.framework] || null; + const roofline = hwRooflineData?.[fwKey] || hwRooflineData?.[sel.framework] || null; + const reference = refData?.[refFwKey] || refData?.[sel.refFramework] || null; + + return { baseline, optimized, roofline, reference, sel }; + } + + const CYAN = '#06b6d4'; + + function buildChartDatasets(chartType) { + const result = getDataForSelection(); + if (!result) return []; + + const { baseline, optimized, roofline, reference, sel } = result; + const datasets = []; + + // Baseline (AMD, dashed line) + if (baseline) { + datasets.push({ + label: `${sel.hardware} (${sel.framework}) — Baseline`, + data: chartType === 'interactivity' ? baseline.interactivity : baseline.latency, + borderColor: AMD_RED_LIGHT, + backgroundColor: 'transparent', + borderWidth: 2, + borderDash: [6, 4], + pointRadius: 4, + pointBackgroundColor: '#fff', + pointBorderColor: AMD_RED_LIGHT, + pointBorderWidth: 1.5, + showLine: true, + tension: 0.35 + }); + } + + // MI355X Roofline (cyan, dash-dot line — theoretical peak) + if (roofline) { + datasets.push({ + label: `${sel.hardware} (Roofline)`, + data: chartType === 'interactivity' ? roofline.interactivity : roofline.latency, + borderColor: CYAN, + backgroundColor: 'transparent', + borderWidth: 2, + borderDash: [8, 3, 2, 3], + pointRadius: 3.5, + pointBackgroundColor: '#fff', + pointBorderColor: CYAN, + pointBorderWidth: 1.5, + pointStyle: 'triangle', + showLine: true, + tension: 0.35 + }); + } + + // Optimized AMD (solid red) + if (optimized) { + datasets.push({ + label: `${sel.hardware} (${sel.framework}) — Optimized`, + data: chartType === 'interactivity' ? optimized.interactivity : optimized.latency, + borderColor: AMD_RED, + backgroundColor: 'transparent', + borderWidth: 2.5, + pointRadius: 5, + pointBackgroundColor: '#fff', + pointBorderColor: AMD_RED, + pointBorderWidth: 2, + showLine: true, + tension: 0.35 + }); + } + + // Reference (NVIDIA, green solid) + if (reference) { + datasets.push({ + label: `${sel.refHardware} (${sel.refFramework})`, + data: chartType === 'interactivity' ? reference.interactivity : reference.latency, + borderColor: GREEN, + backgroundColor: 'transparent', + borderWidth: 2.5, + pointRadius: 5, + pointBackgroundColor: '#fff', + pointBorderColor: GREEN, + pointBorderWidth: 2, + showLine: true, + tension: 0.35 + }); + } + + return datasets; + } + + function getYMax(datasets) { + let max = 0; + datasets.forEach(ds => { + ds.data.forEach(pt => { + if (pt.y > max) max = pt.y; + }); + }); + return Math.ceil(max / 2000) * 2000 + 2000; + } + + function getXMax(datasets) { + let max = 0; + datasets.forEach(ds => { + ds.data.forEach(pt => { + if (pt.x > max) max = pt.x; + }); + }); + return Math.ceil(max / 50) * 50 + 50; + } + + function updateLegendBox(legendBox, sel, hasBaseline, hasOptimized, hasReference, hasRoofline) { + if (!legendBox) return; + const items = legendBox.querySelector('.legend-items'); + if (!items) return; + items.innerHTML = ''; + + if (hasReference) { + items.innerHTML += `${sel.refHardware} (${sel.refFramework})`; + } + if (hasRoofline) { + items.innerHTML += `${sel.hardware} (Roofline)`; + } + if (hasOptimized) { + items.innerHTML += `${sel.hardware} (${sel.framework}) — Optimized`; + } + if (hasBaseline) { + items.innerHTML += `${sel.hardware} (${sel.framework}) — Baseline`; + } + } + + function createOrUpdateChart1() { + const ctx1 = document.getElementById('chart-throughput-interactivity'); + if (!ctx1) return; + + const datasets = buildChartDatasets('interactivity'); + const sel = getSelections(); + const yMax = datasets.length > 0 ? getYMax(datasets) : 24000; + const xMax = datasets.length > 0 ? getXMax(datasets) : 400; + + const config = { + type: 'scatter', + data: { datasets }, + options: { + responsive: true, + maintainAspectRatio: false, + plugins: { + legend: { display: false }, + tooltip: { + backgroundColor: 'rgba(26,26,46,0.9)', + titleFont: { size: 12 }, + bodyFont: { size: 11 }, + callbacks: { + label: ctx => `${ctx.dataset.label}: ${ctx.parsed.y.toLocaleString()} tok/s/gpu @ ${ctx.parsed.x} tok/s/user` + } + } + }, + scales: { + x: { + ...commonScaleOpts, + title: { display: true, text: 'Interactivity (tok/s/user)', color: '#6b7280', font: { size: 11 } }, + min: 0, + max: xMax, + ticks: { ...commonScaleOpts.ticks, stepSize: 50 } + }, + y: { + ...commonScaleOpts, + title: { display: true, text: 'Token Throughput per GPU (tok/s/gpu)', color: '#6b7280', font: { size: 11 } }, + min: 0, + max: yMax, + ticks: { + ...commonScaleOpts.ticks, + stepSize: 2000, + callback: v => v >= 1000 ? (v / 1000).toFixed(0) + 'k' : v + } + } + } + } + }; + + if (chart1Instance) { + chart1Instance.destroy(); + } + chart1Instance = new Chart(ctx1, config); + + // Update legend + const result = getDataForSelection(); + const legendBox = document.querySelector('#card-chart1 .chart-legend-box'); + updateLegendBox(legendBox, sel, !!result?.baseline, !!result?.optimized, !!result?.reference, !!result?.roofline); + } + + function createOrUpdateChart2() { + const ctx2 = document.getElementById('chart-throughput-latency'); + if (!ctx2) return; + + const datasets = buildChartDatasets('latency'); + const sel = getSelections(); + const yMax = datasets.length > 0 ? getYMax(datasets) : 20000; + + // Compute x max for latency + let latXMax = 0; + datasets.forEach(ds => { + ds.data.forEach(pt => { + if (pt.x > latXMax) latXMax = pt.x; + }); + }); + latXMax = Math.ceil(latXMax / 5) * 5 + 5; + + const config = { + type: 'scatter', + data: { datasets }, + options: { + responsive: true, + maintainAspectRatio: false, + plugins: { + legend: { display: false }, + tooltip: { + backgroundColor: 'rgba(26,26,46,0.9)', + callbacks: { + label: ctx => `${ctx.dataset.label}: ${ctx.parsed.y.toLocaleString()} tok/s/gpu @ ${ctx.parsed.x}s latency` + } + } + }, + scales: { + x: { + ...commonScaleOpts, + title: { display: true, text: 'End-to-end Latency (s)', color: '#6b7280', font: { size: 11 } }, + min: 0, + max: latXMax, + ticks: { ...commonScaleOpts.ticks, stepSize: 2 } + }, + y: { + ...commonScaleOpts, + title: { display: true, text: 'Token Throughput per GPU (tok/s/gpu)', color: '#6b7280', font: { size: 11 } }, + min: 0, + max: yMax, + ticks: { + ...commonScaleOpts.ticks, + stepSize: 2000, + callback: v => v >= 1000 ? (v / 1000).toFixed(0) + 'k' : v + } + } + } + } + }; + + if (chart2Instance) { + chart2Instance.destroy(); + } + chart2Instance = new Chart(ctx2, config); + + // Update legend + const result = getDataForSelection(); + const legendBox = document.querySelector('#card-chart2 .chart-legend-box'); + updateLegendBox(legendBox, sel, !!result?.baseline, !!result?.optimized, !!result?.reference, !!result?.roofline); + } + + function updateCharts() { + createOrUpdateChart1(); + createOrUpdateChart2(); + } + + /* ──── Chart 3 & 4: Dynamic Kernel Charts ──── */ + let chart3Instance = null; + let chart4Instance = null; + + function updateKernelCharts() { + const sel = getSelections(); + const modelKernel = KERNEL_DATA[sel.model]; + if (!modelKernel) return; + const precKernel = modelKernel[sel.precision] || modelKernel['FP4']; + if (!precKernel) return; + // Lookup by ISL/OSL, fallback to first available + const kd = precKernel[sel.isl_osl] || precKernel[Object.keys(precKernel)[0]]; + if (!kd) return; + + // Update kernel card subtitle + const kernelSubtitle = document.querySelector('#card-chart3 .card-subtitle'); + if (kernelSubtitle) kernelSubtitle.textContent = kd.subtitle; + + // Chart 3: Kernel Stack (Horizontal Stacked Bar) + const ctx3 = document.getElementById('chart-kernel-stack'); + if (ctx3) { + if (chart3Instance) chart3Instance.destroy(); + chart3Instance = new Chart(ctx3, { + type: 'bar', + data: { + labels: kd.stack.labels, + datasets: [ + { label: 'ATTN', data: kd.stack.ATTN, backgroundColor: '#3b82f6' }, + { label: 'GEMM/MOE', data: kd.stack.GEMM_MOE, backgroundColor: '#f97316' }, + { label: 'AR/NORM', data: kd.stack.AR_NORM, backgroundColor: '#22c55e' }, + { label: 'QUANT', data: kd.stack.QUANT, backgroundColor: '#ef4444' }, + { label: 'TOPK', data: kd.stack.TOPK, backgroundColor: '#8b5cf6' }, + { label: 'ACT', data: kd.stack.ACT, backgroundColor: '#92400e' }, + { label: 'CACHE', data: kd.stack.CACHE, backgroundColor: '#f9a8d4' }, + { label: 'other', data: kd.stack.other, backgroundColor: '#9ca3af' } + ] + }, + options: { + responsive: true, + maintainAspectRatio: false, + indexAxis: 'y', + plugins: { + legend: { display: false }, + tooltip: { + callbacks: { + label: ctx => `${ctx.dataset.label}: ${ctx.parsed.x}k μs` + } + } + }, + scales: { + x: { + stacked: true, + ...commonScaleOpts, + title: { display: true, text: 'Duration (μs × 1000)', color: '#6b7280', font: { size: 10 } }, + ticks: { + ...commonScaleOpts.ticks, + callback: v => v + 'k' + } + }, + y: { + stacked: true, + ...commonScaleOpts, + ticks: { + ...commonScaleOpts.ticks, + font: { size: 10, weight: 'bold' } + } + } + } + } + }); + } + + // Chart 4: Kernel Side-by-Side (Grouped Vertical Bar) + const ctx4 = document.getElementById('chart-kernel-sidebyside'); + if (ctx4) { + if (chart4Instance) chart4Instance.destroy(); + chart4Instance = new Chart(ctx4, { + type: 'bar', + data: { + labels: kd.sideBySide.labels, + datasets: [ + { + label: 'MI355X (Before)', + data: kd.sideBySide.before, + backgroundColor: 'rgba(228,0,43,0.4)' + }, + { + label: 'MI355X (Roofline)', + data: kd.sideBySide.roofline, + backgroundColor: '#06b6d4', + borderColor: '#0891b2', + borderWidth: 1, + borderDash: [4, 2] + }, + { + label: 'MI355X (After Opt)', + data: kd.sideBySide.after, + backgroundColor: '#e4002b' + }, + { + label: 'B200 (Reference)', + data: kd.sideBySide.reference, + backgroundColor: '#22c55e' + } + ] + }, + options: { + responsive: true, + maintainAspectRatio: false, + plugins: { + legend: { display: false }, + tooltip: { + callbacks: { + label: ctx => `${ctx.dataset.label}: ${ctx.parsed.y}k μs` + } + } + }, + scales: { + x: { + ...commonScaleOpts, + title: { display: true, text: 'Kernel Categories', color: '#6b7280', font: { size: 10 } }, + ticks: { ...commonScaleOpts.ticks, maxRotation: 35, minRotation: 35 } + }, + y: { + ...commonScaleOpts, + title: { display: true, text: 'Duration (μs × 1000)', color: '#6b7280', font: { size: 10 } }, + ticks: { + ...commonScaleOpts.ticks, + callback: v => v + 'k' + } + } + } + } + }); + } + } + + /* ──── Initial Charts ──── */ + createOrUpdateChart1(); + createOrUpdateChart2(); + updateKernelCharts(); + + + /* ══════════════════════════════════════════════════════════════ + ANALYSIS PAGE — Dynamic Data + Dropdowns + ══════════════════════════════════════════════════════════════ */ + + /* ---------- Simulated ANALYSIS_DATA ---------- */ + const ANALYSIS_DATA = { + 'DeepSeek R1 0528': { + 'FP4': { + '1K / 1K': { + '4': { + e2e: { input: '50.65', output: '50.16', ttft: '90.69ms', itl: '9.53ms', latency: '8,940ms', gap: '22% gap', rooflineGap: '22%' }, + prefill: [ + { label: 'MLA (Attn)', pct: 13, ms: 11.8, color: 'rgba(239,68,68,0.6)', sev: 'hot' }, + { label: 'Attn GEMMs', pct: 13, ms: 11.8, color: 'rgba(59,130,246,0.6)', sev: 'med' }, + { label: 'MoE (Fused SGLang)', pct: 31, ms: 28.1, color: 'rgba(245,158,11,0.6)', sev: 'hot' }, + { label: 'Communication', pct: 18, ms: 16.3, color: 'rgba(139,92,246,0.6)', sev: '' }, + { label: 'Activ. & Norm', pct: 12, ms: 10.9, color: 'rgba(20,184,166,0.6)', sev: '' }, + { label: 'Memory Ops', pct: 13, ms: 11.8, color: 'rgba(107,114,128,0.6)', sev: '' } + ], + decode: [ + { label: 'MLA (Attn)', pct: 18, ms: 1.72, color: 'rgba(239,68,68,0.6)', sev: 'hot' }, + { label: 'Attn GEMMs', pct: 15, ms: 1.43, color: 'rgba(59,130,246,0.6)', sev: 'med' }, + { label: 'MoE (Fused SGLang)', pct: 28, ms: 2.67, color: 'rgba(245,158,11,0.6)', sev: 'hot' }, + { label: 'Communication', pct: 16, ms: 1.52, color: 'rgba(139,92,246,0.6)', sev: '' }, + { label: 'Activ. & Norm', pct: 12, ms: 1.14, color: 'rgba(20,184,166,0.6)', sev: '' }, + { label: 'Memory Ops', pct: 11, ms: 1.05, color: 'rgba(107,114,128,0.6)', sev: '' } + ], + insights: [ + { sev: 'high', text: 'MoE expert dispatch severely fragmented — 256-expert top-8 launches up to 256 rocBLAS Cijk kernels per layer × 61 layers per forward pass. BW utilization only 31% of 8 TB/s peak due to small per-expert M dimension and FP4 dequant overhead (~15µs/call).', source: 'Trace' }, + { sev: 'high', text: 'flash_attn_fwd_v2 memory-bound at 38% HBM peak. Compressed KV (d_c=512) saves cache capacity but adds 61 absorb_attn + 61 output_proj = 122 MLA projection kernel launches per forward — latent projection overhead dominates attention time.', source: 'Roofline' }, + { sev: 'med', text: 'RMSNorm unfused: each instance decomposes into 8 separate kernels (cast→pow→mean→add→rsqrt→mul→cast→mul) × 123 instances = 984 kernel launches. Hidden state tensor read/written up to 8× instead of once per normalization.', source: 'Trace' }, + { sev: 'tip', text: 'SwiGLU activation unfused — silu and elementwise mul execute as 2 separate passes over intermediate tensors. Each pass reads/writes the full activation buffer, causing redundant HBM traffic per expert per layer.', source: 'Trace' }, + { sev: 'na', text: 'CUDA Graph not applicable on ROCm. ~3000+ individual kernel launches contribute to ~10% idle time from CPU dispatch overhead between launches.', source: 'Not Support' } + ] + }, + '128': { + e2e: { input: '506.5', output: '458.0', ttft: '499ms', itl: '11.95ms', latency: '11,579ms', gap: '20% gap', rooflineGap: '22%' }, + prefill: [ + { label: 'MLA (Attn)', pct: 11, ms: 54.9, color: 'rgba(239,68,68,0.6)', sev: 'med' }, + { label: 'Attn GEMMs', pct: 14, ms: 69.8, color: 'rgba(59,130,246,0.6)', sev: 'med' }, + { label: 'MoE (Fused SGLang)', pct: 33, ms: 164.7, color: 'rgba(245,158,11,0.6)', sev: 'hot' }, + { label: 'Communication', pct: 20, ms: 99.8, color: 'rgba(139,92,246,0.6)', sev: '' }, + { label: 'Activ. & Norm', pct: 10, ms: 49.9, color: 'rgba(20,184,166,0.6)', sev: '' }, + { label: 'Memory Ops', pct: 12, ms: 59.9, color: 'rgba(107,114,128,0.6)', sev: '' } + ], + decode: [ + { label: 'MLA (Attn)', pct: 16, ms: 1.91, color: 'rgba(239,68,68,0.6)', sev: 'med' }, + { label: 'Attn GEMMs', pct: 16, ms: 1.91, color: 'rgba(59,130,246,0.6)', sev: 'med' }, + { label: 'MoE (Fused SGLang)', pct: 30, ms: 3.59, color: 'rgba(245,158,11,0.6)', sev: 'hot' }, + { label: 'Communication', pct: 18, ms: 2.15, color: 'rgba(139,92,246,0.6)', sev: '' }, + { label: 'Activ. & Norm', pct: 10, ms: 1.20, color: 'rgba(20,184,166,0.6)', sev: '' }, + { label: 'Memory Ops', pct: 10, ms: 1.20, color: 'rgba(107,114,128,0.6)', sev: '' } + ], + insights: [ + { sev: 'high', text: 'Expert dispatch scales poorly at conc=128 — M=131K causes tile waste in rocBLAS Cijk kernels. BW utilization only 38% peak despite larger batches. 256-expert synchronization barriers add ~50µs/layer.', source: 'Trace' }, + { sev: 'high', text: 'AllReduce on 512-dim latent vectors across 8×TP dominated by synchronization barriers. Ring-AllReduce achieves only 72% Infinity Fabric peak — significant compute-comm serialization observed.', source: 'Trace' }, + { sev: 'med', text: 'QKV absorb projections (M=131K, N=512, K=7168) at 62% peak TFLOPS. N=512 aligns cleanly with tile MT256×128×64 (4 tiles, zero waste) — remaining 38% gap from compute pipeline latency and memory access patterns.', source: 'Roofline' }, + { sev: 'tip', text: 'RMSNorm unfused across 123 instances (8 kernels each = 984 launches). Combined with unfused SwiGLU (2 kernels × 123 instances), Activ.&Norm category contributes 10% of compute with high launch overhead.', source: 'Trace' } + ] + } + }, + '1K / 8K': { + '4': { + e2e: { input: '42.90', output: '357.6', ttft: '77.0ms', itl: '11.40ms', latency: '85,044ms', gap: '25% gap', rooflineGap: '29%' }, + prefill: [ + { label: 'MLA (Attn)', pct: 10, ms: 7.7, color: 'rgba(239,68,68,0.6)', sev: 'med' }, + { label: 'Attn GEMMs', pct: 12, ms: 9.2, color: 'rgba(59,130,246,0.6)', sev: 'med' }, + { label: 'MoE (Fused SGLang)', pct: 34, ms: 26.2, color: 'rgba(245,158,11,0.6)', sev: 'hot' }, + { label: 'Communication', pct: 20, ms: 15.4, color: 'rgba(139,92,246,0.6)', sev: '' }, + { label: 'Activ. & Norm', pct: 11, ms: 8.5, color: 'rgba(20,184,166,0.6)', sev: '' }, + { label: 'Memory Ops', pct: 13, ms: 10.0, color: 'rgba(107,114,128,0.6)', sev: '' } + ], + decode: [ + { label: 'MLA (Attn)', pct: 22, ms: 2.51, color: 'rgba(239,68,68,0.6)', sev: 'hot' }, + { label: 'Attn GEMMs', pct: 16, ms: 1.82, color: 'rgba(59,130,246,0.6)', sev: 'med' }, + { label: 'MoE (Fused SGLang)', pct: 26, ms: 2.96, color: 'rgba(245,158,11,0.6)', sev: 'hot' }, + { label: 'Communication', pct: 15, ms: 1.71, color: 'rgba(139,92,246,0.6)', sev: '' }, + { label: 'Activ. & Norm', pct: 11, ms: 1.25, color: 'rgba(20,184,166,0.6)', sev: '' }, + { label: 'Memory Ops', pct: 10, ms: 1.14, color: 'rgba(107,114,128,0.6)', sev: '' } + ], + insights: [ + { sev: 'high', text: 'Decode-bound regime: flash_attn_fwd reads 61-layer KV-cache per step at 3800 GB/s (48% peak). Compressed KV (d_c=512) helps but 8K output length amplifies cache reads 8× vs 1K — HBM bandwidth is the primary bottleneck.', source: 'Trace' }, + { sev: 'high', text: 'Expert dispatch per decode step: BW utilization only 35% peak. FP4 dequant adds ~12µs/expert vs native FP8 — up to 256 active experts per layer × 61 layers of dequant ops per step, totaling significant overhead.', source: 'Roofline' }, + { sev: 'med', text: 'KV-cache paging at OSL=8K: page fault rate 4.2× higher than 1K. Current block size 256 causes severe fragmentation at long output sequences, increasing memory management overhead.', source: 'Trace' }, + { sev: 'tip', text: 'Unfused RoPE: each Q/K rotation decomposes into 5 kernels (neg+cat+mul+mul+add) × 2(Q,K) × 61 layers = 610 launches/step. The cat (rotate_half) kernel at 18µs/call is 3× costlier than the elementwise ops due to non-contiguous data reorganization.', source: 'Trace' } + ] + }, + '128': { + e2e: { input: '429.0', output: '3,141', ttft: '455ms', itl: '13.78ms', latency: '103,128ms', gap: '23% gap', rooflineGap: '29%' }, + prefill: [ + { label: 'MLA (Attn)', pct: 9, ms: 41.0, color: 'rgba(239,68,68,0.6)', sev: '' }, + { label: 'Attn GEMMs', pct: 13, ms: 59.2, color: 'rgba(59,130,246,0.6)', sev: 'med' }, + { label: 'MoE (Fused SGLang)', pct: 32, ms: 145.6, color: 'rgba(245,158,11,0.6)', sev: 'hot' }, + { label: 'Communication', pct: 22, ms: 100.1, color: 'rgba(139,92,246,0.6)', sev: 'med' }, + { label: 'Activ. & Norm', pct: 12, ms: 54.6, color: 'rgba(20,184,166,0.6)', sev: '' }, + { label: 'Memory Ops', pct: 12, ms: 54.6, color: 'rgba(107,114,128,0.6)', sev: '' } + ], + decode: [ + { label: 'MLA (Attn)', pct: 20, ms: 2.76, color: 'rgba(239,68,68,0.6)', sev: 'hot' }, + { label: 'Attn GEMMs', pct: 15, ms: 2.07, color: 'rgba(59,130,246,0.6)', sev: 'med' }, + { label: 'MoE (Fused SGLang)', pct: 27, ms: 3.72, color: 'rgba(245,158,11,0.6)', sev: 'hot' }, + { label: 'Communication', pct: 17, ms: 2.34, color: 'rgba(139,92,246,0.6)', sev: '' }, + { label: 'Activ. & Norm', pct: 11, ms: 1.52, color: 'rgba(20,184,166,0.6)', sev: '' }, + { label: 'Memory Ops', pct: 10, ms: 1.38, color: 'rgba(107,114,128,0.6)', sev: '' } + ], + insights: [ + { sev: 'high', text: 'Expert dispatch overhead ~45µs/layer in decode. GEMM BW utilization drops to 33% peak at conc=128 due to batch fragmentation across 256 experts — effective per-expert batch too small for efficient tile utilization.', source: 'Trace' }, + { sev: 'high', text: 'AllReduce on (128K, 512) latent vectors: ring bandwidth only 68% peak. 8×TP barrier sync adds ~180µs per AllReduce — compute and communication fully serialized with no overlap.', source: 'Trace' }, + { sev: 'med', text: 'KV-cache L2 thrashing at conc=128 × OSL=8K — 128 concurrent sequences exceed L2 capacity. HBM re-read rate at 43% peak, indicating severe cache miss pressure from competing KV-cache accesses.', source: 'Roofline' }, + { sev: 'tip', text: 'RMSNorm + SwiGLU together unfused across 61 layers: 123 RMSNorm instances (8 kernels each) + 122 SwiGLU instances (2 kernels each) = 1228 kernel launches in the Activ.&Norm category alone.', source: 'Trace' } + ] + } + }, + '8K / 1K': { + '4': { + e2e: { input: '281.6', output: '42.89', ttft: '291.5ms', itl: '8.34ms', latency: '8,029ms', gap: '27% gap', rooflineGap: '26%' }, + prefill: [ + { label: 'MLA (Attn)', pct: 18, ms: 52.5, color: 'rgba(239,68,68,0.6)', sev: 'hot' }, + { label: 'Attn GEMMs', pct: 15, ms: 43.7, color: 'rgba(59,130,246,0.6)', sev: 'med' }, + { label: 'MoE (Fused SGLang)', pct: 28, ms: 81.6, color: 'rgba(245,158,11,0.6)', sev: 'hot' }, + { label: 'Communication', pct: 17, ms: 49.6, color: 'rgba(139,92,246,0.6)', sev: '' }, + { label: 'Activ. & Norm', pct: 12, ms: 35.0, color: 'rgba(20,184,166,0.6)', sev: '' }, + { label: 'Memory Ops', pct: 10, ms: 29.2, color: 'rgba(107,114,128,0.6)', sev: '' } + ], + decode: [ + { label: 'MLA (Attn)', pct: 15, ms: 1.25, color: 'rgba(239,68,68,0.6)', sev: 'med' }, + { label: 'Attn GEMMs', pct: 14, ms: 1.17, color: 'rgba(59,130,246,0.6)', sev: 'med' }, + { label: 'MoE (Fused SGLang)', pct: 30, ms: 2.50, color: 'rgba(245,158,11,0.6)', sev: 'hot' }, + { label: 'Communication', pct: 18, ms: 1.50, color: 'rgba(139,92,246,0.6)', sev: '' }, + { label: 'Activ. & Norm', pct: 12, ms: 1.00, color: 'rgba(20,184,166,0.6)', sev: '' }, + { label: 'Memory Ops', pct: 11, ms: 0.92, color: 'rgba(107,114,128,0.6)', sev: '' } + ], + insights: [ + { sev: 'high', text: 'Prefill-bound at ISL=8K — flash_attn on (4, H, 8192, d_c=512): BW only 45% peak. KV-cache write amplification at 61 layers × 8K sequence generates massive HBM traffic, saturating the memory subsystem.', source: 'Trace' }, + { sev: 'high', text: '256-expert top-8 at M=32768: BW utilization only 32% peak. FP4 dequant adds ~15µs per expert GEMM — total 3.7ms dequant overhead per layer accumulates to significant fraction across 61 layers.', source: 'Roofline' }, + { sev: 'med', text: 'QKV absorb projections at (M=32K, N=512, K=7168): 68% peak TFLOPS. M tiles cleanly but FP4 dequant in the compute path reduces effective throughput by ~8%, creating a dequant pipeline stall.', source: 'Trace' }, + { sev: 'tip', text: 'RMSNorm unfused: 123 instances × 8 kernels = 984 launches. At ISL=8K the hidden state tensor is larger (32K×7168), amplifying the redundant data movement from each unfused kernel pass.', source: 'Trace' } + ] + }, + '128': { + e2e: { input: '2,255', output: '370.7', ttft: '1,086ms', itl: '10.55ms', latency: '10,810ms', gap: '24% gap', rooflineGap: '26%' }, + prefill: [ + { label: 'MLA (Attn)', pct: 20, ms: 217.1, color: 'rgba(239,68,68,0.6)', sev: 'hot' }, + { label: 'Attn GEMMs', pct: 14, ms: 152.0, color: 'rgba(59,130,246,0.6)', sev: 'med' }, + { label: 'MoE (Fused SGLang)', pct: 27, ms: 293.1, color: 'rgba(245,158,11,0.6)', sev: 'hot' }, + { label: 'Communication', pct: 19, ms: 206.3, color: 'rgba(139,92,246,0.6)', sev: 'med' }, + { label: 'Activ. & Norm', pct: 10, ms: 108.6, color: 'rgba(20,184,166,0.6)', sev: '' }, + { label: 'Memory Ops', pct: 10, ms: 108.6, color: 'rgba(107,114,128,0.6)', sev: '' } + ], + decode: [ + { label: 'MLA (Attn)', pct: 17, ms: 1.79, color: 'rgba(239,68,68,0.6)', sev: 'hot' }, + { label: 'Attn GEMMs', pct: 15, ms: 1.58, color: 'rgba(59,130,246,0.6)', sev: 'med' }, + { label: 'MoE (Fused SGLang)', pct: 29, ms: 3.06, color: 'rgba(245,158,11,0.6)', sev: 'hot' }, + { label: 'Communication', pct: 18, ms: 1.90, color: 'rgba(139,92,246,0.6)', sev: '' }, + { label: 'Activ. & Norm', pct: 11, ms: 1.16, color: 'rgba(20,184,166,0.6)', sev: '' }, + { label: 'Memory Ops', pct: 10, ms: 1.06, color: 'rgba(107,114,128,0.6)', sev: '' } + ], + insights: [ + { sev: 'high', text: 'Memory subsystem near saturation at 90% peak (7200 GB/s) — M=1M GEMM tiles are well-utilized but HBM bandwidth becomes the primary bottleneck. FP4 dequant compounds memory pressure with additional read traffic.', source: 'Trace' }, + { sev: 'high', text: 'AllReduce on (1M, 512) tensors: ring bandwidth drops to 65% peak under heavy memory pressure. Compute and communication fully serialized — no overlap observed in trace.', source: 'Trace' }, + { sev: 'med', text: 'TTFT 680ms exceeds interactive threshold (200ms). Prefill processes entire 8K×128 batch sequentially with no chunking — full sequence materialized before first token output.', source: 'Roofline' }, + { sev: 'tip', text: '~3000+ individual kernel launches observed, contributing to ~10% idle time from CPU dispatch overhead. Single-GPU parallelism (TP=8) leaves inter-launch gaps between sequential kernel submissions.', source: 'Trace' } + ] + } + } + }, + 'FP8': { + '1K / 1K': { + '4': { + e2e: { input: '59.11', output: '58.89', ttft: '77.0ms', itl: '8.34ms', latency: '7,834ms', gap: '17% gap', rooflineGap: '17%' }, + prefill: [ + { label: 'MLA (Attn)', pct: 14, ms: 10.8, color: 'rgba(239,68,68,0.6)', sev: 'med' }, + { label: 'Attn GEMMs', pct: 12, ms: 9.2, color: 'rgba(59,130,246,0.6)', sev: 'med' }, + { label: 'MoE (Fused SGLang)', pct: 28, ms: 21.6, color: 'rgba(245,158,11,0.6)', sev: 'hot' }, + { label: 'Communication', pct: 18, ms: 13.9, color: 'rgba(139,92,246,0.6)', sev: '' }, + { label: 'Activ. & Norm', pct: 14, ms: 10.8, color: 'rgba(20,184,166,0.6)', sev: '' }, + { label: 'Memory Ops', pct: 14, ms: 10.8, color: 'rgba(107,114,128,0.6)', sev: '' } + ], + decode: [ + { label: 'MLA (Attn)', pct: 16, ms: 1.33, color: 'rgba(239,68,68,0.6)', sev: 'med' }, + { label: 'Attn GEMMs', pct: 14, ms: 1.17, color: 'rgba(59,130,246,0.6)', sev: 'med' }, + { label: 'MoE (Fused SGLang)', pct: 26, ms: 2.17, color: 'rgba(245,158,11,0.6)', sev: 'hot' }, + { label: 'Communication', pct: 18, ms: 1.50, color: 'rgba(139,92,246,0.6)', sev: '' }, + { label: 'Activ. & Norm', pct: 14, ms: 1.17, color: 'rgba(20,184,166,0.6)', sev: '' }, + { label: 'Memory Ops', pct: 12, ms: 1.00, color: 'rgba(107,114,128,0.6)', sev: '' } + ], + insights: [ + { sev: 'high', text: 'Native FP8 GEMM eliminates dequant overhead — BW utilization improves to 45% peak (vs 31% at FP4). However, 256-expert top-8 dispatch still launches up to 256 expert GEMMs per layer × 61 layers per forward pass, maintaining high launch overhead.', source: 'Trace' }, + { sev: 'med', text: 'flash_attn_fwd at 58% HBM peak — improved over FP4 (53%) but still memory-bound. Compressed KV (d_c=512) keeps cache efficient, yet 61 absorb_attn + 61 output_proj = 122 MLA projection launches per forward remain the dominant overhead.', source: 'Roofline' }, + { sev: 'med', text: 'RMSNorm share rises to 14% of compute at FP8 — compute ops speed up with native tensor cores but normalization stays unfused. 984 kernel launches (123 instances × 8 kernels) become proportionally more significant.', source: 'Trace' }, + { sev: 'tip', text: 'GEMMs achieve 72% peak TFLOPS with native FP8 tensor core utilization — no dequant pipeline stalls. Gap vs B200 narrows from 25% (FP4) to 20%, confirming dequant overhead as a key FP4 penalty.', source: 'Roofline' } + ] + }, + '128': { + e2e: { input: '591.1', output: '538.3', ttft: '444ms', itl: '10.55ms', latency: '10,226ms', gap: '15% gap', rooflineGap: '17%' }, + prefill: [ + { label: 'MLA (Attn)', pct: 12, ms: 53.3, color: 'rgba(239,68,68,0.6)', sev: 'med' }, + { label: 'Attn GEMMs', pct: 13, ms: 57.8, color: 'rgba(59,130,246,0.6)', sev: 'med' }, + { label: 'MoE (Fused SGLang)', pct: 30, ms: 133.3, color: 'rgba(245,158,11,0.6)', sev: 'hot' }, + { label: 'Communication', pct: 21, ms: 93.3, color: 'rgba(139,92,246,0.6)', sev: 'med' }, + { label: 'Activ. & Norm', pct: 12, ms: 53.3, color: 'rgba(20,184,166,0.6)', sev: '' }, + { label: 'Memory Ops', pct: 12, ms: 53.3, color: 'rgba(107,114,128,0.6)', sev: '' } + ], + decode: [ + { label: 'MLA (Attn)', pct: 14, ms: 1.48, color: 'rgba(239,68,68,0.6)', sev: 'med' }, + { label: 'Attn GEMMs', pct: 15, ms: 1.58, color: 'rgba(59,130,246,0.6)', sev: 'med' }, + { label: 'MoE (Fused SGLang)', pct: 28, ms: 2.95, color: 'rgba(245,158,11,0.6)', sev: 'hot' }, + { label: 'Communication', pct: 20, ms: 2.11, color: 'rgba(139,92,246,0.6)', sev: 'med' }, + { label: 'Activ. & Norm', pct: 12, ms: 1.27, color: 'rgba(20,184,166,0.6)', sev: '' }, + { label: 'Memory Ops', pct: 11, ms: 1.16, color: 'rgba(107,114,128,0.6)', sev: '' } + ], + insights: [ + { sev: 'high', text: 'Expert GEMM at M=131K tiles well but dispatch barrier adds ~50µs/layer sync cost. BW utilization 42% peak — limited by 256-expert scatter pattern causing non-coalesced memory access.', source: 'Trace' }, + { sev: 'high', text: 'AllReduce on (131K, 512): TP activation payload remains in BF16 regardless of weight precision — sync overhead dominates. 65ms spent in synchronization barriers with no compute-comm overlap observed.', source: 'Trace' }, + { sev: 'med', text: 'Absorb projections at 70% peak TFLOPS — FP8 native path avoids dequant penalty. Tile MT256×128×64 aligns well with N=512 but 70% utilization indicates remaining inefficiency in the compute pipeline.', source: 'Roofline' }, + { sev: 'tip', text: 'Idle time ~9% from kernel launches. ~2500+ individual kernel submissions observed — CPU dispatch overhead between kernels leaves GPU idle during inter-launch gaps.', source: 'Trace' } + ] + } + }, + '1K / 8K': { + '4': { + e2e: { input: '50.07', output: '418.7', ttft: '67.1ms', itl: '10.00ms', latency: '74,599ms', gap: '21% gap', rooflineGap: '23%' }, + prefill: [ + { label: 'MLA (Attn)', pct: 11, ms: 7.4, color: 'rgba(239,68,68,0.6)', sev: 'med' }, + { label: 'Attn GEMMs', pct: 11, ms: 7.4, color: 'rgba(59,130,246,0.6)', sev: '' }, + { label: 'MoE (Fused SGLang)', pct: 32, ms: 21.5, color: 'rgba(245,158,11,0.6)', sev: 'hot' }, + { label: 'Communication', pct: 20, ms: 13.4, color: 'rgba(139,92,246,0.6)', sev: '' }, + { label: 'Activ. & Norm', pct: 13, ms: 8.7, color: 'rgba(20,184,166,0.6)', sev: '' }, + { label: 'Memory Ops', pct: 13, ms: 8.7, color: 'rgba(107,114,128,0.6)', sev: '' } + ], + decode: [ + { label: 'MLA (Attn)', pct: 20, ms: 2.00, color: 'rgba(239,68,68,0.6)', sev: 'hot' }, + { label: 'Attn GEMMs', pct: 14, ms: 1.40, color: 'rgba(59,130,246,0.6)', sev: 'med' }, + { label: 'MoE (Fused SGLang)', pct: 27, ms: 2.70, color: 'rgba(245,158,11,0.6)', sev: 'hot' }, + { label: 'Communication', pct: 17, ms: 1.70, color: 'rgba(139,92,246,0.6)', sev: '' }, + { label: 'Activ. & Norm', pct: 12, ms: 1.20, color: 'rgba(20,184,166,0.6)', sev: '' }, + { label: 'Memory Ops', pct: 10, ms: 1.00, color: 'rgba(107,114,128,0.6)', sev: '' } + ], + insights: [ + { sev: 'high', text: 'Decode-bound: flash_attn reads 8K KV-cache per step at 50% HBM peak across 61 layers — massive read traffic even with compressed KV (d_c=512). Memory bandwidth is the primary bottleneck limiting decode throughput.', source: 'Trace' }, + { sev: 'high', text: 'FP8 expert GEMMs at 68% peak TFLOPS. Each decode token routes to 8 experts — at BS=4 up to 256 active experts/layer × 61 layers per step. Dispatch overhead ~35µs/layer limits throughput due to frequent small-kernel launches.', source: 'Roofline' }, + { sev: 'med', text: 'Unfused RoPE: each Q/K rotation decomposes into 5 kernels (neg+cat+mul+mul+add) × 2(Q,K) × 61 layers = 610 launches/step. The cat (rotate_half) kernel at 22µs/call performs non-contiguous data reorganization, 3× costlier than the elementwise ops.', source: 'Trace' }, + { sev: 'tip', text: 'FP8 narrows gap from 25% (FP4) to 21% — confirming dequant overhead as significant FP4 penalty. Paged attention KV-cache currently occupies majority of HBM at 8K output length.', source: 'Roofline' } + ] + }, + '128': { + e2e: { input: '500.7', output: '3,665', ttft: '403ms', itl: '12.17ms', latency: '91,080ms', gap: '19% gap', rooflineGap: '23%' }, + prefill: [ + { label: 'MLA (Attn)', pct: 10, ms: 40.3, color: 'rgba(239,68,68,0.6)', sev: '' }, + { label: 'Attn GEMMs', pct: 12, ms: 48.3, color: 'rgba(59,130,246,0.6)', sev: '' }, + { label: 'MoE (Fused SGLang)', pct: 31, ms: 124.8, color: 'rgba(245,158,11,0.6)', sev: 'hot' }, + { label: 'Communication', pct: 22, ms: 88.6, color: 'rgba(139,92,246,0.6)', sev: 'med' }, + { label: 'Activ. & Norm', pct: 13, ms: 52.3, color: 'rgba(20,184,166,0.6)', sev: '' }, + { label: 'Memory Ops', pct: 12, ms: 48.3, color: 'rgba(107,114,128,0.6)', sev: '' } + ], + decode: [ + { label: 'MLA (Attn)', pct: 18, ms: 2.19, color: 'rgba(239,68,68,0.6)', sev: 'hot' }, + { label: 'Attn GEMMs', pct: 14, ms: 1.70, color: 'rgba(59,130,246,0.6)', sev: 'med' }, + { label: 'MoE (Fused SGLang)', pct: 28, ms: 3.41, color: 'rgba(245,158,11,0.6)', sev: 'hot' }, + { label: 'Communication', pct: 19, ms: 2.31, color: 'rgba(139,92,246,0.6)', sev: '' }, + { label: 'Activ. & Norm', pct: 11, ms: 1.34, color: 'rgba(20,184,166,0.6)', sev: '' }, + { label: 'Memory Ops', pct: 10, ms: 1.22, color: 'rgba(107,114,128,0.6)', sev: '' } + ], + insights: [ + { sev: 'high', text: 'Expert GEMMs at M=131K achieve 94% tile utilization but only 66% peak TFLOPS — limited by 256-expert dispatch synchronization at batch=128. Dispatch barriers serialize expert execution despite good tile fill.', source: 'Trace' }, + { sev: 'high', text: 'AllReduce latency scales superlinearly above conc=64. TP activation payload remains in BF16 (weight precision does not affect AllReduce size) and sync barriers dominate at 8×TP — AllReduce time grows disproportionately with concurrency, indicating synchronization-bound scaling.', source: 'Trace' }, + { sev: 'med', text: '128 concurrent KV-caches × 61 layers cause severe L2 thrashing — effective BW drops to 40% peak. Competing cache line evictions from parallel sequence attention cause repeated HBM re-reads.', source: 'Roofline' }, + { sev: 'tip', text: 'Unfused RMSNorm (123 instances × 8 kernels = 984 launches) and SwiGLU (122 instances × 2 kernels) contribute disproportionate launch overhead at high concurrency, as kernel dispatch cost remains constant while per-kernel work shrinks.', source: 'Trace' } + ] + } + }, + '8K / 1K': { + '4': { + e2e: { input: '330.0', output: '50.17', ttft: '249.2ms', itl: '7.32ms', latency: '7,041ms', gap: '23% gap', rooflineGap: '20%' }, + prefill: [ + { label: 'MLA (Attn)', pct: 16, ms: 39.9, color: 'rgba(239,68,68,0.6)', sev: 'hot' }, + { label: 'Attn GEMMs', pct: 14, ms: 34.9, color: 'rgba(59,130,246,0.6)', sev: 'med' }, + { label: 'MoE (Fused SGLang)', pct: 29, ms: 72.3, color: 'rgba(245,158,11,0.6)', sev: 'hot' }, + { label: 'Communication', pct: 18, ms: 44.9, color: 'rgba(139,92,246,0.6)', sev: '' }, + { label: 'Activ. & Norm', pct: 12, ms: 29.9, color: 'rgba(20,184,166,0.6)', sev: '' }, + { label: 'Memory Ops', pct: 11, ms: 27.4, color: 'rgba(107,114,128,0.6)', sev: '' } + ], + decode: [ + { label: 'MLA (Attn)', pct: 14, ms: 1.02, color: 'rgba(239,68,68,0.6)', sev: 'med' }, + { label: 'Attn GEMMs', pct: 13, ms: 0.95, color: 'rgba(59,130,246,0.6)', sev: '' }, + { label: 'MoE (Fused SGLang)', pct: 30, ms: 2.20, color: 'rgba(245,158,11,0.6)', sev: 'hot' }, + { label: 'Communication', pct: 18, ms: 1.32, color: 'rgba(139,92,246,0.6)', sev: '' }, + { label: 'Activ. & Norm', pct: 13, ms: 0.95, color: 'rgba(20,184,166,0.6)', sev: '' }, + { label: 'Memory Ops', pct: 12, ms: 0.88, color: 'rgba(107,114,128,0.6)', sev: '' } + ], + insights: [ + { sev: 'high', text: 'Prefill-bound at ISL=8K — flash_attn at 53% HBM peak. Despite FP8 native GEMMs achieving 71% peak TFLOPS, the attention memory bandwidth remains the dominant bottleneck at long sequence lengths.', source: 'Trace' }, + { sev: 'med', text: 'Absorb projections at M=32K: rocBLAS achieves 69% peak. Tile MT256×128×64 leaves ~3% waste. M=32768 is already power-of-2 aligned, indicating the remaining 31% gap comes from compute pipeline inefficiency rather than tile waste.', source: 'Roofline' }, + { sev: 'med', text: 'Unfused elementwise patterns: RMSNorm (8 kernels × 123 instances = 984) + SwiGLU (2 kernels × 61 instances = 122) = ~1,106 kernel launches. Each kernel reads/writes the full hidden state tensor, causing up to 10× redundant HBM traffic.', source: 'Trace' }, + { sev: 'tip', text: '26% gap vs B200 — 4pp better than FP4, confirming dequant overhead contribution. At ISL=8K, per-GPU attention memory pressure limits batch scaling — single-GPU holds all 61 layers\' KV-cache.', source: 'Roofline' } + ] + }, + '128': { + e2e: { input: '2,638', output: '429.0', ttft: '935ms', itl: '9.19ms', latency: '9,406ms', gap: '20% gap', rooflineGap: '20%' }, + prefill: [ + { label: 'MLA (Attn)', pct: 18, ms: 168.2, color: 'rgba(239,68,68,0.6)', sev: 'hot' }, + { label: 'Attn GEMMs', pct: 13, ms: 121.5, color: 'rgba(59,130,246,0.6)', sev: 'med' }, + { label: 'MoE (Fused SGLang)', pct: 28, ms: 261.7, color: 'rgba(245,158,11,0.6)', sev: 'hot' }, + { label: 'Communication', pct: 20, ms: 186.9, color: 'rgba(139,92,246,0.6)', sev: 'med' }, + { label: 'Activ. & Norm', pct: 11, ms: 102.8, color: 'rgba(20,184,166,0.6)', sev: '' }, + { label: 'Memory Ops', pct: 10, ms: 93.5, color: 'rgba(107,114,128,0.6)', sev: '' } + ], + decode: [ + { label: 'MLA (Attn)', pct: 15, ms: 1.38, color: 'rgba(239,68,68,0.6)', sev: 'med' }, + { label: 'Attn GEMMs', pct: 14, ms: 1.29, color: 'rgba(59,130,246,0.6)', sev: 'med' }, + { label: 'MoE (Fused SGLang)', pct: 30, ms: 2.76, color: 'rgba(245,158,11,0.6)', sev: 'hot' }, + { label: 'Communication', pct: 19, ms: 1.75, color: 'rgba(139,92,246,0.6)', sev: '' }, + { label: 'Activ. & Norm', pct: 12, ms: 1.10, color: 'rgba(20,184,166,0.6)', sev: '' }, + { label: 'Memory Ops', pct: 10, ms: 0.92, color: 'rgba(107,114,128,0.6)', sev: '' } + ], + insights: [ + { sev: 'high', text: 'M=1M expert GEMMs at 67% peak TFLOPS despite good tile fill — memory subsystem contention limits throughput. flash_attn at 55% HBM peak confirms near-saturation of the memory subsystem at this scale.', source: 'Trace' }, + { sev: 'high', text: 'AllReduce on (1M, 512): ring-AllReduce at only 64% Infinity Fabric peak. Compute and communication fully serialized — ~100ms spent in synchronization with GPU idle during AllReduce.', source: 'Trace' }, + { sev: 'med', text: 'TTFT 585ms exceeds interactive threshold (200ms). Full 8K×128 prefill processed as single contiguous batch — no chunking observed, delaying first token output until entire prefill completes.', source: 'Roofline' }, + { sev: 'tip', text: '23% gap vs B200 — 4pp better than FP4, confirming dequant elimination as significant win. However, remaining gap is dominated by memory bandwidth saturation and communication overhead at this scale.', source: 'Trace' } + ] + } + } + } + }, + 'gpt-oss 120B': { + 'FP4': { + '1K / 1K': { + '4': { + e2e: { input: '67.52', output: '66.26', ttft: '67.6ms', itl: '7.96ms', latency: '7,472ms', gap: '32% gap', rooflineGap: '19%' }, + prefill: [ + { label: 'GQA (Attn)', pct: 22, ms: 14.9, color: 'rgba(239,68,68,0.6)', sev: 'hot' }, + { label: 'Attn GEMMs', pct: 20, ms: 13.5, color: 'rgba(59,130,246,0.6)', sev: 'med' }, + { label: 'MoE (MXFP4)', pct: 33, ms: 22.3, color: 'rgba(245,158,11,0.6)', sev: 'hot' }, + { label: 'Communication', pct: 13, ms: 8.8, color: 'rgba(139,92,246,0.6)', sev: '' }, + { label: 'Activ. & Norm', pct: 7, ms: 4.7, color: 'rgba(20,184,166,0.6)', sev: '' }, + { label: 'Memory Ops', pct: 5, ms: 3.4, color: 'rgba(107,114,128,0.6)', sev: '' } + ], + decode: [ + { label: 'GQA (Attn)', pct: 20, ms: 1.59, color: 'rgba(239,68,68,0.6)', sev: 'hot' }, + { label: 'Attn GEMMs', pct: 16, ms: 1.27, color: 'rgba(59,130,246,0.6)', sev: 'med' }, + { label: 'MoE (MXFP4)', pct: 30, ms: 2.39, color: 'rgba(245,158,11,0.6)', sev: 'hot' }, + { label: 'Communication', pct: 16, ms: 1.27, color: 'rgba(139,92,246,0.6)', sev: '' }, + { label: 'Activ. & Norm', pct: 10, ms: 0.80, color: 'rgba(20,184,166,0.6)', sev: '' }, + { label: 'Memory Ops', pct: 8, ms: 0.64, color: 'rgba(107,114,128,0.6)', sev: '' } + ], + insights: [ + { sev: 'high', text: '128-expert top-4: each expert GEMM launches rocBLAS Cijk with MXFP4 dequant. BW utilization only 31% peak. Dequant adds ~18µs/call — 2.3ms overhead per layer accumulates significantly across 36 layers.', source: 'Trace' }, + { sev: 'high', text: 'Sliding(128)/full alternating attention pattern: flash_attn BW only 53% peak. GQA 8:1 ratio keeps KV-cache compact (2MB/layer) but alternating sliding window introduces branch divergence in the attention kernel.', source: 'Roofline' }, + { sev: 'med', text: 'QKV projections at 66% peak TFLOPS. GQA KV projection (N=360) has poor tile fill for tile MT256×128×64 — N=360 leaves fractional tiles, wasting ~12% of compute resources on padding.', source: 'Trace' }, + { sev: 'tip', text: 'RMSNorm unfused: 73 instances × 8 kernels = 584 launches. SwiGLU unfused: 36 instances × 2 kernels = 72 launches. Combined 656 Activ.&Norm kernel launches with redundant HBM passes per instance.', source: 'Trace' }, + { sev: 'na', text: 'CUDA Graph not applicable on ROCm. ~2000+ individual kernel launches contribute to 8-10% idle time from CPU dispatch overhead between launches.', source: 'Not Support' } + ] + }, + '128': { + e2e: { input: '675.2', output: '605.1', ttft: '392ms', itl: '9.52ms', latency: '9,356ms', gap: '30% gap', rooflineGap: '19%' }, + prefill: [ + { label: 'GQA (Attn)', pct: 20, ms: 78.4, color: 'rgba(239,68,68,0.6)', sev: 'hot' }, + { label: 'Attn GEMMs', pct: 19, ms: 74.5, color: 'rgba(59,130,246,0.6)', sev: 'med' }, + { label: 'MoE (MXFP4)', pct: 30, ms: 117.6, color: 'rgba(245,158,11,0.6)', sev: 'hot' }, + { label: 'Communication', pct: 17, ms: 66.6, color: 'rgba(139,92,246,0.6)', sev: 'med' }, + { label: 'Activ. & Norm', pct: 8, ms: 31.4, color: 'rgba(20,184,166,0.6)', sev: '' }, + { label: 'Memory Ops', pct: 6, ms: 23.5, color: 'rgba(107,114,128,0.6)', sev: '' } + ], + decode: [ + { label: 'GQA (Attn)', pct: 18, ms: 1.71, color: 'rgba(239,68,68,0.6)', sev: 'hot' }, + { label: 'Attn GEMMs', pct: 15, ms: 1.43, color: 'rgba(59,130,246,0.6)', sev: 'med' }, + { label: 'MoE (MXFP4)', pct: 28, ms: 2.67, color: 'rgba(245,158,11,0.6)', sev: 'hot' }, + { label: 'Communication', pct: 19, ms: 1.81, color: 'rgba(139,92,246,0.6)', sev: 'med' }, + { label: 'Activ. & Norm', pct: 12, ms: 1.14, color: 'rgba(20,184,166,0.6)', sev: '' }, + { label: 'Memory Ops', pct: 8, ms: 0.76, color: 'rgba(107,114,128,0.6)', sev: '' } + ], + insights: [ + { sev: 'high', text: 'M=128K expert GEMMs: tile MT128×224×64 at only 64% peak. MXFP4 dequant amortizes better at large M but dispatch barrier still ~40µs/layer — 128-expert synchronization limits scaling despite good per-expert tile fill.', source: 'Trace' }, + { sev: 'high', text: 'AllReduce on (128K, 2880): batch payload amplified to significant size. Ring-AllReduce at 74% Infinity Fabric peak — compute and communication not overlapped, with ~45ms spent in synchronization barriers.', source: 'Trace' }, + { sev: 'med', text: 'flash_attn at 55% HBM peak, memory-bound. QKV proj at M=128K achieves 68% TFLOPS but GQA KV projection (N=360) wastes tiles — N not aligned to tile MT256×128×64 boundaries, causing fractional tile inefficiency.', source: 'Roofline' }, + { sev: 'tip', text: 'RMSNorm unfused (73 instances × 8 kernels) + SwiGLU unfused (36 instances × 2 kernels) = 656 kernel launches in Activ.&Norm. At conc=128, per-kernel work is small but launch overhead remains constant.', source: 'Trace' } + ] + } + }, + '1K / 8K': { + '4': { + e2e: { input: '50.56', output: '538.0', ttft: '57.3ms', itl: '9.52ms', latency: '71,044ms', gap: '35% gap', rooflineGap: '26%' }, + prefill: [ + { label: 'GQA (Attn)', pct: 21, ms: 12.0, color: 'rgba(239,68,68,0.6)', sev: 'hot' }, + { label: 'Attn GEMMs', pct: 17, ms: 9.7, color: 'rgba(59,130,246,0.6)', sev: 'med' }, + { label: 'MoE (MXFP4)', pct: 30, ms: 17.2, color: 'rgba(245,158,11,0.6)', sev: 'hot' }, + { label: 'Communication', pct: 16, ms: 9.2, color: 'rgba(139,92,246,0.6)', sev: '' }, + { label: 'Activ. & Norm', pct: 9, ms: 5.2, color: 'rgba(20,184,166,0.6)', sev: '' }, + { label: 'Memory Ops', pct: 7, ms: 4.0, color: 'rgba(107,114,128,0.6)', sev: '' } + ], + decode: [ + { label: 'GQA (Attn)', pct: 28, ms: 2.67, color: 'rgba(239,68,68,0.6)', sev: 'hot' }, + { label: 'Attn GEMMs', pct: 14, ms: 1.33, color: 'rgba(59,130,246,0.6)', sev: 'med' }, + { label: 'MoE (MXFP4)', pct: 24, ms: 2.28, color: 'rgba(245,158,11,0.6)', sev: 'hot' }, + { label: 'Communication', pct: 16, ms: 1.52, color: 'rgba(139,92,246,0.6)', sev: '' }, + { label: 'Activ. & Norm', pct: 10, ms: 0.95, color: 'rgba(20,184,166,0.6)', sev: '' }, + { label: 'Memory Ops', pct: 8, ms: 0.76, color: 'rgba(107,114,128,0.6)', sev: '' } + ], + insights: [ + { sev: 'high', text: 'Decode-bound: flash_attn reads 32MB KV/layer × 36 layers per step. BW only 45% peak with severe L2 thrashing — 8K output length saturates the cache hierarchy, forcing repeated HBM re-reads.', source: 'Trace' }, + { sev: 'high', text: '512 expert GEMM calls per decode step. MXFP4 dequant adds ~18µs/call — total 9.2ms dequant overhead per step. This dequant penalty is a constant per-step tax independent of expert computation.', source: 'Roofline' }, + { sev: 'med', text: 'sliding_window=128 causes page eviction thrashing at OSL=8K. Page fault rate 4.2× higher than at 1K — current block size 256 creates fragmentation as attention window slides across long sequences.', source: 'Trace' }, + { sev: 'tip', text: 'Unfused RoPE: each Q/K rotation decomposes into 5 kernels (neg+cat+mul+mul+add) × 2(Q,K) × 36 layers = 360 launches/step. The cat (rotate_half) kernel at 28µs/call is 3× costlier than elementwise ops due to non-contiguous data reorganization.', source: 'Trace' } + ] + }, + '128': { + e2e: { input: '505.6', output: '4,692', ttft: '363ms', itl: '11.65ms', latency: '87,190ms', gap: '33% gap', rooflineGap: '26%' }, + prefill: [ + { label: 'GQA (Attn)', pct: 19, ms: 68.9, color: 'rgba(239,68,68,0.6)', sev: 'med' }, + { label: 'Attn GEMMs', pct: 16, ms: 58.1, color: 'rgba(59,130,246,0.6)', sev: 'med' }, + { label: 'MoE (MXFP4)', pct: 28, ms: 101.6, color: 'rgba(245,158,11,0.6)', sev: 'hot' }, + { label: 'Communication', pct: 20, ms: 72.6, color: 'rgba(139,92,246,0.6)', sev: 'med' }, + { label: 'Activ. & Norm', pct: 10, ms: 36.3, color: 'rgba(20,184,166,0.6)', sev: '' }, + { label: 'Memory Ops', pct: 7, ms: 25.4, color: 'rgba(107,114,128,0.6)', sev: '' } + ], + decode: [ + { label: 'GQA (Attn)', pct: 25, ms: 2.91, color: 'rgba(239,68,68,0.6)', sev: 'hot' }, + { label: 'Attn GEMMs', pct: 14, ms: 1.63, color: 'rgba(59,130,246,0.6)', sev: 'med' }, + { label: 'MoE (MXFP4)', pct: 25, ms: 2.91, color: 'rgba(245,158,11,0.6)', sev: 'hot' }, + { label: 'Communication', pct: 17, ms: 1.98, color: 'rgba(139,92,246,0.6)', sev: 'med' }, + { label: 'Activ. & Norm', pct: 11, ms: 1.28, color: 'rgba(20,184,166,0.6)', sev: '' }, + { label: 'Memory Ops', pct: 8, ms: 0.93, color: 'rgba(107,114,128,0.6)', sev: '' } + ], + insights: [ + { sev: 'high', text: '128 concurrent KV-caches at OSL=8K consume ~25GB HBM — L2 miss rate ~85%. BW drops to 40% peak due to severe cache thrashing at conc=128 × OSL=8K. Competing cache line evictions dominate memory access latency.', source: 'Trace' }, + { sev: 'high', text: 'AllReduce payload 737MB per call. Ring-AllReduce at 70% Infinity Fabric peak — compute and communication fully serialized with ~40ms spent waiting on synchronization barriers.', source: 'Trace' }, + { sev: 'med', text: 'Per-expert GEMM M~4 on average (128 tokens × top-4 / 128 experts) at high concurrency — only 35% peak TFLOPS. Small-M GEMMs severely underutilize the compute units. MXFP4 dequant adds constant ~18µs overhead per expert regardless of M size.', source: 'Roofline' }, + { sev: 'tip', text: 'KV-cache occupies majority of HBM at conc=128 × OSL=8K. Current FP16 KV storage doubles memory footprint compared to lower-precision alternatives — limiting maximum concurrent sequence capacity.', source: 'Trace' } + ] + } + }, + '8K / 1K': { + '4': { + e2e: { input: '351.6', output: '56.67', ttft: '217.3ms', itl: '6.96ms', latency: '6,704ms', gap: '34% gap', rooflineGap: '22%' }, + prefill: [ + { label: 'GQA (Attn)', pct: 26, ms: 56.5, color: 'rgba(239,68,68,0.6)', sev: 'hot' }, + { label: 'Attn GEMMs', pct: 19, ms: 41.3, color: 'rgba(59,130,246,0.6)', sev: 'med' }, + { label: 'MoE (MXFP4)', pct: 26, ms: 56.5, color: 'rgba(245,158,11,0.6)', sev: 'hot' }, + { label: 'Communication', pct: 14, ms: 30.4, color: 'rgba(139,92,246,0.6)', sev: '' }, + { label: 'Activ. & Norm', pct: 9, ms: 19.6, color: 'rgba(20,184,166,0.6)', sev: '' }, + { label: 'Memory Ops', pct: 6, ms: 13.0, color: 'rgba(107,114,128,0.6)', sev: '' } + ], + decode: [ + { label: 'GQA (Attn)', pct: 20, ms: 1.39, color: 'rgba(239,68,68,0.6)', sev: 'hot' }, + { label: 'Attn GEMMs', pct: 15, ms: 1.04, color: 'rgba(59,130,246,0.6)', sev: 'med' }, + { label: 'MoE (MXFP4)', pct: 32, ms: 2.23, color: 'rgba(245,158,11,0.6)', sev: 'hot' }, + { label: 'Communication', pct: 16, ms: 1.11, color: 'rgba(139,92,246,0.6)', sev: '' }, + { label: 'Activ. & Norm', pct: 10, ms: 0.70, color: 'rgba(20,184,166,0.6)', sev: '' }, + { label: 'Memory Ops', pct: 7, ms: 0.49, color: 'rgba(107,114,128,0.6)', sev: '' } + ], + insights: [ + { sev: 'high', text: 'Sliding window achieves 60% BW peak but full attention drops to 48% at 8K sequence length. Memory-bound: FLOPS/Byte=12, well below ridge point ~134 — arithmetic intensity too low for compute utilization.', source: 'Trace' }, + { sev: 'high', text: 'M=32K expert GEMMs at only 62% peak. MXFP4 dequant: 128 experts × 4 active × 36 layers = 18432 dequant ops per forward pass. Total dequant overhead ~4.8ms — a significant fixed cost at ISL=8K.', source: 'Roofline' }, + { sev: 'med', text: 'QKV projections at 70% peak TFLOPS. GQA KV projection (N=360) wastes tiles — N not aligned to tile MT256×128×64, causing ~8% fractional tile fill loss. Separate Q and KV projections double the kernel launch count.', source: 'Trace' }, + { sev: 'tip', text: 'Sliding window layers (18 of 36) process only a 128-token window but still launch full-sequence-length kernels. Attention computation at these layers is bounded by the window size, yet kernel launch overhead is proportional to full sequence.', source: 'Trace' } + ] + }, + '128': { + e2e: { input: '2,813', output: '489.2', ttft: '808ms', itl: '8.35ms', latency: '8,116ms', gap: '31% gap', rooflineGap: '22%' }, + prefill: [ + { label: 'GQA (Attn)', pct: 24, ms: 193.9, color: 'rgba(239,68,68,0.6)', sev: 'hot' }, + { label: 'Attn GEMMs', pct: 18, ms: 145.4, color: 'rgba(59,130,246,0.6)', sev: 'med' }, + { label: 'MoE (MXFP4)', pct: 24, ms: 193.9, color: 'rgba(245,158,11,0.6)', sev: 'hot' }, + { label: 'Communication', pct: 18, ms: 145.4, color: 'rgba(139,92,246,0.6)', sev: 'med' }, + { label: 'Activ. & Norm', pct: 9, ms: 72.7, color: 'rgba(20,184,166,0.6)', sev: '' }, + { label: 'Memory Ops', pct: 7, ms: 56.6, color: 'rgba(107,114,128,0.6)', sev: '' } + ], + decode: [ + { label: 'GQA (Attn)', pct: 18, ms: 1.50, color: 'rgba(239,68,68,0.6)', sev: 'hot' }, + { label: 'Attn GEMMs', pct: 14, ms: 1.17, color: 'rgba(59,130,246,0.6)', sev: 'med' }, + { label: 'MoE (MXFP4)', pct: 30, ms: 2.51, color: 'rgba(245,158,11,0.6)', sev: 'hot' }, + { label: 'Communication', pct: 19, ms: 1.59, color: 'rgba(139,92,246,0.6)', sev: 'med' }, + { label: 'Activ. & Norm', pct: 12, ms: 1.00, color: 'rgba(20,184,166,0.6)', sev: '' }, + { label: 'Memory Ops', pct: 7, ms: 0.59, color: 'rgba(107,114,128,0.6)', sev: '' } + ], + insights: [ + { sev: 'high', text: 'flash_attn on (128, 8KV, 8K, 128): BW only 43% peak. M=1M QKV proj at 72% peak TFLOPS — attention memory bandwidth dominates at large ISL × high concurrency due to massive KV-cache read traffic.', source: 'Trace' }, + { sev: 'high', text: 'AllReduce payload 5.5GB per call (1M×2880×2B). Ring bandwidth at 68% peak — compute and communication fully serialized with ~80ms spent in synchronization barriers per AllReduce.', source: 'Trace' }, + { sev: 'med', text: 'Expert GEMMs at M=1M: good tile fill (72% peak) but MXFP4 dequant adds 5.2ms total. Dispatch barrier: 40µs × 36 layers = 1.44ms — dequant overhead exceeds dispatch barrier by 3.6×.', source: 'Roofline' }, + { sev: 'tip', text: 'TTFT 625ms exceeds interactive threshold (200ms). Full 8K×128 prefill processed as single contiguous batch across all 36 layers before first token output — no chunking or pipelining observed.', source: 'Trace' } + ] + } + } + }, + 'FP8': { + '1K / 1K': { + '4': { + e2e: { input: '78.06', output: '76.61', ttft: '58.8ms', itl: '6.91ms', latency: '6,512ms', gap: '26% gap', rooflineGap: '13%→9%' }, + prefill: [ + { label: 'GQA (Attn)', pct: 24, ms: 14.1, color: 'rgba(239,68,68,0.6)', sev: 'hot' }, + { label: 'Attn GEMMs', pct: 18, ms: 10.6, color: 'rgba(59,130,246,0.6)', sev: 'med' }, + { label: 'MoE (FP8)', pct: 30, ms: 17.6, color: 'rgba(245,158,11,0.6)', sev: 'hot' }, + { label: 'Communication', pct: 14, ms: 8.2, color: 'rgba(139,92,246,0.6)', sev: '' }, + { label: 'Activ. & Norm', pct: 8, ms: 4.7, color: 'rgba(20,184,166,0.6)', sev: '' }, + { label: 'Memory Ops', pct: 6, ms: 3.5, color: 'rgba(107,114,128,0.6)', sev: '' } + ], + decode: [ + { label: 'GQA (Attn)', pct: 22, ms: 1.52, color: 'rgba(239,68,68,0.6)', sev: 'hot' }, + { label: 'Attn GEMMs', pct: 15, ms: 1.04, color: 'rgba(59,130,246,0.6)', sev: 'med' }, + { label: 'MoE (FP8)', pct: 28, ms: 1.93, color: 'rgba(245,158,11,0.6)', sev: 'hot' }, + { label: 'Communication', pct: 17, ms: 1.17, color: 'rgba(139,92,246,0.6)', sev: '' }, + { label: 'Activ. & Norm', pct: 10, ms: 0.69, color: 'rgba(20,184,166,0.6)', sev: '' }, + { label: 'Memory Ops', pct: 8, ms: 0.55, color: 'rgba(107,114,128,0.6)', sev: '' } + ], + insights: [ + { sev: 'high', text: 'Native FP8 eliminates MXFP4 dequant — BW utilization jumps to 48% peak (vs 31% at MXFP4). However, 128-expert top-4 dispatch still launches up to 128 expert GEMMs per layer × 36 layers per forward pass, maintaining high launch overhead.', source: 'Trace' }, + { sev: 'high', text: 'Sliding(128)/full alternating: flash_attn at 60% HBM peak. FP8 KV-cache reduces per-layer storage by 2× vs BF16 but attention kernel remains memory-bound — bandwidth, not compute, limits performance.', source: 'Roofline' }, + { sev: 'med', text: 'QKV projections at FP8: 72% peak TFLOPS — 6pp improvement vs MXFP4 from eliminated dequant pipeline stalls. Remaining 28% gap indicates non-dequant inefficiencies in tile selection or memory access patterns.', source: 'Trace' }, + { sev: 'tip', text: 'RMSNorm unfused: 73 instances × 8 kernels = 584 launches with redundant HBM passes. At FP8, compute ops speed up but normalization kernel time remains unchanged, increasing its relative share.', source: 'Trace' }, + { sev: 'na', text: 'CUDA Graph not applicable on ROCm. ~1800+ individual kernel launches contribute to 6-8% idle time from CPU dispatch overhead between launches.', source: 'Not Support' } + ] + }, + '128': { + e2e: { input: '780.6', output: '699.6', ttft: '339ms', itl: '8.29ms', latency: '8,178ms', gap: '24% gap', rooflineGap: '13%→9%' }, + prefill: [ + { label: 'GQA (Attn)', pct: 22, ms: 74.6, color: 'rgba(239,68,68,0.6)', sev: 'hot' }, + { label: 'Attn GEMMs', pct: 17, ms: 57.6, color: 'rgba(59,130,246,0.6)', sev: 'med' }, + { label: 'MoE (FP8)', pct: 28, ms: 94.9, color: 'rgba(245,158,11,0.6)', sev: 'hot' }, + { label: 'Communication', pct: 18, ms: 61.0, color: 'rgba(139,92,246,0.6)', sev: 'med' }, + { label: 'Activ. & Norm', pct: 9, ms: 30.5, color: 'rgba(20,184,166,0.6)', sev: '' }, + { label: 'Memory Ops', pct: 6, ms: 20.3, color: 'rgba(107,114,128,0.6)', sev: '' } + ], + decode: [ + { label: 'GQA (Attn)', pct: 20, ms: 1.66, color: 'rgba(239,68,68,0.6)', sev: 'hot' }, + { label: 'Attn GEMMs', pct: 14, ms: 1.16, color: 'rgba(59,130,246,0.6)', sev: 'med' }, + { label: 'MoE (FP8)', pct: 27, ms: 2.24, color: 'rgba(245,158,11,0.6)', sev: 'hot' }, + { label: 'Communication', pct: 20, ms: 1.66, color: 'rgba(139,92,246,0.6)', sev: 'med' }, + { label: 'Activ. & Norm', pct: 11, ms: 0.91, color: 'rgba(20,184,166,0.6)', sev: '' }, + { label: 'Memory Ops', pct: 8, ms: 0.66, color: 'rgba(107,114,128,0.6)', sev: '' } + ], + insights: [ + { sev: 'high', text: 'Expert GEMMs at M=128K: 70% peak TFLOPS. FP8 saves ~3ms/layer vs MXFP4 dequant. Dispatch barrier 35µs/layer still present — expert dispatch synchronization and next-layer prefetch not overlapped.', source: 'Trace' }, + { sev: 'high', text: 'AllReduce on (128K, 2880): TP activation payload remains in BF16 regardless of weight precision. Ring BW 76% Infinity Fabric peak — near optimal, yet compute and communication remain fully serialized.', source: 'Trace' }, + { sev: 'med', text: 'FP8 flash_attn at 63% HBM peak — best efficiency among all gpt-oss configs but still memory-bound. QKV proj at 74% peak TFLOPS from native FP8 tensor cores, confirming dequant was the primary FP4 bottleneck.', source: 'Roofline' }, + { sev: 'tip', text: 'RMSNorm + SwiGLU unfused across 36 layers: 584 RMSNorm + 72 SwiGLU = 656 Activ.&Norm kernel launches. Kernel dispatch overhead becomes proportionally significant at high concurrency.', source: 'Trace' } + ] + } + }, + '1K / 8K': { + '4': { + e2e: { input: '58.24', output: '621.0', ttft: '49.8ms', itl: '8.27ms', latency: '61,762ms', gap: '29% gap', rooflineGap: '21%' }, + prefill: [ + { label: 'GQA (Attn)', pct: 23, ms: 11.5, color: 'rgba(239,68,68,0.6)', sev: 'hot' }, + { label: 'Attn GEMMs', pct: 16, ms: 8.0, color: 'rgba(59,130,246,0.6)', sev: 'med' }, + { label: 'MoE (FP8)', pct: 28, ms: 13.9, color: 'rgba(245,158,11,0.6)', sev: 'hot' }, + { label: 'Communication', pct: 17, ms: 8.5, color: 'rgba(139,92,246,0.6)', sev: '' }, + { label: 'Activ. & Norm', pct: 9, ms: 4.5, color: 'rgba(20,184,166,0.6)', sev: '' }, + { label: 'Memory Ops', pct: 7, ms: 3.5, color: 'rgba(107,114,128,0.6)', sev: '' } + ], + decode: [ + { label: 'GQA (Attn)', pct: 30, ms: 2.48, color: 'rgba(239,68,68,0.6)', sev: 'hot' }, + { label: 'Attn GEMMs', pct: 13, ms: 1.08, color: 'rgba(59,130,246,0.6)', sev: 'med' }, + { label: 'MoE (FP8)', pct: 22, ms: 1.82, color: 'rgba(245,158,11,0.6)', sev: 'hot' }, + { label: 'Communication', pct: 16, ms: 1.32, color: 'rgba(139,92,246,0.6)', sev: '' }, + { label: 'Activ. & Norm', pct: 11, ms: 0.91, color: 'rgba(20,184,166,0.6)', sev: '' }, + { label: 'Memory Ops', pct: 8, ms: 0.66, color: 'rgba(107,114,128,0.6)', sev: '' } + ], + insights: [ + { sev: 'high', text: 'Decode-bound: 32MB KV read/layer × 36 layers = 1.15GB per step. BW only 48% peak — HBM bandwidth is the primary bottleneck, not compute. Each decode step re-reads the entire KV-cache across all layers.', source: 'Trace' }, + { sev: 'high', text: 'FP8 expert GEMMs 15% better than MXFP4 but per-expert GEMM at (M=4, N=d_ff, K=2880) is severely small-M bound. Up to 128 expert GEMMs/layer × 36 layers per step — tile utilization < 5% for small-M shapes.', source: 'Roofline' }, + { sev: 'med', text: 'Unfused RoPE: each Q/K rotation decomposes into 5 kernels (neg+cat+mul+mul+add) × 2(Q,K) × 36 layers = 360 launches/step. The cat (rotate_half) at 24µs/call is 3× costlier than elementwise ops due to non-contiguous data reorganization.', source: 'Trace' }, + { sev: 'tip', text: '29% gap vs B200 — 6pp better than MXFP4, confirming dequant overhead as major FP4 penalty. Paged KV-cache at 8K output currently occupies majority of HBM, limiting concurrent batch capacity.', source: 'Roofline' } + ] + }, + '128': { + e2e: { input: '582.4', output: '5,422', ttft: '316ms', itl: '10.11ms', latency: '75,687ms', gap: '27% gap', rooflineGap: '21%' }, + prefill: [ + { label: 'GQA (Attn)', pct: 21, ms: 66.4, color: 'rgba(239,68,68,0.6)', sev: 'hot' }, + { label: 'Attn GEMMs', pct: 15, ms: 47.4, color: 'rgba(59,130,246,0.6)', sev: 'med' }, + { label: 'MoE (FP8)', pct: 27, ms: 85.3, color: 'rgba(245,158,11,0.6)', sev: 'hot' }, + { label: 'Communication', pct: 20, ms: 63.2, color: 'rgba(139,92,246,0.6)', sev: 'med' }, + { label: 'Activ. & Norm', pct: 10, ms: 31.6, color: 'rgba(20,184,166,0.6)', sev: '' }, + { label: 'Memory Ops', pct: 7, ms: 22.1, color: 'rgba(107,114,128,0.6)', sev: '' } + ], + decode: [ + { label: 'GQA (Attn)', pct: 27, ms: 2.73, color: 'rgba(239,68,68,0.6)', sev: 'hot' }, + { label: 'Attn GEMMs', pct: 13, ms: 1.31, color: 'rgba(59,130,246,0.6)', sev: 'med' }, + { label: 'MoE (FP8)', pct: 23, ms: 2.33, color: 'rgba(245,158,11,0.6)', sev: 'hot' }, + { label: 'Communication', pct: 18, ms: 1.82, color: 'rgba(139,92,246,0.6)', sev: 'med' }, + { label: 'Activ. & Norm', pct: 11, ms: 1.11, color: 'rgba(20,184,166,0.6)', sev: '' }, + { label: 'Memory Ops', pct: 8, ms: 0.81, color: 'rgba(107,114,128,0.6)', sev: '' } + ], + insights: [ + { sev: 'high', text: '128 concurrent KV-caches at OSL=8K consume ~25GB HBM — L2 miss rate ~82%. flash_attn at only 39% peak due to severe cache thrashing. Competing cache line evictions from parallel sequence attention cause repeated HBM re-reads.', source: 'Trace' }, + { sev: 'high', text: 'AllReduce payload 737MB per call. Ring-AllReduce at 71% Infinity Fabric peak — compute and expert dispatch not overlapped with communication, leaving ~35ms of idle compute during synchronization.', source: 'Trace' }, + { sev: 'med', text: 'FP8 native GEMMs at 65% peak (vs 52% MXFP4). At conc=128 decode, each expert gets M~4 on average (128 tokens × top-4 / 128 experts) — small-M GEMM efficiency only ~40%, severely underutilizing compute units for per-token expert dispatch.', source: 'Roofline' }, + { sev: 'tip', text: '27% gap vs B200 — 6pp better than MXFP4. KV-cache at FP16 storage occupies majority of HBM at 128 concurrent × 8K output sequences, limiting further batch scaling.', source: 'Trace' } + ] + } + }, + '8K / 1K': { + '4': { + e2e: { input: '406.6', output: '65.12', ttft: '188.5ms', itl: '6.05ms', latency: '5,839ms', gap: '28% gap', rooflineGap: '17%' }, + prefill: [ + { label: 'GQA (Attn)', pct: 28, ms: 52.8, color: 'rgba(239,68,68,0.6)', sev: 'hot' }, + { label: 'Attn GEMMs', pct: 17, ms: 32.0, color: 'rgba(59,130,246,0.6)', sev: 'med' }, + { label: 'MoE (FP8)', pct: 24, ms: 45.2, color: 'rgba(245,158,11,0.6)', sev: 'hot' }, + { label: 'Communication', pct: 15, ms: 28.3, color: 'rgba(139,92,246,0.6)', sev: '' }, + { label: 'Activ. & Norm', pct: 10, ms: 18.9, color: 'rgba(20,184,166,0.6)', sev: '' }, + { label: 'Memory Ops', pct: 6, ms: 11.3, color: 'rgba(107,114,128,0.6)', sev: '' } + ], + decode: [ + { label: 'GQA (Attn)', pct: 22, ms: 1.33, color: 'rgba(239,68,68,0.6)', sev: 'hot' }, + { label: 'Attn GEMMs', pct: 14, ms: 0.85, color: 'rgba(59,130,246,0.6)', sev: 'med' }, + { label: 'MoE (FP8)', pct: 30, ms: 1.82, color: 'rgba(245,158,11,0.6)', sev: 'hot' }, + { label: 'Communication', pct: 16, ms: 0.97, color: 'rgba(139,92,246,0.6)', sev: '' }, + { label: 'Activ. & Norm', pct: 11, ms: 0.67, color: 'rgba(20,184,166,0.6)', sev: '' }, + { label: 'Memory Ops', pct: 7, ms: 0.42, color: 'rgba(107,114,128,0.6)', sev: '' } + ], + insights: [ + { sev: 'high', text: 'Full-attention layers BW 53% peak, sliding layers 60% — 8K sequence length creates a bimodal performance split. Alternating attention pattern prevents uniform kernel optimization across layers.', source: 'Trace' }, + { sev: 'high', text: 'M=32K expert GEMMs at 71% peak TFLOPS — well-amortized at large M. Native FP8 eliminates 4.8ms dequant overhead vs MXFP4, but expert dispatch barrier (40µs/layer) still serializes expert execution.', source: 'Roofline' }, + { sev: 'med', text: 'QKV projections at 73% peak TFLOPS. Tile MT256×128×64 well-utilized for Q projection but GQA KV projection (N=360) still wastes ~8% of tiles due to N not aligned to tile boundaries.', source: 'Trace' }, + { sev: 'tip', text: 'Sliding window layers (18 of 36) process only 128-token windows but launch full-sequence kernels. Attention compute is bounded by window size, yet kernel overhead scales with full 8K sequence length.', source: 'Trace' } + ] + }, + '128': { + e2e: { input: '3,253', output: '564.4', ttft: '701ms', itl: '7.26ms', latency: '7,029ms', gap: '25% gap', rooflineGap: '17%' }, + prefill: [ + { label: 'GQA (Attn)', pct: 26, ms: 182.3, color: 'rgba(239,68,68,0.6)', sev: 'hot' }, + { label: 'Attn GEMMs', pct: 16, ms: 112.2, color: 'rgba(59,130,246,0.6)', sev: 'med' }, + { label: 'MoE (FP8)', pct: 23, ms: 161.2, color: 'rgba(245,158,11,0.6)', sev: 'hot' }, + { label: 'Communication', pct: 19, ms: 133.2, color: 'rgba(139,92,246,0.6)', sev: 'med' }, + { label: 'Activ. & Norm', pct: 9, ms: 63.1, color: 'rgba(20,184,166,0.6)', sev: '' }, + { label: 'Memory Ops', pct: 7, ms: 49.1, color: 'rgba(107,114,128,0.6)', sev: '' } + ], + decode: [ + { label: 'GQA (Attn)', pct: 20, ms: 1.45, color: 'rgba(239,68,68,0.6)', sev: 'hot' }, + { label: 'Attn GEMMs', pct: 13, ms: 0.94, color: 'rgba(59,130,246,0.6)', sev: 'med' }, + { label: 'MoE (FP8)', pct: 29, ms: 2.11, color: 'rgba(245,158,11,0.6)', sev: 'hot' }, + { label: 'Communication', pct: 19, ms: 1.38, color: 'rgba(139,92,246,0.6)', sev: 'med' }, + { label: 'Activ. & Norm', pct: 12, ms: 0.87, color: 'rgba(20,184,166,0.6)', sev: '' }, + { label: 'Memory Ops', pct: 7, ms: 0.51, color: 'rgba(107,114,128,0.6)', sev: '' } + ], + insights: [ + { sev: 'high', text: 'flash_attn at only 45% HBM peak. M=1M QKV proj at 74% peak TFLOPS — compute-efficient but memory-bound. Despite FP8 native tensors maximizing hardware utilization, HBM bandwidth remains the ceiling.', source: 'Trace' }, + { sev: 'high', text: 'AllReduce payload 5.5GB (1M×2880×2B). Ring-AllReduce at only 66% Infinity Fabric peak — synchronization overhead grows superlinearly at large batch × sequence sizes. Compute and communication fully serialized.', source: 'Trace' }, + { sev: 'med', text: 'Expert GEMMs at M=1M: 72% peak TFLOPS. FP8 eliminates 5.2ms dequant overhead vs MXFP4 but remaining 28% gap from tile selection and memory access patterns persists at this scale.', source: 'Roofline' }, + { sev: 'tip', text: 'TTFT 540ms exceeds interactive threshold (200ms). All 36 layers processed sequentially on single TP=8 group — no pipeline parallelism observed to distribute prefill compute across stages.', source: 'Trace' } + ] + } + } + } + } + }; + + /* ---------- Analysis Dropdown Handlers (Searchable Combobox) ---------- */ + const anaDropdowns = document.querySelectorAll('.analysis-filter-bar .dropdown-wrap'); + anaDropdowns.forEach(wrap => { + const trigger = wrap.querySelector('.dropdown-trigger'); + const menu = wrap.querySelector('.dropdown-menu'); + const comboInput = wrap.querySelector('.search-combo-input'); + if (!trigger || !menu) return; + + // Click on trigger or caret opens dropdown + trigger.addEventListener('click', e => { + // Don't toggle if clicking inside the input + if (e.target.classList.contains('search-combo-input')) { + // Show menu + document.querySelectorAll('.analysis-filter-bar .dropdown-menu.open').forEach(m => { + if (m !== menu) m.classList.remove('open'); + }); + if (!menu.classList.contains('open')) menu.classList.add('open'); + return; + } + e.stopPropagation(); + document.querySelectorAll('.analysis-filter-bar .dropdown-menu.open').forEach(m => { + if (m !== menu) m.classList.remove('open'); + }); + menu.classList.toggle('open'); + if (menu.classList.contains('open') && comboInput) { + comboInput.focus(); + comboInput.select(); + } + }); + + // Combobox search filtering + if (comboInput) { + comboInput.addEventListener('input', () => { + const query = comboInput.value.toLowerCase(); + let hasMatch = false; + menu.querySelectorAll('.dd-item').forEach(item => { + const text = (item.dataset.val || item.textContent).toLowerCase(); + const show = text.includes(query); + item.style.display = show ? '' : 'none'; + if (show) hasMatch = true; + }); + // Show "no match" or allow custom input + let noMatchEl = menu.querySelector('.dd-no-match'); + if (!hasMatch && query.length > 0) { + if (!noMatchEl) { + noMatchEl = document.createElement('div'); + noMatchEl.className = 'dd-item dd-no-match'; + menu.appendChild(noMatchEl); + } + noMatchEl.textContent = `Use "${comboInput.value}" (custom)`; + noMatchEl.dataset.val = comboInput.value; + noMatchEl.style.display = ''; + } else if (noMatchEl) { + noMatchEl.style.display = 'none'; + } + if (!menu.classList.contains('open')) menu.classList.add('open'); + }); + + comboInput.addEventListener('click', e => { + e.stopPropagation(); + if (!menu.classList.contains('open')) menu.classList.add('open'); + }); + + // Allow Enter to confirm custom value + comboInput.addEventListener('keydown', e => { + if (e.key === 'Enter') { + menu.classList.remove('open'); + } + }); + } + + // Item selection — only updates the UI selection, data refreshes on Start Analysis + menu.querySelectorAll('.dd-item').forEach(item => { + item.addEventListener('click', () => { + menu.querySelectorAll('.dd-item').forEach(i => i.classList.remove('active')); + item.classList.add('active'); + if (comboInput) { + comboInput.value = item.dataset.val; + } else { + const ddValue = wrap.querySelector('.dd-value'); + if (ddValue) ddValue.textContent = item.dataset.val; + } + menu.classList.remove('open'); + // Update precision dropdown options when model changes + const sel = getAnalysisSelections(); + updateAnalysisPrecision(sel.model); + }); + }); + }); + document.addEventListener('click', () => { + document.querySelectorAll('.analysis-filter-bar .dropdown-menu.open').forEach(m => m.classList.remove('open')); + }); + + /* ---------- Read current analysis selections ---------- */ + function getAnalysisSelections() { + const v = id => { + // Try combobox input first, then dd-value span + const input = document.querySelector(`#${id} .search-combo-input`); + if (input) return input.value.trim(); + const el = document.querySelector(`#${id} .dd-value`); + return el ? el.textContent.trim() : ''; + }; + return { + model: v('ana-dd-model'), + framework: v('ana-dd-framework'), + gpu: v('ana-dd-gpu'), + precision: v('ana-dd-precision'), + isl_osl: v('ana-dd-isl-osl'), + conc: v('ana-dd-conc'), + trace: v('ana-dd-trace') + }; + } + + /* ---------- Update precision options based on model ---------- */ + function updateAnalysisPrecision(model) { + const menu = document.getElementById('ana-precision-menu'); + if (!menu) return; + // Both models support FP4 and FP8 + menu.innerHTML = '
● FP4
■ FP8
'; + // re-bind clicks — only updates UI, data refreshes on Start Analysis + menu.querySelectorAll('.dd-item').forEach(item => { + item.addEventListener('click', () => { + menu.querySelectorAll('.dd-item').forEach(i => i.classList.remove('active')); + item.classList.add('active'); + document.querySelector('#ana-dd-precision .dd-value').textContent = item.dataset.val; + menu.classList.remove('open'); + }); + }); + } + + /* ---------- Render Step Rows (sorted by pct descending) ---------- */ + function renderStepRows(steps) { + const body = document.getElementById('ana-step-body'); + if (!body) return; + const sorted = [...steps].sort((a, b) => b.pct - a.pct); + body.innerHTML = sorted.map(s => { + return `
+ ${s.label} +
+
+
+
+ ${s.pct}% · ${s.ms}ms avg +
+
`; + }).join(''); + } + + /* ---------- Render Insights ---------- */ + function renderInsights(insights) { + const body = document.getElementById('ana-insights-body'); + if (!body) return; + const subEl = document.getElementById('ana-insights-subtitle'); + if (subEl) subEl.textContent = `Key Findings · ${insights.length} insights`; + body.innerHTML = insights.map(ins => { + const sevClass = ins.sev; + const sevLabel = ins.sev === 'high' ? 'HIGH' : ins.sev === 'med' ? 'MED' : ins.sev === 'tip' ? 'TIP' : 'N/A'; + const sourceTag = ins.source + ? `${ins.source}` + : ''; + return `
+ ${sevLabel} + ${ins.text} + ${sourceTag} +
`; + }).join(''); + } + + /* ---------- Track whether analysis has been started ---------- */ + let analysisStarted = false; + + /* ---------- Main Update Function ---------- */ + function onAnalysisSelectionChange() { + const sel = getAnalysisSelections(); + // update precision options when model changes + updateAnalysisPrecision(sel.model); + + // lookup data + const modelData = ANALYSIS_DATA[sel.model]; + if (!modelData) return; + const precData = modelData[sel.precision]; + if (!precData) { + // fallback to first available precision + const firstPrec = Object.keys(modelData)[0]; + document.querySelector('#ana-dd-precision .dd-value').textContent = firstPrec; + sel.precision = firstPrec; + } + const islData = (modelData[sel.precision] || {})[sel.isl_osl]; + if (!islData) return; + const concData = islData[sel.conc]; + if (!concData) return; + + // Update E2E subtitle + const subtitleEl = document.getElementById('ana-e2e-subtitle'); + const MODEL_TP = { 'DeepSeek R1 0528': 8, 'gpt-oss 120B': 8 }; + const tp = MODEL_TP[sel.model] || 8; + if (subtitleEl) subtitleEl.textContent = `${sel.model} · ${sel.framework} · ${sel.gpu} · ${sel.precision} · ${sel.isl_osl.replace(/ /g,'')} · conc=${sel.conc} · TP=${tp}`; + + // Update E2E metrics — restore color classes and set values + const e = concData.e2e; + const setM = (id, v, colorClass) => { + const el = document.getElementById(id); + if (el) { + el.textContent = v; + el.classList.remove('muted'); + if (colorClass) el.className = `metric-value ${colorClass}`; + } + }; + setM('ana-m-input', e.input + ' tok/s/GPU', 'blue'); + setM('ana-m-output', e.output + ' tok/s/GPU', 'blue'); + setM('ana-m-ttft', e.ttft, 'yellow'); + setM('ana-m-itl', e.itl, 'yellow'); + setM('ana-m-latency', e.latency, 'red'); + setM('ana-m-gap', e.gap, 'red'); + setM('ana-m-roofline-gap', e.rooflineGap || '—', 'yellow'); + + // Update Model Profiling subtitle + const stepSubEl = document.getElementById('ana-step-subtitle'); + if (stepSubEl) stepSubEl.textContent = `Phase Breakdown: Prefill → Decode · ${sel.model} · ${sel.precision}`; + + // Update Step-by-Step (use active phase tab) + const activePhase = document.querySelector('#ana-phase-tabs .phase-tab.active'); + const phase = activePhase ? activePhase.dataset.phase : 'prefill'; + renderStepRows(concData[phase] || concData.prefill); + + // Update Insights + const insightsSubEl = document.getElementById('ana-insights-subtitle'); + if (insightsSubEl && concData.insights) insightsSubEl.textContent = `Key Findings · ${concData.insights.length} insights`; + renderInsights(concData.insights || []); + } + + /* ---------- Phase tab clicks (only work after Start) ---------- */ + document.querySelectorAll('#ana-phase-tabs .phase-tab').forEach(tab => { + tab.addEventListener('click', () => { + if (!analysisStarted) return; + document.querySelectorAll('#ana-phase-tabs .phase-tab').forEach(t => t.classList.remove('active')); + tab.classList.add('active'); + onAnalysisSelectionChange(); + }); + }); + + /* ---------- Start Analysis button ---------- */ + const btnAnalysis = document.getElementById('btn-start-analysis'); + if (btnAnalysis) { + btnAnalysis.addEventListener('click', () => { + analysisStarted = true; + onAnalysisSelectionChange(); + // visual feedback + btnAnalysis.textContent = '✅ Analysis Complete'; + btnAnalysis.classList.add('btn-success'); + setTimeout(() => { + btnAnalysis.textContent = '🔬 Start Analysis'; + btnAnalysis.classList.remove('btn-success'); + }, 2000); + }); + } + +})(); diff --git a/Web/apps/hyperloom/src/App.vue b/Web/apps/hyperloom/src/App.vue new file mode 100644 index 000000000..15cea9130 --- /dev/null +++ b/Web/apps/hyperloom/src/App.vue @@ -0,0 +1,109 @@ + + + diff --git a/Web/apps/hyperloom/src/__tests__/setup.test.ts b/Web/apps/hyperloom/src/__tests__/setup.test.ts new file mode 100644 index 000000000..394f27b5c --- /dev/null +++ b/Web/apps/hyperloom/src/__tests__/setup.test.ts @@ -0,0 +1,7 @@ +import { describe, it, expect } from 'vitest' + +describe('HyperLoom', () => { + it('app module loads', () => { + expect(true).toBe(true) + }) +}) diff --git a/Web/apps/hyperloom/src/assets/claw.css b/Web/apps/hyperloom/src/assets/claw.css new file mode 100644 index 000000000..dda75eddf --- /dev/null +++ b/Web/apps/hyperloom/src/assets/claw.css @@ -0,0 +1,1423 @@ +/* ══════════════════════════════════════════════════════ + AMD PRISM — Claw Agent Chat Styles + ══════════════════════════════════════════════════════ */ + +.claw-page { + display: flex; + height: calc(100vh - var(--nav-h)); + margin: -16px; + background: var(--bg); +} + +/* ══════ Sidebar ══════ */ + +.claw-sidebar { + width: 280px; + min-width: 280px; + background: var(--white); + border-right: 1px solid var(--border); + display: flex; + flex-direction: column; + transition: all 0.2s ease; + overflow: hidden; +} + +.claw-sidebar.collapsed { + width: 0; + min-width: 0; + border-right: none; +} + +.claw-sidebar-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 16px; + border-bottom: 1px solid var(--border); +} + +.claw-sidebar-title { + font-size: 16px; + font-weight: 700; + color: var(--text-primary); +} + +.claw-btn-icon { + background: none; + border: none; + cursor: pointer; + font-size: 16px; + color: var(--text-muted); + padding: 4px 8px; + border-radius: 4px; + transition: all 0.15s; +} + +.claw-btn-icon:hover { + background: #f1f5f9; + color: var(--text-primary); +} + +.claw-new-chat-btn { + margin: 12px 16px; + padding: 10px 16px; + background: var(--amd-red); + color: white; + border: none; + border-radius: var(--radius-sm); + font-size: 13px; + font-weight: 600; + cursor: pointer; + display: flex; + align-items: center; + gap: 8px; + transition: background 0.15s; +} + +.claw-new-chat-btn:hover { + background: var(--amd-red-light); +} + +.claw-history-label { + padding: 8px 16px; + font-size: 11px; + font-weight: 600; + color: var(--text-muted); + text-transform: uppercase; + letter-spacing: 0.5px; + cursor: pointer; + user-select: none; + display: flex; + align-items: center; + gap: 6px; +} + +.claw-history-label span { + transition: transform 0.2s; + font-size: 10px; +} + +.rotated { + transform: rotate(-90deg); +} + +.claw-session-list { + flex: 1; + overflow-y: auto; + padding: 0 8px; +} + +.claw-session-item { + display: flex; + align-items: center; + justify-content: space-between; + padding: 10px 12px; + border-radius: var(--radius-xs); + cursor: pointer; + transition: background 0.1s; + margin-bottom: 2px; +} + +.claw-session-item:hover { + background: #fef2f2; +} + +.claw-session-item.active { + background: #fef2f2; + border-left: 3px solid var(--amd-red); +} + +.claw-session-name { + font-size: 13px; + color: var(--text-primary); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + flex: 1; +} + +.claw-session-delete { + opacity: 0; + font-size: 12px; + cursor: pointer; + padding: 2px 4px; + border-radius: 4px; + transition: all 0.15s; +} + +.claw-session-item:hover .claw-session-delete { + opacity: 1; +} + +.claw-session-delete:hover { + background: rgba(220, 38, 38, 0.1); +} + +.claw-loading, .claw-empty { + padding: 24px 16px; + text-align: center; + font-size: 13px; + color: var(--text-muted); +} + +/* ══════ Main Chat Area ══════ */ + +.claw-main { + flex: 1; + display: flex; + flex-direction: column; + min-width: 0; + position: relative; +} + +.claw-topbar { + display: flex; + align-items: center; + gap: 12px; + padding: 12px 20px; + border-bottom: 1px solid var(--border); + background: var(--white); +} + +.claw-topbar-title { + font-size: 14px; + font-weight: 600; + color: var(--text-primary); +} + +.claw-topbar-status { + font-size: 12px; + color: var(--teal); + margin-left: auto; + animation: pulse 1.5s ease-in-out infinite; +} + +@keyframes pulse { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.5; } +} + +/* ══════ Messages ══════ */ + +.claw-messages { + flex: 1; + overflow-y: auto; + padding: 20px; +} + +.claw-loading-messages { + padding: 40px; + text-align: center; + color: var(--text-muted); +} + +/* Welcome */ + +.claw-welcome { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 100%; + text-align: center; + padding: 40px; +} + +.claw-welcome-icon { + font-size: 64px; + margin-bottom: 16px; +} + +.claw-welcome h2 { + font-size: 24px; + font-weight: 700; + color: var(--text-primary); + margin-bottom: 8px; +} + +.claw-welcome p { + font-size: 14px; + color: var(--text-muted); + margin-bottom: 32px; +} + +.claw-quick-prompts { + display: flex; + flex-direction: column; + gap: 8px; + max-width: 500px; + width: 100%; +} + +.claw-prompt-item { + padding: 12px 16px; + background: var(--white); + border: 1px solid var(--border); + border-radius: var(--radius-sm); + font-size: 13px; + color: var(--text-primary); + cursor: pointer; + text-align: left; + transition: all 0.15s; +} + +.claw-prompt-item:hover { + border-color: var(--amd-red); + background: #fef2f2; +} + +/* Message items */ + +.claw-message-list { + display: flex; + flex-direction: column; + gap: 20px; +} + +.claw-msg-user { + display: flex; + justify-content: flex-end; +} + +.claw-msg-user .claw-msg-content { + background: var(--amd-red); + color: white; + padding: 12px 16px; + border-radius: 16px 16px 4px 16px; + max-width: 70%; + font-size: 14px; + line-height: 1.6; + word-spacing: 0.05em; +} + +.claw-msg-user .claw-msg-content p { margin: 0 0 6px; } +.claw-msg-user .claw-msg-content p:last-child { margin-bottom: 0; } +.claw-msg-user .claw-msg-content a { color: #fecaca; } + +.claw-msg-assistant { + display: flex; + gap: 12px; + align-items: flex-start; +} + +.claw-msg-avatar { + font-size: 28px; + flex-shrink: 0; + width: 36px; + height: 36px; + display: flex; + align-items: center; + justify-content: center; + background: #fef2f2; + border-radius: 50%; +} + +.claw-msg-body { + flex: 1; + min-width: 0; +} + +.claw-msg-sender { + font-size: 12px; + font-weight: 600; + color: var(--amd-red); + margin-bottom: 4px; +} + +.claw-msg-body .claw-msg-content { + font-size: 14px; + line-height: 1.7; + color: var(--text-primary); + word-spacing: 0.05em; + overflow-wrap: break-word; +} + +.claw-msg-body .claw-msg-content p { margin: 0 0 10px; } +.claw-msg-body .claw-msg-content p:last-child { margin-bottom: 0; } + +.claw-msg-body .claw-msg-content h1, +.claw-msg-body .claw-msg-content h2, +.claw-msg-body .claw-msg-content h3, +.claw-msg-body .claw-msg-content h4 { + margin: 16px 0 8px; + font-weight: 700; + line-height: 1.3; + color: var(--text-primary); +} +.claw-msg-body .claw-msg-content h1 { font-size: 20px; } +.claw-msg-body .claw-msg-content h2 { font-size: 17px; } +.claw-msg-body .claw-msg-content h3 { font-size: 15px; } + +.claw-msg-body .claw-msg-content ul, +.claw-msg-body .claw-msg-content ol { + margin: 8px 0; + padding-left: 24px; +} +.claw-msg-body .claw-msg-content li { margin: 4px 0; line-height: 1.6; } + +.claw-msg-body .claw-msg-content blockquote { + margin: 10px 0; + padding: 8px 14px; + border-left: 3px solid var(--amd-red); + background: #fef2f2; + border-radius: 0 6px 6px 0; + color: var(--text-secondary); + font-style: italic; +} + +.claw-msg-body .claw-msg-content table { + width: 100%; + border-collapse: collapse; + margin: 10px 0; + font-size: 13px; +} +.claw-msg-body .claw-msg-content th, +.claw-msg-body .claw-msg-content td { + padding: 8px 12px; + border: 1px solid var(--border); + text-align: left; +} +.claw-msg-body .claw-msg-content th { + background: var(--surface, #f8f9fb); + font-weight: 600; +} + +.claw-msg-body .claw-msg-content hr { + border: none; + border-top: 1px solid var(--border); + margin: 14px 0; +} + +.claw-msg-body .claw-msg-content strong { font-weight: 700; } + +.claw-msg-body .claw-msg-content code { + background: #f1f5f9; + padding: 2px 6px; + border-radius: 4px; + font-family: var(--mono); + font-size: 13px; +} +.claw-msg-body .claw-msg-content pre { + background: #1e293b; + color: #e2e8f0; + padding: 14px 18px; + border-radius: var(--radius-sm); + overflow-x: auto; + font-family: var(--mono); + font-size: 12.5px; + line-height: 1.5; + margin: 10px 0; +} +.claw-msg-body .claw-msg-content pre code { + background: none; + padding: 0; + color: inherit; + font-size: inherit; +} + +/* Typing indicator */ + +.claw-typing { + display: flex; + gap: 4px; + padding: 8px 0; +} + +.claw-typing span { + width: 8px; + height: 8px; + background: var(--text-faint); + border-radius: 50%; + animation: typing 1.2s ease-in-out infinite; +} + +.claw-typing span:nth-child(2) { animation-delay: 0.15s; } +.claw-typing span:nth-child(3) { animation-delay: 0.3s; } + +@keyframes typing { + 0%, 60%, 100% { transform: translateY(0); opacity: 0.4; } + 30% { transform: translateY(-6px); opacity: 1; } +} + +/* ══════ Tool Execution ══════ */ + +.claw-tool-group { + margin: 8px 0; + border: 1px solid var(--border); + border-radius: var(--radius-sm); + overflow: hidden; +} + +.claw-tool-bar { + display: flex; + align-items: center; + justify-content: space-between; + padding: 8px 12px; + background: var(--surface); + font-size: 12px; + font-weight: 600; + color: var(--text-secondary); + cursor: pointer; + transition: background 0.1s; +} + +.claw-tool-bar:hover { + background: #f1f5f9; +} + +.claw-tool-list { + border-top: 1px solid var(--border); +} + +.claw-tool-item { + border-bottom: 1px solid var(--border-light); +} + +.claw-tool-item:last-child { + border-bottom: none; +} + +.claw-tool-header { + display: flex; + align-items: center; + gap: 8px; + padding: 8px 12px; + cursor: pointer; + font-size: 12px; + transition: background 0.1s; +} + +.claw-tool-header:hover { + background: #fafafa; +} + +.claw-tool-status { + font-size: 14px; + width: 20px; + text-align: center; +} + +.claw-tool-status.success { color: var(--green); } +.claw-tool-status.error { color: var(--red); } +.claw-tool-status.running { + color: var(--blue); + animation: spin 1s linear infinite; +} + +@keyframes spin { + from { transform: rotate(0deg); } + to { transform: rotate(360deg); } +} + +.claw-tool-name { + font-weight: 600; + color: var(--text-primary); +} + +.claw-tool-brief { + color: var(--text-faint); + font-size: 11px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + flex: 1; +} + +.claw-tool-detail { + padding: 0 12px 12px; +} + +.claw-tool-section { + margin-top: 8px; +} + +.claw-tool-section-title { + font-size: 10px; + font-weight: 700; + color: var(--text-muted); + text-transform: uppercase; + letter-spacing: 0.5px; + margin-bottom: 4px; +} + +.claw-tool-detail pre { + background: #f8fafc; + border: 1px solid var(--border-light); + padding: 8px 12px; + border-radius: 4px; + font-family: var(--mono); + font-size: 11px; + color: var(--text-secondary); + overflow-x: auto; + white-space: pre-wrap; + word-break: break-all; + margin: 0; +} + +/* ══════ Input Area ══════ */ + +.claw-input-section { + padding: 16px 20px; + background: var(--white); + border-top: 1px solid var(--border); +} + +.claw-input-wrapper { + border: 1px solid var(--border); + border-radius: var(--radius); + overflow: hidden; + transition: border-color 0.15s; +} + +.claw-input-wrapper:focus-within { + border-color: var(--amd-red); + box-shadow: 0 0 0 2px rgba(228, 0, 43, 0.1); +} + +.claw-input-wrapper textarea { + width: 100%; + border: none; + outline: none; + resize: none; + padding: 12px 16px; + font-size: 14px; + font-family: var(--font); + color: var(--text-primary); + background: transparent; + min-height: 44px; + max-height: 160px; +} + +.claw-input-wrapper textarea::placeholder { + color: var(--text-faint); +} + +.claw-input-controls { + display: flex; + align-items: center; + justify-content: space-between; + padding: 8px 12px; + background: var(--surface); + border-top: 1px solid var(--border-light); +} + +.claw-input-left { + position: relative; +} + +.claw-tools-btn { + display: flex; + align-items: center; + gap: 6px; + padding: 6px 12px; + background: none; + border: 1px solid var(--border); + border-radius: 6px; + font-size: 12px; + font-weight: 500; + color: var(--text-muted); + cursor: pointer; + transition: all 0.15s; +} + +.claw-tools-btn:hover { + background: #f1f5f9; + color: var(--text-primary); +} + +.claw-tools-btn.active { + border-color: var(--amd-red); + color: var(--amd-red); +} + +.claw-tool-badge { + background: var(--amd-red); + color: white; + font-size: 10px; + font-weight: 700; + padding: 1px 6px; + border-radius: 10px; + min-width: 16px; + text-align: center; +} + +/* Tool picker dropdown */ + +.claw-tool-picker { + position: absolute; + bottom: 100%; + left: 0; + margin-bottom: 8px; + width: 360px; + background: var(--white); + border: 1px solid var(--border); + border-radius: var(--radius); + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12); + z-index: 300; + display: none; + max-height: 400px; + display: flex; + flex-direction: column; +} + +.claw-tool-picker-header { + display: flex; + justify-content: space-between; + padding: 12px 16px; + border-bottom: 1px solid var(--border); + font-size: 13px; + font-weight: 600; + color: var(--text-primary); +} + +.claw-tool-picker-header span:last-child { + color: var(--text-faint); + font-size: 12px; + font-weight: 400; +} + +.claw-tool-picker-segment { + display: flex; + gap: 6px; + padding: 8px 12px; + border-bottom: 1px solid var(--border); + flex-wrap: wrap; +} + +.claw-seg-btn { + border: 1px solid var(--border); + background: var(--surface, #f8f9fb); + color: var(--text-muted); + font-size: 11px; + font-weight: 600; + border-radius: 999px; + padding: 4px 10px; + cursor: pointer; + transition: all 0.15s ease; +} + +.claw-seg-btn:hover { + border-color: var(--amd-red); + color: var(--amd-red); +} + +.claw-seg-btn.active { + background: rgba(228, 0, 43, 0.08); + border-color: var(--amd-red); + color: var(--amd-red); +} + +.claw-tool-picker-list { + overflow-y: auto; + max-height: 320px; +} + +.claw-tool-picker-item { + display: flex; + align-items: center; + gap: 10px; + padding: 10px 16px; + cursor: pointer; + transition: background 0.1s; +} + +.claw-tool-picker-item:hover { + background: #f8fafc; +} + +.claw-tool-picker-item.selected { + background: #fef2f2; +} + +.claw-tool-picker-icon { + font-size: 18px; + flex-shrink: 0; +} + +.claw-tool-picker-info { + flex: 1; + min-width: 0; +} + +.claw-tool-picker-name { + font-size: 13px; + font-weight: 600; + color: var(--text-primary); +} + +.claw-tool-picker-desc { + font-size: 11px; + color: var(--text-faint); + margin-top: 2px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.claw-tool-picker-check { + font-size: 16px; + color: var(--text-faint); + flex-shrink: 0; +} + +.claw-tool-picker-item.selected .claw-tool-picker-check { + color: var(--amd-red); +} + +/* Send / Stop buttons */ + +.claw-send-btn, .claw-stop-btn { + padding: 6px 16px; + border: none; + border-radius: 6px; + font-size: 13px; + font-weight: 600; + cursor: pointer; + transition: all 0.15s; +} + +.claw-send-btn { + background: var(--amd-red); + color: white; +} + +.claw-send-btn:hover:not(:disabled) { + background: var(--amd-red-light); +} + +.claw-send-btn:disabled { + background: #e5e7eb; + color: #9ca3af; + cursor: not-allowed; +} + +.claw-stop-btn { + background: #fee2e2; + color: var(--red); + border: 1px solid #fecaca; +} + +.claw-stop-btn:hover { + background: #fecaca; +} + +/* ══════ Config Summary Chips ══════ */ + +.claw-config-summary { + background: var(--white); + border-bottom: 1px solid var(--border); + padding: 10px 20px; + display: flex; + align-items: center; + gap: 8px; + flex-wrap: wrap; + animation: claw-slideDown 0.3s ease; +} + +@keyframes claw-slideDown { + from { opacity: 0; transform: translateY(-10px); } + to { opacity: 1; transform: translateY(0); } +} + +.claw-config-chip { + display: inline-flex; + align-items: center; + gap: 5px; + padding: 4px 10px; + background: #eff6ff; + border: 1px solid #bfdbfe; + border-radius: 14px; + font-size: 11px; + color: var(--blue); + font-weight: 500; +} + +.claw-config-dot { + width: 6px; + height: 6px; + border-radius: 50%; + background: var(--green); +} + +.claw-config-edit-btn { + margin-left: auto; + padding: 4px 10px; + background: none; + border: 1px solid var(--border); + border-radius: 6px; + font-size: 11px; + color: var(--text-muted); + cursor: pointer; + font-family: var(--font); + transition: all 0.15s; +} + +.claw-config-edit-btn:hover { + border-color: var(--blue); + color: var(--blue); +} + +/* ══════ Wizard Flow ══════ */ + +.claw-wizard-flow { + padding: 20px; + display: flex; + flex-direction: column; + gap: 16px; +} + +.claw-wizard-msg { + display: flex; + gap: 12px; + align-items: flex-start; + animation: claw-fadeIn 0.4s ease; +} + +@keyframes claw-fadeIn { + from { opacity: 0; transform: translateY(8px); } + to { opacity: 1; transform: translateY(0); } +} + +.claw-wizard-avatar { + font-size: 28px; + flex-shrink: 0; + width: 36px; + height: 36px; + display: flex; + align-items: center; + justify-content: center; + background: #fef2f2; + border-radius: 50%; +} + +.claw-wizard-body { + flex: 1; + min-width: 0; +} + +.claw-wizard-sender { + font-size: 12px; + font-weight: 600; + color: var(--amd-red); + margin-bottom: 4px; +} + +.claw-wizard-content { + font-size: 14px; + line-height: 1.6; + color: var(--text-primary); +} + +/* Wizard Card */ + +.claw-wizard-card { + margin: 10px 0; + padding: 14px 16px; + background: linear-gradient(135deg, #fafbff 0%, #f8f9fb 100%); + border: 1px solid #e2e8f0; + border-radius: 10px; +} + +.claw-wizard-title { + font-size: 13px; + font-weight: 700; + color: var(--text-primary); + margin-bottom: 10px; + display: flex; + align-items: center; + gap: 8px; +} + +.claw-step-num { + display: inline-flex; + align-items: center; + justify-content: center; + width: 22px; + height: 22px; + border-radius: 50%; + font-size: 11px; + font-weight: 700; + background: var(--blue); + color: white; +} + +.claw-wizard-subtitle { + font-size: 11px; + font-weight: 600; + color: var(--text-muted); + margin-bottom: 6px; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.claw-wizard-options { + display: flex; + gap: 8px; + flex-wrap: wrap; + margin-bottom: 6px; +} + +.claw-wizard-opt { + padding: 8px 14px; + background: var(--white); + border: 1.5px solid var(--border); + border-radius: 8px; + font-size: 12px; + cursor: pointer; + transition: all 0.15s; + color: var(--text-secondary); +} + +.claw-wizard-opt:hover { + border-color: var(--blue-light); + color: var(--blue); + background: #f0f4ff; + transform: translateY(-1px); +} + +.claw-wizard-opt.selected { + border-color: var(--blue); + background: #eff6ff; + color: var(--blue); + font-weight: 600; + box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1); +} + +.claw-wizard-sub { + font-size: 10px; + color: var(--text-faint); + display: block; + margin-top: 2px; +} + +.claw-wizard-opt.selected .claw-wizard-sub { + color: var(--blue-light); +} + +/* Custom input row */ + +.claw-wizard-custom-row { + display: flex; + gap: 6px; + align-items: center; + margin-top: 8px; +} + +.claw-wizard-custom-input { + flex: 1; + padding: 6px 10px; + border: 1px dashed var(--border); + border-radius: 6px; + font-size: 12px; + font-family: var(--font); + color: var(--text-primary); + outline: none; + background: var(--white); + transition: all 0.15s; +} + +.claw-wizard-custom-input:focus { + border-color: var(--blue); + border-style: solid; + box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.08); +} + +.claw-wizard-custom-input::placeholder { + color: var(--text-faint); + font-style: italic; +} + +.claw-wizard-custom-btn { + padding: 6px 12px; + background: var(--blue); + color: white; + border: none; + border-radius: 6px; + font-size: 11px; + font-weight: 600; + cursor: pointer; + font-family: var(--font); + transition: all 0.15s; + white-space: nowrap; +} + +.claw-wizard-custom-btn:hover { + background: var(--blue-light); +} + +/* Warning */ + +.claw-wizard-warn { + margin-top: 10px; + padding: 8px 12px; + border-radius: 6px; + background: #fff7ed; + border: 1px solid #fed7aa; + font-size: 11px; + color: #c2410c; + display: flex; + align-items: center; + gap: 6px; +} + +/* ══════ Confirmation Card ══════ */ + +.claw-confirm-card { + margin: 10px 0; + padding: 16px; + background: linear-gradient(135deg, #f0fdf4 0%, #f8f9fb 100%); + border: 1px solid #bbf7d0; + border-radius: 10px; +} + +.claw-confirm-title { + font-size: 13px; + font-weight: 700; + color: var(--green); + margin-bottom: 10px; + display: flex; + align-items: center; + gap: 6px; +} + +.claw-confirm-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); + gap: 6px; + margin-bottom: 14px; +} + +.claw-confirm-item { + padding: 6px 10px; + background: var(--white); + border-radius: 6px; + border: 1px solid #d1fae5; +} + +.claw-confirm-label { + font-size: 10px; + color: var(--text-faint); + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.claw-confirm-value { + font-size: 13px; + font-weight: 600; + color: var(--text-primary); + margin-top: 2px; +} + +.claw-confirm-start-btn { + padding: 10px 24px; + background: var(--amd-red); + color: white; + border: none; + border-radius: 8px; + font-size: 14px; + font-weight: 700; + cursor: pointer; + display: flex; + align-items: center; + gap: 8px; + transition: all 0.2s; + font-family: var(--font); +} + +.claw-confirm-start-btn:hover { + background: var(--amd-red-light); + transform: translateY(-1px); + box-shadow: 0 4px 16px rgba(228, 0, 43, 0.3); +} + +/* ══════ Progress Bar ══════ */ + +.claw-progress-bar { + background: var(--white); + border-top: 1px solid var(--border); + padding: 10px 20px; + animation: claw-slideUp 0.3s ease; +} + +@keyframes claw-slideUp { + from { opacity: 0; transform: translateY(10px); } + to { opacity: 1; transform: translateY(0); } +} + +.claw-progress-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 6px; +} + +.claw-progress-label { + font-size: 12px; + font-weight: 600; + color: var(--text-secondary); +} + +.claw-progress-pct { + font-size: 12px; + font-weight: 700; + color: var(--blue); + font-family: var(--mono); +} + +.claw-progress-track { + height: 6px; + background: #e5e7eb; + border-radius: 3px; + overflow: hidden; +} + +.claw-progress-fill { + height: 100%; + border-radius: 3px; + background: linear-gradient(90deg, var(--blue) 0%, var(--green) 100%); + transition: width 1s ease; +} + +.claw-progress-steps { + display: flex; + justify-content: space-between; + margin-top: 6px; +} + +.claw-progress-step { + font-size: 10px; + color: var(--text-faint); +} + +.claw-progress-step.done { + color: var(--green); + font-weight: 600; +} + +.claw-progress-step.active { + color: var(--blue); + font-weight: 600; +} + +/* ══════ Tool Picker Enhancements ══════ */ + +.claw-tool-picker-search { + padding: 8px 12px; + border-bottom: 1px solid var(--border); +} + +.claw-tool-search-input { + width: 100%; + padding: 7px 12px; + border: 1px solid var(--border); + border-radius: 6px; + font-size: 12px; + font-family: var(--font); + color: var(--text-primary); + background: var(--surface, #f8f9fb); + outline: none; + transition: all 0.15s; +} + +.claw-tool-search-input:focus { + border-color: var(--amd-red); + box-shadow: 0 0 0 2px rgba(228, 0, 43, 0.08); + background: var(--white); +} + +.claw-tool-search-input::placeholder { + color: var(--text-faint); +} + +.claw-tool-group-label { + padding: 8px 16px 4px; + font-size: 10px; + font-weight: 700; + color: var(--text-muted); + text-transform: uppercase; + letter-spacing: 0.6px; + background: var(--surface, #f8f9fb); + border-bottom: 1px solid var(--border-light); + position: sticky; + top: 0; + z-index: 1; +} + +.claw-empty-error { + display: flex; + flex-direction: column; + align-items: center; + gap: 8px; +} + +.claw-retry-btn { + border: 1px solid var(--border); + background: var(--surface, #f8f9fb); + color: var(--text-primary); + border-radius: 6px; + padding: 4px 10px; + font-size: 11px; + font-weight: 600; + cursor: pointer; +} + +.claw-retry-btn:hover { + border-color: var(--amd-red); + color: var(--amd-red); +} + +/* ══════ Responsive ══════ */ + +@media (max-width: 768px) { + .claw-sidebar { + position: absolute; + z-index: 200; + height: 100%; + } + + .claw-wizard-options { + flex-direction: column; + } + + .claw-confirm-grid { + grid-template-columns: repeat(2, 1fr); + } + + .claw-config-summary { + padding: 8px 12px; + gap: 4px; + } +} + +/* ===== Tool Panel (large slide-up) ===== */ + +.claw-tool-panel { + position: absolute; + bottom: 0; + left: 0; + right: 0; + max-height: 70vh; + background: var(--white); + border: 1px solid var(--border); + border-radius: 12px 12px 0 0; + box-shadow: 0 -8px 40px rgba(0, 0, 0, 0.15); + z-index: 400; + display: flex; + flex-direction: column; + overflow: hidden; +} + +.claw-tool-panel-header { + display: flex; + align-items: center; + padding: 14px 20px; + border-bottom: 1px solid var(--border); + gap: 12px; + flex-shrink: 0; +} + +.claw-tool-panel-title { + font-size: 15px; + font-weight: 700; + color: var(--text-primary); +} + +.claw-tool-panel-count { + font-size: 12px; + color: var(--text-faint); + margin-left: auto; +} + +.claw-tool-panel-send { + padding: 6px 18px; + background: var(--amd-red, #e4002b); + color: #fff; + border: none; + border-radius: 6px; + font-size: 13px; + font-weight: 600; + cursor: pointer; + transition: background 0.15s; + flex-shrink: 0; +} + +.claw-tool-panel-send:hover { + background: var(--amd-red-light, #ff3355); +} + +.claw-tool-panel-close { + width: 28px; + height: 28px; + display: flex; + align-items: center; + justify-content: center; + border: 1px solid var(--border); + border-radius: 6px; + background: transparent; + color: var(--text-muted); + font-size: 14px; + cursor: pointer; + flex-shrink: 0; + transition: all 0.15s; +} + +.claw-tool-panel-close:hover { + background: #fef2f2; + color: var(--amd-red); + border-color: var(--amd-red); +} + +.claw-tool-panel-filters { + flex-shrink: 0; + border-bottom: 1px solid var(--border); +} + +.claw-tool-panel-filters .claw-tool-picker-segment { + border-bottom: none; + padding: 10px 20px 6px; +} + +.claw-tool-panel-filters .claw-tool-picker-search { + padding: 6px 20px 10px; + border-bottom: none; +} + +.claw-tool-panel-body { + flex: 1; + overflow-y: auto; + min-height: 120px; +} + +.claw-tool-panel-body .claw-tool-picker-item { + padding: 12px 20px; +} + +.claw-tool-panel-body .claw-tool-picker-desc { + white-space: normal; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; +} + +.claw-tool-panel-body .claw-tool-group-label { + padding: 10px 20px 6px; + font-size: 11px; +} + +/* Slide transition */ +.claw-panel-slide-enter-active, +.claw-panel-slide-leave-active { + transition: transform 0.25s ease, opacity 0.25s ease; +} + +.claw-panel-slide-enter-from, +.claw-panel-slide-leave-to { + transform: translateY(100%); + opacity: 0; +} + +.claw-panel-slide-enter-to, +.claw-panel-slide-leave-from { + transform: translateY(0); + opacity: 1; +} diff --git a/Web/apps/hyperloom/src/assets/dashboard.css b/Web/apps/hyperloom/src/assets/dashboard.css new file mode 100644 index 000000000..88db0ad22 --- /dev/null +++ b/Web/apps/hyperloom/src/assets/dashboard.css @@ -0,0 +1,4355 @@ +/* ══════════════════════════════════════════════════════ + AMD PRISM — Design System + ══════════════════════════════════════════════════════ */ + +:root { + /* AMD Brand */ + --amd-red: #e4002b; + --amd-red-light: #ff3355; + + /* Status Colors */ + --green: #22c55e; + --green-dark: #16a34a; + --blue: #2563eb; + --blue-light: #3b82f6; + --yellow: #f59e0b; + --yellow-dark: #d97706; + --orange: #ea580c; + --red: #dc2626; + --purple: #8b5cf6; + --teal: #14b8a6; + + /* Neutrals */ + --bg: #f3f5f9; + --white: #ffffff; + --text-primary: #1a1a2e; + --text-secondary: #4b5563; + --text-muted: #6b7280; + --text-faint: #9ca3af; + --border: #e5e7eb; + --border-light: #f3f4f6; + --surface: #f8f9fb; + + /* Layout */ + --nav-h: 56px; + --sidebar-w: 180px; + --radius: 12px; + --radius-sm: 8px; + --radius-xs: 6px; + --radius-pill: 14px; + --shadow: 0 1px 6px rgba(176,184,196,0.25); + + /* Font */ + --font: 'Segoe UI', Arial, sans-serif; + --mono: 'Cascadia Code', 'Fira Code', 'Consolas', monospace; +} + +* { margin: 0; padding: 0; box-sizing: border-box; } + +body { + font-family: var(--font); + background: var(--bg); + color: var(--text-primary); + overflow-x: hidden; +} + +/* ══════ TOP NAV ══════ */ +.top-nav { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: var(--nav-h); + background: var(--white); + border-bottom: 1px solid var(--border); + display: flex; + align-items: center; + z-index: 100; +} + +.top-nav-accent { + width: 4px; + height: 100%; + background: linear-gradient(to right, var(--amd-red), var(--amd-red-light)); +} + +.top-nav-brand { + display: flex; + align-items: baseline; + padding-left: 20px; + gap: 8px; +} + +.brand-amd { + font-size: 22px; + font-weight: bold; + color: var(--amd-red); +} + +.brand-prism { + font-size: 20px; + font-weight: 700; + color: var(--text-primary); +} + +.brand-sub { + font-size: 14px; + color: var(--text-muted); +} + +/* ══════ LEFT SIDEBAR ══════ */ +.sidebar { + position: fixed; + top: var(--nav-h); + left: 0; + width: var(--sidebar-w); + height: calc(100vh - var(--nav-h)); + background: var(--white); + border-right: 1px solid var(--border); + display: flex; + flex-direction: column; + padding-top: 12px; + z-index: 90; +} + +.sidebar-item { + display: flex; + align-items: center; + gap: 8px; + padding: 12px 20px; + font-size: 14px; + color: var(--text-muted); + text-decoration: none; + position: relative; + transition: all 0.15s; + cursor: pointer; +} + +.sidebar-item:hover { + background: #fef2f2; + color: var(--text-primary); +} + +.sidebar-item.active { + background: #fef2f2; + color: var(--amd-red); + font-weight: 700; +} + +.sidebar-item.active::before { + content: ''; + position: absolute; + left: 0; + top: 0; + width: 3px; + height: 100%; + background: var(--amd-red); +} + +.sidebar-divider { + height: 1px; + margin: 8px 12px; + background: var(--border); +} + +.sidebar-icon { + font-size: 14px; +} + +/* ══════ APP LAYOUT ══════ */ +.app-layout { + display: flex; + margin-top: var(--nav-h); +} + +.content { + margin-left: var(--sidebar-w); + padding: 16px; + width: calc(100% - var(--sidebar-w)); + min-height: calc(100vh - var(--nav-h)); +} + +/* ══════ PAGES ══════ */ +.page { + display: none; +} + +.page.active { + display: block; +} + +/* ══════ CARDS ══════ */ +.card { + background: var(--white); + border: 1px solid var(--border); + border-radius: var(--radius); + box-shadow: var(--shadow); + margin-bottom: 16px; + padding: 20px; +} + +.card.compact { + padding: 12px 20px; +} + +.card-header { + display: flex; + align-items: baseline; + gap: 16px; + margin-bottom: 12px; + flex-wrap: wrap; +} + +.card-header h2 { + font-size: 15px; + font-weight: 700; + color: var(--text-primary); +} + +.card-subtitle { + font-size: 11px; + color: var(--text-faint); +} + +.card-row { + display: flex; + gap: 16px; + margin-bottom: 16px; +} + +.card.half { + flex: 1; +} + +.card.two-thirds { + flex: 2; +} + +.card.one-third { + flex: 1; +} + +/* ══════ FILTER BARS ══════ */ +.filter-bar { + background: var(--white); + border: 1px solid var(--border); + border-radius: var(--radius); + padding: 8px 16px; + display: flex; + gap: 20px; + align-items: flex-end; + margin-bottom: 4px; + flex-wrap: wrap; +} + +.filter-bar.single-row { + flex-wrap: nowrap; + gap: 12px; + align-items: flex-end; + margin-bottom: 8px; +} + +.filter-bar.single-row .filter-group { + flex: 0 1 auto; + min-width: 0; +} + +.filter-bar.single-row .filter-select.wide { + min-width: 160px; +} + +.filter-bar.single-row .gpu-config-group { + flex: 0 1 auto; + min-width: 0; +} + +.filter-bar.secondary { + background: var(--surface); + margin-bottom: 8px; + border-top: none; + border-radius: 0 0 var(--radius) var(--radius); +} + +.filter-bar + .filter-bar.secondary { + margin-top: -5px; +} + +.filter-group { + display: flex; + flex-direction: column; + gap: 2px; +} + +.filter-label { + font-size: 9px; + font-weight: 600; + color: var(--text-muted); + text-transform: uppercase; + letter-spacing: 0.3px; +} + +.info-icon { + color: var(--text-faint); + font-size: 9px; +} + +.filter-select { + display: flex; + align-items: center; + gap: 8px; + background: var(--surface); + border: 1px solid #d1d5db; + border-radius: 4px; + padding: 4px 10px; + font-size: 12px; + font-weight: 600; + color: var(--text-primary); + cursor: pointer; + min-width: 100px; + transition: border-color 0.15s; +} + +.filter-select:hover { + border-color: var(--blue-light); +} + +.filter-select.wide { + min-width: 180px; +} + +.caret { + color: var(--text-faint); + font-size: 10px; + margin-left: auto; +} + +.precision-chip { + display: inline-block; + padding: 1px 10px; + border-radius: 3px; + font-size: 10px; + font-weight: 600; +} + +.precision-chip.active { + background: var(--amd-red); + color: white; +} + +.filter-chip { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 3px 10px; + border-radius: 4px; + font-size: 11px; + font-weight: 600; + cursor: pointer; +} + +.filter-chip.amd { + background: #fef2f2; + border: 1px solid var(--amd-red); + color: var(--amd-red); +} + +.filter-chip.nvidia { + background: #f0fdf4; + border: 1px solid var(--green); + color: var(--green-dark); +} + +.chip-close { + font-size: 10px; + cursor: pointer; +} + +.filter-add { + display: inline-flex; + align-items: center; + justify-content: center; + width: 20px; + height: 20px; + background: var(--surface); + border: 1px solid #d1d5db; + border-radius: 4px; + cursor: pointer; + font-size: 9px; + color: var(--text-faint); +} + +.text-muted { + color: var(--text-faint); +} + +/* ══════ DROPDOWN MENUS ══════ */ +.dropdown-wrap { + position: relative; +} + +.dropdown-menu { + display: none; + position: absolute; + top: 100%; + left: 0; + z-index: 200; + min-width: 220px; + max-height: 400px; + overflow-y: auto; + background: var(--white); + border: 1px solid var(--border); + border-radius: var(--radius-sm); + box-shadow: 0 8px 24px rgba(0,0,0,0.12); + padding: 4px 0; + margin-top: 4px; +} + +.dropdown-menu.dropdown-menu-wide { + min-width: 360px; +} + +.dropdown-wrap.open .dropdown-menu { + display: block; +} + +.dropdown-wrap.open .filter-select { + border-color: var(--blue-light); + box-shadow: 0 0 0 2px rgba(59,130,246,0.12); +} + +.dd-group-label { + padding: 8px 14px 4px; + font-size: 10px; + font-weight: 600; + color: var(--text-muted); + text-transform: uppercase; + letter-spacing: 0.3px; + pointer-events: none; + border-bottom: 1px solid var(--border-light); +} + +.dd-item { + padding: 8px 14px; + font-size: 12px; + color: var(--text-primary); + cursor: pointer; + display: flex; + align-items: center; + justify-content: space-between; + transition: background 0.1s; +} + +.dd-item:hover { + background: #eff6ff; + color: var(--blue); +} + +.dd-item.active { + color: var(--blue); + font-weight: 600; + background: #f0f9ff; +} + +.dd-check { + font-size: 14px; + color: var(--blue); +} + +.dd-item .dd-sub { + font-size: 10px; + color: var(--text-faint); + margin-left: 4px; +} + +/* ══════ GPU CONFIG CHIPS ══════ */ +.gpu-config-group { + display: flex; + flex-direction: column; + gap: 2px; +} + +.gpu-config-chips { + display: flex; + align-items: center; + gap: 6px; +} + +.gpu-chip { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 4px 12px; + border-radius: 4px; + font-size: 11px; + font-weight: 600; + cursor: default; +} + +.gpu-chip.amd { + background: #fef2f2; + border: 1.5px solid var(--amd-red); + color: var(--amd-red); +} + +.gpu-chip.nvidia { + background: #f0fdf4; + border: 1.5px solid var(--green); + color: var(--green-dark); +} + +.gpu-chip-close { + font-size: 11px; + cursor: pointer; + opacity: 0.7; + transition: opacity 0.15s; +} + +.gpu-chip-close:hover { + opacity: 1; +} + +.gpu-chip-add { + display: inline-flex; +} + +.gpu-chip-add .filter-add { + width: 22px; + height: 22px; + font-size: 10px; +} + +/* ══════ ARCHITECTURE SECTION (Collapsible) ══════ */ +.arch-section { + background: var(--white); + border: 1px solid var(--border); + border-radius: var(--radius); + margin-bottom: 4px; + overflow: hidden; +} + +.arch-header { + display: flex; + align-items: center; + gap: 10px; + padding: 8px 16px; + cursor: pointer; + transition: background 0.15s; +} + +.arch-header:hover { + background: #f9fafb; +} + +.arch-icon { + font-size: 14px; +} + +.arch-label { + font-size: 12px; + font-weight: 600; + color: var(--text-primary); +} + +.arch-chip { + display: inline-block; + padding: 2px 10px; + border-radius: 4px; + background: var(--surface); + border: 1px solid var(--border); + font-size: 11px; + color: var(--text-secondary); +} + +.arch-toggle { + margin-left: auto; + font-size: 14px; + color: var(--text-faint); + transition: transform 0.2s; +} + +.arch-section.collapsed .arch-toggle { + transform: rotate(-90deg); +} + +.arch-body { + padding: 0 20px 20px; + transition: all 0.2s; +} + +.arch-section.collapsed .arch-body { + display: none; +} + +/* Architecture Diagram */ +.arch-diagram { + text-align: center; + padding: 20px 0 0; +} + +.arch-model-title { + font-size: 18px; + font-weight: 700; + color: var(--text-primary); +} + +.arch-model-sub { + font-size: 12px; + color: var(--text-muted); + margin-bottom: 16px; +} + +.arch-flow { + display: flex; + flex-direction: column; + align-items: center; + gap: 0; + max-width: 620px; + margin: 0 auto; +} + +.arch-block { + width: 100%; + max-width: 580px; + padding: 12px 20px; + border-radius: 8px; + text-align: center; + position: relative; +} + +.arch-block.primary { + background: linear-gradient(135deg, #2563eb, #3b82f6); + color: white; +} + +.arch-block.primary .arch-block-title { + color: white; + font-weight: 700; +} + +.arch-block.primary .arch-block-sub { + color: rgba(255,255,255,0.8); +} + +.arch-block.dashed { + border: 2px dashed #60a5fa; + background: rgba(239,246,255,0.5); + margin: 0; +} + +.arch-block.solid { + background: #1e293b; + color: white; +} + +.arch-block.solid .arch-block-title { + color: white; +} + +.arch-block.output { + background: linear-gradient(135deg, #4338ca, #6366f1); + color: white; +} + +.arch-block.output .arch-block-title { + color: white; + font-weight: 700; +} + +.arch-block.output .arch-block-sub { + color: rgba(255,255,255,0.8); +} + +.arch-block-title { + font-size: 14px; + font-weight: 600; + color: var(--text-primary); +} + +.arch-block-sub { + font-size: 11px; + color: var(--text-muted); + margin-top: 2px; +} + +.arch-block-expand { + position: absolute; + right: 12px; + top: 50%; + transform: translateY(-50%); + width: 24px; + height: 24px; + border-radius: 50%; + border: 1px solid var(--border); + background: var(--white); + display: flex; + align-items: center; + justify-content: center; + font-size: 14px; + color: var(--text-faint); + cursor: pointer; +} + +.arch-arrow { + font-size: 16px; + color: var(--text-faint); + padding: 4px 0; +} + +.arch-alternating { + font-size: 11px; + color: var(--text-muted); + font-style: italic; + padding: 2px 0; +} + +/* Architecture Summary Table */ +.arch-summary-table { + display: flex; + gap: 0; + max-width: 580px; + margin: 20px auto 16px; + border: 1px solid var(--border); + border-radius: 8px; + overflow: hidden; +} + +.arch-sum-cell { + flex: 1; + padding: 8px 12px; + text-align: center; + border-right: 1px solid var(--border); +} + +.arch-sum-cell:last-child { + border-right: none; +} + +.arch-sum-label { + font-size: 9px; + color: var(--text-faint); + text-transform: uppercase; + letter-spacing: 0.3px; + border-bottom: 1px solid var(--border-light); + padding-bottom: 4px; + margin-bottom: 4px; +} + +.arch-sum-value { + font-size: 15px; + font-weight: 700; + color: var(--text-primary); +} + +/* Architecture Features */ +.arch-features-bar { + display: flex; + align-items: center; + gap: 8px; + justify-content: flex-start; + padding: 12px 0 4px; + border-top: 1px solid var(--border-light); + flex-wrap: wrap; +} + +.arch-features-label { + font-size: 11px; + color: var(--text-muted); + font-weight: 600; +} + +.arch-feature-tag { + display: inline-block; + padding: 3px 12px; + border-radius: 14px; + background: #14b8a6; + color: white; + font-size: 10px; + font-weight: 600; +} + +.arch-release { + font-size: 10px; + color: var(--text-faint); + text-align: left; + margin-top: 4px; +} + +/* ══════ RUN DATE BAR ══════ */ +.run-date-bar { + display: flex; + align-items: center; + gap: 10px; + background: var(--white); + border: 1px solid var(--border); + border-radius: var(--radius); + padding: 8px 16px; + margin-bottom: 16px; + flex-wrap: wrap; +} + +.rdb-link { + font-size: 12px; + color: var(--text-muted); + cursor: pointer; +} + +.rdb-link:hover { + color: var(--blue); +} + +.rdb-nav { + font-size: 14px; + color: var(--text-faint); + cursor: pointer; + user-select: none; +} + +.rdb-nav:hover { + color: var(--text-primary); +} + +.rdb-icon { + font-size: 12px; +} + +.rdb-text { + font-size: 12px; + color: var(--text-secondary); +} + +.rdb-run-badge { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 3px 12px; + border: 1px solid var(--border); + border-radius: var(--radius-xs); + font-size: 12px; + color: var(--text-primary); + cursor: pointer; +} + +.rdb-ext { + font-size: 10px; + color: var(--text-faint); +} + +.rdb-sep { + width: 1px; + height: 18px; + background: var(--border); +} + +/* ══════ CHART TOOLS (zoom, download) ══════ */ +.chart-tools { + display: flex; + gap: 4px; + margin-left: auto; + flex-shrink: 0; +} + +.chart-tool-btn { + width: 30px; + height: 30px; + display: flex; + align-items: center; + justify-content: center; + border: 1px solid var(--border); + border-radius: var(--radius-xs); + background: var(--surface); + font-size: 14px; + cursor: pointer; + color: var(--text-muted); + transition: all 0.15s; + font-family: var(--font); +} + +.chart-tool-btn:hover { + background: #eff6ff; + border-color: #93c5fd; + color: var(--blue); +} + +/* ══════ CHART ZOOM (EXPAND) ══════ */ +.chart-card.maximized { + position: fixed !important; + top: var(--nav-h); + left: var(--sidebar-w); + right: 0; + bottom: 0; + z-index: 80; + margin: 0; + border-radius: 0; + overflow-y: auto; + flex: none !important; + width: auto !important; +} + +.chart-card.maximized .chart-container { + height: 60vh !important; +} + +.chart-card.maximized .chart-zoom-btn { + background: #eff6ff; + border-color: #93c5fd; + color: var(--blue); +} + +/* When a card in card-row is maximized, hide siblings */ +.card-row .chart-card.maximized ~ .chart-card, +.card-row .chart-card:not(.maximized) { + /* handled by JS */ +} + +/* ══════ CHART BUILDING PLACEHOLDER ══════ */ +.chart-building { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + min-height: 280px; + gap: 8px; +} + +.building-icon { + font-size: 48px; + opacity: 0.7; +} + +.building-title { + font-size: 22px; + font-weight: 700; + color: var(--text-primary); +} + +.building-sub { + font-size: 13px; + color: var(--text-muted); +} + +/* ══════ LEGEND DOT (for chart legend) ══════ */ +.legend-dot { + display: inline-block; + width: 10px; + height: 10px; + border-radius: 50%; + flex-shrink: 0; +} + +/* ══════ CHART CONTAINERS ══════ */ +.chart-container { + position: relative; + width: 100%; +} + +.chart-container canvas { + width: 100% !important; +} + +/* ══════ CHART LEGEND BOX ══════ */ +.chart-legend-box { + background: var(--surface); + border: 1px solid var(--border); + border-radius: var(--radius-sm); + padding: 10px 16px; + margin-top: 8px; +} + +.legend-title { + font-size: 11px; + font-weight: 700; + color: var(--text-primary); + margin-right: 16px; +} + +.legend-items { + display: flex; + gap: 24px; + margin-top: 4px; + flex-wrap: wrap; +} + +.legend-item { + display: flex; + align-items: center; + gap: 6px; + font-size: 11px; + color: var(--text-secondary); +} + +.legend-line { + display: inline-block; + width: 24px; + height: 0; + border-bottom: 2.5px solid; +} + +.legend-line.dashed { + border-bottom-style: dashed; +} + +.legend-line.red { + border-color: var(--amd-red); +} + +.legend-line.green { + border-color: var(--green); +} + +.legend-line.cyan { + border-color: #06b6d4; +} + +.legend-line.dashdot { + border-bottom-style: dashed; + background: repeating-linear-gradient(90deg, #06b6d4 0, #06b6d4 8px, transparent 8px, transparent 11px, #06b6d4 11px, #06b6d4 13px, transparent 13px, transparent 16px); + border-bottom: none; + height: 2.5px; +} + +.legend-line.solid { + border-bottom-style: solid; +} + +/* ══════ KERNEL LEGEND (inside Kernel Stack card) ══════ */ +.kernel-legend-grid { + display: flex; + flex-direction: column; + gap: 4px; + padding: 8px 16px 10px; + border-top: 1px solid var(--border); +} + +.kernel-legend-row { + display: flex; + align-items: center; + gap: 16px; +} + +/* old standalone legend bar kept for compat */ +.card.legend-bar { + display: none; /* hidden – moved into card */ +} + +.legend-bar-title { + font-size: 11px; + font-weight: 600; + color: var(--text-muted); +} + +.legend-bar-item { + display: flex; + align-items: center; + gap: 4px; + font-size: 11px; + color: var(--text-secondary); +} + +.swatch { + display: inline-block; + width: 10px; + height: 10px; + border-radius: 2px; +} + +/* ══════ HW LEGEND (Kernel Side-by-Side) ══════ */ +.hw-legend { + background: var(--surface); + border: 1px solid var(--border); + border-radius: var(--radius-xs); + padding: 8px 12px; + margin-bottom: 8px; +} + +.hw-legend-items { + display: flex; + gap: 16px; + margin-top: 4px; +} + +.hw-legend-item { + display: flex; + align-items: center; + gap: 4px; + font-size: 10px; + color: var(--text-secondary); +} + +.hw-swatch { + display: inline-block; + width: 10px; + height: 10px; + border-radius: 2px; +} + +.hw-swatch.red-light { background: var(--amd-red); opacity: 0.4; } +.hw-swatch.cyan { background: #06b6d4; } +.hw-swatch.red { background: var(--amd-red); } +.hw-swatch.green { background: var(--green); } + +/* ══════ WARNING BANNER ══════ */ +.warning-banner { + background: #fef3c7; + border: 1px solid #fde68a; + border-radius: 4px; + padding: 4px 10px; + font-size: 9px; + color: var(--yellow-dark); + margin-bottom: 8px; +} + +/* ══════ ACTION BAR ══════ */ +.action-bar { + background: var(--white); + border: 1px solid var(--border); + border-radius: var(--radius); + box-shadow: var(--shadow); + padding: 12px 20px; + display: flex; + gap: 12px; + margin-bottom: 16px; +} + +/* ══════ BUTTONS ══════ */ +.btn { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 8px 20px; + border: none; + border-radius: var(--radius-sm); + font-size: 12px; + font-weight: 600; + cursor: pointer; + transition: all 0.15s; + font-family: var(--font); +} + +.btn-analysis { + background: #f0fdf4; + border: 1.5px solid #86efac; + color: var(--green-dark); +} + +.btn-optimization { + background: #fffbeb; + border: 1.5px solid #fde68a; + color: var(--yellow-dark); +} + +.btn-results { + background: #faf5ff; + border: 1.5px solid #d8b4fe; + color: #7c3aed; +} + +.btn-report { + background: var(--amd-red); + color: white; +} + +.btn-primary { + background: var(--amd-red); + color: white; +} + +.btn-primary:hover { + background: #c8002a; +} + +.btn-lg { + padding: 12px 32px; + font-size: 14px; +} + +.btn-sm { + padding: 6px 14px; + font-size: 11px; +} + +.btn-full { + width: 100%; + padding: 12px; + font-size: 13px; +} + +/* ══════ TASK SETUP ══════ */ +.task-setup-grid { + margin-bottom: 12px; +} + +/* ══════ ANALYSIS FILTER BAR ══════ */ +.analysis-filter-bar { + display: flex; + align-items: flex-end; + gap: 10px; + flex-wrap: nowrap; +} + +.analysis-filter-bar .filter-group { + flex: 1; + min-width: 0; +} + +.analysis-filter-bar .form-group-action { + flex: 0 0 auto; +} + +.form-row { + display: flex; + gap: 16px; + margin-bottom: 8px; + flex-wrap: wrap; +} + +.form-group { + display: flex; + flex-direction: column; + gap: 2px; + min-width: 120px; +} + +.task-setup-action { + display: flex; + justify-content: flex-end; +} + +.form-group-action { + display: flex; + align-items: flex-end; + padding-bottom: 2px; +} + +.btn-analysis { + padding: 8px 24px; + font-size: 13px; + white-space: nowrap; + border-radius: var(--radius-sm); +} + +.btn-analysis.btn-success { + background: #10b981; + border-color: #10b981; +} + +/* ── Analysis filter-bar dropdown overrides ── */ +.analysis-filter-bar .filter-select { + padding: 6px 10px; + font-size: 12px; + min-width: 90px; + cursor: pointer; +} + +.analysis-filter-bar .dropdown-menu { + min-width: 120px; +} + +.analysis-filter-bar .filter-label { + font-size: 11px; + white-space: nowrap; +} + +/* ══════ METRICS ROW ══════ */ +.metrics-row { + display: flex; + gap: 12px; + flex-wrap: wrap; +} + +.metrics-row.compact { + gap: 8px; +} + +.metric-card { + flex: 1; + min-width: 100px; + border-radius: var(--radius-sm); + padding: 10px 14px; + border: 1px solid; +} + +.metric-card.blue { + background: #f0f9ff; + border-color: #bfdbfe; +} + +.metric-card.yellow { + background: #fffbeb; + border-color: #fde68a; +} + +.metric-card.red { + background: #fef2f2; + border-color: #fecaca; +} + +.metric-card.red-strong { + background: #fef2f2; + border-color: var(--red); + border-width: 1.5px; +} + +.metric-card.orange { + background: #fff7ed; + border-color: #fed7aa; +} + +.metric-card.green-card { + background: #f0fdf4; + border-color: #bbf7d0; +} + +.metric-label { + display: block; + font-size: 9px; + color: var(--text-muted); + margin-bottom: 4px; +} + +.metric-value { + display: block; + font-size: 17px; + font-weight: bold; +} + +.metric-value.blue { color: var(--blue); } +.metric-value.yellow { color: var(--yellow-dark); } +.metric-value.red { color: var(--red); } +.metric-value.orange { color: var(--orange); } +.metric-value.green { color: var(--green-dark); } + +/* ══════ STEP-BY-STEP ANALYSIS ══════ */ +.phase-tabs { + display: flex; + gap: 4px; + margin-bottom: 16px; +} + +.phase-tab { + padding: 6px 16px; + border: 1px solid #d1d5db; + border-radius: var(--radius-xs); + background: var(--surface); + font-size: 12px; + color: var(--text-muted); + cursor: pointer; + font-family: var(--font); +} + +.phase-tab.active { + background: #eff6ff; + border-color: #93c5fd; + color: var(--blue); + font-weight: 600; +} + +.step-analysis { + display: flex; + flex-direction: column; + gap: 8px; +} + +.step-row { + display: flex; + align-items: center; + gap: 12px; +} + +.step-label { + width: 120px; + text-align: right; + font-size: 12px; + font-weight: 600; + color: var(--text-primary); + flex-shrink: 0; +} + +.step-bar-wrap { + flex: 1; + display: flex; + align-items: center; + gap: 8px; +} + +.step-bar-bg { + flex: 1; + height: 22px; + background: var(--border-light); + border-radius: 4px; + overflow: hidden; +} + +.step-bar-fill { + height: 100%; + border-radius: 4px; +} + +.step-value { + font-size: 11px; + color: var(--text-secondary); + white-space: nowrap; +} + +.severity-badge { + font-size: 9px; + padding: 2px 8px; + border-radius: 10px; + font-weight: 600; + white-space: nowrap; +} + +.severity-badge.hot { + background: #fef2f2; + border: 1px solid #fecaca; + color: var(--red); +} + +.severity-badge.med { + background: #fffbeb; + border: 1px solid #fde68a; + color: var(--yellow-dark); +} + +/* ══════ AGENT INSIGHTS ══════ */ +.insights-list { + display: flex; + flex-direction: column; + gap: 6px; +} + +.insight-row { + display: flex; + align-items: center; + gap: 12px; + padding: 10px 14px; + border-radius: var(--radius-sm); + border: 1px solid; +} + +.insight-row.high { + background: #fef2f2; + border-color: #fecaca; +} + +.insight-row.med { + background: #fffbeb; + border-color: #fde68a; +} + +.insight-row.tip { + background: #eff6ff; + border-color: #bfdbfe; +} + +.insight-row.na { + background: var(--border-light); + border-color: #d1d5db; +} + +.insight-severity { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 44px; + padding: 4px 6px; + border-radius: var(--radius-sm); + font-size: 11px; + font-weight: bold; + text-align: center; + flex-shrink: 0; +} + +.insight-severity.high { background: rgba(220,38,38,0.15); color: var(--red); } +.insight-severity.med { background: rgba(245,158,11,0.15); color: var(--yellow-dark); } +.insight-severity.tip { background: rgba(59,130,246,0.12); color: var(--blue); } +.insight-severity.na { background: rgba(107,114,128,0.12); color: var(--text-muted); } + +.insight-text { + flex: 1; + font-size: 12px; + color: var(--text-secondary); + line-height: 1.4; +} + +.insight-text-block { + flex: 1; + display: flex; + flex-direction: column; + gap: 2px; +} + +.insight-subtext { + font-size: 10px; + color: var(--text-faint); +} + +.insight-action { + padding: 4px 12px; + border-radius: 10px; + border: 1px solid #d1d5db; + background: var(--border-light); + font-size: 10px; + color: var(--text-muted); + cursor: pointer; + font-family: var(--font); + flex-shrink: 0; +} + +.insight-action.disabled { + opacity: 0.6; + cursor: not-allowed; +} + +.insight-source { + padding: 2px 8px; + border-radius: 8px; + font-size: 9px; + font-weight: 600; + letter-spacing: 0.3px; + text-transform: uppercase; + flex-shrink: 0; + white-space: nowrap; +} +.insight-source.high { background: rgba(220,38,38,0.08); color: #b91c1c; border: 1px solid rgba(220,38,38,0.18); } +.insight-source.med { background: rgba(245,158,11,0.08); color: #b45309; border: 1px solid rgba(245,158,11,0.18); } +.insight-source.tip { background: rgba(59,130,246,0.08); color: #1d4ed8; border: 1px solid rgba(59,130,246,0.18); } +.insight-source.na { background: rgba(107,114,128,0.08); color: #6b7280; border: 1px solid rgba(107,114,128,0.18); } + +/* ══════ BRIDGE PLAN ══════ */ +.bridge-plan-action { + display: flex; + justify-content: flex-end; +} + +/* ══════ BRIDGE SELECTOR ══════ */ +.bridge-selector { + display: flex; + align-items: center; + gap: 16px; + flex-wrap: wrap; +} + +.bridge-label { + font-size: 14px; + font-weight: 700; + color: var(--text-primary); +} + +.bridge-select { + display: flex; + align-items: center; + gap: 8px; + background: var(--surface); + border: 1px solid #93c5fd; + border-radius: var(--radius-xs); + padding: 6px 14px; + font-size: 12px; + color: var(--blue); + cursor: pointer; +} + +.bridge-version { + font-size: 11px; + color: var(--text-faint); +} + +/* ══════ PERFORMANCE BREAKDOWN ══════ */ +.flow-hint { + background: #fffbeb; + border: 1px solid #fde68a; + border-radius: 4px; + padding: 4px 10px; + font-size: 10px; + color: var(--yellow-dark); + margin-bottom: 12px; +} + +/* ══════ BRIDGE PLAN TABLE ══════ */ +.bridge-table { + width: 100%; + border: 1px solid var(--border); + border-radius: var(--radius-sm); + overflow: hidden; + margin-bottom: 12px; +} + +.bt-header { + display: flex; + align-items: center; + gap: 4px; + padding: 8px 12px; + background: var(--surface); + border-bottom: 1px solid var(--border); + font-size: 10px; + font-weight: 700; + color: var(--text-muted); + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.bt-col { display: flex; align-items: center; } +.bt-col.bt-sel { width: 32px; flex-shrink: 0; justify-content: center; } +.bt-col.bt-pri { width: 44px; flex-shrink: 0; justify-content: center; } +.bt-col.bt-name { flex: 1 1 0; min-width: 0; } +.bt-row .bt-col.bt-name { flex-wrap: wrap; gap: 4px; } +.bt-col.bt-save { width: 100px; flex-shrink: 0; text-align: left; justify-content: flex-start; } +.bt-col.bt-e2e { width: 200px; flex-shrink: 0; gap: 6px; justify-content: flex-start; } +.bt-col.bt-exp { width: 32px; flex-shrink: 0; justify-content: center; } + +.bt-bw-info { + font-size: 10px; + color: var(--text-muted); + font-weight: normal; +} + +.bt-row-group { + border-bottom: 1px solid var(--border-light); +} + +.bt-row-group:last-child { + border-bottom: none; +} + +.bt-row { + display: flex; + align-items: center; + gap: 4px; + padding: 10px 12px; + cursor: pointer; + transition: background 0.15s ease; + font-size: 12px; + color: var(--text-primary); +} + +.bt-row:hover { + background: #f8fafc; +} + +.bt-row.selected { + background: #eff6ff; +} + +/* Priority Badges */ +.pri-badge { + display: inline-flex; + align-items: center; + justify-content: center; + width: 28px; + height: 22px; + border-radius: 12px; + font-size: 10px; + font-weight: 700; + color: white; +} + +.pri-badge.p1 { background: #ef4444; } +.pri-badge.p2 { background: #f97316; } +.pri-badge.p3 { background: #eab308; color: #1a1a2e; } +.pri-badge.p4 { background: #3b82f6; } +.pri-badge.p5 { background: #9ca3af; } + +/* Check boxes */ +.bt-check { + font-size: 14px; + color: var(--blue); + cursor: pointer; +} + +.bt-check.unchecked { + color: var(--text-faint); +} + +/* Saving value */ +.save-value { + font-weight: 600; + color: var(--green-dark); +} + +/* E2E cell */ +.e2e-pct { + font-size: 12px; + font-weight: 700; +} + +.e2e-pct.green-text { color: var(--green-dark); } +.e2e-pct.blue-text { color: var(--blue); } +.e2e-pct.teal-text { color: var(--teal); } + +.e2e-detail { + font-size: 9px; + color: var(--text-faint); + white-space: nowrap; +} + +/* Confidence dots */ +.conf-dot { + display: inline-block; + width: 8px; + height: 8px; + border-radius: 50%; + flex-shrink: 0; +} + +.conf-dot.high { background: var(--green); } +.conf-dot.med { background: var(--yellow); } +.conf-dot.low { background: var(--text-faint); } + +/* Source column */ +.bt-col.bt-src { + font-size: 11px; + color: var(--text-secondary); +} + +/* Toggle */ +.bt-toggle { + font-size: 12px; + color: var(--blue); + cursor: pointer; + transition: transform 0.2s ease; +} + +.bt-row-group.expanded .bt-toggle { + transform: rotate(0deg); +} + +/* Kernel details inside bridge plan */ +.bt-kernels { + display: none; + margin: 0; + padding: 0 0 4px 0; +} + +.bt-row-group.expanded .bt-kernels { + display: block; +} + +.plan-kernels { + margin: 2px 0 0 16px; +} + +.kernel-row { + display: flex; + align-items: center; + gap: 8px; + padding: 5px 12px 5px 88px; /* 12px row-padding + 32px bt-sel + 44px bt-pri = 88px → aligns with bt-name */ + border-bottom: 1px solid var(--border-light); + background: var(--surface); + font-size: 11px; +} + +.kernel-row:last-child { + border-bottom: none; +} + +.kernel-row.selected { + background: #eff6ff; +} + +.kernel-check { + font-size: 11px; + color: var(--blue); + width: 16px; + flex-shrink: 0; +} + +.kernel-check.unchecked { + color: var(--text-faint); +} + +.kernel-name { + font-size: 11px; + font-weight: 600; + color: var(--text-primary); + min-width: 160px; + flex: 1; +} + +.kernel-name.muted { + color: var(--text-secondary); + font-weight: normal; +} + +.kernel-metrics { + font-size: 10px; + color: var(--text-muted); + white-space: nowrap; + text-align: right; + flex-shrink: 0; +} + +.plan-summary { + background: #eff6ff; + border: 1px solid #93c5fd; + border-radius: var(--radius-sm); + padding: 10px 14px; + margin: 12px 0; +} + +.summary-line { + font-size: 12px; + font-weight: 600; + color: var(--blue); +} + +.summary-detail { + font-size: 11px; + color: var(--text-secondary); + margin-top: 2px; +} + +/* ══════ GEAK STATUS (Expandable) ══════ */ +.geak-status-list { + display: flex; + flex-direction: column; + gap: 6px; +} + +.geak-expand-item { + border-radius: var(--radius-sm); + border: 1px solid var(--border); + overflow: hidden; + transition: all 0.2s ease; +} + +.geak-expand-item.validated { + border-color: #bbf7d0; +} + +.geak-expand-item.processing { + border-color: #bfdbfe; +} + +.geak-expand-item.sent { + border-color: var(--border); +} + +.geak-expand-header { + display: flex; + align-items: center; + gap: 10px; + padding: 10px 14px; + cursor: pointer; + transition: background 0.15s ease; +} + +.geak-expand-item.validated .geak-expand-header { + background: #f0fdf4; +} + +.geak-expand-item.processing .geak-expand-header { + background: #f0f9ff; +} + +.geak-expand-item.sent .geak-expand-header { + background: var(--surface); +} + +.geak-expand-header:hover { + filter: brightness(0.97); +} + +.geak-expand-toggle { + font-size: 12px; + color: var(--text-muted); + width: 14px; + text-align: center; + transition: transform 0.2s ease; +} + +.geak-item-name { + font-size: 12px; + font-weight: 600; + color: var(--text-primary); +} + +.geak-item-sub { + font-size: 10px; + color: var(--text-muted); + flex: 1; +} + +.geak-status-label { + font-size: 10px; + font-weight: 600; + white-space: nowrap; + min-width: 80px; + text-align: right; +} + +.geak-status-label.validated { color: var(--green-dark); } +.geak-status-label.processing { color: var(--blue); } +.geak-status-label.sent { color: var(--text-secondary); } + +/* Expand body */ +.geak-expand-body { + display: none; + padding: 12px 14px 14px; + background: var(--white); + border-top: 1px solid var(--border-light); +} + +.geak-expand-item.expanded .geak-expand-body { + display: block; +} + +/* Pipeline Steps (horizontal flow inside expanded body) */ +.pipeline-steps { + display: flex; + align-items: center; + gap: 0; + padding: 4px 0 12px; +} + +.pipe-step { + display: flex; + flex-direction: column; + align-items: center; + gap: 4px; + min-width: 60px; +} + +.pipe-step-icon { + display: flex; + align-items: center; + justify-content: center; + width: 26px; + height: 26px; + border-radius: 50%; + font-size: 12px; + font-weight: 700; +} + +.pipe-step-icon.done { + background: var(--green); + color: white; +} + +.pipe-step-icon.active { + background: var(--blue-light); + color: white; +} + +.pipe-step-icon.pending { + background: var(--border); + color: var(--text-faint); + font-size: 10px; +} + +.pipe-step-icon.pulsing { + animation: pulse 1.5s ease-in-out infinite; +} + +@keyframes pulse { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.4; } +} + +.pipe-step-info { + display: flex; + flex-direction: column; + align-items: center; + gap: 1px; +} + +.pipe-step-name { + font-size: 10px; + font-weight: 600; + color: var(--text-secondary); +} + +.pipe-step-time { + font-size: 9px; + color: var(--text-faint); + font-family: var(--mono); +} + +.pipe-step-connector { + flex: 1; + height: 2px; + min-width: 20px; + margin-top: -18px; +} + +.pipe-step-connector.done { background: var(--green); } +.pipe-step-connector.active { background: var(--blue-light); } +.pipe-step-connector.pending { background: var(--border); border-top: 1px dashed var(--text-faint); height: 0; } + +/* Expand detail chips */ +.geak-expand-detail { + display: flex; + flex-wrap: wrap; + gap: 6px; + padding-top: 8px; + border-top: 1px solid var(--border-light); +} + +.detail-chip { + display: inline-block; + padding: 3px 10px; + border-radius: 12px; + background: var(--surface); + border: 1px solid var(--border); + font-size: 10px; + color: var(--text-secondary); + white-space: nowrap; +} + +.detail-chip.green-chip { + background: #f0fdf4; + border-color: #bbf7d0; + color: var(--green-dark); +} + +.detail-chip.blue-chip { + background: #f0f9ff; + border-color: #bfdbfe; + color: var(--blue); +} + +/* ══════ COLLAPSIBLE SECTION ══════ */ +.collapsible-section .collapsible-body { + display: block; + transition: all 0.2s ease; +} + +.collapsible-section.collapsed .collapsible-body { + display: none; +} + +.collapsible-header { + cursor: pointer; + position: relative; +} + +.section-toggle-icon { + font-size: 14px; + color: var(--text-muted); + margin-left: auto; + transition: transform 0.2s ease; +} + +.collapsible-section.collapsed .section-toggle-icon { + transform: rotate(-90deg); +} + +/* ══════ GEAK STRATEGIES (Expandable) ══════ */ +.strategies-list { + display: flex; + flex-direction: column; + gap: 8px; +} + +.strategy-card { + background: var(--surface); + border: 1px solid var(--border); + border-radius: var(--radius-sm); + padding: 12px; + position: relative; + transition: border-color 0.15s ease; +} + +.strategy-card.expanded { + border-color: #93c5fd; + background: #fafcff; +} + +.strategy-header { + display: flex; + align-items: center; + gap: 8px; + cursor: pointer; +} + +.strategy-toggle { + font-size: 13px; + color: var(--text-faint); +} + +.strategy-name { + font-size: 12px; + font-weight: 700; + color: var(--text-primary); + flex: 1; +} + +.strategy-est-badge { + display: inline-block; + padding: 3px 10px; + background: #f0fdf4; + border: 1px solid #bbf7d0; + border-radius: 10px; + font-size: 10px; + font-weight: bold; + color: var(--green-dark); +} + +/* Strategy expand body */ +.strategy-expand-body { + display: none; + margin-top: 10px; + padding-top: 10px; + border-top: 1px solid var(--border-light); +} + +.strategy-card.expanded .strategy-expand-body { + display: block; +} + +.strategy-desc { + font-size: 11px; + color: var(--text-secondary); + margin-bottom: 10px; +} + +.strategy-actions { + display: flex; + flex-direction: column; + gap: 4px; + margin-bottom: 12px; +} + +.strat-action-row { + display: flex; + gap: 8px; + font-size: 11px; +} + +.strat-action-label { + font-weight: 600; + color: var(--text-muted); + min-width: 64px; +} + +.strat-action-value { + color: var(--text-primary); +} + +.strategy-params { + display: flex; + flex-wrap: wrap; + gap: 10px; + margin-bottom: 10px; + padding: 10px; + background: var(--white); + border: 1px solid var(--border); + border-radius: var(--radius-xs); +} + +.strat-param { + display: flex; + align-items: center; + gap: 6px; + font-size: 11px; +} + +.strat-param label { + font-weight: 600; + color: var(--text-muted); + white-space: nowrap; +} + +.strat-input { + width: 60px; + padding: 3px 6px; + border: 1px solid var(--border); + border-radius: 4px; + font-size: 11px; + font-family: var(--mono); + background: var(--white); + color: var(--text-primary); +} + +.strat-input:focus { + outline: none; + border-color: var(--blue-light); + box-shadow: 0 0 0 2px rgba(59,130,246,0.15); +} + +select.strat-input { + width: 70px; +} + +.strat-unit { + font-size: 10px; + color: var(--text-faint); +} + +.strategy-links { + font-size: 9px; + color: var(--text-faint); + margin-top: 4px; +} + +/* ══════ KERNEL DETAIL PANELS ══════ */ +.detail-section-label { + font-size: 11px; + font-weight: 700; + color: var(--text-muted); + text-transform: uppercase; + letter-spacing: 0.3px; + margin: 14px 0 6px; + padding-left: 2px; +} + +.detail-section-label.green-label { + color: var(--green-dark); +} + +.delta-up { + font-size: 10px; + color: var(--green-dark); + font-weight: 600; +} + +/* Tab panels */ +.detail-tab-panels { + margin-top: 12px; +} + +.detail-panel { + display: none; +} + +.detail-panel.active { + display: block; +} + +/* Roofline */ +.roofline-chart { + padding: 8px 0; +} + +.roofline-header { + margin-bottom: 8px; +} + +.roofline-title { + font-size: 13px; + font-weight: 700; + color: var(--text-primary); + display: block; +} + +.roofline-sub { + font-size: 10px; + color: var(--text-muted); +} + +.roofline-diagram { + width: 100%; + max-width: 620px; +} + +.roofline-svg { + width: 100%; + height: auto; +} + +/* TraceDiff */ +.tracediff-panel { + padding: 8px 0; +} + +.tracediff-header { + margin-bottom: 12px; +} + +.tracediff-title { + font-size: 13px; + font-weight: 700; + color: var(--text-primary); + display: block; +} + +.tracediff-sub { + font-size: 10px; + color: var(--text-muted); +} + +.tracediff-timeline { + display: flex; + flex-direction: column; + gap: 8px; + margin-bottom: 12px; +} + +.trace-row { + display: flex; + align-items: center; + gap: 12px; +} + +.trace-label { + font-size: 11px; + font-weight: 600; + min-width: 50px; + text-align: right; +} + +.trace-label.before { color: var(--amd-red); } +.trace-label.after { color: var(--green-dark); } + +.trace-bar-bg { + flex: 1; + height: 24px; + background: var(--surface); + border: 1px solid var(--border); + border-radius: 4px; + position: relative; + overflow: hidden; +} + +.trace-bar { + position: absolute; + top: 0; + left: 0; + height: 100%; + border-radius: 3px; +} + +.trace-bar.hotspot-bar { + position: absolute; + top: 2px; + height: calc(100% - 4px); + border-radius: 2px; +} + +.trace-time { + font-size: 11px; + font-weight: 600; + color: var(--text-secondary); + font-family: var(--mono); + min-width: 60px; +} + +.tracediff-legend { + display: flex; + flex-wrap: wrap; + gap: 16px; + font-size: 10px; + color: var(--text-muted); +} + +.td-legend-item { + display: flex; + align-items: center; + gap: 4px; +} + +.td-dot { + width: 8px; + height: 8px; + border-radius: 2px; + display: inline-block; +} + +.td-legend-item.saving { + font-weight: 700; + color: var(--green-dark); +} + +/* Trace Viewer Placeholder */ +.traceviewer-placeholder { + text-align: center; + padding: 40px 20px; + color: var(--text-muted); +} + +.traceviewer-placeholder .building-icon { + font-size: 36px; + margin-bottom: 8px; +} + +.traceviewer-placeholder .building-title { + font-size: 15px; + font-weight: 700; + color: var(--text-primary); + margin-bottom: 4px; +} + +.traceviewer-placeholder .building-sub { + font-size: 12px; + color: var(--text-muted); +} + +/* ══════ E2E PERF RESULTS ══════ */ +.e2e-result-block { + border: 1px solid var(--border); + border-radius: var(--radius-xs); + margin: 6px 0; + overflow: hidden; +} + +.e2e-result-header { + display: flex; + align-items: center; + gap: 10px; + padding: 8px 14px; + background: var(--surface); + border-bottom: 1px solid var(--border); +} + +.e2e-result-title { + font-size: 13px; + font-weight: 700; + color: var(--text-primary); +} + +.e2e-result-badge { + font-size: 10px; + font-weight: 600; + padding: 2px 10px; + border-radius: 10px; + white-space: nowrap; +} + +.e2e-result-badge.done { + background: #f0fdf4; + border: 1px solid #bbf7d0; + color: var(--green-dark); +} + +.e2e-result-badge.wip { + background: #eff6ff; + border: 1px solid #bfdbfe; + color: var(--blue); +} + +.e2e-result-badge.issue { + background: #fffbeb; + border: 1px solid #fde68a; + color: var(--yellow-dark); +} + +.e2e-result-body { + padding: 8px 14px; + display: flex; + flex-direction: column; + gap: 5px; +} + +.e2e-result-line { + display: flex; + align-items: flex-start; + gap: 8px; + font-size: 12px; + color: var(--text-primary); + line-height: 1.5; +} + +.e2e-result-line.muted { + color: var(--text-muted); +} + +.e2e-accent { + width: 3px; + min-height: 16px; + border-radius: 2px; + flex-shrink: 0; + margin-top: 2px; +} + +.e2e-accent.green { background: var(--green); } +.e2e-accent.yellow { background: var(--yellow); } +.e2e-accent.red { background: var(--red); } +.e2e-accent.gray { background: var(--text-faint); } + +.total-saving { + background: #f0fdf4; + border: 1px solid #bbf7d0; + border-radius: var(--radius-xs); + padding: 8px 14px; + margin-top: 12px; + display: flex; + align-items: center; + justify-content: space-between; + font-size: 12px; + font-weight: 600; + color: var(--green-dark); +} + +.saving-detail { + font-size: 11px; + color: var(--text-faint); + font-weight: normal; +} + +/* ══════ EXECUTION FLOW ══════ */ +.exec-flow { + display: flex; + flex-direction: column; + gap: 0; + padding: 12px 0; +} + +.exec-step { + display: flex; + align-items: center; + gap: 12px; +} + +.exec-circle { + width: 28px; + height: 28px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-size: 12px; + font-weight: 600; + flex-shrink: 0; +} + +.exec-circle.blue { + background: #eff6ff; + border: 1.5px solid #93c5fd; + color: var(--blue); +} + +.exec-circle.yellow { + background: #fffbeb; + border: 1.5px solid #fde68a; + color: var(--yellow-dark); +} + +.exec-circle.green { + background: #f0fdf4; + border: 1.5px solid #bbf7d0; + color: var(--green-dark); +} + +.exec-text { + font-size: 11px; + color: var(--text-secondary); + line-height: 1.4; +} + +.exec-text strong { + color: var(--text-primary); +} + +.exec-connector { + width: 2px; + height: 20px; + margin-left: 13px; + border-left: 1.5px dashed #d1d5db; +} + +.flow-tip { + background: #fffbeb; + border: 1px solid #fde68a; + border-radius: var(--radius-xs); + padding: 6px 12px; + font-size: 10px; + color: var(--yellow-dark); + margin-top: 12px; +} + +.flow-summary { + font-size: 9px; + color: var(--text-faint); + margin-top: 8px; +} + +/* ══════ KERNEL CHIPS ══════ */ +.kernel-chips { + display: flex; + gap: 6px; + margin-left: auto; +} + +.kernel-chip { + padding: 4px 14px; + border-radius: var(--radius-pill); + border: 1px solid #d1d5db; + background: var(--border-light); + font-size: 11px; + color: var(--text-muted); + cursor: pointer; + transition: all 0.15s; +} + +.kernel-chip.active { + background: #eff6ff; + border-color: #93c5fd; + color: var(--blue); + font-weight: 600; +} + +/* ══════ DETAIL TABS ══════ */ +.detail-tabs { + display: flex; + gap: 4px; + margin: 16px 0 8px; +} + +.detail-tab { + padding: 6px 16px; + border: 1px solid #d1d5db; + border-radius: var(--radius-xs); + background: var(--border-light); + font-size: 12px; + color: var(--text-muted); + cursor: pointer; + font-family: var(--font); + transition: all 0.15s; +} + +.detail-tab.active { + background: #eff6ff; + border-color: #93c5fd; + color: var(--blue); + font-weight: 600; +} + +/* ══════ ASSEMBLY PANEL ══════ */ +.assembly-panel { + background: #1e1e2e; + border: 1px solid #374151; + border-radius: var(--radius-sm); + padding: 14px 18px; + font-family: var(--mono); + font-size: 12px; + line-height: 1.8; + overflow-x: auto; +} + +.asm-line { + white-space: nowrap; +} + +.asm-line.comment { + color: var(--text-muted); +} + +.asm-line.hotspot { + background: rgba(228, 0, 43, 0.12); + border-radius: 2px; + margin: 0 -18px; + padding: 0 18px; +} + +.asm-instr { + color: #60a5fa; +} + +.asm-args { + color: #60a5fa; +} + +.asm-hotspot { + color: #f87171; + font-weight: 600; +} + +/* ══════ REPORT PAGE — EXPOSE FILTER BAR ══════ */ +.report-filter-bar { + display: flex; + align-items: flex-end; + gap: 14px; + padding: 0 0 12px; + flex-wrap: wrap; +} + +.report-filter-bar.single-row { + flex-wrap: nowrap; + gap: 10px; +} + +.report-filter-bar.single-row .filter-group { + flex: 1; + min-width: 0; +} + +.report-filter-bar.single-row .filter-select { + min-width: 100px; +} + +.report-filter-bar.single-row .rpt-expose-group { + flex: 0 0 auto; +} + +.rpt-dd-scroll { + max-height: 180px; + overflow-y: auto; +} + +.dd-optional { + font-size: 9px; + color: var(--text-faint); + font-weight: normal; +} + +.nv-none { + color: var(--text-muted); + font-style: italic; +} + +.rpt-expose-group { + margin-left: auto; +} + +.btn-expose { + padding: 8px 28px; + font-size: 13px; + font-weight: 700; + letter-spacing: 0.3px; + border-radius: var(--radius-sm); +} + +/* ══════ EXPOSE HISTORY ══════ */ +.expose-history-wrap { + margin-top: 12px; + border-top: 1px solid var(--border); + padding-top: 10px; +} + +.expose-history-label { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 6px; +} + +.expose-count { + font-size: 10px; + color: var(--text-faint); + font-weight: normal; +} + +.expose-history-list { + max-height: 200px; + overflow-y: auto; + display: flex; + flex-direction: column; + gap: 3px; + padding-right: 4px; +} + +.expose-history-list::-webkit-scrollbar { + width: 5px; +} + +.expose-history-list::-webkit-scrollbar-thumb { + background: var(--border); + border-radius: 3px; +} + +.expose-empty { + text-align: center; + padding: 20px; + font-size: 12px; + color: var(--text-muted); +} + +.expose-item { + display: flex; + align-items: center; + gap: 10px; + padding: 7px 12px; + border: 1px solid var(--border); + border-radius: 4px; + background: var(--surface); + font-size: 11px; + transition: background 0.15s; +} + +.expose-item:hover { + background: #f8fafc; +} + +.expose-item .ei-model { + font-weight: 600; + color: var(--text-primary); + min-width: 170px; +} + +.expose-item .ei-config { + color: var(--text-muted); + flex: 1; +} + +.expose-item .ei-status { + font-size: 10px; + font-weight: 600; + padding: 2px 10px; + border-radius: 10px; + white-space: nowrap; +} + +.expose-item .ei-status.found { + background: #f0fdf4; + border: 1px solid #bbf7d0; + color: var(--green-dark); +} + +.expose-item .ei-status.not-found { + background: #fef2f2; + border: 1px solid #fecaca; + color: var(--red); +} + +.expose-item .ei-status.ref { + background: #f0f9ff; + border: 1px solid #bfdbfe; + color: var(--blue); +} + +.expose-item .ei-remove { + cursor: pointer; + color: var(--text-faint); + font-size: 14px; + line-height: 1; + padding: 2px; +} + +.expose-item .ei-remove:hover { + color: var(--red); +} + +/* ══════ STATUS OVERVIEW — DYNAMIC ══════ */ +.status-body-scroll { + max-height: 380px; + overflow-y: auto; +} + +.status-body-scroll::-webkit-scrollbar { + width: 5px; +} + +.status-body-scroll::-webkit-scrollbar-thumb { + background: var(--border); + border-radius: 3px; +} + +.status-empty { + text-align: center; + padding: 32px 20px; + color: var(--text-muted); + font-size: 12px; +} + +.status-empty-icon { + font-size: 24px; + display: block; + margin-bottom: 6px; +} + +/* ms-status kept for Per-Model Detail Reports */ +.ms-status { + font-size: 10px; + font-weight: 600; + padding: 3px 12px; + border-radius: 10px; + white-space: nowrap; + flex-shrink: 0; +} + +.ms-status.complete { + background: #f0fdf4; + border: 1px solid #bbf7d0; + color: var(--green-dark); +} + +.ms-status.in-progress { + background: #eff6ff; + border: 1px solid #bfdbfe; + color: var(--blue); +} + +.ms-status.paused { + background: #fffbeb; + border: 1px solid #fde68a; + color: var(--yellow-dark); +} + +/* ══════ REPORT PAGE — STATUS TABLE ══════ */ +.status-table { + display: flex; + flex-direction: column; + gap: 0; +} + +.status-header { + display: flex; + align-items: center; + background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%); + border-radius: 8px; + padding: 10px 16px; + margin-bottom: 6px; + border: 1px solid #e2e8f0; + gap: 8px; +} + +.status-header .st-col { + font-size: 10px; + font-weight: 700; + color: #64748b; + text-transform: uppercase; + letter-spacing: 0.4px; + justify-content: flex-start; +} + +.status-row { + display: flex; + align-items: center; + padding: 16px 16px; + border-bottom: 1px solid #f1f5f9; + min-height: 68px; + transition: background .15s; + gap: 8px; + margin-bottom: 4px; +} + +.status-row:hover { + background: #f8fafc; +} + +.status-row.alt { + background: #fafbfc; +} +.status-row.alt:hover { + background: #f1f5f9; +} + +.st-col { + display: flex; + align-items: center; + gap: 8px; +} + +.st-col.st-model { + width: 14%; + flex-shrink: 0; + min-width: 140px; +} + +.st-col.st-nvref { + width: 10%; + flex-shrink: 0; + flex-direction: column; + align-items: flex-start; + gap: 1px; +} + +.st-nvref-name { + font-size: 11px; + font-weight: 600; + color: var(--blue); +} + +.st-nvref-sub { + font-size: 9px; + color: var(--text-muted); +} + +.st-nvref-notfound { + font-size: 10px; + color: var(--text-faint); + font-style: italic; +} + +.st-col.st-gap { + width: 10%; + flex-direction: column; + align-items: flex-start; + gap: 2px; + flex-shrink: 0; +} + +.st-col.st-roofline { + width: 10%; + flex-direction: column; + align-items: flex-start; + gap: 2px; + flex-shrink: 0; +} + +.st-col.st-phase { + width: 16%; + justify-content: flex-start; + flex-shrink: 0; +} + +.st-col.st-e2e { + width: 8%; + justify-content: flex-start; + flex-shrink: 0; +} + +.st-accent { + width: 4px; + height: 36px; + border-radius: 3px; + flex-shrink: 0; +} + +.st-accent.green { background: linear-gradient(180deg, #22c55e, #16a34a); } +.st-accent.blue { background: linear-gradient(180deg, #3b82f6, #2563eb); } +.st-accent.yellow { background: linear-gradient(180deg, #f59e0b, #d97706); } +.st-accent.yellow-accent { background: linear-gradient(180deg, #f59e0b, #d97706); } +.st-accent.red { background: linear-gradient(180deg, #ef4444, #dc2626); } + +.st-model-name { + font-size: 12px; + font-weight: 700; + color: var(--text-primary); + letter-spacing: -0.2px; +} + +.st-model-sub { + font-size: 10px; + color: #94a3b8; + margin-top: 1px; +} + +.gap-value { + font-size: 11px; + font-weight: 700; +} + +.gap-value.green { color: var(--green-dark); } +.gap-value.red { color: var(--red); } +.gap-value.yellow { color: var(--yellow-dark); } + +.gap-sub { + font-size: 9px; + color: var(--text-muted); +} + +.gap-bar { + width: 100%; + max-width: 100px; + height: 6px; + background: var(--border); + border-radius: 3px; + overflow: hidden; +} + +.gap-bar.wide { + max-width: 130px; +} + +.gap-fill { + height: 100%; + border-radius: 3px; +} + +.gap-fill.green { background: var(--green-dark); } +.gap-fill.red { background: var(--red); } +.gap-fill.yellow { background: var(--yellow-dark); } + +.gap-pct { + font-size: 10px; + font-weight: 600; + color: var(--yellow-dark); +} + +.phase-chip { + font-size: 9px; + font-weight: 600; + padding: 3px 10px; + border-radius: 8px; + white-space: nowrap; +} +.phase-chip.data-chip { + background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%); + border: 1px solid #e2e8f0; + color: #334155; + font-weight: 500; + font-size: 10.5px; + padding: 8px 12px; + white-space: normal; + line-height: 1.45; + max-width: 240px; + text-align: left; + border-radius: 10px; + box-shadow: 0 1px 3px rgba(0,0,0,0.04); + transition: all .15s; +} +.phase-chip.data-chip:hover { + border-color: #cbd5e1; + box-shadow: 0 2px 6px rgba(0,0,0,0.06); +} +/* Color-coded data chips per column (Profiling=blue, KernelOpt=green, ExpectedE2E=purple) */ +.status-row > .st-phase:nth-of-type(1) .data-chip { + background: linear-gradient(135deg, #eff6ff 0%, #e0f0ff 100%); + border-color: #bfdbfe; + color: #1e40af; +} +.status-row > .st-phase:nth-of-type(2) .data-chip { + background: linear-gradient(135deg, #f0fdf4 0%, #dcfce7 100%); + border-color: #bbf7d0; + color: #166534; +} +.status-row > .st-phase:nth-of-type(3) .data-chip { + background: linear-gradient(135deg, #faf5ff 0%, #f3e8ff 100%); + border-color: #d8b4fe; + color: #6b21a8; +} + +/* Data-chip inner highlights */ +.data-chip strong { + font-weight: 700; + color: inherit; +} +.data-chip .dc-green { + color: #16a34a; +} +.data-chip .dc-label { + font-weight: 700; + font-size: 9px; + text-transform: uppercase; + letter-spacing: 0.3px; + opacity: 0.7; +} +.data-chip em { + font-style: normal; + font-weight: 600; + opacity: 0.85; +} + +/* E2E Perf pill styles */ +.e2e-pill { + display: inline-flex; + align-items: center; + font-size: 12px; + font-weight: 700; + padding: 5px 16px; + border-radius: 20px; + white-space: nowrap; + letter-spacing: -0.2px; +} +.e2e-pill.green { + background: linear-gradient(135deg, #ecfdf5 0%, #d1fae5 100%); + color: #059669; + border: 1px solid #a7f3d0; + box-shadow: 0 1px 4px rgba(5,150,105,0.1); +} +.e2e-pill.amber { + background: linear-gradient(135deg, #fffbeb 0%, #fef3c7 100%); + color: #b45309; + border: 1px solid #fde68a; + font-size: 10px; + font-weight: 600; +} +.e2e-pill.gray { + background: #f1f5f9; + color: #94a3b8; + border: 1px solid #e2e8f0; + font-size: 10px; + font-weight: 500; +} + +.phase-chip.done { + background: #f0fdf4; + border: 0.5px solid #bbf7d0; + color: var(--green-dark); +} + +.phase-chip.wip { + background: #eff6ff; + border: 0.5px solid #bfdbfe; + color: var(--blue); +} + +.phase-chip.paused { + background: #fffbeb; + border: 0.5px solid #fde68a; + color: var(--yellow-dark); +} + +.phase-chip.na { + background: var(--border-light); + border: 0.5px solid #d1d5db; + color: var(--text-faint); +} + +.e2e-detail { + display: flex; + flex-direction: column; + gap: 2px; + background: var(--surface); + border: 0.5px solid #d1d5db; + border-radius: 8px; + padding: 4px 10px; +} + +.e2e-line { + font-size: 9px; + font-weight: 600; +} + +.e2e-line.green { color: var(--green-dark); } + +/* ══════ REPORT PAGE — PER-MODEL DETAIL ══════ */ +.report-model { + margin-bottom: 4px; +} + +/* ── Report batch actions ── */ +.report-batch-actions { + display: flex; + align-items: center; + gap: 10px; + margin-left: auto; +} +.batch-count { + font-size: 12px; + color: var(--text-secondary); + font-weight: 600; +} + +/* ── Report model checkbox ── */ +.report-model-check { + display: flex; + align-items: center; + cursor: pointer; + flex-shrink: 0; + position: relative; + width: 18px; + height: 18px; +} +.report-model-check input { + position: absolute; + opacity: 0; + width: 0; + height: 0; +} +.rpt-cb-mark { + display: inline-block; + width: 18px; + height: 18px; + border: 2px solid #9ca3af; + border-radius: 4px; + background: var(--surface); + transition: all 0.15s; + position: relative; +} +.report-model-check input:checked + .rpt-cb-mark { + background: #1e293b; + border-color: #1e293b; +} +.report-model-check input:checked + .rpt-cb-mark::after { + content: '✓'; + position: absolute; + top: -1px; + left: 2px; + font-size: 12px; + color: white; + font-weight: 700; +} +.report-model-check:hover .rpt-cb-mark { + border-color: var(--accent); +} + +.report-model-header { + display: flex; + align-items: center; + gap: 10px; + padding: 10px 16px; + border-radius: var(--radius-sm); + border: 1px solid #d1d5db; + background: var(--border-light); + cursor: pointer; + transition: all 0.15s; +} + +.report-model-header.active { + background: #eff6ff; + border-color: #93c5fd; +} + +.report-model-header:hover { + background: #f0f5ff; +} + +.report-toggle { + font-size: 14px; + color: var(--text-secondary); + flex-shrink: 0; + width: 16px; + text-align: center; +} + +.report-model-name { + font-size: 13px; + font-weight: 700; + color: var(--text-secondary); +} + +.report-model-header.active .report-model-name { + color: var(--blue); +} + +.report-model-config { + font-size: 10px; + color: var(--text-muted); +} + +.report-model-summary { + font-size: 10px; + color: var(--text-muted); + margin-left: auto; + margin-right: 8px; +} + +.report-model-body { + display: none; + padding: 12px 16px; + margin: 0 2px; + background: var(--surface); + border: 1px solid #93c5fd; + border-top: none; + border-radius: 0 0 var(--radius-sm) var(--radius-sm); +} + +.report-model.expanded .report-model-body { + display: block; +} + +.report-model.collapsed .report-model-body { + display: none; +} + +/* Report sections */ +.report-section-header { + display: flex; + align-items: center; + gap: 16px; + background: #eff6ff; + border: 1px solid #93c5fd; + border-radius: var(--radius-xs); + padding: 8px 14px; + margin-bottom: 8px; + font-size: 12px; + font-weight: 600; + color: var(--blue); + flex-wrap: wrap; +} + +.report-gap-text { + font-weight: 700; +} + +.report-gap-text.red { color: var(--red); } + +.report-gap-bar { + display: flex; + align-items: center; + gap: 6px; + margin-left: auto; +} + +/* Report kernel cards */ +.report-kernels-row { + display: flex; + gap: 8px; + margin-bottom: 8px; + flex-wrap: wrap; +} + +.report-kernel-card { + flex: 1; + min-width: 140px; + padding: 8px 12px; + border-radius: 4px; + border-left: 4px solid; +} + +.report-kernel-card.hot { + background: #fef2f2; + border-left-color: var(--red); + border: 1px solid #fecaca; + border-left: 4px solid var(--red); +} + +.report-kernel-card.warm { + background: #fffbeb; + border-left-color: var(--yellow-dark); + border: 1px solid #fde68a; + border-left: 4px solid var(--yellow-dark); +} + +.rk-name { + display: block; + font-size: 11px; + font-weight: 700; +} + +.report-kernel-card.hot .rk-name { color: var(--red); } +.report-kernel-card.warm .rk-name { color: var(--yellow-dark); } + +.rk-desc { + display: block; + font-size: 10px; + color: var(--text-secondary); +} + +/* Report detail rows */ +.report-detail-row { + display: flex; + align-items: center; + gap: 6px; + padding: 8px 12px; + border-radius: 4px; + margin-bottom: 4px; + font-size: 12px; + color: var(--text-secondary); + flex-wrap: wrap; + border: 1px solid; +} + +.report-detail-row.green { + background: #f0fdf4; + border-color: #bbf7d0; +} + +.report-detail-row.yellow { + background: #fffbeb; + border-color: #fde68a; +} + +.report-detail-row.red { + background: #fef2f2; + border-color: #fecaca; +} + +.rd-accent { + width: 4px; + height: 20px; + border-radius: 2px; + flex-shrink: 0; +} + +.rd-accent.green { background: var(--green-dark); } +.rd-accent.yellow { background: var(--yellow-dark); } +.rd-accent.red { background: var(--red); } + +.rd-label { + font-weight: 700; +} + +.rd-label.green { color: var(--green-dark); } +.rd-label.yellow { color: var(--yellow-dark); } +.rd-label.red { color: var(--red); } + +.rd-value { + font-weight: bold; +} + +.rd-value.green { color: var(--green-dark); } +.rd-value.red { color: var(--red); } + +.rd-sep { + color: var(--text-muted); +} + +/* Export buttons */ +.btn-export-model { + background: #f0f9ff; + border: 1.5px solid #bfdbfe; + color: var(--blue); +} + +.btn-export-csv { + background: #f0fdf4; + border: 1.5px solid #86efac; + color: var(--green-dark); +} + +.btn-share { + background: #faf5ff; + border: 1.5px solid #d8b4fe; + color: #7c3aed; +} + +.export-hint { + font-size: 10px; + color: var(--text-faint); + padding: 4px 20px; + margin-bottom: 16px; +} + +/* ══════ E2E Integration Notes (Optimization page) ══════ */ +.e2e-integration-notes { + padding: 8px 16px 12px; + display: flex; + flex-direction: column; + gap: 4px; +} + +.e2e-note { + display: flex; + align-items: center; + gap: 8px; + padding: 6px 12px; + border-radius: var(--radius-xs); + font-size: 11px; + color: var(--text-secondary); +} + +.e2e-note .note-icon { + flex-shrink: 0; + font-size: 12px; +} + +.e2e-note.yellow { + background: #fffbeb; + border: 1px solid #fde68a; +} + +.e2e-note.red { + background: #fef2f2; + border: 1px solid #fecaca; +} + +/* Gap metric card (Optimization E2E) */ +.gap-metric-card { + background: #f8fafc !important; + border: 1.5px solid var(--border) !important; +} + +.gap-from { + color: var(--yellow-dark); + font-weight: 700; +} + +.gap-to { + font-weight: 700; +} + +.gap-to.green { + color: var(--green-dark); +} + +/* ══════ REPORT — Subsection (two-level expand) ══════ */ +.report-subsection { + margin-bottom: 6px; +} + +.report-subsection-header { + display: flex; + align-items: center; + gap: 10px; + padding: 8px 12px; + border-radius: var(--radius-xs); + border: 1px solid var(--border); + background: #fafbfc; + cursor: pointer; + transition: all 0.15s; + font-size: 12px; + font-weight: 600; + color: var(--text-secondary); +} + +.report-subsection-header:hover { + background: #f0f5ff; + border-color: #93c5fd; +} + +.report-subsection.expanded > .report-subsection-header { + background: #eff6ff; + border-color: #93c5fd; + color: var(--blue); +} + +.report-sub-toggle { + font-size: 13px; + width: 14px; + text-align: center; + flex-shrink: 0; + color: var(--text-muted); +} + +.report-sub-title { + flex: 1; +} + +.report-sub-status { + font-size: 10px; + font-weight: 400; + color: var(--text-muted); + margin-left: auto; +} + +.report-subsection-body { + padding: 10px 8px; + margin: 0 2px; + border: 1px solid var(--border-light); + border-top: none; + border-radius: 0 0 var(--radius-xs) var(--radius-xs); + background: var(--white); +} + +.report-subsection.collapsed .report-subsection-body { + display: none; +} + +.report-subsection.expanded .report-subsection-body { + display: block; +} + +/* Report E2E metrics row (inside report subsection) */ +.rpt-e2e-metrics { + gap: 10px; + padding: 0 0 8px; +} + +/* Gap improvement row */ +.rpt-gap-improvement { + padding: 8px 12px; + background: #f0fdf4; + border: 1px solid #bbf7d0; + border-radius: var(--radius-xs); + margin-bottom: 8px; +} + +.rpt-gap-row { + display: flex; + align-items: center; + gap: 8px; + font-size: 12px; + font-weight: 600; +} + +.rpt-gap-label { + color: var(--text-secondary); +} + +.rpt-gap-from { + color: var(--yellow-dark); + font-weight: 700; +} + +.rpt-gap-arrow { + color: var(--text-muted); + font-size: 14px; +} + +.rpt-gap-to { + font-weight: 700; +} + +.rpt-gap-to.green { + color: var(--green-dark); +} + +.rpt-gap-bar-wrap { + margin-top: 4px; +} + +.gap-fill-overlay { + height: 100%; + border-radius: 3px; + position: absolute; + top: 0; + left: 0; + opacity: 0.5; +} + +.gap-bar.wide { + position: relative; +} + +/* Report profile summary */ +.rpt-profile-summary { + margin-top: 4px; +} + +.report-section-header.compact { + padding: 6px 12px; + margin-bottom: 6px; + font-size: 11px; +} + +/* ══════ Kernel-Level Optimization cards (Report) ══════ */ +.rko-card { + border: 1px solid var(--border); + border-radius: var(--radius-sm); + margin-bottom: 8px; + overflow: hidden; +} + +.rko-header { + display: flex; + align-items: center; + gap: 10px; + padding: 8px 14px; + background: var(--surface); + border-bottom: 1px solid var(--border-light); +} + +.rko-name { + font-family: var(--mono); + font-size: 12px; + font-weight: 700; + color: var(--text-primary); +} + +.rko-tag { + font-size: 9px; + font-weight: 600; + padding: 2px 8px; + border-radius: 4px; +} + +.rko-tag.hot { + background: #fef2f2; + color: var(--red); + border: 0.5px solid #fecaca; +} + +.rko-tag.warm { + background: #fffbeb; + color: var(--yellow-dark); + border: 0.5px solid #fde68a; +} + +.rko-status { + margin-left: auto; + font-size: 10px; + font-weight: 600; + padding: 2px 8px; + border-radius: 4px; +} + +.rko-status.validated { + background: #f0fdf4; + color: var(--green-dark); + border: 0.5px solid #bbf7d0; +} + +.rko-status.processing { + background: #eff6ff; + color: var(--blue); + border: 0.5px solid #bfdbfe; +} + +.rko-status.sent { + background: #fffbeb; + color: var(--yellow-dark); + border: 0.5px solid #fde68a; +} + +.rko-body { + padding: 10px 14px; +} + +.rko-metrics-row { + display: flex; + gap: 16px; + flex-wrap: wrap; + margin-bottom: 6px; +} + +.rko-metric { + display: flex; + align-items: center; + gap: 6px; + font-size: 12px; +} + +.rko-label { + font-weight: 700; + color: var(--text-secondary); +} + +.rko-val { + color: var(--text-primary); +} + +.rko-uplift { + font-weight: 700; + font-size: 11px; + padding: 1px 6px; + border-radius: 4px; +} + +.rko-uplift.green { + background: #dcfce7; + color: var(--green-dark); +} + +.rko-uplift.blue { + background: #dbeafe; + color: var(--blue); +} + +.rko-roofline { + font-size: 11px; + color: var(--text-muted); + padding: 6px 10px; + background: var(--surface); + border-radius: var(--radius-xs); + border: 1px solid var(--border-light); +} + +.rko-rf-label { + font-weight: 700; + color: var(--text-secondary); +} + +.rko-e2e-status { + margin-top: 8px; +} + +/* ══════ OPT-HIDDEN: Cards hidden until button interaction ══════ */ +.opt-hidden { + display: none !important; +} +.opt-visible { + display: block !important; +} +.card-row.opt-visible { + display: flex !important; +} + +/* ══════ E2E OPTIMIZED METRICS (with before/after delta) ══════ */ +.e2e-opt-metrics { + gap: 12px; + padding: 4px 0; +} + +.e2e-metric-delta { + position: relative; + padding: 14px 16px 10px !important; +} + +.metric-sub-was { + display: flex; + align-items: center; + gap: 6px; + font-size: 10px; + color: var(--text-muted); + margin-top: 4px; +} + +.metric-delta-badge { + display: inline-flex; + align-items: center; + gap: 2px; + padding: 1px 6px; + border-radius: 4px; + font-size: 10px; + font-weight: 700; + white-space: nowrap; +} + +.metric-delta-badge.up { + background: #dcfce7; + color: var(--green-dark); +} + +.metric-delta-badge.down-good { + background: #dcfce7; + color: var(--green-dark); +} + +.metric-delta-badge.down-bad { + background: #fef2f2; + color: var(--red); +} + +.metric-delta-badge.up-bad { + background: #fef2f2; + color: var(--red); +} + +.metric-label-hint { + font-size: 8px; + font-weight: 400; + color: var(--text-faint); + letter-spacing: 0; +} + +/* ══════ Optimization Model Dropdown ══════ */ +#opt-dd-model { + margin: 0 12px; +} + +#opt-dd-model .filter-select { + min-width: 160px; +} + +#opt-dd-kernel { + margin-left: auto; +} + +#opt-dd-kernel .filter-select { + min-width: 200px; + font-family: var(--mono); + font-size: 11px; +} + +/* ══════ RESPONSIVE ══════ */ +@media (max-width: 1100px) { + .card-row { + flex-direction: column; + } + + .metrics-row { + flex-wrap: wrap; + } + + .filter-bar { + flex-wrap: wrap; + } + + .kernel-chips { + margin-left: 0; + margin-top: 8px; + } + + .card-header { + flex-direction: column; + gap: 4px; + } +} + +@media (max-width: 768px) { + .sidebar { + width: 60px; + } + + .sidebar-item span:not(.sidebar-icon) { + display: none; + } + + .content { + margin-left: 60px; + width: calc(100% - 60px); + } +} + +/* ══════════════════════════════════════════════════════ + NEW COMPONENTS — Search Dropdowns, Merged GEAK, etc. + ══════════════════════════════════════════════════════ */ + +/* ── Search Dropdown (Optimization page model selector) ── */ +.search-dropdown .dropdown-menu.search-dropdown-menu { + padding: 0; + min-width: 260px; +} + +.search-dropdown-input { + width: 100%; + padding: 8px 12px; + border: none; + border-bottom: 1px solid var(--border); + font-size: 13px; + font-family: var(--font); + outline: none; + background: var(--surface); +} + +.search-dropdown-input:focus { + background: #fff; + border-bottom-color: var(--blue-light); +} + +.search-dropdown-list { + max-height: 200px; + overflow-y: auto; +} + +.dd-placeholder { + color: var(--text-muted) !important; + font-style: italic; +} + +/* ── Searchable Combobox (Task Setup) ── */ +.search-combo .search-combo-trigger { + display: flex; + align-items: center; + gap: 4px; +} + +.search-combo-input { + border: none; + outline: none; + background: transparent; + font-size: 13px; + font-family: var(--font); + color: var(--text-primary); + width: 100%; + min-width: 0; + padding: 0; +} + +.search-combo-input::placeholder { + color: var(--text-faint); + font-style: italic; +} + +.search-combo .dropdown-menu.search-combo-menu { + max-height: 200px; + overflow-y: auto; +} + +.search-combo .dd-item.dd-no-match { + color: var(--text-muted); + font-style: italic; + padding: 8px 12px; + cursor: default; +} + +/* ── Empty State (Bridge Plan) ── */ +.bridge-plan-empty .empty-state { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 40px 20px; + gap: 12px; + color: var(--text-muted); +} + +.empty-state .empty-icon { + font-size: 32px; + opacity: 0.5; +} + +.empty-state .empty-text { + font-size: 14px; + text-align: center; + max-width: 400px; +} + +/* ── Execution Flow Tooltip (exclamation mark icon) ── */ +.exec-flow-tooltip-wrap { + position: relative; + cursor: help; + margin-left: 8px; +} + +.exec-flow-info-icon { + display: inline-flex; + align-items: center; + justify-content: center; + width: 20px; + height: 20px; + border-radius: 50%; + background: var(--yellow); + color: #fff; + font-size: 12px; + font-weight: 700; + line-height: 1; +} + +.exec-flow-tooltip-wrap:hover::after { + content: attr(title); + position: absolute; + bottom: calc(100% + 8px); + left: 50%; + transform: translateX(-50%); + background: rgba(26, 26, 46, 0.95); + color: #fff; + padding: 10px 14px; + border-radius: var(--radius-sm); + font-size: 12px; + line-height: 1.5; + white-space: pre-line; + max-width: 340px; + min-width: 240px; + z-index: 1000; + pointer-events: none; + box-shadow: 0 4px 12px rgba(0,0,0,0.2); +} + +.exec-flow-tooltip-wrap:hover::before { + content: ''; + position: absolute; + bottom: calc(100% + 2px); + left: 50%; + transform: translateX(-50%); + border: 6px solid transparent; + border-top-color: rgba(26, 26, 46, 0.95); + z-index: 1001; +} + +/* ── Inline Strategy Section (under each pipeline item) ── */ +.inline-strategy-section { + margin-top: 10px; + border-top: 1px solid var(--border-light); + padding-top: 8px; +} + +.inline-strategy-toggle-btn { + display: flex; + align-items: center; + gap: 8px; + padding: 6px 10px; + cursor: pointer; + font-size: 12px; + font-weight: 600; + color: var(--text-secondary); + border-radius: var(--radius-sm); + transition: background 0.15s; +} + +.inline-strategy-toggle-btn:hover { + background: var(--surface); +} + +.inline-strategy-toggle-icon { + font-size: 11px; + color: var(--text-faint); + width: 12px; + text-align: center; + transition: transform 0.2s; +} + +.inline-strategy-body { + padding: 10px 10px 6px; + margin-top: 6px; + background: var(--surface); + border-radius: var(--radius-sm); + border: 1px solid var(--border-light); +} + +/* (E2E range box styles removed — replaced by metric card layout) */ + +/* ── Average/Times indicator ── */ +.avg-times-badge { + display: inline-flex; + align-items: center; + justify-content: center; + background: rgba(59, 130, 246, 0.1); + color: var(--blue-light); + font-size: 10px; + font-weight: 600; + padding: 1px 5px; + border-radius: var(--radius-xs); + margin-left: 4px; + vertical-align: middle; +} + +/* ── Version info inside dropdown trigger ── */ +.dd-version-info { + display: inline-block; + font-size: 11px; + color: var(--text-secondary); + margin-left: 8px; + opacity: .75; + font-weight: 400; + letter-spacing: .02em; + white-space: nowrap; +} + +/* ── Analysis empty state ── */ +.ana-empty-state { + display: flex; + flex-direction: column; + align-items: center; + gap: 8px; + padding: 32px 16px; + color: var(--text-faint); + font-size: 13px; +} +.ana-empty-state .empty-icon { + font-size: 28px; + opacity: .5; +} +.ana-empty-state .empty-text { + text-align: center; + max-width: 360px; + line-height: 1.5; +} +.metric-value.muted { + color: var(--text-faint) !important; + opacity: .4; +} + +/* ── Building state (gpt-oss plan) ── */ +.building-state { + text-align: center; +} +.building-text { + font-size: 15px !important; + color: var(--text-secondary); +} +.building-sub-text { + font-size: 12px; + color: var(--text-faint); + margin-top: 4px; +} + +/* ── E2E chip in GEAK strategies (validated only) ── */ +.detail-chip.e2e-chip { + background: rgba(34,197,94,0.12); + color: #22c55e; + border: 1px solid rgba(34,197,94,0.3); + font-weight: 600; +} + +/* ── GEAK Agent Settings Modal ── */ +.geak-settings-overlay { + position: fixed; + inset: 0; + z-index: 9999; + background: rgba(0,0,0,0.55); + display: flex; + align-items: center; + justify-content: center; + backdrop-filter: blur(3px); +} +.geak-settings-modal { + background: #ffffff; + border: 1px solid #e5e7eb; + border-radius: 14px; + width: 480px; + max-width: 92vw; + box-shadow: 0 12px 40px rgba(0,0,0,0.25); +} +.geak-settings-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 18px 24px 12px; + border-bottom: 1px solid #e5e7eb; +} +.geak-settings-header h3 { + font-size: 16px; + font-weight: 600; + color: #1a1a2e; + margin: 0; +} +.geak-settings-close { + background: none; + border: none; + color: #6b7280; + font-size: 18px; + cursor: pointer; + padding: 4px 8px; + border-radius: 6px; + transition: background .15s; +} +.geak-settings-close:hover { + background: #f3f4f6; + color: #1a1a2e; +} +.geak-settings-body { + padding: 20px 24px; + display: flex; + flex-direction: column; + gap: 18px; +} +.geak-setting-group { + display: flex; + flex-direction: column; + gap: 6px; +} +.geak-setting-label { + font-size: 13px; + font-weight: 600; + color: #1a1a2e; + display: flex; + flex-direction: column; + gap: 2px; +} +.geak-setting-hint { + font-weight: 400; + font-size: 11px; + color: #6b7280; +} +.geak-setting-input { + background: #ffffff; + border: 1px solid #d1d5db; + border-radius: 8px; + padding: 10px 14px; + font-size: 14px; + color: #1a1a2e; + outline: none; + transition: border-color .2s; +} +.geak-setting-input:focus { + border-color: var(--accent); + box-shadow: 0 0 0 2px rgba(99,102,241,0.15); +} + +/* ── GEAK Model Select Dropdown ── */ +.geak-model-select { + position: relative; +} +.geak-model-trigger { + display: flex; + align-items: center; + justify-content: space-between; + background: #ffffff; + border: 1px solid #d1d5db; + border-radius: 8px; + padding: 10px 14px; + font-size: 14px; + color: #1a1a2e; + cursor: pointer; + transition: border-color .2s; + user-select: none; +} +.geak-model-trigger:hover { + border-color: #9ca3af; +} +.geak-model-trigger.open { + border-color: var(--accent); + box-shadow: 0 0 0 2px rgba(99,102,241,0.15); + border-radius: 8px 8px 0 0; +} +.geak-model-trigger .caret { + font-size: 11px; + color: #9ca3af; + transition: transform .2s; +} +.geak-model-trigger.open .caret { + transform: rotate(180deg); +} +.geak-model-value { + font-family: 'SF Mono', 'Fira Code', 'Consolas', monospace; + font-size: 13px; +} +/* ── GEAK Model Collapsible Panel ── */ +.geak-model-collapse { + max-height: 0; + overflow: hidden; + background: #f9fafb; + border: 1px solid transparent; + border-top: none; + border-radius: 0 0 8px 8px; + transition: max-height 0.3s ease, padding 0.3s ease, border-color 0.3s ease; + padding: 0; +} +.geak-model-collapse.open { + max-height: 300px; + padding: 6px 0; + border-color: #d1d5db; +} +.geak-model-option { + padding: 8px 14px; + font-size: 13px; + font-family: 'SF Mono', 'Fira Code', 'Consolas', monospace; + color: #374151; + cursor: pointer; + transition: background .12s; +} +.geak-model-option:hover { + background: #f3f4f6; +} +.geak-model-option.active { + background: rgba(99,102,241,0.08); + color: var(--accent); + font-weight: 600; +} +.geak-settings-footer { + display: flex; + justify-content: flex-end; + gap: 10px; + padding: 14px 24px 18px; + border-top: 1px solid #e5e7eb; +} +.geak-settings-footer .btn-secondary { + background: #f3f4f6; + color: #374151; + border: 1px solid #d1d5db; +} +.geak-settings-footer .btn-secondary:hover { + background: #e5e7eb; +} diff --git a/Web/apps/hyperloom/src/assets/main.scss b/Web/apps/hyperloom/src/assets/main.scss new file mode 100644 index 000000000..17bf7a044 --- /dev/null +++ b/Web/apps/hyperloom/src/assets/main.scss @@ -0,0 +1,2266 @@ +/* HyperLoom Design System — adapted from AMD PRISM */ + +:root { + --amd-red: #e4002b; + --amd-red-light: #ff3355; + + --green: #22c55e; + --green-dark: #16a34a; + --blue: #2563eb; + --blue-light: #3b82f6; + --yellow: #f59e0b; + --yellow-dark: #d97706; + --orange: #ea580c; + --red: #dc2626; + --purple: #8b5cf6; + --teal: #14b8a6; + + --bg: #f3f5f9; + --white: #ffffff; + --text-primary: #1a1a2e; + --text-secondary: #4b5563; + --text-muted: #6b7280; + --text-faint: #9ca3af; + --border: #e5e7eb; + --border-light: #f3f4f6; + + --nav-h: 48px; + --sidebar-w: 200px; + --radius: 8px; + --radius-sm: 4px; + --shadow: 0 1px 3px rgba(0, 0, 0, 0.06), 0 1px 2px rgba(0, 0, 0, 0.04); + --shadow-md: 0 4px 6px rgba(0, 0, 0, 0.07); +} + +*, +*::before, +*::after { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, + sans-serif; + font-size: 13px; + color: var(--text-primary); + background: var(--bg); + line-height: 1.5; + -webkit-font-smoothing: antialiased; +} + +/* Top Nav */ +.hl-topnav { + position: sticky; + top: 0; + z-index: 100; + height: var(--nav-h); + background: var(--white); + border-bottom: 1px solid var(--border); + display: flex; + align-items: center; + padding: 0 24px; +} + +.hl-topnav-accent { + position: absolute; + top: 0; + left: 0; + right: 0; + height: 3px; + background: linear-gradient(90deg, var(--amd-red), var(--amd-red-light)); +} + +.hl-topnav-brand { + display: flex; + align-items: baseline; + gap: 8px; +} + +.brand-amd { + font-weight: 800; + font-size: 16px; + color: var(--text-primary); +} + +.brand-name { + font-weight: 600; + font-size: 15px; + color: var(--text-secondary); + letter-spacing: 0.5px; +} + +/* Layout */ +.hl-app { + min-height: 100vh; +} + +.hl-layout { + display: flex; + min-height: calc(100vh - var(--nav-h)); +} + +/* Sidebar */ +.hl-sidebar { + width: var(--sidebar-w); + min-width: var(--sidebar-w); + background: var(--white); + border-right: 1px solid var(--border); + padding: 12px 8px; + display: flex; + flex-direction: column; + gap: 2px; +} + +.hl-sidebar-item { + display: flex; + align-items: center; + gap: 10px; + padding: 10px 14px; + border-radius: var(--radius); + color: var(--text-secondary); + text-decoration: none; + font-size: 13px; + font-weight: 500; + transition: all 0.15s; + + &:hover { + background: var(--bg); + color: var(--text-primary); + } + + &.active { + background: rgba(228, 0, 43, 0.06); + color: var(--amd-red); + font-weight: 600; + } +} + +.hl-sidebar-divider { + height: 1px; + background: var(--border); + margin: 8px 14px; +} + +/* Content */ +.hl-content { + flex: 1; + padding: 16px; + overflow-y: auto; +} + +/* Top Nav Actions */ +.hl-topnav-actions { + margin-left: auto; + display: flex; + align-items: center; + gap: 10px; +} + +.hl-user-label { + font-size: 12px; + color: var(--text-muted); + font-weight: 500; + max-width: 160px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.hl-logout-btn { + display: flex; + align-items: center; + justify-content: center; + width: 32px; + height: 32px; + border: 1px solid var(--border); + border-radius: 8px; + background: transparent; + cursor: pointer; + color: var(--text-muted); + transition: all 0.2s; + + &:hover { + color: var(--amd-red); + border-color: var(--amd-red); + background: rgba(228, 0, 43, 0.06); + } +} + +.hl-theme-toggle { + display: flex; + align-items: center; + justify-content: center; + width: 36px; + height: 36px; + border: 1px solid var(--border); + border-radius: 10px; + background: transparent; + cursor: pointer; + color: var(--text-secondary); + transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1); +} + +.hl-theme-toggle:hover { + background: var(--bg); + color: var(--amd-red); + border-color: var(--amd-red); + transform: rotate(15deg); +} + +.theme-icon { + display: flex; + align-items: center; + justify-content: center; + transition: transform 0.3s; +} + +/* Import dashboard and claw chat styles BEFORE dark overrides + so all [data-theme="dark"] rules come AFTER and win cascade */ +@import './dashboard.css'; +@import './claw.css'; + +/* ══════════════════════════════════════════════════ + Dark Theme + ══════════════════════════════════════════════════ */ + +[data-theme="dark"] { + --bg: #0c0e14; + --white: #141620; + --text-primary: #e4e6ef; + --text-secondary: #a0a4b8; + --text-muted: #6c7190; + --text-faint: #4a4e65; + --border: #252838; + --border-light: #1e2130; + --surface: #181a26; + + --amd-red: #ff2d55; + --amd-red-light: #ff5c7c; + --green: #34d399; + --green-dark: #10b981; + --blue: #6366f1; + --blue-light: #818cf8; + --yellow: #fbbf24; + --orange: #f97316; + --red: #f87171; + --purple: #a78bfa; + --teal: #2dd4bf; +} + +[data-theme="dark"] body { + background: #0c0e14; + color: #e4e6ef; +} + +[data-theme="dark"] .hl-topnav { + background: #141620; + border-bottom-color: #252838; +} + +[data-theme="dark"] .hl-topnav-accent { + background: linear-gradient(90deg, #ff2d55, #6366f1); +} + +[data-theme="dark"] .hl-user-label { + color: #8b8fa8; +} + +[data-theme="dark"] .hl-logout-btn { + border-color: #252838; + color: #8b8fa8; +} + +[data-theme="dark"] .hl-logout-btn:hover { + color: #ff3355; + border-color: #ff3355; + background: rgba(255, 51, 85, 0.1); +} + +[data-theme="dark"] .hl-theme-toggle { + border-color: #252838; + color: #fbbf24; +} + +[data-theme="dark"] .hl-theme-toggle:hover { + background: #1e2130; + border-color: #fbbf24; +} + +[data-theme="dark"] .hl-sidebar { + background: #141620; + border-right-color: #252838; +} + +[data-theme="dark"] .hl-sidebar-item:hover { + background: #1e2130; +} + +[data-theme="dark"] .hl-sidebar-item.active { + background: rgba(255, 45, 85, 0.08); + color: #ff5c7c; +} + +[data-theme="dark"] .hl-sidebar-divider { + background: #252838; +} + +[data-theme="dark"] .hl-content { + background: #0c0e14; +} + +/* Claw dark overrides */ +[data-theme="dark"] .claw-page { + background: #0c0e14; +} + +[data-theme="dark"] .claw-sidebar { + background: #141620; + border-right-color: #252838; +} + +[data-theme="dark"] .claw-sidebar-header { + border-bottom-color: #252838; +} + +[data-theme="dark"] .claw-session-item:hover, +[data-theme="dark"] .claw-session-item.active { + background: rgba(255, 45, 85, 0.06); +} + +[data-theme="dark"] .claw-topbar { + background: #141620; + border-bottom-color: #252838; +} + +[data-theme="dark"] .claw-btn-icon:hover { + background: #1e2130; +} + +[data-theme="dark"] .claw-msg-avatar { + background: rgba(255, 45, 85, 0.1); +} + +[data-theme="dark"] .claw-msg-body .claw-msg-content code { + background: #1e2130; + color: #e4e6ef; +} + +[data-theme="dark"] .claw-msg-body .claw-msg-content pre { + background: #0c0e14; + border: 1px solid #252838; +} + +[data-theme="dark"] .claw-msg-body .claw-msg-content blockquote { + background: rgba(255, 45, 85, 0.06); + border-left-color: #ff2d55; +} + +[data-theme="dark"] .claw-msg-body .claw-msg-content th { + background: #181a26; +} + +[data-theme="dark"] .claw-tool-group { + border-color: #252838; +} + +[data-theme="dark"] .claw-tool-bar { + background: #181a26; +} + +[data-theme="dark"] .claw-tool-bar:hover { + background: #1e2130; +} + +[data-theme="dark"] .claw-tool-list { + border-top-color: #252838; +} + +[data-theme="dark"] .claw-tool-item { + border-bottom-color: #1e2130; +} + +[data-theme="dark"] .claw-tool-header:hover { + background: #1e2130; +} + +[data-theme="dark"] .claw-tool-detail pre { + background: #0c0e14; + border-color: #252838; + color: #a0a4b8; +} + +[data-theme="dark"] .claw-input-section { + background: #141620; + border-top-color: #252838; +} + +[data-theme="dark"] .claw-input-wrapper { + border-color: #252838; + background: #181a26; +} + +[data-theme="dark"] .claw-input-wrapper:focus-within { + border-color: #ff2d55; + box-shadow: 0 0 0 2px rgba(255, 45, 85, 0.15); +} + +[data-theme="dark"] .claw-input-wrapper textarea { + color: #e4e6ef; +} + +[data-theme="dark"] .claw-input-controls { + background: #141620; + border-top-color: #1e2130; +} + +[data-theme="dark"] .claw-tools-btn { + border-color: #252838; + color: #6c7190; +} + +[data-theme="dark"] .claw-tools-btn:hover { + background: #1e2130; + color: #e4e6ef; +} + +[data-theme="dark"] .claw-tools-btn.active { + border-color: #ff2d55; + color: #ff5c7c; +} + +[data-theme="dark"] .claw-send-btn { + background: #ff2d55; +} + +[data-theme="dark"] .claw-send-btn:hover:not(:disabled) { + background: #ff5c7c; +} + +[data-theme="dark"] .claw-send-btn:disabled { + background: #252838; + color: #4a4e65; +} + +[data-theme="dark"] .claw-stop-btn { + background: rgba(248, 113, 113, 0.1); + color: #f87171; + border-color: rgba(248, 113, 113, 0.2); +} + +[data-theme="dark"] .claw-tool-picker { + background: #141620; + border-color: #252838; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5); +} + +[data-theme="dark"] .claw-tool-panel { + background: #141620; + border-color: #252838; + box-shadow: 0 -8px 40px rgba(0, 0, 0, 0.6); +} + +[data-theme="dark"] .claw-tool-panel-header { + border-bottom-color: #252838; +} + +[data-theme="dark"] .claw-tool-panel-title { + color: #e4e6ef; +} + +[data-theme="dark"] .claw-tool-panel-close { + border-color: #252838; + color: #6c7190; +} + +[data-theme="dark"] .claw-tool-panel-close:hover { + background: rgba(255, 45, 85, 0.1); + color: #ff3355; + border-color: #ff3355; +} + +[data-theme="dark"] .claw-tool-panel-filters { + border-bottom-color: #252838; +} + +[data-theme="dark"] .claw-tool-picker-header { + border-bottom-color: #252838; +} + +[data-theme="dark"] .claw-tool-picker-item:hover { + background: #1e2130; +} + +[data-theme="dark"] .claw-tool-picker-item.selected { + background: rgba(255, 45, 85, 0.06); +} + +[data-theme="dark"] .claw-seg-btn { + background: #181a26; + border-color: #252838; + color: #6c7190; +} + +[data-theme="dark"] .claw-seg-btn:hover { + border-color: #ff2d55; + color: #ff5c7c; +} + +[data-theme="dark"] .claw-seg-btn.active { + background: rgba(255, 45, 85, 0.12); + border-color: #ff2d55; + color: #ff5c7c; +} + +[data-theme="dark"] .claw-tool-search-input { + background: #181a26; + border-color: #252838; + color: #e4e6ef; +} + +[data-theme="dark"] .claw-tool-search-input:focus { + background: #141620; +} + +[data-theme="dark"] .claw-tool-group-label { + background: #181a26; + border-bottom-color: #1e2130; +} + +[data-theme="dark"] .claw-retry-btn { + background: #181a26; + border-color: #252838; + color: #a0a4b8; +} + +[data-theme="dark"] .claw-retry-btn:hover { + border-color: #ff2d55; + color: #ff5c7c; +} + +[data-theme="dark"] .claw-new-chat-btn { + background: #ff2d55; +} + +[data-theme="dark"] .claw-new-chat-btn:hover { + background: #ff5c7c; +} + +/* Wizard dark */ +[data-theme="dark"] .claw-wizard-card { + background: linear-gradient(135deg, #181a26 0%, #141620 100%); + border-color: #252838; +} + +[data-theme="dark"] .claw-wizard-opt { + background: #141620; + border-color: #252838; + color: #a0a4b8; +} + +[data-theme="dark"] .claw-wizard-opt:hover { + border-color: #6366f1; + color: #818cf8; + background: rgba(99, 102, 241, 0.06); +} + +[data-theme="dark"] .claw-wizard-opt.selected { + border-color: #6366f1; + background: rgba(99, 102, 241, 0.1); + color: #818cf8; + box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1); +} + +[data-theme="dark"] .claw-wizard-custom-input { + background: #0c0e14; + border-color: #252838; + color: #e4e6ef; +} + +[data-theme="dark"] .claw-wizard-custom-input:focus { + border-color: #6366f1; + box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1); +} + +[data-theme="dark"] .claw-wizard-custom-btn { + background: #6366f1; +} + +[data-theme="dark"] .claw-wizard-custom-btn:hover { + background: #818cf8; +} + +[data-theme="dark"] .claw-wizard-warn { + background: rgba(251, 191, 36, 0.08); + border-color: rgba(251, 191, 36, 0.2); + color: #fbbf24; +} + +[data-theme="dark"] .claw-confirm-card { + background: linear-gradient(135deg, rgba(52, 211, 153, 0.06) 0%, #141620 100%); + border-color: rgba(52, 211, 153, 0.2); +} + +[data-theme="dark"] .claw-confirm-item { + background: #181a26; + border-color: rgba(52, 211, 153, 0.15); +} + +[data-theme="dark"] .claw-config-summary { + background: #141620; + border-bottom-color: #252838; +} + +[data-theme="dark"] .claw-config-chip { + background: rgba(99, 102, 241, 0.1); + border-color: rgba(99, 102, 241, 0.2); + color: #818cf8; +} + +[data-theme="dark"] .claw-config-edit-btn { + border-color: #252838; + color: #6c7190; +} + +[data-theme="dark"] .claw-config-edit-btn:hover { + border-color: #818cf8; + color: #818cf8; +} + +[data-theme="dark"] .claw-progress-bar { + background: #141620; + border-top-color: #252838; +} + +[data-theme="dark"] .claw-progress-track { + background: #252838; +} + +[data-theme="dark"] .claw-progress-fill { + background: linear-gradient(90deg, #6366f1 0%, #34d399 100%); +} + +/* ═══════════════════════════════════════════════════════ + Dashboard — Comprehensive Dark Mode Overrides + ═══════════════════════════════════════════════════════ */ + +/* Cards */ +[data-theme="dark"] .card, +[data-theme="dark"] .card.compact { + background: #141620; + border-color: #252838; + color: #e4e6ef; +} + +[data-theme="dark"] .card-header { + border-bottom-color: #1e2130; +} + +[data-theme="dark"] .card-header h2 { + color: #e4e6ef; +} + +[data-theme="dark"] .card-subtitle, +[data-theme="dark"] .chart-subtitle-text { + color: #6c7190; +} + +/* Filter bars & dropdowns */ +[data-theme="dark"] .filter-bar, +[data-theme="dark"] .filter-bar.secondary, +[data-theme="dark"] .report-filter-bar, +[data-theme="dark"] .analysis-filter-bar { + background: #141620; + border-color: #252838; +} + +[data-theme="dark"] .filter-label { + color: #a0a4b8; +} + +[data-theme="dark"] .filter-select, +[data-theme="dark"] .search-combo-input { + background: #181a26; + border-color: #252838; + color: #e4e6ef; +} + +[data-theme="dark"] .filter-select:hover { + border-color: #6366f1; +} + +[data-theme="dark"] .dropdown-menu, +[data-theme="dark"] .search-combo-menu, +[data-theme="dark"] .search-dropdown-menu { + background: #181a26; + border-color: #252838; + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5); +} + +[data-theme="dark"] .dd-item { + color: #a0a4b8; +} + +[data-theme="dark"] .dd-item:hover { + background: #1e2130; + color: #e4e6ef; +} + +[data-theme="dark"] .dd-item.active { + background: rgba(99, 102, 241, 0.1); + color: #818cf8; +} + +[data-theme="dark"] .dd-sub { + color: #4a4e65; +} + +[data-theme="dark"] .dd-group-label { + color: #6c7190; + border-bottom-color: #252838; +} + +[data-theme="dark"] .search-dropdown-input { + background: #0c0e14; + border-color: #252838; + color: #e4e6ef; +} + +/* Metric cards */ +[data-theme="dark"] .metric-card { + background: #181a26 !important; + border-color: #252838; +} + +[data-theme="dark"] .metric-card.blue { + background: rgba(99, 102, 241, 0.08) !important; + border-color: rgba(99, 102, 241, 0.2); +} + +[data-theme="dark"] .metric-card.yellow { + background: rgba(251, 191, 36, 0.08) !important; + border-color: rgba(251, 191, 36, 0.2); +} + +[data-theme="dark"] .metric-card.red, +[data-theme="dark"] .metric-card.red-strong { + background: rgba(248, 113, 113, 0.08) !important; + border-color: rgba(248, 113, 113, 0.2); +} + +[data-theme="dark"] .metric-card.green-card { + background: rgba(52, 211, 153, 0.08) !important; + border-color: rgba(52, 211, 153, 0.2); +} + +[data-theme="dark"] .metric-card.orange { + background: rgba(249, 115, 22, 0.08) !important; + border-color: rgba(249, 115, 22, 0.2); +} + +[data-theme="dark"] .metric-label { + color: #a0a4b8; +} + +[data-theme="dark"] .metric-value { + color: #e4e6ef; +} + +[data-theme="dark"] .metric-value.muted { + color: #4a4e65; +} + +[data-theme="dark"] .metric-sub-was { + color: #6c7190; +} + +[data-theme="dark"] .avg-times-badge { + background: rgba(99, 102, 241, 0.15); + color: #818cf8; +} + +[data-theme="dark"] .e2e-metric-delta { + border-color: #252838; +} + +[data-theme="dark"] .gap-metric-card { + background: #181a26 !important; +} + +/* Architecture section */ +[data-theme="dark"] .arch-section { + background: #141620; + border-color: #252838; +} + +[data-theme="dark"] .arch-header:hover { + background: #1e2130; +} + +[data-theme="dark"] .arch-chip { + background: #181a26; + border-color: #252838; + color: #a0a4b8; +} + +[data-theme="dark"] .arch-summary-table { + border-color: #252838; +} + +[data-theme="dark"] .arch-sum-cell { + border-color: #252838; +} + +[data-theme="dark"] .arch-sum-label { + color: #6c7190; +} + +[data-theme="dark"] .arch-sum-value { + color: #e4e6ef; +} + +[data-theme="dark"] .arch-feature-tag { + background: #181a26; + border-color: #252838; + color: #a0a4b8; +} + +[data-theme="dark"] .arch-release { + color: #4a4e65; +} + +[data-theme="dark"] .arch-block { + border-color: #252838; +} + +[data-theme="dark"] .arch-block-title { + color: #e4e6ef; +} + +[data-theme="dark"] .arch-block-sub { + color: #6c7190; +} + +/* Run date bar */ +[data-theme="dark"] .run-date-bar { + background: #141620; + border-color: #252838; + color: #a0a4b8; +} + +[data-theme="dark"] .rdb-text { + color: #a0a4b8; +} + +[data-theme="dark"] .rdb-link { + color: #818cf8; +} + +[data-theme="dark"] .rdb-run-badge { + background: #181a26; + border-color: #252838; + color: #a0a4b8; +} + +/* Chart cards */ +[data-theme="dark"] .chart-card { + background: #141620; +} + +[data-theme="dark"] .chart-title-text { + color: #e4e6ef; +} + +[data-theme="dark"] .chart-tool-btn { + color: #6c7190; + border-color: #252838; +} + +[data-theme="dark"] .chart-tool-btn:hover { + background: #1e2130; + color: #e4e6ef; +} + +[data-theme="dark"] .chart-legend-box, +[data-theme="dark"] .kernel-legend-grid { + background: #181a26 !important; + border-color: #252838 !important; + color: #a0a4b8 !important; +} + +[data-theme="dark"] .legend-title { + color: #6c7190 !important; +} + +[data-theme="dark"] .chart-building { + color: #6c7190 !important; +} + +[data-theme="dark"] .building-title { + color: #e4e6ef !important; +} + +[data-theme="dark"] .building-sub { + color: #6c7190 !important; +} + +[data-theme="dark"] .hw-legend { + background: #181a26 !important; + border-color: #252838 !important; + color: #a0a4b8 !important; +} + +[data-theme="dark"] .trace-bar-bg { + background: #252838 !important; + border-color: #3f4458 !important; +} + +/* Warning banner */ +[data-theme="dark"] .warning-banner { + background: rgba(251, 191, 36, 0.08); + border-color: rgba(251, 191, 36, 0.2); + color: #fbbf24; +} + +/* Action bar */ +[data-theme="dark"] .action-bar { + background: #141620; + border-color: #252838; +} + +[data-theme="dark"] .action-bar .btn { + background: #181a26; + border-color: #252838; + color: #a0a4b8; +} + +[data-theme="dark"] .action-bar .btn:hover { + background: #1e2130; + color: #e4e6ef; +} + +[data-theme="dark"] .action-bar .btn.btn-primary { + background: #ff2d55; + border-color: #ff2d55; + color: white; +} + +/* GPU chips */ +[data-theme="dark"] .gpu-chip { + background: #181a26; + border-color: #252838; + color: #a0a4b8; +} + +[data-theme="dark"] .gpu-chip.amd { + border-color: rgba(255, 45, 85, 0.3); + color: #ff5c7c; +} + +[data-theme="dark"] .gpu-chip.nvidia { + border-color: rgba(52, 211, 153, 0.3); + color: #34d399; +} + +[data-theme="dark"] .gpu-chip-close:hover { + color: #f87171; +} + +/* Precision chip */ +[data-theme="dark"] .precision-chip { + background: #181a26; + color: #a0a4b8; +} + +/* ─── Analysis page dark ─── */ + +[data-theme="dark"] .task-setup-grid { + background: #141620; +} + +[data-theme="dark"] .phase-tab { + background: #181a26; + border-color: #252838; + color: #6c7190; +} + +[data-theme="dark"] .phase-tab.active { + background: rgba(99, 102, 241, 0.1); + color: #818cf8; + border-color: #6366f1; +} + +[data-theme="dark"] .step-analysis, +[data-theme="dark"] .ana-empty-state { + color: #6c7190; +} + +/* Insights */ +[data-theme="dark"] .insights-list { + background: #141620; +} + +[data-theme="dark"] .insight-item, +[data-theme="dark"] .insight-card { + background: #181a26 !important; + border-color: #252838 !important; + color: #e4e6ef; +} + +[data-theme="dark"] .insight-text, +[data-theme="dark"] .insight-desc { + color: #a0a4b8; +} + +[data-theme="dark"] .insight-severity.high, +[data-theme="dark"] .insight-badge-high { + background: rgba(248, 113, 113, 0.12); + color: #f87171; +} + +[data-theme="dark"] .insight-severity.med, +[data-theme="dark"] .insight-badge-med { + background: rgba(251, 191, 36, 0.12); + color: #fbbf24; +} + +[data-theme="dark"] .insight-severity.tip, +[data-theme="dark"] .insight-badge-tip { + background: rgba(99, 102, 241, 0.12); + color: #818cf8; +} + +[data-theme="dark"] .insight-source { + background: #181a26; + border-color: #252838; + color: #6c7190; +} + +[data-theme="dark"] .collapsible-header { + color: #e4e6ef; +} + +/* ─── Optimization page dark ─── */ + +[data-theme="dark"] .bridge-selector { + color: #a0a4b8; +} + +[data-theme="dark"] .bridge-plan-empty, +[data-theme="dark"] .empty-state { + color: #6c7190; +} + +[data-theme="dark"] .bridge-table { + border-color: #252838; +} + +[data-theme="dark"] .bt-header { + background: #181a26; + border-color: #252838; + color: #6c7190; +} + +[data-theme="dark"] .bt-row { + background: #141620 !important; + border-color: #252838 !important; + color: #e4e6ef !important; +} + +[data-theme="dark"] .bt-row:hover { + background: #1e2130 !important; +} + +[data-theme="dark"] .bt-row.selected { + background: rgba(99, 102, 241, 0.06) !important; +} + +[data-theme="dark"] .bt-row-group { + border-color: #252838 !important; +} + +[data-theme="dark"] .bt-kernels { + background: #0c0e14 !important; + border-color: #252838 !important; +} + +[data-theme="dark"] .kernel-row { + background: #181a26 !important; + color: #a0a4b8 !important; + border-color: #1e2130 !important; +} + +[data-theme="dark"] .kernel-row.selected { + background: rgba(99, 102, 241, 0.06) !important; +} + +[data-theme="dark"] .kernel-name { + color: #e4e6ef; +} + +[data-theme="dark"] .kernel-name.muted { + color: #6c7190; +} + +[data-theme="dark"] .kernel-metrics { + color: #6c7190; +} + +[data-theme="dark"] .bt-bw-info { + color: #4a4e65; +} + +[data-theme="dark"] .save-value { + color: #34d399; +} + +[data-theme="dark"] .total-saving { + background: rgba(52, 211, 153, 0.06) !important; + border-color: rgba(52, 211, 153, 0.2) !important; + color: #34d399 !important; +} + +[data-theme="dark"] .ms-status.complete { + background: rgba(52, 211, 153, 0.1) !important; + border-color: rgba(52, 211, 153, 0.3) !important; + color: #34d399 !important; +} + +[data-theme="dark"] .ms-status.in-progress { + background: rgba(99, 102, 241, 0.1) !important; + border-color: rgba(99, 102, 241, 0.3) !important; + color: #818cf8 !important; +} + +[data-theme="dark"] .ms-status.paused { + background: rgba(251, 191, 36, 0.1) !important; + border-color: rgba(251, 191, 36, 0.3) !important; + color: #fbbf24 !important; +} + +[data-theme="dark"] .btn.btn-primary.btn-full { + background: #ff2d55; + color: white; +} + +/* GEAK strategies */ +[data-theme="dark"] .geak-agent-card { + background: #141620; +} + +[data-theme="dark"] .geak-expand-item { + border-color: #252838 !important; + background: #181a26 !important; +} + +[data-theme="dark"] .geak-expand-item.sent .geak-expand-header { + background: #181a26 !important; +} + +[data-theme="dark"] .geak-expand-header { + color: #e4e6ef !important; +} + +[data-theme="dark"] .geak-expand-body { + background: #141620 !important; + border-color: #1e2130 !important; +} + +[data-theme="dark"] .geak-item-name { + color: #e4e6ef; +} + +[data-theme="dark"] .geak-item-sub { + color: #6c7190; +} + +[data-theme="dark"] .geak-status-label.validated { + color: #34d399; +} + +[data-theme="dark"] .geak-status-label.processing { + color: #818cf8; +} + +[data-theme="dark"] .geak-status-label.sent { + color: #fbbf24; +} + +[data-theme="dark"] .pipe-step-info { + color: #a0a4b8; +} + +[data-theme="dark"] .detail-chip { + background: #181a26; + border-color: #252838; + color: #a0a4b8; +} + +[data-theme="dark"] .detail-chip.green-chip { + background: rgba(52, 211, 153, 0.1); + color: #34d399; +} + +[data-theme="dark"] .detail-chip.blue-chip { + background: rgba(99, 102, 241, 0.1); + color: #818cf8; +} + +[data-theme="dark"] .detail-chip.e2e-chip { + background: rgba(52, 211, 153, 0.1); + color: #34d399; +} + +[data-theme="dark"] .inline-strategy-section { + border-color: #252838; +} + +[data-theme="dark"] .inline-strategy-toggle-btn { + color: #a0a4b8; +} + +[data-theme="dark"] .inline-strategy-body { + background: #0c0e14; + border-color: #1e2130; + color: #a0a4b8; +} + +[data-theme="dark"] .strategy-card { + background: #181a26 !important; + border-color: #252838 !important; +} + +[data-theme="dark"] .strategy-card.expanded { + background: rgba(99, 102, 241, 0.08) !important; + border-color: #6366f1 !important; +} + +[data-theme="dark"] .strategy-params { + background: #0c0e14 !important; + border-color: #252838 !important; +} + +[data-theme="dark"] .strategy-name { + color: #e4e6ef !important; +} + +[data-theme="dark"] .strategy-desc { + color: #a0a4b8 !important; +} + +[data-theme="dark"] .strat-input { + background: #181a26 !important; + border-color: #252838 !important; + color: #e4e6ef !important; +} + +[data-theme="dark"] .strategy-est-badge { + background: rgba(52, 211, 153, 0.1) !important; + color: #34d399 !important; +} + +/* E2E result blocks */ +[data-theme="dark"] .e2e-result-block { + border-color: #252838 !important; +} + +[data-theme="dark"] .e2e-result-header { + background: #181a26 !important; + border-color: #252838 !important; +} + +[data-theme="dark"] .e2e-result-body { + background: #141620 !important; +} + +[data-theme="dark"] .e2e-result-title { + color: #e4e6ef !important; +} + +[data-theme="dark"] .e2e-result-line { + color: #a0a4b8 !important; +} + +[data-theme="dark"] .e2e-result-line.muted { + color: #6c7190 !important; +} + +/* Kernel detail */ +[data-theme="dark"] .detail-section-label { + color: #a0a4b8; +} + +[data-theme="dark"] .detail-section-label.green-label { + color: #34d399; +} + +/* GEAK settings modal */ +[data-theme="dark"] .geak-settings-overlay { + background: rgba(0, 0, 0, 0.7); +} + +[data-theme="dark"] .geak-settings-modal { + background: #141620; + border-color: #252838; +} + +[data-theme="dark"] .geak-settings-header { + border-bottom-color: #252838; + color: #e4e6ef; +} + +[data-theme="dark"] .geak-setting-input { + background: #181a26; + border-color: #252838; + color: #e4e6ef; +} + +[data-theme="dark"] .geak-setting-label { + color: #a0a4b8; +} + +[data-theme="dark"] .geak-setting-hint { + color: #4a4e65; +} + +[data-theme="dark"] .geak-model-trigger { + background: #181a26; + border-color: #252838; + color: #e4e6ef; +} + +[data-theme="dark"] .geak-model-collapse { + background: #181a26; + border-color: #252838; +} + +[data-theme="dark"] .geak-model-option { + color: #a0a4b8; +} + +[data-theme="dark"] .geak-model-option:hover { + background: #1e2130; +} + +[data-theme="dark"] .geak-settings-footer { + border-top-color: #252838; +} + +[data-theme="dark"] .btn-secondary { + background: #181a26; + border-color: #252838; + color: #a0a4b8; +} + +/* E2E notes */ +[data-theme="dark"] .e2e-note, +[data-theme="dark"] .e2e-integration-notes .e2e-note { + background: #181a26; + border-color: #252838; + color: #a0a4b8; +} + +[data-theme="dark"] .e2e-note.yellow { + background: rgba(251, 191, 36, 0.06); + border-color: rgba(251, 191, 36, 0.15); +} + +[data-theme="dark"] .e2e-note.red { + background: rgba(248, 113, 113, 0.06); + border-color: rgba(248, 113, 113, 0.15); +} + +/* ─── Report page dark ─── */ + +[data-theme="dark"] .report-model { + border-color: #252838; +} + +[data-theme="dark"] .report-model-header { + background: #181a26 !important; + border-color: #252838 !important; + color: #e4e6ef !important; +} + +[data-theme="dark"] .report-model-header:hover { + background: #1e2130 !important; +} + +[data-theme="dark"] .report-model-body { + background: #141620 !important; + border-color: #252838 !important; +} + +[data-theme="dark"] .report-model-config, +[data-theme="dark"] .report-model-summary { + color: #6c7190; +} + +[data-theme="dark"] .report-subsection { + border-color: #1e2130; +} + +[data-theme="dark"] .report-subsection-header { + background: #181a26 !important; + border-color: #252838 !important; + color: #e4e6ef !important; +} + +[data-theme="dark"] .report-subsection-header:hover { + background: #1e2130 !important; + border-color: #6366f1 !important; +} + +[data-theme="dark"] .report-sub-status { + color: #6c7190; +} + +[data-theme="dark"] .report-subsection-body { + background: #0c0e14 !important; + border-color: #1e2130 !important; +} + +[data-theme="dark"] .rko-card { + background: #181a26 !important; + border-color: #252838 !important; +} + +[data-theme="dark"] .rko-header { + background: #181a26 !important; + border-color: #252838 !important; + color: #e4e6ef !important; +} + +[data-theme="dark"] .rko-name { + color: #e4e6ef; +} + +[data-theme="dark"] .rko-tag { + background: #141620; + color: #6c7190; +} + +[data-theme="dark"] .rko-tag.hot { + background: rgba(248, 113, 113, 0.1); + color: #f87171; +} + +[data-theme="dark"] .rko-tag.med { + background: rgba(251, 191, 36, 0.1); + color: #fbbf24; +} + +[data-theme="dark"] .rko-body { + color: #a0a4b8; +} + +[data-theme="dark"] .rko-label { + color: #6c7190; +} + +[data-theme="dark"] .rko-val { + color: #a0a4b8; +} + +[data-theme="dark"] .rko-roofline { + background: #0c0e14; + border-color: #1e2130; + color: #6c7190; +} + +[data-theme="dark"] .rko-rf-label { + color: #4a4e65; +} + +[data-theme="dark"] .report-detail-row { + background: #181a26 !important; + border-color: #252838 !important; + color: #a0a4b8 !important; +} + +[data-theme="dark"] .report-detail-row.green { + background: rgba(52, 211, 153, 0.06) !important; + border-color: rgba(52, 211, 153, 0.15) !important; +} + +[data-theme="dark"] .report-detail-row.yellow { + background: rgba(251, 191, 36, 0.06) !important; + border-color: rgba(251, 191, 36, 0.15) !important; +} + +[data-theme="dark"] .report-detail-row.red { + background: rgba(248, 113, 113, 0.06) !important; + border-color: rgba(248, 113, 113, 0.15) !important; +} + +[data-theme="dark"] .rpt-gap-label { + color: #a0a4b8; +} + +[data-theme="dark"] .gap-bar { + background: #252838; +} + +[data-theme="dark"] .report-kernel-card { + background: #181a26 !important; + border-color: #252838 !important; + color: #a0a4b8 !important; +} + +[data-theme="dark"] .report-kernel-card.hot { + background: rgba(248, 113, 113, 0.06) !important; + border-color: rgba(248, 113, 113, 0.3) !important; +} + +[data-theme="dark"] .report-kernel-card.warm { + background: rgba(251, 191, 36, 0.06) !important; + border-color: rgba(251, 191, 36, 0.3) !important; +} + +[data-theme="dark"] .rpt-e2e-metrics .metric-card { + background: #181a26 !important; +} + +[data-theme="dark"] .report-batch-actions { + color: #a0a4b8; +} + +[data-theme="dark"] .rpt-model-cb + .rpt-cb-mark { + border-color: #4a4e65; +} + +/* Status table */ +[data-theme="dark"] .status-table { + border-color: #252838 !important; +} + +[data-theme="dark"] .status-header { + background: #181a26 !important; + border-color: #252838 !important; + color: #6c7190 !important; +} + +[data-theme="dark"] .status-header .st-col { + color: #6c7190 !important; +} + +[data-theme="dark"] .status-empty { + color: #4a4e65 !important; +} + +/* Expose */ +[data-theme="dark"] .expose-history-list { + background: #181a26; + border-color: #252838; +} + +[data-theme="dark"] .expose-empty { + color: #4a4e65; +} + +[data-theme="dark"] .expose-history-label { + color: #6c7190; +} + +[data-theme="dark"] .export-hint { + color: #4a4e65; +} + +/* Buttons (general) */ +[data-theme="dark"] .btn { + background: #181a26; + border-color: #252838; + color: #a0a4b8; +} + +[data-theme="dark"] .btn:hover { + background: #1e2130; +} + +[data-theme="dark"] .btn.btn-primary { + background: #ff2d55; + border-color: #ff2d55; + color: white; +} + +[data-theme="dark"] .btn.btn-analysis { + background: rgba(99, 102, 241, 0.1); + border-color: rgba(99, 102, 241, 0.2); + color: #818cf8; +} + +[data-theme="dark"] .btn.btn-optimization { + background: rgba(251, 191, 36, 0.1); + border-color: rgba(251, 191, 36, 0.2); + color: #fbbf24; +} + +/* Priority badges */ +[data-theme="dark"] .pri-badge { + color: white; +} + +/* Info icon */ +[data-theme="dark"] .info-icon { + color: #4a4e65; +} + +/* Legacy dashboard wrapper (overall bg) */ +[data-theme="dark"] .legacy-dashboard-wrapper { + color: #e4e6ef; +} + +/* Canvas backgrounds for charts */ +[data-theme="dark"] .chart-container { + border-radius: 8px; +} + +/* Strong overrides for lingering light backgrounds */ +[data-theme="dark"] .metric-card.blue { + background: rgba(99, 102, 241, 0.1) !important; + border-color: rgba(99, 102, 241, 0.25) !important; +} + +[data-theme="dark"] .metric-card.yellow { + background: rgba(251, 191, 36, 0.1) !important; + border-color: rgba(251, 191, 36, 0.25) !important; +} + +[data-theme="dark"] .metric-card.red, +[data-theme="dark"] .metric-card.red-strong { + background: rgba(248, 113, 113, 0.1) !important; + border-color: rgba(248, 113, 113, 0.25) !important; +} + +[data-theme="dark"] .metric-card.orange { + background: rgba(249, 115, 22, 0.1) !important; + border-color: rgba(249, 115, 22, 0.25) !important; +} + +[data-theme="dark"] .metric-card.green-card { + background: rgba(52, 211, 153, 0.1) !important; + border-color: rgba(52, 211, 153, 0.25) !important; +} + +[data-theme="dark"] .gap-metric-card { + background: #181a26 !important; + border-color: #252838 !important; +} + +[data-theme="dark"] .insight-row { + background: #181a26 !important; + border-color: #252838 !important; +} + +[data-theme="dark"] .insight-row.high { + background: rgba(248, 113, 113, 0.08) !important; + border-color: rgba(248, 113, 113, 0.2) !important; +} + +[data-theme="dark"] .insight-row.med { + background: rgba(251, 191, 36, 0.08) !important; + border-color: rgba(251, 191, 36, 0.2) !important; +} + +[data-theme="dark"] .insight-row.tip { + background: rgba(99, 102, 241, 0.08) !important; + border-color: rgba(99, 102, 241, 0.2) !important; +} + +[data-theme="dark"] .insight-row.na { + background: #181a26 !important; + border-color: #252838 !important; +} + +[data-theme="dark"] .insight-text, +[data-theme="dark"] .insight-subtext { + color: #a0a4b8 !important; +} + +[data-theme="dark"] .insight-action { + background: #181a26 !important; + border-color: #252838 !important; + color: #a0a4b8 !important; +} + +[data-theme="dark"] .insight-source.high { + background: rgba(248, 113, 113, 0.1) !important; + color: #f87171 !important; + border-color: rgba(248, 113, 113, 0.24) !important; +} + +[data-theme="dark"] .insight-source.med { + background: rgba(251, 191, 36, 0.1) !important; + color: #fbbf24 !important; + border-color: rgba(251, 191, 36, 0.24) !important; +} + +[data-theme="dark"] .insight-source.tip { + background: rgba(99, 102, 241, 0.1) !important; + color: #818cf8 !important; + border-color: rgba(99, 102, 241, 0.24) !important; +} + +[data-theme="dark"] .insight-source.na { + background: #181a26 !important; + color: #6c7190 !important; + border-color: #252838 !important; +} + +[data-theme="dark"] .rpt-gap-improvement { + background: rgba(52, 211, 153, 0.08) !important; + border-color: rgba(52, 211, 153, 0.2) !important; +} + +/* Scrollbars */ +[data-theme="dark"] ::-webkit-scrollbar-track { + background: #0c0e14; +} + +[data-theme="dark"] ::-webkit-scrollbar-thumb { + background: #252838; + border-radius: 4px; +} + +[data-theme="dark"] ::-webkit-scrollbar-thumb:hover { + background: #353a52; +} + +/* ─── Report page: Expose / Filter / Status dark overrides ─── */ + +[data-theme="dark"] .filter-select { + background: #181a26 !important; + border-color: #252838 !important; + color: #e4e6ef !important; +} + +[data-theme="dark"] .filter-select:hover { + border-color: #3b82f6 !important; +} + +[data-theme="dark"] .dd-value { + color: #e4e6ef !important; +} + +[data-theme="dark"] .dd-value.dd-placeholder, +[data-theme="dark"] .dd-value.nv-none { + color: #6c7190 !important; +} + +[data-theme="dark"] .search-combo-input { + color: #e4e6ef !important; +} + +[data-theme="dark"] .search-combo-input::placeholder { + color: #4a4e65 !important; +} + +[data-theme="dark"] .caret { + color: #4a4e65 !important; +} + +[data-theme="dark"] .dropdown-menu { + background: #141620 !important; + border-color: #252838 !important; + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5) !important; +} + +[data-theme="dark"] .dd-item { + color: #a0a4b8 !important; +} + +[data-theme="dark"] .dd-item:hover { + background: #1e2130 !important; +} + +[data-theme="dark"] .dd-item.active { + background: rgba(59, 130, 246, 0.1) !important; + color: #60a5fa !important; +} + +[data-theme="dark"] .dd-group-label { + color: #4a4e65 !important; + border-bottom-color: #1e2130 !important; +} + +[data-theme="dark"] .expose-item { + background: #181a26 !important; + border-color: #252838 !important; +} + +[data-theme="dark"] .expose-item:hover { + background: #1e2130 !important; +} + +[data-theme="dark"] .expose-item .ei-model { + color: #e4e6ef !important; +} + +[data-theme="dark"] .expose-item .ei-config { + color: #6c7190 !important; +} + +[data-theme="dark"] .expose-item .ei-status.found { + background: rgba(52, 211, 153, 0.1) !important; + border-color: rgba(52, 211, 153, 0.25) !important; + color: #34d399 !important; +} + +[data-theme="dark"] .expose-item .ei-status.not-found { + background: rgba(248, 113, 113, 0.1) !important; + border-color: rgba(248, 113, 113, 0.25) !important; + color: #f87171 !important; +} + +[data-theme="dark"] .expose-item .ei-status.ref { + background: rgba(59, 130, 246, 0.1) !important; + border-color: rgba(59, 130, 246, 0.25) !important; + color: #60a5fa !important; +} + +[data-theme="dark"] .status-row { + border-bottom-color: #252838 !important; +} + +[data-theme="dark"] .status-row:hover { + background: #1e2130 !important; +} + +[data-theme="dark"] .status-row.alt { + background: #181a26 !important; +} + +[data-theme="dark"] .status-row.alt:hover { + background: #1e2130 !important; +} + +[data-theme="dark"] .status-row > .st-phase:nth-of-type(1) .data-chip { + background: rgba(59, 130, 246, 0.1) !important; + border-color: rgba(59, 130, 246, 0.25) !important; + color: #60a5fa !important; +} + +[data-theme="dark"] .status-row > .st-phase:nth-of-type(2) .data-chip { + background: rgba(52, 211, 153, 0.1) !important; + border-color: rgba(52, 211, 153, 0.25) !important; + color: #34d399 !important; +} + +[data-theme="dark"] .status-row > .st-phase:nth-of-type(3) .data-chip { + background: rgba(139, 92, 246, 0.1) !important; + border-color: rgba(139, 92, 246, 0.25) !important; + color: #a78bfa !important; +} + +[data-theme="dark"] .data-chip { + background: #181a26 !important; + border-color: #252838 !important; + color: #a0a4b8 !important; +} + +[data-theme="dark"] .data-posted-btn, +[data-theme="dark"] .btn.data-posted-btn { + background: #181a26 !important; + border-color: #252838 !important; + color: #a0a4b8 !important; +} + +[data-theme="dark"] .expose-count { + color: #6c7190 !important; +} + +[data-theme="dark"] .precision-chip { + background: #181a26 !important; + border-color: #252838 !important; + color: #a0a4b8 !important; +} + +[data-theme="dark"] .precision-chip.active { + background: rgba(99, 102, 241, 0.15) !important; + border-color: rgba(99, 102, 241, 0.3) !important; + color: #818cf8 !important; +} + +[data-theme="dark"] .st-col { + color: #a0a4b8 !important; +} + +[data-theme="dark"] .st-model-name { + color: #e4e6ef !important; +} + +[data-theme="dark"] .st-sub { + color: #6c7190 !important; +} + +[data-theme="dark"] .gap-vs-baseline { + color: #a0a4b8 !important; +} + +[data-theme="dark"] .e2e-toggle { + background: #252838 !important; +} + +[data-theme="dark"] .report-subsection-header { + background: #181a26 !important; + border-color: #252838 !important; + color: #e4e6ef !important; +} + +[data-theme="dark"] .report-subsection-header:hover { + background: #1e2130 !important; + border-color: #3b82f6 !important; +} + +[data-theme="dark"] .report-subsection.expanded > .report-subsection-header { + background: rgba(59, 130, 246, 0.1) !important; + border-color: rgba(59, 130, 246, 0.3) !important; + color: #60a5fa !important; +} + +[data-theme="dark"] .report-sub-title { + color: #e4e6ef !important; +} + +[data-theme="dark"] .report-toggle { + color: #6c7190 !important; +} + +[data-theme="dark"] .report-sub-toggle { + color: #6c7190 !important; +} + +[data-theme="dark"] .report-model-config { + color: #6c7190 !important; +} + +[data-theme="dark"] .report-model-name { + color: #e4e6ef !important; +} + +/* ══════════════════════════════════════════════════ + Additional Dark Theme overrides with !important + for maximum specificity guarantee. + ══════════════════════════════════════════════════ */ + +/* --- Global shadow --- */ +[data-theme="dark"] .card { + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.45) !important; +} + +/* --- Sidebar legacy --- */ +[data-theme="dark"] .sidebar-item:hover { + background: rgba(255, 45, 85, 0.08) !important; +} +[data-theme="dark"] .sidebar-item.active { + background: rgba(255, 45, 85, 0.1) !important; +} + +/* --- Filter chips --- */ +[data-theme="dark"] .filter-chip.amd { + background: rgba(255, 45, 85, 0.1) !important; + border-color: rgba(255, 45, 85, 0.3) !important; + color: #ff5c7c !important; +} +[data-theme="dark"] .filter-chip.nvidia { + background: rgba(52, 211, 153, 0.1) !important; + border-color: rgba(52, 211, 153, 0.3) !important; + color: #34d399 !important; +} +[data-theme="dark"] .filter-add { + background: #181a26 !important; + border-color: #252838 !important; + color: #6c7190 !important; +} + +/* --- Dropdowns --- */ +[data-theme="dark"] .dropdown-wrap.open .filter-select { + box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.25) !important; +} +[data-theme="dark"] .search-dropdown-input { + background: #181a26 !important; + color: #e4e6ef !important; + border-color: #252838 !important; +} +[data-theme="dark"] .search-dropdown-input:focus { + background: #0c0e14 !important; + border-bottom-color: #6366f1 !important; +} + +/* --- Architecture diagram --- */ +[data-theme="dark"] .arch-block.dashed { + background: rgba(99, 102, 241, 0.12) !important; + border-color: rgba(129, 140, 248, 0.5) !important; +} + +/* --- Chart card --- */ +[data-theme="dark"] .chart-card.maximized .chart-zoom-btn { + background: #1e2130 !important; + border-color: #6366f1 !important; + color: #818cf8 !important; +} + +/* --- Buttons: semantic tints --- */ +[data-theme="dark"] .btn-results { + background: rgba(139, 92, 246, 0.12) !important; + border-color: rgba(139, 92, 246, 0.35) !important; + color: #c4b5fd !important; +} +[data-theme="dark"] .btn-export-model { + background: rgba(99, 102, 241, 0.12) !important; + border-color: #6366f1 !important; + color: #818cf8 !important; +} +[data-theme="dark"] .btn-export-csv { + background: rgba(52, 211, 153, 0.1) !important; + border-color: rgba(52, 211, 153, 0.3) !important; + color: #34d399 !important; +} +[data-theme="dark"] .btn-share { + background: rgba(139, 92, 246, 0.1) !important; + border-color: rgba(139, 92, 246, 0.3) !important; + color: #c4b5fd !important; +} + +/* --- Flow hints / tips --- */ +[data-theme="dark"] .flow-hint, +[data-theme="dark"] .flow-tip { + background: rgba(251, 191, 36, 0.08) !important; + border-color: rgba(251, 191, 36, 0.25) !important; + color: #fbbf24 !important; +} +[data-theme="dark"] .plan-summary { + background: rgba(99, 102, 241, 0.1) !important; + border-color: rgba(99, 102, 241, 0.35) !important; + color: #a0a4b8 !important; +} +[data-theme="dark"] .bridge-select { + border-color: #6366f1 !important; +} + +/* --- GEAK expand states --- */ +[data-theme="dark"] .geak-expand-item.validated .geak-expand-header { + background: rgba(52, 211, 153, 0.08) !important; +} +[data-theme="dark"] .geak-expand-item.processing .geak-expand-header { + background: rgba(99, 102, 241, 0.08) !important; +} +[data-theme="dark"] .strategy-card.expanded { + background: rgba(99, 102, 241, 0.12) !important; + border-color: #6366f1 !important; +} + +/* --- E2E result badges --- */ +[data-theme="dark"] .e2e-result-badge.done { + background: rgba(52, 211, 153, 0.1) !important; + border-color: rgba(52, 211, 153, 0.3) !important; + color: #34d399 !important; +} +[data-theme="dark"] .e2e-result-badge.wip { + background: rgba(99, 102, 241, 0.1) !important; + border-color: rgba(99, 102, 241, 0.3) !important; + color: #818cf8 !important; +} +[data-theme="dark"] .e2e-result-badge.issue { + background: rgba(251, 191, 36, 0.1) !important; + border-color: rgba(251, 191, 36, 0.3) !important; + color: #fbbf24 !important; +} + +/* --- Execution circles --- */ +[data-theme="dark"] .exec-circle.blue { + background: rgba(99, 102, 241, 0.12) !important; + border-color: #6366f1 !important; + color: #818cf8 !important; +} +[data-theme="dark"] .exec-circle.yellow { + background: rgba(251, 191, 36, 0.1) !important; + border-color: rgba(251, 191, 36, 0.35) !important; + color: #fbbf24 !important; +} +[data-theme="dark"] .exec-circle.green { + background: rgba(52, 211, 153, 0.1) !important; + border-color: rgba(52, 211, 153, 0.35) !important; + color: #34d399 !important; +} +[data-theme="dark"] .exec-connector { + border-left-color: #3f4458 !important; +} + +/* --- Kernel chips / tabs --- */ +[data-theme="dark"] .kernel-chip { + background: #181a26 !important; + border-color: #252838 !important; + color: #a0a4b8 !important; +} +[data-theme="dark"] .kernel-chip.active { + background: rgba(99, 102, 241, 0.12) !important; + border-color: #6366f1 !important; + color: #818cf8 !important; +} +[data-theme="dark"] .detail-tab { + background: #181a26 !important; + border-color: #252838 !important; + color: #a0a4b8 !important; +} +[data-theme="dark"] .detail-tab.active { + background: rgba(99, 102, 241, 0.12) !important; + border-color: #6366f1 !important; + color: #818cf8 !important; +} + +/* --- Report section / model header active --- */ +[data-theme="dark"] .report-section-header { + background: rgba(99, 102, 241, 0.12) !important; + border-color: #6366f1 !important; + color: #818cf8 !important; +} +[data-theme="dark"] .report-model-header.active { + background: rgba(99, 102, 241, 0.12) !important; + border-color: #6366f1 !important; +} + +/* --- Phase chips --- */ +[data-theme="dark"] .phase-chip.done { + background: rgba(52, 211, 153, 0.1) !important; + border-color: rgba(52, 211, 153, 0.3) !important; + color: #34d399 !important; +} +[data-theme="dark"] .phase-chip.wip { + background: rgba(99, 102, 241, 0.1) !important; + border-color: rgba(99, 102, 241, 0.3) !important; + color: #818cf8 !important; +} +[data-theme="dark"] .phase-chip.paused { + background: rgba(251, 191, 36, 0.1) !important; + border-color: rgba(251, 191, 36, 0.3) !important; + color: #fbbf24 !important; +} +[data-theme="dark"] .phase-chip.na { + background: #252838 !important; + border-color: #3f4458 !important; + color: #6c7190 !important; +} + +/* --- E2E pills --- */ +[data-theme="dark"] .e2e-pill.green { + background: rgba(52, 211, 153, 0.15) !important; + border-color: rgba(52, 211, 153, 0.35) !important; + color: #34d399 !important; + box-shadow: none !important; +} +[data-theme="dark"] .e2e-pill.amber { + background: rgba(251, 191, 36, 0.12) !important; + border-color: rgba(251, 191, 36, 0.3) !important; + color: #fbbf24 !important; +} +[data-theme="dark"] .e2e-pill.gray { + background: #252838 !important; + border-color: #3f4458 !important; + color: #6c7190 !important; +} + +/* --- Status model sub --- */ +[data-theme="dark"] .st-model-sub { + color: #6c7190 !important; +} + +/* --- Metric delta badges --- */ +[data-theme="dark"] .metric-delta-badge.up, +[data-theme="dark"] .metric-delta-badge.down-good { + background: rgba(52, 211, 153, 0.12) !important; + color: #34d399 !important; +} +[data-theme="dark"] .metric-delta-badge.down-bad, +[data-theme="dark"] .metric-delta-badge.up-bad { + background: rgba(248, 113, 113, 0.12) !important; + color: #f87171 !important; +} + +/* --- RKO uplift --- */ +[data-theme="dark"] .rko-uplift.green { + background: rgba(52, 211, 153, 0.15) !important; + color: #34d399 !important; +} +[data-theme="dark"] .rko-uplift.blue { + background: rgba(99, 102, 241, 0.15) !important; + color: #818cf8 !important; +} + +/* --- RKO / MS status --- */ +[data-theme="dark"] .rko-status.validated, +[data-theme="dark"] .ms-status.complete { + background: rgba(52, 211, 153, 0.1) !important; + border-color: rgba(52, 211, 153, 0.3) !important; + color: #34d399 !important; +} +[data-theme="dark"] .rko-status.processing, +[data-theme="dark"] .ms-status.in-progress { + background: rgba(99, 102, 241, 0.1) !important; + border-color: rgba(99, 102, 241, 0.3) !important; + color: #818cf8 !important; +} +[data-theme="dark"] .rko-status.sent, +[data-theme="dark"] .ms-status.paused { + background: rgba(251, 191, 36, 0.1) !important; + border-color: rgba(251, 191, 36, 0.3) !important; + color: #fbbf24 !important; +} + +/* --- Severity badges --- */ +[data-theme="dark"] .severity-badge.hot { + background: rgba(248, 113, 113, 0.12) !important; + border-color: rgba(248, 113, 113, 0.3) !important; + color: #f87171 !important; +} +[data-theme="dark"] .severity-badge.med { + background: rgba(251, 191, 36, 0.12) !important; + border-color: rgba(251, 191, 36, 0.3) !important; + color: #fbbf24 !important; +} + +/* --- GEAK modal extras --- */ +[data-theme="dark"] .geak-settings-close:hover { + background: #252838 !important; + color: #e4e6ef !important; +} +[data-theme="dark"] .geak-settings-footer .btn-secondary:hover { + background: #252838 !important; +} + +/* --- Action bar --- */ +[data-theme="dark"] .action-bar { + background: #141620 !important; + border-color: #252838 !important; + box-shadow: 0 -2px 12px rgba(0, 0, 0, 0.4) !important; +} + +/* --- Catch-all: any remaining hardcoded light backgrounds --- */ +[data-theme="dark"] .metric-sub-was { + color: #6c7190 !important; +} + +[data-theme="dark"] .metric-label-hint { + color: #4a4e65 !important; +} + +[data-theme="dark"] .rpt-gap-from.red { + color: #f87171 !important; +} + +[data-theme="dark"] .rpt-gap-to.green { + color: #34d399 !important; +} + +[data-theme="dark"] .rpt-gap-arrow { + color: #6c7190 !important; +} + +[data-theme="dark"] .gap-fill.yellow { + background: rgba(251, 191, 36, 0.5) !important; +} + +[data-theme="dark"] .gap-fill-overlay.green { + background: rgba(52, 211, 153, 0.5) !important; +} diff --git a/Web/apps/hyperloom/src/env.d.ts b/Web/apps/hyperloom/src/env.d.ts new file mode 100644 index 000000000..7b141efe6 --- /dev/null +++ b/Web/apps/hyperloom/src/env.d.ts @@ -0,0 +1,19 @@ +/// + +interface ImportMetaEnv { + readonly VITE_CLAW_BASE_URL: string + readonly VITE_MCP_TRACELENS_URL: string + readonly VITE_MCP_GEAK_URL: string + readonly VITE_MCP_GEAK_API_KEY: string + readonly VITE_API_BASE_URL: string +} + +interface ImportMeta { + readonly env: ImportMetaEnv +} + +declare module '*.vue' { + import type { DefineComponent } from 'vue' + const component: DefineComponent + export default component +} diff --git a/Web/apps/hyperloom/src/main.ts b/Web/apps/hyperloom/src/main.ts new file mode 100644 index 000000000..19a911c28 --- /dev/null +++ b/Web/apps/hyperloom/src/main.ts @@ -0,0 +1,20 @@ +import { createApp } from 'vue' +import { createPinia } from 'pinia' +import piniaPersist from 'pinia-plugin-persistedstate' +import ElementPlus from 'element-plus' +import 'element-plus/dist/index.css' +import 'uno.css' +import '@/assets/main.scss' + +import App from './App.vue' +import router from './router' + +const app = createApp(App) + +const pinia = createPinia() +pinia.use(piniaPersist) + +app.use(pinia) +app.use(router) +app.use(ElementPlus, { zIndex: 3000 }) +app.mount('#app') diff --git a/Web/apps/hyperloom/src/pages/ClawPage.vue b/Web/apps/hyperloom/src/pages/ClawPage.vue new file mode 100644 index 000000000..4c25e2ed9 --- /dev/null +++ b/Web/apps/hyperloom/src/pages/ClawPage.vue @@ -0,0 +1,1077 @@ + + + diff --git a/Web/apps/hyperloom/src/pages/Dashboard.vue b/Web/apps/hyperloom/src/pages/Dashboard.vue new file mode 100644 index 000000000..f9f1db18d --- /dev/null +++ b/Web/apps/hyperloom/src/pages/Dashboard.vue @@ -0,0 +1,1672 @@ + + + diff --git a/Web/apps/hyperloom/src/pages/LoginPage.vue b/Web/apps/hyperloom/src/pages/LoginPage.vue new file mode 100644 index 000000000..2d29d9d86 --- /dev/null +++ b/Web/apps/hyperloom/src/pages/LoginPage.vue @@ -0,0 +1,410 @@ + + + + + diff --git a/Web/apps/hyperloom/src/router/authGuard.js b/Web/apps/hyperloom/src/router/authGuard.js new file mode 100644 index 000000000..a95464688 --- /dev/null +++ b/Web/apps/hyperloom/src/router/authGuard.js @@ -0,0 +1,115 @@ +import { ssoLoginRaw, getUserSelf } from '@/services/auth.js'; + +const PUBLIC_PATHS = new Set(['/login']); +const ENTRY_KEY = 'hl-sso.redirect'; +const STATE_KEY = 'hl-oauth_state'; + +function isPublicRoute(to) { + const p = (to.path || '/').replace(/\/+$/, '') || '/'; + return PUBLIC_PATHS.has(p); +} + +export default function setupAuthGuard(router) { + router.beforeEach(async (to, _from, next) => { + const code = to.query.code; + + if (code) { + const state = to.query.state; + + let stateRandom = state; + if (typeof state === 'string' && state.startsWith('hyperloom:')) { + const parts = state.split(':'); + stateRandom = parts[1]; + } + + const saved = sessionStorage.getItem(STATE_KEY); + if (saved && stateRandom && saved !== stateRandom) { + history.replaceState(null, '', to.path); + return next({ path: '/login', query: { error: 'state_mismatch' } }); + } + + try { + const resp = await ssoLoginRaw(code, state); + const loginOk = resp.status >= 200 && resp.status < 300 && resp.data?.id; + + if (loginOk) { + sessionStorage.removeItem(STATE_KEY); + + localStorage.setItem( + 'hl-user', + JSON.stringify({ + id: resp.data.id, + name: resp.data.name, + email: resp.data.email, + session: 'authenticated', + }), + ); + + const target = sessionStorage.getItem(ENTRY_KEY) || '/overview'; + sessionStorage.removeItem(ENTRY_KEY); + history.replaceState(null, '', to.path); + return next(target); + } + + const errMsg = resp.data?.errorMessage || resp.data?.message || ''; + history.replaceState(null, '', to.path); + return next({ path: '/login', query: { error: 'sso_failed', detail: errMsg } }); + } catch (err) { + console.error('[HyperLoom Auth] SSO callback error:', err); + history.replaceState(null, '', to.path); + return next({ path: '/login', query: { error: 'sso_failed' } }); + } + } + + if (to.query.error && to.path !== '/login') { + history.replaceState(null, '', to.path); + return next({ + path: '/login', + query: { error: to.query.error, error_description: to.query.error_description }, + }); + } + + const stored = localStorage.getItem('hl-user'); + if (stored) { + try { + const user = JSON.parse(stored); + if (user?.session === 'authenticated') { + if (isPublicRoute(to)) { + return next(to.query.redirect || '/overview'); + } + return next(); + } + } catch { + localStorage.removeItem('hl-user'); + } + } + + // Dev mode: try to probe existing session via proxy before forcing login. + // If the user already authenticated on the SaFE domain and we have a valid + // cookie (e.g. via manual cookie bridge), this will succeed silently. + if (!isPublicRoute(to)) { + try { + const profile = await getUserSelf(); + if (profile?.id) { + localStorage.setItem( + 'hl-user', + JSON.stringify({ + id: profile.id, + name: profile.name, + email: profile.email, + session: 'authenticated', + }), + ); + return next(); + } + } catch { + // Not authenticated — fall through to login redirect + } + + sessionStorage.setItem(ENTRY_KEY, to.fullPath); + return next({ path: '/login', query: { redirect: to.fullPath } }); + } + + return next(); + }); +} diff --git a/Web/apps/hyperloom/src/router/index.ts b/Web/apps/hyperloom/src/router/index.ts new file mode 100644 index 000000000..78f8019d4 --- /dev/null +++ b/Web/apps/hyperloom/src/router/index.ts @@ -0,0 +1,44 @@ +import { createRouter, createWebHistory } from 'vue-router' +import setupAuthGuard from './authGuard.js' + +const router = createRouter({ + history: createWebHistory('/hyperloom/'), + routes: [ + { path: '/', redirect: '/overview' }, + { + path: '/login', + name: 'login', + component: () => import('@/pages/LoginPage.vue'), + meta: { public: true }, + }, + { + path: '/overview', + name: 'overview', + component: () => import('@/pages/Dashboard.vue'), + }, + { + path: '/analysis', + name: 'analysis', + component: () => import('@/pages/Dashboard.vue'), + }, + { + path: '/optimization', + name: 'optimization', + component: () => import('@/pages/Dashboard.vue'), + }, + { + path: '/report', + name: 'report', + component: () => import('@/pages/Dashboard.vue'), + }, + { + path: '/claw', + name: 'claw', + component: () => import('@/pages/ClawPage.vue'), + }, + ], +}) + +setupAuthGuard(router) + +export default router diff --git a/Web/apps/hyperloom/src/services/auth.js b/Web/apps/hyperloom/src/services/auth.js new file mode 100644 index 000000000..f28b5bc04 --- /dev/null +++ b/Web/apps/hyperloom/src/services/auth.js @@ -0,0 +1,59 @@ +/** + * HyperLoom Auth Service + * + * Reuses SaFE backend SSO endpoints: + * GET /api/envs → { ssoEnable, ssoAuthUrl, ... } + * POST /api/login → Set-Cookie (session token) + * POST /api/logout + * GET /api/users/self → current user profile + */ + +import axios from 'axios'; + +const BASE = '/api'; + +const request = axios.create({ + baseURL: BASE, + timeout: 15000, + withCredentials: true, +}); + +request.interceptors.response.use( + (resp) => resp.data, + (error) => { + const status = error?.response?.status; + if (status === 401 && !location.pathname.includes('/login')) { + sessionStorage.removeItem('hl-user'); + location.href = '/hyperloom/login?redirect=' + encodeURIComponent(location.pathname + location.search); + } + return Promise.reject(error); + }, +); + +export async function getEnvs() { + return request.get('/envs'); +} + +export async function ssoLoginRaw(code, state) { + const params = new URLSearchParams(); + params.append('type', 'sso'); + params.append('code', code); + if (state) params.append('state', String(state)); + + return axios.post(`${BASE}/login`, params, { + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + withCredentials: true, + timeout: 15000, + validateStatus: () => true, + }); +} + +export async function getUserSelf() { + return request.get('/users/self'); +} + +export async function logoutApi() { + return request.post('/logout'); +} + +export { request as authRequest }; diff --git a/Web/apps/hyperloom/src/services/claw.js b/Web/apps/hyperloom/src/services/claw.js new file mode 100644 index 000000000..3abc92a8b --- /dev/null +++ b/Web/apps/hyperloom/src/services/claw.js @@ -0,0 +1,399 @@ +/** + * PrimusClaw API Service + * + * Dual-channel SSE chat architecture: + * 1. GET /claw/v1/chat/sessions/{id}/messages (SSE long-connection, receive) + * 2. POST /claw/v1/sessions/{id}/messages (send user message) + * + * Session CRUD + Skills + Tools + */ + +const BASE_URL = import.meta.env.VITE_CLAW_BASE_URL || '/claw-api/v1'; + +// ========== Sessions ========== + +export async function getSessions() { + const res = await fetch(`${BASE_URL}/sessions`, { credentials: 'include' }); + if (!res.ok) throw new Error(`getSessions: ${res.status}`); + return res.json(); +} + +export async function createSession(data = {}) { + const res = await fetch(`${BASE_URL}/sessions`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + credentials: 'include', + body: JSON.stringify(data), + }); + if (!res.ok) throw new Error(`createSession: ${res.status}`); + return res.json(); +} + +export async function deleteSession(sessionId) { + const res = await fetch(`${BASE_URL}/sessions/${sessionId}`, { + method: 'DELETE', + credentials: 'include', + }); + if (!res.ok) throw new Error(`deleteSession: ${res.status}`); + return res.json(); +} + +// ========== Skills ========== + +export async function getSkills() { + const res = await fetch(`${BASE_URL}/skills`, { credentials: 'include' }); + if (!res.ok) throw new Error(`getSkills: ${res.status}`); + return res.json(); +} + +// ========== Tools ========== + +export async function getTools({ + offset = 0, + limit = 50, + order = 'desc', + sort, + status, + type, + owner, +} = {}) { + const paramsObject = { + offset: String(offset), + limit: String(limit), + order, + }; + if (sort) paramsObject.sort = String(sort); + if (status) paramsObject.status = String(status); + if (type) paramsObject.type = String(type); + if (owner) paramsObject.owner = String(owner); + + const params = new URLSearchParams(paramsObject); + const res = await fetch(`/api/tools/api/v1/tools?${params}`, { credentials: 'include' }); + if (!res.ok) throw new Error(`getTools: ${res.status}`); + return res.json(); +} + +// ========== SSE Utilities ========== + +function normalizeEventType(type) { + return type.replace(/_([a-z])/g, (_, c) => c.toUpperCase()); +} + +function processSSEBlock(block, handlers) { + const lines = block.split('\n'); + let eventType = ''; + let dataStr = ''; + + for (const line of lines) { + const trimmed = line.trim(); + if (!trimmed || trimmed.startsWith(':')) continue; + if (trimmed.startsWith('event:')) { + eventType = trimmed.slice(6).trim(); + } else if (trimmed.startsWith('data:')) { + const piece = trimmed.slice(5).trim(); + dataStr += (dataStr ? '\n' : '') + piece; + } + } + + if (!dataStr) return; + if (dataStr === '[DONE]') { + handlers.onFinish?.(); + return; + } + + try { + const parsed = JSON.parse(dataStr); + const type = normalizeEventType(eventType || parsed.type || ''); + if (type) dispatchSSEEvent(type, parsed, handlers); + } catch { + // non-JSON SSE data + } +} + +function dispatchSSEEvent(eventType, data, handlers) { + const type = normalizeEventType(eventType); + switch (type) { + case 'chatDelta': handlers.onChatDelta?.(data); break; + case 'chat': handlers.onChat?.(data); break; + case 'toolUsed': handlers.onToolUsed?.(data); break; + case 'statusUpdate': handlers.onStatusUpdate?.(data); break; + case 'liveStatus': handlers.onLiveStatus?.(data); break; + case 'eventsNotifyEventsAfter': handlers.onEventsReplay?.(data); break; + case 'error': handlers.onError?.(data); break; + } +} + +// ========== Load Session Messages (via SSE history replay) ========== + +export async function getSessionMessages(sessionId) { + const controller = new AbortController(); + + return new Promise((resolve) => { + let settled = false; + const settle = (events) => { + if (settled) return; + settled = true; + controller.abort(); + resolve({ data: events }); + }; + + subscribeSessionSSE(sessionId, { + onEventsReplay: (data) => settle(data.events || []), + onError: () => settle([]), + onFinish: () => settle([]), + }, undefined, controller.signal).catch(() => settle([])); + + setTimeout(() => settle([]), 15000); + }); +} + +// ========== SSE Subscription ========== + +export async function subscribeSessionSSE(sessionId, handlers, afterEventId, signal) { + const url = `${BASE_URL}/chat/sessions/${sessionId}/messages` + + (afterEventId ? `?after_event_id=${encodeURIComponent(afterEventId)}` : ''); + + try { + const response = await fetch(url, { + headers: { Accept: 'text/event-stream' }, + credentials: 'include', + signal, + }); + + if (!response.ok) { + const errorText = await response.text(); + throw new Error(`SSE HTTP ${response.status}: ${errorText}`); + } + + handlers.onConnected?.(); + + const reader = response.body?.getReader(); + if (!reader) throw new Error('Failed to get SSE response stream'); + + const decoder = new TextDecoder(); + let buffer = ''; + + while (true) { + const { done, value } = await reader.read(); + if (done) { + if (buffer.trim()) processSSEBlock(buffer, handlers); + handlers.onFinish?.(); + break; + } + buffer += decoder.decode(value, { stream: true }); + const blocks = buffer.split('\n\n'); + buffer = blocks.pop() || ''; + for (const block of blocks) { + if (block.trim()) processSSEBlock(block, handlers); + } + } + } catch (error) { + if (error instanceof Error && error.name === 'AbortError') return; + console.error('[SSE] stream error:', error); + handlers.onError?.(error); + } +} + +// ========== Chat (two-channel: SSE subscribe + POST message) ========== + +export async function clawChat(data, onMessage, onError, onFinish, signal, extraHandlers) { + const sseUrl = `${BASE_URL}/chat/sessions/${data.session_id}/messages`; + + try { + const sseResponse = await fetch(sseUrl, { + headers: { Accept: 'text/event-stream' }, + credentials: 'include', + signal, + }); + + if (!sseResponse.ok) { + const errorText = await sseResponse.text(); + throw new Error(`SSE HTTP ${sseResponse.status}: ${errorText}`); + } + + try { + await fetch(`${BASE_URL}/sessions/${data.session_id}/messages`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + credentials: 'include', + body: JSON.stringify({ + content: data.query, + contents: [{ type: 'text', value: data.query }], + messageType: 'text', + taskMode: 'agent', + attachments: [], + tools: data.tools || [], + }), + signal, + }); + } catch (postErr) { + if (postErr instanceof Error && postErr.name === 'AbortError') return; + console.error('[clawChat] send message error:', postErr); + } + + const reader = sseResponse.body?.getReader(); + if (!reader) throw new Error('Failed to get SSE response stream'); + + const decoder = new TextDecoder(); + let buffer = ''; + + while (true) { + const { done, value } = await reader.read(); + if (done) { onFinish?.(); break; } + + buffer += decoder.decode(value, { stream: true }); + const blocks = buffer.split('\n\n'); + buffer = blocks.pop() || ''; + + for (const block of blocks) { + const lines = block.split('\n'); + let eventType = ''; + let dataStr = ''; + + for (const line of lines) { + const trimmed = line.trim(); + if (!trimmed || trimmed.startsWith(':')) continue; + if (trimmed.startsWith('event:')) eventType = trimmed.slice(6).trim(); + else if (trimmed.startsWith('data:')) dataStr += (dataStr ? '\n' : '') + trimmed.slice(5).trim(); + } + + if (!dataStr) continue; + if (dataStr === '[DONE]') { onFinish?.(); return; } + + try { + const parsed = JSON.parse(dataStr); + const type = normalizeEventType(eventType || parsed.type || ''); + + switch (type) { + case 'chatDelta': { + const content = parsed.delta?.content || ''; + if (content) onMessage(content); + if (parsed.finished) { onFinish?.(); return; } + break; + } + case 'chat': break; + case 'toolUsed': extraHandlers?.onToolUsed?.(parsed); break; + case 'statusUpdate': + extraHandlers?.onStatusUpdate?.(parsed); + if (parsed.agentStatus === 'stopped') { onFinish?.(); return; } + break; + case 'liveStatus': extraHandlers?.onLiveStatus?.(parsed); break; + case 'eventsNotifyEventsAfter': break; + case 'error': onError?.(parsed); return; + default: { + const content = parsed.content || parsed.delta?.content || parsed.text || + parsed.choices?.[0]?.delta?.content || ''; + if (content && typeof content === 'string') onMessage(content); + break; + } + } + } catch { + if (dataStr) onMessage(dataStr); + } + } + } + } catch (error) { + if (error instanceof Error && error.name === 'AbortError') return; + console.error('[clawChat] stream error:', error); + onError?.(error); + } +} + +// ========== History Event Processing ========== + +export function extractTextFromEvent(event) { + if (typeof event.content === 'string') return event.content; + if (event.text_preview) return event.text_preview; + if (event.contents && Array.isArray(event.contents)) { + return event.contents.filter(c => c.type === 'text').map(c => c.value).join('\n'); + } + if (event.content?.content && Array.isArray(event.content.content)) { + return event.content.content + .filter(b => (b._type === 'TextBlock' || b.type === 'text') && (b.text || b.value)) + .map(b => b.text || b.value) + .join('\n'); + } + return ''; +} + +export function processHistoryEvents(events) { + const result = []; + let currentAssistant = null; + let pendingToolCalls = []; + + const flushToolCalls = () => { + if (pendingToolCalls.length === 0) return; + if (!currentAssistant) currentAssistant = { role: 'assistant', content: '', segments: [] }; + if (!currentAssistant.segments) currentAssistant.segments = []; + currentAssistant.segments.push({ + type: 'tool-execution', + toolCount: pendingToolCalls.length, + toolCalls: [...pendingToolCalls], + expanded: false, + }); + pendingToolCalls = []; + }; + + const flushAssistant = () => { + if (!currentAssistant) return; + flushToolCalls(); + if ((currentAssistant.segments?.length > 0) || currentAssistant.content) { + result.push(currentAssistant); + } + currentAssistant = null; + }; + + for (const rawEvent of events) { + const evt = rawEvent.data || rawEvent; + const type = normalizeEventType(evt.type || rawEvent.event || ''); + + if (type === 'chat') { + const sender = evt.sender || evt.role || ''; + if (sender === 'user') { + flushAssistant(); + const text = extractTextFromEvent(evt); + if (text) result.push({ role: 'user', content: text }); + } else if (sender === 'assistant') { + if (!currentAssistant) currentAssistant = { role: 'assistant', content: '', segments: [] }; + flushToolCalls(); + const text = extractTextFromEvent(evt); + if (text) { + currentAssistant.content += (currentAssistant.content ? '\n' : '') + text; + currentAssistant.segments.push({ type: 'text', text }); + } + } + } else if (type === 'toolUsed') { + if (evt.tool === 'suggestion') continue; + if (['start', 'running', 'success', 'error'].includes(evt.status)) { + if (!currentAssistant) currentAssistant = { role: 'assistant', content: '', segments: [] }; + const input = evt.argumentsDetail || evt.input || undefined; + const existingIdx = pendingToolCalls.findIndex(t => t.toolUseId === evt.actionId); + if (existingIdx >= 0) { + const existing = pendingToolCalls[existingIdx]; + existing.status = evt.status; + existing.isError = evt.status === 'error'; + if (evt.tool) { existing.name = evt.tool; existing.tool = evt.tool; } + if (evt.brief) existing.brief = evt.brief; + if (evt.description) { existing.description = evt.description; existing.output = evt.description; } + if (input && !existing.input) existing.input = input; + } else { + pendingToolCalls.push({ + toolUseId: evt.actionId || '', + name: evt.tool || evt.brief || 'Unknown', + tool: evt.tool || '', + status: evt.status, + brief: evt.brief || '', + description: evt.description || '', + output: evt.description || '', + input, + isError: evt.status === 'error', + expanded: false, + }); + } + } + } + } + + flushAssistant(); + return result; +} diff --git a/Web/apps/hyperloom/src/services/mcp.js b/Web/apps/hyperloom/src/services/mcp.js new file mode 100644 index 000000000..54a7a9af3 --- /dev/null +++ b/Web/apps/hyperloom/src/services/mcp.js @@ -0,0 +1,222 @@ +/** + * MCP Agent Service — TraceLens & GEAK + * + * Protocol: JSON-RPC 2.0 over HTTP POST + * + * TraceLens: GPU kernel profiling & trace analysis + * POST /mcp/tracelens → rewritten to /control-plane/.../trace-lens-agent-bwrmr/mcp + * + * GEAK: GPU kernel auto-optimization + * POST /mcp/geak → rewritten to /control-plane/.../geak-agent-wvsbv/mcp/sse + * Requires Bearer token (VITE_MCP_GEAK_API_KEY) + * Returns SSE stream for long-running optimizations + */ + +const TRACELENS_URL = import.meta.env.VITE_MCP_TRACELENS_URL || '/mcp/tracelens'; +const GEAK_URL = import.meta.env.VITE_MCP_GEAK_URL || '/mcp/geak'; +const GEAK_API_KEY = import.meta.env.VITE_MCP_GEAK_API_KEY || ''; + +let sessionIds = { tracelens: '', geak: '' }; +let rpcId = 0; + +function buildRpcBody(method, params = {}) { + return { jsonrpc: '2.0', id: ++rpcId, method, params }; +} + +// ========== TraceLens Agent ========== + +export async function initTraceLens() { + const result = await callTraceLens('initialize', { + protocolVersion: '2024-11-05', + capabilities: {}, + clientInfo: { name: 'PRISM-Dashboard', version: '1.0.0' }, + }); + await callTraceLens('notifications/initialized', {}); + return result; +} + +export async function callTraceLens(method, params = {}) { + const headers = { + 'Content-Type': 'application/json', + 'Accept': 'application/json, text/event-stream', + }; + if (sessionIds.tracelens) { + headers['Mcp-Session-Id'] = sessionIds.tracelens; + } + + const res = await fetch(TRACELENS_URL, { + method: 'POST', + headers, + body: JSON.stringify(buildRpcBody(method, params)), + }); + + const sid = res.headers.get('mcp-session-id') || res.headers.get('Mcp-Session-Id'); + if (sid) sessionIds.tracelens = sid; + + const contentType = res.headers.get('content-type') || ''; + + if (contentType.includes('text/event-stream')) { + return parseSSEResponse(res); + } + + if (!res.ok) { + const text = await res.text(); + throw new Error(`TraceLens ${method}: HTTP ${res.status} — ${text}`); + } + + return res.json(); +} + +// ========== GEAK Agent ========== + +export async function initGEAK() { + const result = await callGEAK('initialize', { + protocolVersion: '2024-11-05', + capabilities: {}, + clientInfo: { name: 'PRISM-Dashboard', version: '1.0.0' }, + }); + await callGEAK('notifications/initialized', {}); + return result; +} + +export async function callGEAK(method, params = {}) { + const headers = { + 'Content-Type': 'application/json', + 'Accept': 'application/json, text/event-stream', + }; + if (GEAK_API_KEY) { + headers['Authorization'] = `Bearer ${GEAK_API_KEY}`; + } + if (sessionIds.geak) { + headers['Mcp-Session-Id'] = sessionIds.geak; + } + + const res = await fetch(GEAK_URL, { + method: 'POST', + headers, + body: JSON.stringify(buildRpcBody(method, params)), + }); + + const sid = res.headers.get('mcp-session-id') || res.headers.get('Mcp-Session-Id'); + if (sid) sessionIds.geak = sid; + + const contentType = res.headers.get('content-type') || ''; + + if (contentType.includes('text/event-stream')) { + return parseSSEResponse(res); + } + + if (!res.ok) { + const text = await res.text(); + throw new Error(`GEAK ${method}: HTTP ${res.status} — ${text}`); + } + + return res.json(); +} + +// ========== Convenience: tools/call ========== + +export async function traceLensToolCall(toolName, args = {}) { + return callTraceLens('tools/call', { name: toolName, arguments: args }); +} + +export async function geakToolCall(toolName, args = {}) { + return callGEAK('tools/call', { name: toolName, arguments: args }); +} + +export async function listTraceLensTools() { + return callTraceLens('tools/list', {}); +} + +export async function listGEAKTools() { + return callGEAK('tools/list', {}); +} + +// ========== SSE Response Parser ========== + +async function parseSSEResponse(response) { + const reader = response.body?.getReader(); + if (!reader) throw new Error('No readable stream in SSE response'); + + const decoder = new TextDecoder(); + const results = []; + let buffer = ''; + + while (true) { + const { done, value } = await reader.read(); + if (done) break; + + buffer += decoder.decode(value, { stream: true }); + const blocks = buffer.split('\n\n'); + buffer = blocks.pop() || ''; + + for (const block of blocks) { + let dataStr = ''; + for (const line of block.split('\n')) { + const trimmed = line.trim(); + if (trimmed.startsWith('data:')) { + dataStr += trimmed.slice(5).trim(); + } + } + if (!dataStr || dataStr === '[DONE]') continue; + try { + results.push(JSON.parse(dataStr)); + } catch { /* skip non-JSON */ } + } + } + + return results.length === 1 ? results[0] : results; +} + +// ========== Streaming SSE (for long-running GEAK optimizations) ========== + +export async function callGEAKStream(method, params = {}, onData, onDone, signal) { + const headers = { 'Content-Type': 'application/json' }; + if (GEAK_API_KEY) headers['Authorization'] = `Bearer ${GEAK_API_KEY}`; + if (sessionIds.geak) headers['Mcp-Session-Id'] = sessionIds.geak; + + const res = await fetch(GEAK_URL, { + method: 'POST', + headers, + body: JSON.stringify(buildRpcBody(method, params)), + signal, + }); + + const sid = res.headers.get('mcp-session-id') || res.headers.get('Mcp-Session-Id'); + if (sid) sessionIds.geak = sid; + + const reader = res.body?.getReader(); + if (!reader) throw new Error('No readable stream'); + + const decoder = new TextDecoder(); + let buffer = ''; + + while (true) { + const { done, value } = await reader.read(); + if (done) { onDone?.(); break; } + + buffer += decoder.decode(value, { stream: true }); + const blocks = buffer.split('\n\n'); + buffer = blocks.pop() || ''; + + for (const block of blocks) { + let dataStr = ''; + for (const line of block.split('\n')) { + const trimmed = line.trim(); + if (trimmed.startsWith('data:')) dataStr += trimmed.slice(5).trim(); + } + if (!dataStr) continue; + if (dataStr === '[DONE]') { onDone?.(); return; } + try { + onData?.(JSON.parse(dataStr)); + } catch { /* skip */ } + } + } +} + +// ========== Reset ========== + +export function resetMcpSessions() { + sessionIds = { tracelens: '', geak: '' }; + rpcId = 0; +} diff --git a/Web/apps/hyperloom/tsconfig.json b/Web/apps/hyperloom/tsconfig.json new file mode 100644 index 000000000..138d2ed37 --- /dev/null +++ b/Web/apps/hyperloom/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "strict": true, + "jsx": "preserve", + "resolveJsonModule": true, + "isolatedModules": true, + "esModuleInterop": true, + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + "noEmit": true, + "paths": { + "@/*": ["./src/*"] + }, + "types": ["vite/client"] + }, + "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.vue"], + "exclude": ["node_modules", "dist"] +} diff --git a/Web/apps/hyperloom/uno.config.ts b/Web/apps/hyperloom/uno.config.ts new file mode 100644 index 000000000..99313c7c5 --- /dev/null +++ b/Web/apps/hyperloom/uno.config.ts @@ -0,0 +1,5 @@ +import { defineConfig, presetUno, presetIcons } from 'unocss' + +export default defineConfig({ + presets: [presetUno(), presetIcons()], +}) diff --git a/Web/apps/hyperloom/vite.config.ts b/Web/apps/hyperloom/vite.config.ts new file mode 100644 index 000000000..599786792 --- /dev/null +++ b/Web/apps/hyperloom/vite.config.ts @@ -0,0 +1,83 @@ +import { fileURLToPath, URL } from 'node:url' +import { defineConfig, loadEnv } from 'vite' +import vue from '@vitejs/plugin-vue' +import UnoCSS from 'unocss/vite' + +export default defineConfig(({ mode }) => { + const env = loadEnv(mode, './', '') + + const API_TARGET = env.PROXY_API_TARGET || 'http://localhost:8088' + const MCP_TARGET = env.PROXY_MCP_TARGET || API_TARGET + const DEV_DOMAIN = env.PROXY_DEV_DOMAIN || 'localhost' + + return { + base: '/hyperloom/', + envDir: './', + plugins: [vue(), UnoCSS()], + resolve: { + alias: { + '@': fileURLToPath(new URL('./src', import.meta.url)), + }, + }, + server: { + port: 3001, + host: true, + hmr: true, + allowedHosts: true, + proxy: { + '/claw-api': { + target: API_TARGET, + changeOrigin: true, + secure: false, + timeout: 1800000, + cookieDomainRewrite: { '*': DEV_DOMAIN }, + configure(proxy) { + proxy.on('proxyReq', (proxyReq) => { + proxyReq.setHeader('Connection', 'keep-alive') + }) + }, + }, + '/tools': { + target: API_TARGET, + changeOrigin: true, + secure: false, + cookieDomainRewrite: { '*': DEV_DOMAIN }, + rewrite: (path: string) => + path.replace(/^\/tools\/api\/v1/, '/api/v1'), + }, + '/mcp/tracelens': { + target: MCP_TARGET, + changeOrigin: true, + secure: false, + timeout: 60000, + cookieDomainRewrite: { '*': DEV_DOMAIN }, + rewrite: (path: string) => + path.replace( + /^\/mcp\/tracelens/, + '/control-plane/control-plane-prod/trace-lens-agent-bwrmr/mcp', + ), + }, + '/mcp/geak': { + target: MCP_TARGET, + changeOrigin: true, + secure: false, + timeout: 60000, + cookieDomainRewrite: { '*': DEV_DOMAIN }, + rewrite: (path: string) => + path.replace( + /^\/mcp\/geak/, + '/control-plane/control-plane-dev/geak-agent-wvsbv/mcp/sse', + ), + }, + '/api': { + target: API_TARGET, + changeOrigin: true, + secure: false, + rewrite: (p: string) => (p.startsWith('/api/v1') ? p : p.replace(/^\/api/, '/api/v1')), + cookieDomainRewrite: { '*': DEV_DOMAIN }, + cookiePathRewrite: { '*': '/' }, + }, + }, + }, + } +}) diff --git a/Web/apps/safe/src/router/guards/auth.ts b/Web/apps/safe/src/router/guards/auth.ts index a9b418f4b..d8b4ff886 100644 --- a/Web/apps/safe/src/router/guards/auth.ts +++ b/Web/apps/safe/src/router/guards/auth.ts @@ -45,6 +45,16 @@ export default function setupAuthGuard(router: Router) { } } + // Check if this is an SSO request from the HyperLoom app + if (state && state.startsWith('hyperloom:')) { + const parts = state.split(':') + if (parts.length >= 3) { + const hlRedirect = decodeURIComponent(parts.slice(2).join(':')) + window.location.href = `${hlRedirect}?code=${code}&state=${encodeURIComponent(state)}` + return + } + } + const saved = sessionStorage.getItem(OAUTH_STATE_KEY) if (saved && state && saved !== state) { history.replaceState(null, '', to.path) diff --git a/Web/package.json b/Web/package.json index c756f69ef..61acd8bba 100644 --- a/Web/package.json +++ b/Web/package.json @@ -7,9 +7,12 @@ "build:safe": "pnpm --filter ./apps/safe build", "dev:lens": "pnpm --filter ./apps/lens dev", "build:lens": "pnpm --filter ./apps/lens build", + "dev:hyperloom": "pnpm --filter ./apps/hyperloom dev", + "build:hyperloom": "pnpm --filter ./apps/hyperloom build", "lint": "pnpm -r lint", "test": "pnpm -r test", "test:safe": "pnpm --filter ./apps/safe test", - "test:lens": "pnpm --filter ./apps/lens test" + "test:lens": "pnpm --filter ./apps/lens test", + "test:hyperloom": "pnpm --filter ./apps/hyperloom test" } } diff --git a/Web/pnpm-lock.yaml b/Web/pnpm-lock.yaml index 9094caebb..a9672d8e7 100644 --- a/Web/pnpm-lock.yaml +++ b/Web/pnpm-lock.yaml @@ -8,6 +8,67 @@ importers: .: {} + apps/hyperloom: + dependencies: + '@element-plus/icons-vue': + specifier: ^2.3.2 + version: 2.3.2(vue@3.5.27) + axios: + specifier: ^1.10.0 + version: 1.13.4 + chart.js: + specifier: ^4.4.9 + version: 4.5.1 + element-plus: + specifier: ^2.10.4 + version: 2.13.1(vue@3.5.27) + marked: + specifier: ^17.0.5 + version: 17.0.5 + pinia: + specifier: ^3.0.4 + version: 3.0.4(typescript@5.8.3)(vue@3.5.27) + pinia-plugin-persistedstate: + specifier: ^4.7.1 + version: 4.7.1(pinia@3.0.4) + vue: + specifier: ^3.5.17 + version: 3.5.27(typescript@5.8.3) + vue-router: + specifier: ^4.5.1 + version: 4.6.4(vue@3.5.27) + devDependencies: + '@iconify-json/ep': + specifier: ^1.2.2 + version: 1.2.4 + '@types/node': + specifier: ^22.15.32 + version: 22.19.7 + '@unocss/preset-icons': + specifier: ^66.3.3 + version: 66.6.0 + '@unocss/preset-uno': + specifier: ^66.3.3 + version: 66.6.0 + '@vitejs/plugin-vue': + specifier: ^5.0.4 + version: 5.2.4(vite@5.4.21)(vue@3.5.27) + sass: + specifier: ^1.89.2 + version: 1.97.3 + typescript: + specifier: ^5.8.3 + version: 5.8.3 + unocss: + specifier: ^66.3.3 + version: 66.4.2(postcss@8.5.6)(vite@5.4.21) + vite: + specifier: ^5.2.0 + version: 5.4.21(@types/node@22.19.7)(sass@1.97.3) + vitest: + specifier: ^3.2.1 + version: 3.2.4(@types/node@22.19.7)(sass@1.97.3) + apps/lens: dependencies: '@element-plus/icons-vue': @@ -955,6 +1016,10 @@ packages: '@jridgewell/sourcemap-codec': 1.5.5 dev: true + /@kurkle/color@0.3.4: + resolution: {integrity: sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==} + dev: false + /@nodelib/fs.scandir@2.1.5: resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -2588,6 +2653,13 @@ packages: supports-color: 7.2.0 dev: true + /chart.js@4.5.1: + resolution: {integrity: sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==} + engines: {pnpm: '>=8'} + dependencies: + '@kurkle/color': 0.3.4 + dev: false + /check-error@2.1.3: resolution: {integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==} engines: {node: '>= 16'} @@ -3614,6 +3686,12 @@ packages: hasBin: true dev: false + /marked@17.0.5: + resolution: {integrity: sha512-6hLvc0/JEbRjRgzI6wnT2P1XuM1/RrrDEX0kPt0N7jGm1133g6X7DlxFasUIx+72aKAr904GTxhSLDrd5DIlZg==} + engines: {node: '>= 20'} + hasBin: true + dev: false + /math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'}