|
|
<!DOCTYPE html> |
|
|
<html lang="en"> |
|
|
<head> |
|
|
<meta charset="UTF-8"> |
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
|
<title>uvnote Integration Test Report</title> |
|
|
<script> |
|
|
|
|
|
(function() { |
|
|
const configTheme = 'dark'; |
|
|
let theme; |
|
|
if (configTheme === 'auto') { |
|
|
theme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; |
|
|
} else { |
|
|
theme = localStorage.getItem('uvnote-theme') || configTheme; |
|
|
} |
|
|
document.documentElement.setAttribute('data-theme', theme); |
|
|
})(); |
|
|
</script> |
|
|
<style> |
|
|
:root[data-theme="light"] { |
|
|
--bg-primary: #ffffff; |
|
|
--bg-secondary: #f6f8fa; |
|
|
--bg-tertiary: #f8f9fa; |
|
|
--bg-code: #f8f9fa; |
|
|
--bg-error: #fdf2f2; |
|
|
--bg-artifact: #e6f3ff; |
|
|
--bg-artifact-hover: #d0e7ff; |
|
|
|
|
|
--text-primary: #333; |
|
|
--text-secondary: #656d76; |
|
|
--text-error: #c53030; |
|
|
--text-link: #0969da; |
|
|
|
|
|
--border-primary: #e1e5e9; |
|
|
--border-error: #e53e3e; |
|
|
--border-cell-failed: #d73a49; |
|
|
|
|
|
--shadow: rgba(0, 0, 0, 0.1); |
|
|
} |
|
|
|
|
|
:root[data-theme="dark"] { |
|
|
--bg-primary: #0a0a0a; |
|
|
--bg-secondary: #121212; |
|
|
--bg-tertiary: #181818; |
|
|
--bg-code: #0d0d0d; |
|
|
--bg-error: #1a0f0f; |
|
|
--bg-artifact: #151515; |
|
|
--bg-artifact-hover: #1a1a1a; |
|
|
|
|
|
--text-primary: #e0e0e0; |
|
|
--text-secondary: #888888; |
|
|
--text-error: #ff6b6b; |
|
|
--text-link: #64b5f6; |
|
|
|
|
|
--border-primary: #2a2a2a; |
|
|
--border-error: #ff6b6b; |
|
|
--border-cell-failed: #ff6b6b; |
|
|
|
|
|
--shadow: rgba(255, 255, 255, 0.05); |
|
|
} |
|
|
html { |
|
|
overscroll-behavior: none; |
|
|
} |
|
|
body { |
|
|
font-family: 'Cascadia Mono', 'Cascadia Code', 'JetBrains Mono', 'SF Mono', Monaco, 'Consolas', monospace; |
|
|
line-height: 1.4; |
|
|
max-width: 1000px; |
|
|
margin: 0 auto; |
|
|
padding: 15px; |
|
|
color: var(--text-primary); |
|
|
background: var(--bg-primary); |
|
|
transition: background-color 0.2s ease, color 0.2s ease; |
|
|
overscroll-behavior: none; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
.controls { |
|
|
position: fixed; |
|
|
top: 20px; |
|
|
right: 20px; |
|
|
display: flex; |
|
|
gap: 0.5rem; |
|
|
z-index: 1000; |
|
|
} |
|
|
|
|
|
.menu-button { |
|
|
position: relative; |
|
|
background: var(--bg-secondary); |
|
|
border: 1px solid var(--border-primary); |
|
|
padding: 8px 12px; |
|
|
border-radius: 4px; |
|
|
color: var(--text-secondary); |
|
|
cursor: pointer; |
|
|
font-family: inherit; |
|
|
font-size: 0.9rem; |
|
|
user-select: none; |
|
|
} |
|
|
|
|
|
.menu-button:hover { |
|
|
color: var(--text-primary); |
|
|
background: var(--bg-tertiary); |
|
|
} |
|
|
|
|
|
.menu-dropdown { |
|
|
position: absolute; |
|
|
top: 100%; |
|
|
right: 0; |
|
|
background: var(--bg-secondary); |
|
|
border: 1px solid var(--border-primary); |
|
|
border-radius: 4px; |
|
|
box-shadow: 0 4px 12px var(--shadow); |
|
|
min-width: 160px; |
|
|
opacity: 0; |
|
|
visibility: hidden; |
|
|
transform: translateY(-8px); |
|
|
transition: all 0.2s ease; |
|
|
z-index: 1001; |
|
|
margin-top: 4px; |
|
|
} |
|
|
|
|
|
.menu-button.active .menu-dropdown { |
|
|
opacity: 1; |
|
|
visibility: visible; |
|
|
transform: translateY(0); |
|
|
} |
|
|
|
|
|
.menu-item { |
|
|
display: block; |
|
|
padding: 8px 12px; |
|
|
color: var(--text-secondary); |
|
|
text-decoration: none; |
|
|
font-size: 0.85rem; |
|
|
border-bottom: 1px solid var(--border-primary); |
|
|
cursor: pointer; |
|
|
} |
|
|
|
|
|
.menu-item:last-child { |
|
|
border-bottom: none; |
|
|
} |
|
|
|
|
|
.menu-item:hover { |
|
|
background: var(--bg-tertiary); |
|
|
color: var(--text-primary); |
|
|
} |
|
|
|
|
|
.menu-checkbox { |
|
|
display: inline-block; |
|
|
width: 16px; |
|
|
font-family: monospace; |
|
|
color: var(--text-link); |
|
|
} |
|
|
|
|
|
.theme-toggle, |
|
|
.reset-toggle { |
|
|
background: var(--bg-secondary); |
|
|
border: 1px solid var(--border-primary); |
|
|
padding: 8px 12px; |
|
|
border-radius: 4px; |
|
|
color: var(--text-secondary); |
|
|
cursor: pointer; |
|
|
font-family: inherit; |
|
|
font-size: 0.9rem; |
|
|
user-select: none; |
|
|
} |
|
|
|
|
|
.theme-toggle:hover, |
|
|
.reset-toggle:hover { |
|
|
color: var(--text-primary); |
|
|
background: var(--bg-tertiary); |
|
|
} |
|
|
|
|
|
.system-info { |
|
|
background: var(--bg-secondary); |
|
|
border: 1px solid var(--border-primary); |
|
|
border-radius: 4px; |
|
|
padding: 8px 12px; |
|
|
margin-bottom: 16px; |
|
|
font-size: 0.85em; |
|
|
color: var(--text-secondary); |
|
|
} |
|
|
|
|
|
.system-info-header { |
|
|
font-weight: 600; |
|
|
color: var(--text-primary); |
|
|
margin-bottom: 2px; |
|
|
} |
|
|
|
|
|
.system-info-content { |
|
|
font-family: monospace; |
|
|
} |
|
|
|
|
|
.theme-toggle, .reset-toggle { |
|
|
background: var(--bg-secondary); |
|
|
border: 1px solid var(--border-primary); |
|
|
border-radius: 2px; |
|
|
padding: 0.4rem 0.6rem; |
|
|
cursor: pointer; |
|
|
font-family: inherit; |
|
|
font-size: 0.8rem; |
|
|
color: var(--text-secondary); |
|
|
user-select: none; |
|
|
transition: all 0.2s ease; |
|
|
text-transform: lowercase; |
|
|
letter-spacing: 0; |
|
|
} |
|
|
|
|
|
.theme-toggle:hover, .reset-toggle:hover { |
|
|
background: var(--bg-tertiary); |
|
|
border-color: var(--text-secondary); |
|
|
color: var(--text-primary); |
|
|
} |
|
|
|
|
|
.minimap { |
|
|
position: fixed; |
|
|
bottom: 20px; |
|
|
right: 20px; |
|
|
width: 220px; |
|
|
max-height: 400px; |
|
|
background: var(--bg-secondary); |
|
|
border: 1px solid var(--border-primary); |
|
|
border-radius: 2px; |
|
|
padding: 0.5rem; |
|
|
font-size: 0.7rem; |
|
|
overflow-y: auto; |
|
|
z-index: 100; |
|
|
opacity: 0.9; |
|
|
transition: opacity 0.2s ease; |
|
|
} |
|
|
|
|
|
.file-explorer { |
|
|
position: fixed; |
|
|
bottom: 20px; |
|
|
right: 20px; |
|
|
left: auto; |
|
|
top: auto; |
|
|
width: 220px; |
|
|
max-height: 400px; |
|
|
background: var(--bg-secondary); |
|
|
border: 1px solid var(--border-primary); |
|
|
border-radius: 2px; |
|
|
padding: 0.5rem; |
|
|
font-size: 0.7rem; |
|
|
overflow-y: auto; |
|
|
z-index: 100; |
|
|
opacity: 0.9; |
|
|
transition: opacity 0.2s ease; |
|
|
} |
|
|
|
|
|
|
|
|
.draw-overlay { |
|
|
position: fixed; |
|
|
top: 0; |
|
|
left: 0; |
|
|
width: 100vw; |
|
|
height: 100vh; |
|
|
z-index: 80; |
|
|
display: block; |
|
|
pointer-events: none; |
|
|
} |
|
|
|
|
|
|
|
|
.tools-widget { |
|
|
position: fixed; |
|
|
bottom: 20px; |
|
|
right: 20px; |
|
|
left: auto; |
|
|
top: auto; |
|
|
width: 220px; |
|
|
background: var(--bg-secondary); |
|
|
border: 1px solid var(--border-primary); |
|
|
border-radius: 2px; |
|
|
padding: 0.5rem; |
|
|
font-size: 0.7rem; |
|
|
z-index: 100; |
|
|
opacity: 0.95; |
|
|
} |
|
|
.tools-title { |
|
|
font-weight: bold; |
|
|
color: var(--text-secondary); |
|
|
margin-bottom: 0.5rem; |
|
|
padding-bottom: 0.25rem; |
|
|
border-bottom: 1px solid var(--border-primary); |
|
|
cursor: grab; |
|
|
user-select: none; |
|
|
} |
|
|
.tools-row { display: flex; gap: 0.4rem; flex-wrap: wrap; } |
|
|
.tool-button { |
|
|
background: var(--bg-tertiary); |
|
|
border: 1px solid var(--border-primary); |
|
|
border-radius: 2px; |
|
|
padding: 0.25rem 0.4rem; |
|
|
cursor: pointer; |
|
|
color: var(--text-secondary); |
|
|
font-family: inherit; |
|
|
font-size: 0.75rem; |
|
|
user-select: none; |
|
|
} |
|
|
.tool-button:hover { color: var(--text-primary); } |
|
|
.tool-button.active { color: var(--text-primary); border-color: var(--text-secondary); background: var(--bg-secondary); } |
|
|
|
|
|
.minimap:hover, .file-explorer:hover { |
|
|
opacity: 1; |
|
|
} |
|
|
|
|
|
.minimap-title { |
|
|
font-weight: bold; |
|
|
color: var(--text-secondary); |
|
|
margin-bottom: 0.5rem; |
|
|
padding-bottom: 0.25rem; |
|
|
border-bottom: 1px solid var(--border-primary); |
|
|
cursor: grab; |
|
|
user-select: none; |
|
|
} |
|
|
|
|
|
.minimap-item { |
|
|
display: block; |
|
|
color: var(--text-secondary); |
|
|
text-decoration: none; |
|
|
padding: 0.15rem 0; |
|
|
border-left: 2px solid transparent; |
|
|
padding-left: 0.5rem; |
|
|
transition: all 0.2s ease; |
|
|
cursor: pointer; |
|
|
} |
|
|
|
|
|
.minimap-item:hover { |
|
|
color: var(--text-primary); |
|
|
border-left-color: var(--text-secondary); |
|
|
} |
|
|
|
|
|
.minimap-item.active { |
|
|
color: var(--text-primary); |
|
|
border-left-color: var(--text-link); |
|
|
} |
|
|
|
|
|
.minimap-heading { |
|
|
font-weight: normal; |
|
|
} |
|
|
|
|
|
.minimap-heading.h1 { padding-left: 0.5rem; } |
|
|
.minimap-heading.h2 { padding-left: 1rem; } |
|
|
.minimap-heading.h3 { padding-left: 1.5rem; } |
|
|
.minimap-heading.h4 { padding-left: 2rem; } |
|
|
.minimap-heading.h5 { padding-left: 2.5rem; } |
|
|
.minimap-heading.h6 { padding-left: 3rem; } |
|
|
|
|
|
.minimap-cell { |
|
|
color: var(--text-link); |
|
|
opacity: 0.8; |
|
|
font-style: italic; |
|
|
} |
|
|
|
|
|
.minimap-cell:hover { |
|
|
opacity: 1; |
|
|
} |
|
|
|
|
|
.file-explorer-title { |
|
|
font-weight: bold; |
|
|
color: var(--text-secondary); |
|
|
margin-bottom: 0.5rem; |
|
|
padding-bottom: 0.25rem; |
|
|
border-bottom: 1px solid var(--border-primary); |
|
|
cursor: grab; |
|
|
user-select: none; |
|
|
} |
|
|
|
|
|
.file-explorer-section { |
|
|
margin-bottom: 0.75rem; |
|
|
} |
|
|
|
|
|
.file-explorer-section-title { |
|
|
font-weight: bold; |
|
|
color: var(--text-secondary); |
|
|
font-size: 0.65rem; |
|
|
margin-bottom: 0.25rem; |
|
|
text-transform: uppercase; |
|
|
letter-spacing: 0.5px; |
|
|
} |
|
|
|
|
|
.file-explorer-item { |
|
|
display: block; |
|
|
color: var(--text-secondary); |
|
|
text-decoration: none; |
|
|
padding: 0.1rem 0; |
|
|
margin-left: 0.5rem; |
|
|
transition: color 0.2s ease; |
|
|
cursor: pointer; |
|
|
font-family: monospace; |
|
|
} |
|
|
|
|
|
.file-explorer-item:hover { |
|
|
color: var(--text-primary); |
|
|
} |
|
|
|
|
|
.file-explorer-item.script { |
|
|
color: var(--text-link); |
|
|
} |
|
|
|
|
|
.file-explorer-item.artifact { |
|
|
color: var(--text-secondary); |
|
|
opacity: 0.8; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@media (max-width: 768px) { |
|
|
.minimap, .file-explorer, .tools-widget { |
|
|
display: none; |
|
|
} |
|
|
} |
|
|
|
|
|
.cell { |
|
|
margin: 1rem 0; |
|
|
border: 1px solid var(--border-primary); |
|
|
border-radius: 2px; |
|
|
overflow: hidden; |
|
|
background: var(--bg-secondary); |
|
|
} |
|
|
.cell-header { |
|
|
background: var(--bg-secondary); |
|
|
padding: 0.5rem 1rem; |
|
|
border-bottom: 1px solid var(--border-primary); |
|
|
font-family: inherit; |
|
|
font-size: 0.85rem; |
|
|
color: var(--text-secondary); |
|
|
cursor: pointer; |
|
|
user-select: none; |
|
|
transition: background-color 0.2s ease; |
|
|
} |
|
|
.cell-header:hover { |
|
|
background: var(--bg-tertiary); |
|
|
} |
|
|
.collapse-indicators { |
|
|
color: var(--text-secondary); |
|
|
font-size: 0.8rem; |
|
|
opacity: 0.7; |
|
|
} |
|
|
.collapse-indicators span:hover { |
|
|
color: var(--text-primary); |
|
|
opacity: 1; |
|
|
} |
|
|
.cell-code { |
|
|
display: block; |
|
|
background: var(--bg-code); |
|
|
} |
|
|
.cell-code.collapsed { |
|
|
display: none; |
|
|
} |
|
|
.cell-code pre { |
|
|
margin: 0; |
|
|
padding: 0.75rem; |
|
|
background: var(--bg-code); |
|
|
overflow-x: auto; |
|
|
color: var(--text-primary); |
|
|
} |
|
|
.cell-output { |
|
|
padding: 0.75rem; |
|
|
background: var(--bg-primary); |
|
|
} |
|
|
.cell-output.collapsed { |
|
|
display: none; |
|
|
} |
|
|
.cell-stdout { |
|
|
background: var(--bg-tertiary); |
|
|
padding: 0.75rem; |
|
|
border-radius: 1px; |
|
|
margin: 0.25rem 0; |
|
|
font-family: inherit; |
|
|
font-size: 0.9rem; |
|
|
white-space: pre-wrap; |
|
|
color: var(--text-primary); |
|
|
} |
|
|
.cell-stderr { |
|
|
background: var(--bg-error); |
|
|
border-left: 2px solid var(--border-error); |
|
|
padding: 1rem; |
|
|
margin: 0.5rem 0; |
|
|
font-family: inherit; |
|
|
font-size: 0.9rem; |
|
|
color: var(--text-error); |
|
|
white-space: pre-wrap; |
|
|
} |
|
|
.uv-install-logs { |
|
|
margin: 0.5rem 0; |
|
|
} |
|
|
.uv-logs-header { |
|
|
cursor: pointer; |
|
|
padding: 0.75rem; |
|
|
border-left: 3px solid var(--border-color); |
|
|
font-family: inherit; |
|
|
font-size: 0.85rem; |
|
|
color: var(--text-secondary); |
|
|
user-select: none; |
|
|
} |
|
|
.uv-logs-content { |
|
|
background: var(--bg-secondary); |
|
|
padding: 1rem; |
|
|
border-left: 3px solid var(--border-color); |
|
|
white-space: pre-wrap; |
|
|
font-family: monospace; |
|
|
font-size: 0.85rem; |
|
|
color: var(--text-secondary); |
|
|
overflow-x: auto; |
|
|
} |
|
|
.cell-artifacts { |
|
|
margin: 1rem 0; |
|
|
} |
|
|
.cell-artifacts h4 { |
|
|
margin: 0 0 0.5rem 0; |
|
|
color: var(--text-secondary); |
|
|
font-size: 0.9rem; |
|
|
} |
|
|
.artifact { |
|
|
display: inline-block; |
|
|
background: var(--bg-artifact); |
|
|
padding: 0.25rem 0.5rem; |
|
|
border-radius: 1px; |
|
|
margin: 0.25rem 0.5rem 0.25rem 0; |
|
|
font-family: inherit; |
|
|
font-size: 0.8rem; |
|
|
color: var(--text-link); |
|
|
text-decoration: none; |
|
|
transition: background-color 0.2s ease; |
|
|
border: 1px solid var(--border-primary); |
|
|
} |
|
|
.artifact:hover { |
|
|
background: var(--bg-artifact-hover); |
|
|
} |
|
|
.artifact-preview { |
|
|
margin-top: 1rem; |
|
|
} |
|
|
.artifact-preview img { |
|
|
max-width: 100%; |
|
|
height: auto; |
|
|
border: 1px solid var(--border-primary); |
|
|
border-radius: 1px; |
|
|
} |
|
|
.artifact-preview svg { |
|
|
max-width: 100%; |
|
|
height: auto; |
|
|
border: 1px solid var(--border-primary); |
|
|
border-radius: 1px; |
|
|
display: block; |
|
|
} |
|
|
|
|
|
.artifact-preview svg g { |
|
|
fill: var(--text-primary) !important; |
|
|
} |
|
|
|
|
|
.artifact-preview svg { |
|
|
background: transparent; |
|
|
} |
|
|
.cell-failed { |
|
|
border-color: var(--border-cell-failed); |
|
|
} |
|
|
.cell-failed .cell-header { |
|
|
background: var(--bg-error); |
|
|
color: var(--text-error); |
|
|
} |
|
|
.cell-commented { |
|
|
opacity: 0.6; |
|
|
border-style: dashed; |
|
|
} |
|
|
.cell-commented .cell-header { |
|
|
background: var(--bg-secondary); |
|
|
color: var(--text-secondary); |
|
|
font-style: italic; |
|
|
} |
|
|
.run-btn { |
|
|
background: var(--bg-tertiary); |
|
|
border: 1px solid var(--border-primary); |
|
|
padding: 2px 6px; |
|
|
border-radius: 2px; |
|
|
color: var(--text-secondary); |
|
|
cursor: pointer; |
|
|
font-size: 0.75em; |
|
|
font-family: inherit; |
|
|
margin-left: 4px; |
|
|
} |
|
|
.run-btn:hover { |
|
|
color: var(--text-primary); |
|
|
background: var(--bg-primary); |
|
|
} |
|
|
.run-btn:disabled { |
|
|
opacity: 0.6; |
|
|
cursor: not-allowed; |
|
|
} |
|
|
.copy-btn { |
|
|
background: var(--bg-tertiary); |
|
|
border: 1px solid var(--border-primary); |
|
|
padding: 2px 6px; |
|
|
border-radius: 2px; |
|
|
color: var(--text-secondary); |
|
|
cursor: pointer; |
|
|
font-size: 0.75em; |
|
|
font-family: inherit; |
|
|
margin-left: 4px; |
|
|
} |
|
|
.copy-btn:hover { |
|
|
color: var(--text-primary); |
|
|
background: var(--bg-primary); |
|
|
} |
|
|
.copy-btn:disabled { |
|
|
opacity: 0.6; |
|
|
cursor: not-allowed; |
|
|
} |
|
|
.raw-btn { |
|
|
background: var(--bg-tertiary); |
|
|
border: 1px solid var(--border-primary); |
|
|
padding: 2px 6px; |
|
|
border-radius: 2px; |
|
|
color: var(--text-secondary); |
|
|
cursor: pointer; |
|
|
font-size: 0.75em; |
|
|
font-family: inherit; |
|
|
margin-left: 4px; |
|
|
text-decoration: none; |
|
|
display: inline-block; |
|
|
} |
|
|
.raw-btn:hover { |
|
|
color: var(--text-primary); |
|
|
background: var(--bg-primary); |
|
|
text-decoration: none; |
|
|
} |
|
|
.output-stale { |
|
|
opacity: 0.5; |
|
|
position: relative; |
|
|
} |
|
|
.output-stale::after { |
|
|
content: '⏳ updating...'; |
|
|
position: absolute; |
|
|
top: 8px; |
|
|
right: 8px; |
|
|
background: var(--bg-secondary); |
|
|
padding: 4px 8px; |
|
|
border-radius: 2px; |
|
|
font-size: 0.75em; |
|
|
color: var(--text-secondary); |
|
|
border: 1px solid var(--border-primary); |
|
|
} |
|
|
h1, h2, h3, h4, h5, h6 { |
|
|
margin-top: 1.5rem; |
|
|
margin-bottom: 0.75rem; |
|
|
color: var(--text-primary); |
|
|
} |
|
|
h1 { |
|
|
margin-top: 0; |
|
|
margin-bottom: 1rem; |
|
|
} |
|
|
p { |
|
|
margin: 0.75rem 0; |
|
|
color: var(--text-primary); |
|
|
} |
|
|
a { |
|
|
color: var(--text-link); |
|
|
} |
|
|
img { |
|
|
max-width: 100%; |
|
|
height: auto; |
|
|
border-radius: 1px; |
|
|
box-shadow: none; |
|
|
} |
|
|
pre, code { |
|
|
font-family: 'Cascadia Mono', 'Cascadia Code', 'JetBrains Mono', 'SF Mono', Monaco, 'Consolas', monospace; |
|
|
} |
|
|
|
|
|
|
|
|
.highlight-with-lines { |
|
|
display: flex; |
|
|
} |
|
|
.line-numbers { |
|
|
background: var(--bg-tertiary); |
|
|
padding: 0.75rem 0.5rem; |
|
|
font-family: 'Cascadia Mono', 'Cascadia Code', 'JetBrains Mono', 'SF Mono', Monaco, 'Consolas', monospace; |
|
|
font-size: 0.9rem; |
|
|
color: var(--text-secondary); |
|
|
user-select: none; |
|
|
text-align: right; |
|
|
border-right: 1px solid var(--border-primary); |
|
|
} |
|
|
.line-numbers .line-number { |
|
|
display: block; |
|
|
line-height: 1.5; |
|
|
} |
|
|
.highlight-with-lines .highlight { |
|
|
flex: 1; |
|
|
} |
|
|
.highlight-with-lines .highlight pre { |
|
|
padding-left: 0.75rem; |
|
|
} |
|
|
|
|
|
|
|
|
.cell-code.collapsed { |
|
|
display: none; |
|
|
} |
|
|
.cell-code.expanded { |
|
|
display: block; |
|
|
} |
|
|
|
|
|
.cell-code { |
|
|
display: block; |
|
|
} |
|
|
|
|
|
|
|
|
pre { line-height: 125%; } |
|
|
td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } |
|
|
span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } |
|
|
td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } |
|
|
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } |
|
|
[data-theme="light"] .highlight .hll { background-color: #ffffcc } |
|
|
[data-theme="light"] .highlight { background: #f8f8f8; } |
|
|
[data-theme="light"] .highlight .c { color: #3D7B7B; font-style: italic } |
|
|
[data-theme="light"] .highlight .err { border: 1px solid #F00 } |
|
|
[data-theme="light"] .highlight .k { color: #008000; font-weight: bold } |
|
|
[data-theme="light"] .highlight .o { color: #666 } |
|
|
[data-theme="light"] .highlight .ch { color: #3D7B7B; font-style: italic } |
|
|
[data-theme="light"] .highlight .cm { color: #3D7B7B; font-style: italic } |
|
|
[data-theme="light"] .highlight .cp { color: #9C6500 } |
|
|
[data-theme="light"] .highlight .cpf { color: #3D7B7B; font-style: italic } |
|
|
[data-theme="light"] .highlight .c1 { color: #3D7B7B; font-style: italic } |
|
|
[data-theme="light"] .highlight .cs { color: #3D7B7B; font-style: italic } |
|
|
[data-theme="light"] .highlight .gd { color: #A00000 } |
|
|
[data-theme="light"] .highlight .ge { font-style: italic } |
|
|
[data-theme="light"] .highlight .ges { font-weight: bold; font-style: italic } |
|
|
[data-theme="light"] .highlight .gr { color: #E40000 } |
|
|
[data-theme="light"] .highlight .gh { color: #000080; font-weight: bold } |
|
|
[data-theme="light"] .highlight .gi { color: #008400 } |
|
|
[data-theme="light"] .highlight .go { color: #717171 } |
|
|
[data-theme="light"] .highlight .gp { color: #000080; font-weight: bold } |
|
|
[data-theme="light"] .highlight .gs { font-weight: bold } |
|
|
[data-theme="light"] .highlight .gu { color: #800080; font-weight: bold } |
|
|
[data-theme="light"] .highlight .gt { color: #04D } |
|
|
[data-theme="light"] .highlight .kc { color: #008000; font-weight: bold } |
|
|
[data-theme="light"] .highlight .kd { color: #008000; font-weight: bold } |
|
|
[data-theme="light"] .highlight .kn { color: #008000; font-weight: bold } |
|
|
[data-theme="light"] .highlight .kp { color: #008000 } |
|
|
[data-theme="light"] .highlight .kr { color: #008000; font-weight: bold } |
|
|
[data-theme="light"] .highlight .kt { color: #B00040 } |
|
|
[data-theme="light"] .highlight .m { color: #666 } |
|
|
[data-theme="light"] .highlight .s { color: #BA2121 } |
|
|
[data-theme="light"] .highlight .na { color: #687822 } |
|
|
[data-theme="light"] .highlight .nb { color: #008000 } |
|
|
[data-theme="light"] .highlight .nc { color: #00F; font-weight: bold } |
|
|
[data-theme="light"] .highlight .no { color: #800 } |
|
|
[data-theme="light"] .highlight .nd { color: #A2F } |
|
|
[data-theme="light"] .highlight .ni { color: #717171; font-weight: bold } |
|
|
[data-theme="light"] .highlight .ne { color: #CB3F38; font-weight: bold } |
|
|
[data-theme="light"] .highlight .nf { color: #00F } |
|
|
[data-theme="light"] .highlight .nl { color: #767600 } |
|
|
[data-theme="light"] .highlight .nn { color: #00F; font-weight: bold } |
|
|
[data-theme="light"] .highlight .nt { color: #008000; font-weight: bold } |
|
|
[data-theme="light"] .highlight .nv { color: #19177C } |
|
|
[data-theme="light"] .highlight .ow { color: #A2F; font-weight: bold } |
|
|
[data-theme="light"] .highlight .w { color: #BBB } |
|
|
[data-theme="light"] .highlight .mb { color: #666 } |
|
|
[data-theme="light"] .highlight .mf { color: #666 } |
|
|
[data-theme="light"] .highlight .mh { color: #666 } |
|
|
[data-theme="light"] .highlight .mi { color: #666 } |
|
|
[data-theme="light"] .highlight .mo { color: #666 } |
|
|
[data-theme="light"] .highlight .sa { color: #BA2121 } |
|
|
[data-theme="light"] .highlight .sb { color: #BA2121 } |
|
|
[data-theme="light"] .highlight .sc { color: #BA2121 } |
|
|
[data-theme="light"] .highlight .dl { color: #BA2121 } |
|
|
[data-theme="light"] .highlight .sd { color: #BA2121; font-style: italic } |
|
|
[data-theme="light"] .highlight .s2 { color: #BA2121 } |
|
|
[data-theme="light"] .highlight .se { color: #AA5D1F; font-weight: bold } |
|
|
[data-theme="light"] .highlight .sh { color: #BA2121 } |
|
|
[data-theme="light"] .highlight .si { color: #A45A77; font-weight: bold } |
|
|
[data-theme="light"] .highlight .sx { color: #008000 } |
|
|
[data-theme="light"] .highlight .sr { color: #A45A77 } |
|
|
[data-theme="light"] .highlight .s1 { color: #BA2121 } |
|
|
[data-theme="light"] .highlight .ss { color: #19177C } |
|
|
[data-theme="light"] .highlight .bp { color: #008000 } |
|
|
[data-theme="light"] .highlight .fm { color: #00F } |
|
|
[data-theme="light"] .highlight .vc { color: #19177C } |
|
|
[data-theme="light"] .highlight .vg { color: #19177C } |
|
|
[data-theme="light"] .highlight .vi { color: #19177C } |
|
|
[data-theme="light"] .highlight .vm { color: #19177C } |
|
|
[data-theme="light"] .highlight .il { color: #666 } |
|
|
|
|
|
pre { line-height: 125%; } |
|
|
td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } |
|
|
span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } |
|
|
td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } |
|
|
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } |
|
|
[data-theme="dark"] .highlight .hll { background-color: #49483e } |
|
|
[data-theme="dark"] .highlight { background: #272822; color: #F8F8F2 } |
|
|
[data-theme="dark"] .highlight .c { color: #959077 } |
|
|
[data-theme="dark"] .highlight .err { color: #ED007E; background-color: #1E0010 } |
|
|
[data-theme="dark"] .highlight .esc { color: #F8F8F2 } |
|
|
[data-theme="dark"] .highlight .g { color: #F8F8F2 } |
|
|
[data-theme="dark"] .highlight .k { color: #66D9EF } |
|
|
[data-theme="dark"] .highlight .l { color: #AE81FF } |
|
|
[data-theme="dark"] .highlight .n { color: #F8F8F2 } |
|
|
[data-theme="dark"] .highlight .o { color: #FF4689 } |
|
|
[data-theme="dark"] .highlight .x { color: #F8F8F2 } |
|
|
[data-theme="dark"] .highlight .p { color: #F8F8F2 } |
|
|
[data-theme="dark"] .highlight .ch { color: #959077 } |
|
|
[data-theme="dark"] .highlight .cm { color: #959077 } |
|
|
[data-theme="dark"] .highlight .cp { color: #959077 } |
|
|
[data-theme="dark"] .highlight .cpf { color: #959077 } |
|
|
[data-theme="dark"] .highlight .c1 { color: #959077 } |
|
|
[data-theme="dark"] .highlight .cs { color: #959077 } |
|
|
[data-theme="dark"] .highlight .gd { color: #FF4689 } |
|
|
[data-theme="dark"] .highlight .ge { color: #F8F8F2; font-style: italic } |
|
|
[data-theme="dark"] .highlight .ges { color: #F8F8F2; font-weight: bold; font-style: italic } |
|
|
[data-theme="dark"] .highlight .gr { color: #F8F8F2 } |
|
|
[data-theme="dark"] .highlight .gh { color: #F8F8F2 } |
|
|
[data-theme="dark"] .highlight .gi { color: #A6E22E } |
|
|
[data-theme="dark"] .highlight .go { color: #66D9EF } |
|
|
[data-theme="dark"] .highlight .gp { color: #FF4689; font-weight: bold } |
|
|
[data-theme="dark"] .highlight .gs { color: #F8F8F2; font-weight: bold } |
|
|
[data-theme="dark"] .highlight .gu { color: #959077 } |
|
|
[data-theme="dark"] .highlight .gt { color: #F8F8F2 } |
|
|
[data-theme="dark"] .highlight .kc { color: #66D9EF } |
|
|
[data-theme="dark"] .highlight .kd { color: #66D9EF } |
|
|
[data-theme="dark"] .highlight .kn { color: #FF4689 } |
|
|
[data-theme="dark"] .highlight .kp { color: #66D9EF } |
|
|
[data-theme="dark"] .highlight .kr { color: #66D9EF } |
|
|
[data-theme="dark"] .highlight .kt { color: #66D9EF } |
|
|
[data-theme="dark"] .highlight .ld { color: #E6DB74 } |
|
|
[data-theme="dark"] .highlight .m { color: #AE81FF } |
|
|
[data-theme="dark"] .highlight .s { color: #E6DB74 } |
|
|
[data-theme="dark"] .highlight .na { color: #A6E22E } |
|
|
[data-theme="dark"] .highlight .nb { color: #F8F8F2 } |
|
|
[data-theme="dark"] .highlight .nc { color: #A6E22E } |
|
|
[data-theme="dark"] .highlight .no { color: #66D9EF } |
|
|
[data-theme="dark"] .highlight .nd { color: #A6E22E } |
|
|
[data-theme="dark"] .highlight .ni { color: #F8F8F2 } |
|
|
[data-theme="dark"] .highlight .ne { color: #A6E22E } |
|
|
[data-theme="dark"] .highlight .nf { color: #A6E22E } |
|
|
[data-theme="dark"] .highlight .nl { color: #F8F8F2 } |
|
|
[data-theme="dark"] .highlight .nn { color: #F8F8F2 } |
|
|
[data-theme="dark"] .highlight .nx { color: #A6E22E } |
|
|
[data-theme="dark"] .highlight .py { color: #F8F8F2 } |
|
|
[data-theme="dark"] .highlight .nt { color: #FF4689 } |
|
|
[data-theme="dark"] .highlight .nv { color: #F8F8F2 } |
|
|
[data-theme="dark"] .highlight .ow { color: #FF4689 } |
|
|
[data-theme="dark"] .highlight .pm { color: #F8F8F2 } |
|
|
[data-theme="dark"] .highlight .w { color: #F8F8F2 } |
|
|
[data-theme="dark"] .highlight .mb { color: #AE81FF } |
|
|
[data-theme="dark"] .highlight .mf { color: #AE81FF } |
|
|
[data-theme="dark"] .highlight .mh { color: #AE81FF } |
|
|
[data-theme="dark"] .highlight .mi { color: #AE81FF } |
|
|
[data-theme="dark"] .highlight .mo { color: #AE81FF } |
|
|
[data-theme="dark"] .highlight .sa { color: #E6DB74 } |
|
|
[data-theme="dark"] .highlight .sb { color: #E6DB74 } |
|
|
[data-theme="dark"] .highlight .sc { color: #E6DB74 } |
|
|
[data-theme="dark"] .highlight .dl { color: #E6DB74 } |
|
|
[data-theme="dark"] .highlight .sd { color: #E6DB74 } |
|
|
[data-theme="dark"] .highlight .s2 { color: #E6DB74 } |
|
|
[data-theme="dark"] .highlight .se { color: #AE81FF } |
|
|
[data-theme="dark"] .highlight .sh { color: #E6DB74 } |
|
|
[data-theme="dark"] .highlight .si { color: #E6DB74 } |
|
|
[data-theme="dark"] .highlight .sx { color: #E6DB74 } |
|
|
[data-theme="dark"] .highlight .sr { color: #E6DB74 } |
|
|
[data-theme="dark"] .highlight .s1 { color: #E6DB74 } |
|
|
[data-theme="dark"] .highlight .ss { color: #E6DB74 } |
|
|
[data-theme="dark"] .highlight .bp { color: #F8F8F2 } |
|
|
[data-theme="dark"] .highlight .fm { color: #A6E22E } |
|
|
[data-theme="dark"] .highlight .vc { color: #F8F8F2 } |
|
|
[data-theme="dark"] .highlight .vg { color: #F8F8F2 } |
|
|
[data-theme="dark"] .highlight .vi { color: #F8F8F2 } |
|
|
[data-theme="dark"] .highlight .vm { color: #F8F8F2 } |
|
|
[data-theme="dark"] .highlight .il { color: #AE81FF } |
|
|
|
|
|
|
|
|
#output-setup { |
|
|
overflow-x: auto; |
|
|
} |
|
|
.cell-stdout { |
|
|
width: max-content; |
|
|
} |
|
|
|
|
|
|
|
|
body[data-tool="arrow"] .main-content { |
|
|
cursor: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="%23e53935" stroke-width="2"><path d="M12 19l7-7 3 3-7 7-3-3z"/><path d="M18 13l-1.5-7.5L2 2l3.5 14.5L13 18l5-5z"/><path d="M2 2l7.586 7.586"/><circle cx="11" cy="11" r="2"/></svg>') 12 12, crosshair; |
|
|
} |
|
|
body[data-tool="pen"] .main-content { |
|
|
cursor: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="%23e53935" stroke-width="2"><path d="M12 19l7-7 3 3-7 7-3-3z"/><path d="M18 13l-1.5-7.5L2 2l3.5 14.5L13 18l5-5z"/><circle cx="4" cy="20" r="2" fill="%23e53935"/></svg>') 4 20, pointer; |
|
|
} |
|
|
body[data-tool="eraser"] .main-content { |
|
|
cursor: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="%23e53935" stroke-width="2"><path d="M20 20H7l-7-7 7-7h13v14z"/><path d="M13 13l7-7"/><path d="M13 13L9 9"/></svg>') 12 12, auto; |
|
|
} |
|
|
|
|
|
|
|
|
.tools-section-title { |
|
|
font-weight: bold; |
|
|
color: var(--text-secondary); |
|
|
font-size: 0.65rem; |
|
|
margin: 0.75rem 0 0.5rem 0; |
|
|
text-transform: uppercase; |
|
|
letter-spacing: 0.5px; |
|
|
} |
|
|
.color-row { |
|
|
display: grid; |
|
|
grid-template-columns: repeat(6, 1fr); |
|
|
gap: 0.25rem; |
|
|
margin-bottom: 0.5rem; |
|
|
} |
|
|
.color-swatch { |
|
|
width: 18px; |
|
|
height: 18px; |
|
|
border: 2px solid var(--border-primary); |
|
|
border-radius: 3px; |
|
|
cursor: pointer; |
|
|
transition: all 0.2s ease; |
|
|
position: relative; |
|
|
} |
|
|
.color-swatch:hover { |
|
|
transform: scale(1.1); |
|
|
border-color: var(--text-secondary); |
|
|
} |
|
|
.color-swatch.selected { |
|
|
border-color: var(--text-primary); |
|
|
box-shadow: 0 0 0 2px var(--text-link); |
|
|
} |
|
|
.color-swatch.selected::after { |
|
|
content: '✓'; |
|
|
position: absolute; |
|
|
top: 50%; |
|
|
left: 50%; |
|
|
transform: translate(-50%, -50%); |
|
|
color: white; |
|
|
font-size: 10px; |
|
|
font-weight: bold; |
|
|
text-shadow: 1px 1px 1px black; |
|
|
} |
|
|
.color-input { |
|
|
width: 24px; |
|
|
height: 24px; |
|
|
border: 2px solid var(--border-primary); |
|
|
border-radius: 3px; |
|
|
cursor: pointer; |
|
|
background: none; |
|
|
padding: 0; |
|
|
grid-column: span 2; |
|
|
justify-self: center; |
|
|
} |
|
|
.color-input:hover { |
|
|
border-color: var(--text-secondary); |
|
|
} |
|
|
|
|
|
|
|
|
.thickness-row { |
|
|
display: flex; |
|
|
align-items: center; |
|
|
gap: 0.5rem; |
|
|
margin-top: 0.75rem; |
|
|
} |
|
|
.thickness-slider { |
|
|
flex: 1; |
|
|
-webkit-appearance: none; |
|
|
appearance: none; |
|
|
height: 4px; |
|
|
background: var(--border-primary); |
|
|
border-radius: 2px; |
|
|
outline: none; |
|
|
opacity: 0.7; |
|
|
transition: opacity 0.2s; |
|
|
} |
|
|
.thickness-slider:hover { |
|
|
opacity: 1; |
|
|
} |
|
|
.thickness-slider::-webkit-slider-thumb { |
|
|
-webkit-appearance: none; |
|
|
appearance: none; |
|
|
width: 12px; |
|
|
height: 12px; |
|
|
background: var(--text-link); |
|
|
border-radius: 50%; |
|
|
cursor: pointer; |
|
|
} |
|
|
.thickness-slider::-moz-range-thumb { |
|
|
width: 12px; |
|
|
height: 12px; |
|
|
background: var(--text-link); |
|
|
border-radius: 50%; |
|
|
cursor: pointer; |
|
|
border: none; |
|
|
} |
|
|
.thickness-value { |
|
|
font-size: 0.7rem; |
|
|
color: var(--text-secondary); |
|
|
min-width: 20px; |
|
|
text-align: right; |
|
|
} |
|
|
|
|
|
.highlight { |
|
|
background: none !important; |
|
|
} |
|
|
|
|
|
|
|
|
.loading-spinner { |
|
|
display: inline-block; |
|
|
width: 16px; |
|
|
height: 16px; |
|
|
border: 2px solid var(--border-primary); |
|
|
border-radius: 50%; |
|
|
border-top-color: var(--text-link); |
|
|
animation: spin 1s linear infinite; |
|
|
margin-right: 8px; |
|
|
vertical-align: middle; |
|
|
} |
|
|
|
|
|
@keyframes spin { |
|
|
to { transform: rotate(360deg); } |
|
|
} |
|
|
|
|
|
.loading-skeleton { |
|
|
display: inline-block; |
|
|
background: var(--bg-tertiary); |
|
|
background: linear-gradient( |
|
|
90deg, |
|
|
var(--bg-tertiary) 25%, |
|
|
var(--bg-secondary) 50%, |
|
|
var(--bg-tertiary) 75% |
|
|
); |
|
|
background-size: 200% 100%; |
|
|
animation: loading-shimmer 2s ease-in-out infinite; |
|
|
border-radius: 2px; |
|
|
height: 1em; |
|
|
width: 80px; |
|
|
vertical-align: middle; |
|
|
} |
|
|
|
|
|
@keyframes loading-shimmer { |
|
|
0% { background-position: -200% 0; } |
|
|
100% { background-position: 200% 0; } |
|
|
} |
|
|
|
|
|
|
|
|
.cell-output:has(.loading-spinner) { |
|
|
opacity: 0.7; |
|
|
background: var(--bg-secondary); |
|
|
border-left: 3px solid var(--text-link); |
|
|
} |
|
|
</style> |
|
|
<script> |
|
|
|
|
|
function clamp(val, min, max) { return Math.max(min, Math.min(max, val)); } |
|
|
|
|
|
function restorePosition(el, storageKey) { |
|
|
try { |
|
|
const raw = localStorage.getItem(storageKey); |
|
|
if (!raw) return; |
|
|
const pos = JSON.parse(raw); |
|
|
if (typeof pos.left === 'number' && typeof pos.top === 'number') { |
|
|
el.style.left = pos.left + 'px'; |
|
|
el.style.top = pos.top + 'px'; |
|
|
el.style.right = 'auto'; |
|
|
el.style.bottom = 'auto'; |
|
|
} |
|
|
} catch (_) {} |
|
|
} |
|
|
|
|
|
function savePosition(el, storageKey) { |
|
|
try { |
|
|
const left = parseFloat(el.style.left || 'NaN'); |
|
|
const top = parseFloat(el.style.top || 'NaN'); |
|
|
if (!Number.isNaN(left) && !Number.isNaN(top)) { |
|
|
localStorage.setItem(storageKey, JSON.stringify({ left, top })); |
|
|
} |
|
|
} catch (_) {} |
|
|
} |
|
|
|
|
|
|
|
|
function makeDraggable(el, storageKey, handleEl) { |
|
|
let dragging = false; |
|
|
let startX = 0, startY = 0; |
|
|
let origLeft = 0, origTop = 0; |
|
|
|
|
|
const onMove = (e) => { |
|
|
if (!dragging) return; |
|
|
const clientX = e.touches ? e.touches[0].clientX : e.clientX; |
|
|
const clientY = e.touches ? e.touches[0].clientY : e.clientY; |
|
|
const dx = clientX - startX; |
|
|
const dy = clientY - startY; |
|
|
const w = el.offsetWidth; |
|
|
const h = el.offsetHeight; |
|
|
const maxX = window.innerWidth - w; |
|
|
const maxY = window.innerHeight - h; |
|
|
const newLeft = clamp(origLeft + dx, 0, maxX); |
|
|
const newTop = clamp(origTop + dy, 0, maxY); |
|
|
el.style.left = newLeft + 'px'; |
|
|
el.style.top = newTop + 'px'; |
|
|
el.style.right = 'auto'; |
|
|
el.style.bottom = 'auto'; |
|
|
}; |
|
|
|
|
|
const endDrag = () => { |
|
|
if (!dragging) return; |
|
|
dragging = false; |
|
|
document.removeEventListener('mousemove', onMove); |
|
|
document.removeEventListener('mouseup', endDrag); |
|
|
document.removeEventListener('touchmove', onMove); |
|
|
document.removeEventListener('touchend', endDrag); |
|
|
handleEl && (handleEl.style.cursor = 'grab'); |
|
|
savePosition(el, storageKey); |
|
|
|
|
|
try { layoutWidgetsStackedBottomRight(); } catch (_) {} |
|
|
}; |
|
|
|
|
|
const startDrag = (e) => { |
|
|
|
|
|
const elRect = el.getBoundingClientRect(); |
|
|
el.style.left = elRect.left + 'px'; |
|
|
el.style.top = elRect.top + 'px'; |
|
|
el.style.right = 'auto'; |
|
|
el.style.bottom = 'auto'; |
|
|
|
|
|
dragging = true; |
|
|
startX = e.touches ? e.touches[0].clientX : e.clientX; |
|
|
startY = e.touches ? e.touches[0].clientY : e.clientY; |
|
|
origLeft = elRect.left; |
|
|
origTop = elRect.top; |
|
|
|
|
|
document.addEventListener('mousemove', onMove); |
|
|
document.addEventListener('mouseup', endDrag); |
|
|
document.addEventListener('touchmove', onMove, { passive: false }); |
|
|
document.addEventListener('touchend', endDrag); |
|
|
handleEl && (handleEl.style.cursor = 'grabbing'); |
|
|
e.preventDefault(); |
|
|
}; |
|
|
|
|
|
(handleEl || el).addEventListener('mousedown', startDrag); |
|
|
(handleEl || el).addEventListener('touchstart', startDrag, { passive: false }); |
|
|
|
|
|
|
|
|
restorePosition(el, storageKey); |
|
|
} |
|
|
function toggleCell(cellId) { |
|
|
const codeElement = document.getElementById('code-' + cellId); |
|
|
const outputElement = document.getElementById('output-' + cellId); |
|
|
|
|
|
if (codeElement) { |
|
|
codeElement.classList.toggle('collapsed'); |
|
|
} |
|
|
if (outputElement) { |
|
|
outputElement.classList.toggle('collapsed'); |
|
|
} |
|
|
|
|
|
updateIndicators(cellId); |
|
|
} |
|
|
|
|
|
function toggleCode(cellId) { |
|
|
const codeElement = document.getElementById('code-' + cellId); |
|
|
if (codeElement) { |
|
|
codeElement.classList.toggle('collapsed'); |
|
|
updateIndicators(cellId); |
|
|
} |
|
|
} |
|
|
|
|
|
function toggleOutput(cellId) { |
|
|
const outputElement = document.getElementById('output-' + cellId); |
|
|
if (outputElement) { |
|
|
outputElement.classList.toggle('collapsed'); |
|
|
updateIndicators(cellId); |
|
|
} |
|
|
} |
|
|
|
|
|
function toggleUvLogs(headerElement) { |
|
|
const contentElement = headerElement.nextElementSibling; |
|
|
if (contentElement) { |
|
|
const isCollapsed = contentElement.style.display === 'none'; |
|
|
contentElement.style.display = isCollapsed ? 'block' : 'none'; |
|
|
headerElement.textContent = isCollapsed ? '▼ UV Install Logs' : '▶ UV Install Logs'; |
|
|
|
|
|
|
|
|
const uvLogsDiv = headerElement.parentElement; |
|
|
if (uvLogsDiv && uvLogsDiv.id && uvLogsDiv.id.startsWith('uv-logs-')) { |
|
|
const cellId = uvLogsDiv.id.replace('uv-logs-', ''); |
|
|
const indicatorElement = document.getElementById('uv-indicator-' + cellId); |
|
|
if (indicatorElement) { |
|
|
indicatorElement.textContent = isCollapsed ? '▼ uv-logs' : '▶ uv-logs'; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
function toggleUvLogsFromHeader(cellId) { |
|
|
const uvLogsElement = document.getElementById('uv-logs-' + cellId); |
|
|
const indicatorElement = document.getElementById('uv-indicator-' + cellId); |
|
|
if (uvLogsElement) { |
|
|
const headerElement = uvLogsElement.querySelector('.uv-logs-header'); |
|
|
const contentElement = uvLogsElement.querySelector('.uv-logs-content'); |
|
|
if (contentElement && headerElement) { |
|
|
const isCollapsed = contentElement.style.display === 'none'; |
|
|
contentElement.style.display = isCollapsed ? 'block' : 'none'; |
|
|
headerElement.textContent = isCollapsed ? '▼ UV Install Logs' : '▶ UV Install Logs'; |
|
|
if (indicatorElement) { |
|
|
indicatorElement.textContent = isCollapsed ? '▼ uv-logs' : '▶ uv-logs'; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
function updateIndicators(cellId) { |
|
|
const codeElement = document.getElementById('code-' + cellId); |
|
|
const outputElement = document.getElementById('output-' + cellId); |
|
|
const indicators = document.querySelector(`[onclick*="${cellId}"]`)?.closest('.cell-header')?.querySelector('.collapse-indicators'); |
|
|
|
|
|
if (indicators) { |
|
|
const codeCollapsed = codeElement && codeElement.classList.contains('collapsed'); |
|
|
const outputCollapsed = outputElement && outputElement.classList.contains('collapsed'); |
|
|
|
|
|
const codeIcon = codeCollapsed ? '▶' : '▼'; |
|
|
const outputIcon = outputCollapsed ? '▶' : '▼'; |
|
|
|
|
|
const codeSpan = indicators.querySelector('[onclick*="toggleCode"]'); |
|
|
const outputSpan = indicators.querySelector('[onclick*="toggleOutput"]'); |
|
|
|
|
|
if (codeSpan) codeSpan.innerHTML = `${codeIcon} code`; |
|
|
if (outputSpan) outputSpan.innerHTML = `${outputIcon} output`; |
|
|
} |
|
|
} |
|
|
|
|
|
function toggleTheme() { |
|
|
const html = document.documentElement; |
|
|
const currentTheme = html.getAttribute('data-theme'); |
|
|
const newTheme = currentTheme === 'dark' ? 'light' : 'dark'; |
|
|
html.setAttribute('data-theme', newTheme); |
|
|
localStorage.setItem('uvnote-theme', newTheme); |
|
|
updateThemeIcon(); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
function updateThemeIcon() { |
|
|
const theme = document.documentElement.getAttribute('data-theme'); |
|
|
const toggle = document.querySelector('.theme-toggle'); |
|
|
if (toggle) { |
|
|
toggle.textContent = theme === 'dark' ? 'light' : 'dark'; |
|
|
} |
|
|
} |
|
|
|
|
|
function resetLayout() { |
|
|
try { |
|
|
|
|
|
const allKeys = Object.keys(localStorage); |
|
|
const uvnoteKeys = allKeys.filter(key => key.startsWith('uvnote-')); |
|
|
uvnoteKeys.forEach(k => localStorage.removeItem(k)); |
|
|
} catch (_) {} |
|
|
|
|
|
location.reload(); |
|
|
} |
|
|
|
|
|
function toggleMenu() { |
|
|
const menuButton = document.querySelector('.menu-button'); |
|
|
if (menuButton) { |
|
|
menuButton.classList.toggle('active'); |
|
|
} |
|
|
} |
|
|
|
|
|
function toggleWidget(widgetName) { |
|
|
let widget; |
|
|
let checkbox; |
|
|
|
|
|
|
|
|
const menuButton = document.querySelector('.menu-button'); |
|
|
if (menuButton) { |
|
|
menuButton.classList.remove('active'); |
|
|
} |
|
|
|
|
|
switch(widgetName) { |
|
|
case 'tools': |
|
|
widget = document.querySelector('.tools-widget'); |
|
|
checkbox = document.getElementById('checkbox-tools'); |
|
|
break; |
|
|
case 'file-explorer': |
|
|
widget = document.querySelector('.file-explorer'); |
|
|
checkbox = document.getElementById('checkbox-file-explorer'); |
|
|
break; |
|
|
case 'minimap': |
|
|
widget = document.querySelector('.minimap'); |
|
|
checkbox = document.getElementById('checkbox-minimap'); |
|
|
break; |
|
|
default: |
|
|
return; |
|
|
} |
|
|
|
|
|
if (widget && checkbox) { |
|
|
const isVisible = getComputedStyle(widget).display !== 'none'; |
|
|
widget.style.display = isVisible ? 'none' : 'block'; |
|
|
checkbox.textContent = isVisible ? '☐' : '☑'; |
|
|
|
|
|
|
|
|
try { |
|
|
localStorage.setItem(`uvnote-widget-${widgetName}`, isVisible ? 'hidden' : 'visible'); |
|
|
} catch (_) {} |
|
|
|
|
|
|
|
|
try { |
|
|
layoutWidgetsStackedBottomRight(); |
|
|
} catch (_) {} |
|
|
} |
|
|
} |
|
|
|
|
|
function initializeWidgetVisibility() { |
|
|
const widgets = [ |
|
|
{ name: 'tools', selector: '.tools-widget' }, |
|
|
{ name: 'file-explorer', selector: '.file-explorer' }, |
|
|
{ name: 'minimap', selector: '.minimap' } |
|
|
]; |
|
|
|
|
|
widgets.forEach(({ name, selector }) => { |
|
|
const savedState = localStorage.getItem(`uvnote-widget-${name}`) || 'hidden'; |
|
|
const widget = document.querySelector(selector); |
|
|
const checkbox = document.getElementById(`checkbox-${name}`); |
|
|
|
|
|
if (widget && checkbox) { |
|
|
const isVisible = savedState === 'visible'; |
|
|
widget.style.display = isVisible ? 'block' : 'none'; |
|
|
checkbox.textContent = isVisible ? '☑' : '☐'; |
|
|
} |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
document.addEventListener('click', function(event) { |
|
|
const menuButton = document.querySelector('.menu-button'); |
|
|
|
|
|
if (menuButton && !menuButton.contains(event.target)) { |
|
|
menuButton.classList.remove('active'); |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
function hasCustomWidgetPositions() { |
|
|
try { |
|
|
return ( |
|
|
localStorage.getItem('uvnote-minimap-pos') || |
|
|
localStorage.getItem('uvnote-file-explorer-pos') || |
|
|
localStorage.getItem('uvnote-tools-pos') |
|
|
); |
|
|
} catch (_) { return false; } |
|
|
} |
|
|
|
|
|
function rectsOverlap(r1, r2) { |
|
|
return !(r1.right <= r2.left || r2.right <= r1.left || r1.bottom <= r2.top || r2.bottom <= r1.top); |
|
|
} |
|
|
|
|
|
function widgetsOverlap(widgets) { |
|
|
for (let i = 0; i < widgets.length; i++) { |
|
|
const a = widgets[i]; |
|
|
const ra = a.getBoundingClientRect(); |
|
|
for (let j = i + 1; j < widgets.length; j++) { |
|
|
const b = widgets[j]; |
|
|
const rb = b.getBoundingClientRect(); |
|
|
if (rectsOverlap(ra, rb)) return true; |
|
|
} |
|
|
} |
|
|
return false; |
|
|
} |
|
|
|
|
|
function applyStackLayout(widgets, order) { |
|
|
if (!widgets.length) return; |
|
|
|
|
|
const fixedWidth = 220; |
|
|
widgets.forEach(el => { el.style.width = fixedWidth + 'px'; }); |
|
|
|
|
|
|
|
|
const gap = 12; |
|
|
const available = Math.max(0, window.innerHeight - 40 - gap * (order.length - 1)); |
|
|
const eachMax = Math.floor(available / order.length); |
|
|
order.forEach(el => { |
|
|
el.style.maxHeight = eachMax + 'px'; |
|
|
el.style.overflowY = 'auto'; |
|
|
}); |
|
|
|
|
|
|
|
|
let bottomOffset = 20; |
|
|
order.forEach(el => { |
|
|
el.style.left = 'auto'; |
|
|
el.style.top = 'auto'; |
|
|
el.style.right = '20px'; |
|
|
el.style.bottom = bottomOffset + 'px'; |
|
|
bottomOffset += el.offsetHeight + gap; |
|
|
}); |
|
|
} |
|
|
|
|
|
function layoutWidgetsStackedBottomRight() { |
|
|
const minimap = document.querySelector('.minimap'); |
|
|
const fileExplorer = document.querySelector('.file-explorer'); |
|
|
const tools = document.querySelector('.tools-widget'); |
|
|
const widgets = [minimap, fileExplorer, tools].filter(el => el && getComputedStyle(el).display !== 'none'); |
|
|
if (!widgets.length) return; |
|
|
|
|
|
const order = [minimap, fileExplorer, tools].filter(Boolean).filter(el => getComputedStyle(el).display !== 'none'); |
|
|
|
|
|
|
|
|
if (hasCustomWidgetPositions() && !widgetsOverlap(widgets)) return; |
|
|
|
|
|
applyStackLayout(widgets, order); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
let _minimapScrollContainer = null; |
|
|
let _minimapScrollHandler = null; |
|
|
function initMinimap() { |
|
|
|
|
|
const minimap = createMinimap(); |
|
|
document.body.appendChild(minimap); |
|
|
|
|
|
const mTitle = minimap.querySelector('.minimap-title'); |
|
|
makeDraggable(minimap, 'uvnote-minimap-pos', mTitle); |
|
|
|
|
|
|
|
|
_minimapScrollContainer = window; |
|
|
|
|
|
if (_minimapScrollContainer) { |
|
|
_minimapScrollHandler = () => updateMinimapActive(); |
|
|
if (_minimapScrollContainer === window) { |
|
|
window.addEventListener('scroll', _minimapScrollHandler); |
|
|
} else { |
|
|
_minimapScrollContainer.addEventListener('scroll', _minimapScrollHandler); |
|
|
} |
|
|
} |
|
|
updateMinimapActive(); |
|
|
} |
|
|
|
|
|
function teardownMinimap() { |
|
|
const minimap = document.querySelector('.minimap'); |
|
|
if (minimap && minimap.parentNode) minimap.parentNode.removeChild(minimap); |
|
|
if (_minimapScrollContainer && _minimapScrollHandler) { |
|
|
if (_minimapScrollContainer === window) { |
|
|
window.removeEventListener('scroll', _minimapScrollHandler); |
|
|
} else { |
|
|
_minimapScrollContainer.removeEventListener('scroll', _minimapScrollHandler); |
|
|
} |
|
|
} |
|
|
_minimapScrollContainer = null; |
|
|
_minimapScrollHandler = null; |
|
|
} |
|
|
|
|
|
function initFileExplorer() { |
|
|
|
|
|
const fileExplorer = createFileExplorer(); |
|
|
document.body.appendChild(fileExplorer); |
|
|
} |
|
|
|
|
|
function createMinimap() { |
|
|
const minimap = document.createElement('div'); |
|
|
minimap.className = 'minimap'; |
|
|
|
|
|
const title = document.createElement('div'); |
|
|
title.className = 'minimap-title'; |
|
|
title.textContent = 'navigation'; |
|
|
minimap.appendChild(title); |
|
|
|
|
|
|
|
|
const root = document.querySelector('.main-content') || document; |
|
|
const headings = root.querySelectorAll('h1, h2, h3, h4, h5, h6'); |
|
|
const cells = root.querySelectorAll('.cell'); |
|
|
|
|
|
|
|
|
const items = []; |
|
|
|
|
|
headings.forEach(heading => { |
|
|
const id = heading.id || generateId(heading.textContent); |
|
|
if (!heading.id) heading.id = id; |
|
|
|
|
|
items.push({ |
|
|
element: heading, |
|
|
type: 'heading', |
|
|
level: parseInt(heading.tagName.charAt(1)), |
|
|
text: heading.textContent.trim(), |
|
|
id: id, |
|
|
position: heading.getBoundingClientRect().top + window.scrollY |
|
|
}); |
|
|
}); |
|
|
|
|
|
cells.forEach(cell => { |
|
|
const header = cell.querySelector('.cell-header'); |
|
|
if (header) { |
|
|
const id = cell.id || `cell-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; |
|
|
if (!cell.id) cell.id = id; |
|
|
|
|
|
items.push({ |
|
|
element: cell, |
|
|
type: 'cell', |
|
|
text: header.textContent.trim(), |
|
|
id: id, |
|
|
position: cell.getBoundingClientRect().top + window.scrollY |
|
|
}); |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
items.sort((a, b) => a.position - b.position); |
|
|
|
|
|
|
|
|
items.forEach(item => { |
|
|
const link = document.createElement('a'); |
|
|
link.className = `minimap-item ${item.type === 'heading' ? 'minimap-heading' : 'minimap-cell'}`; |
|
|
if (item.type === 'heading') { |
|
|
link.classList.add(`h${item.level}`); |
|
|
} |
|
|
link.textContent = item.text.length > 25 ? item.text.substring(0, 22) + '...' : item.text; |
|
|
link.href = `#${item.id}`; |
|
|
link.onclick = function(e) { |
|
|
e.preventDefault(); |
|
|
item.element.scrollIntoView({ behavior: 'smooth', block: 'start' }); |
|
|
}; |
|
|
minimap.appendChild(link); |
|
|
}); |
|
|
|
|
|
return minimap; |
|
|
} |
|
|
|
|
|
function generateId(text) { |
|
|
return text.toLowerCase() |
|
|
.replace(/[^a-z0-9]+/g, '-') |
|
|
.replace(/^-+|-+$/g, '') |
|
|
.substring(0, 20); |
|
|
} |
|
|
|
|
|
function updateMinimapActive() { |
|
|
const minimapItems = document.querySelectorAll('.minimap-item'); |
|
|
const container = _minimapScrollContainer || window; |
|
|
const containerRect = container === window ? null : container.getBoundingClientRect(); |
|
|
const scrollPos = (container === window ? window.scrollY : container.scrollTop) + 100; |
|
|
|
|
|
let activeItem = null; |
|
|
minimapItems.forEach(item => { |
|
|
const targetId = item.getAttribute('href').substring(1); |
|
|
const target = document.getElementById(targetId); |
|
|
|
|
|
if (target) { |
|
|
const rectTop = target.getBoundingClientRect().top; |
|
|
const targetPos = (container === window) |
|
|
? rectTop + window.scrollY |
|
|
: rectTop - containerRect.top + container.scrollTop; |
|
|
if (targetPos <= scrollPos) { |
|
|
activeItem = item; |
|
|
} |
|
|
} |
|
|
|
|
|
item.classList.remove('active'); |
|
|
}); |
|
|
|
|
|
if (activeItem) { |
|
|
activeItem.classList.add('active'); |
|
|
} |
|
|
} |
|
|
|
|
|
function createFileExplorer() { |
|
|
const fileExplorer = document.createElement('div'); |
|
|
fileExplorer.className = 'file-explorer'; |
|
|
|
|
|
const title = document.createElement('div'); |
|
|
title.className = 'file-explorer-title'; |
|
|
title.textContent = 'files'; |
|
|
fileExplorer.appendChild(title); |
|
|
|
|
|
makeDraggable(fileExplorer, 'uvnote-file-explorer-pos', title); |
|
|
|
|
|
|
|
|
const scriptsSection = document.createElement('div'); |
|
|
scriptsSection.className = 'file-explorer-section'; |
|
|
|
|
|
const scriptsTitle = document.createElement('div'); |
|
|
scriptsTitle.className = 'file-explorer-section-title'; |
|
|
scriptsTitle.textContent = 'scripts'; |
|
|
scriptsSection.appendChild(scriptsTitle); |
|
|
|
|
|
|
|
|
const root = document.querySelector('.main-content') || document; |
|
|
const cells = root.querySelectorAll('.cell'); |
|
|
cells.forEach(cell => { |
|
|
const header = cell.querySelector('.cell-header'); |
|
|
if (header) { |
|
|
const cellText = header.textContent.trim(); |
|
|
const cellMatch = cellText.match(/Cell: ([a-zA-Z_][a-zA-Z0-9_]*)/); |
|
|
if (cellMatch) { |
|
|
const cellId = cellMatch[1]; |
|
|
const scriptItem = document.createElement('div'); |
|
|
scriptItem.className = 'file-explorer-item script'; |
|
|
scriptItem.textContent = `${cellId}.py`; |
|
|
scriptItem.onclick = function() { |
|
|
cell.scrollIntoView({ behavior: 'smooth', block: 'start' }); |
|
|
}; |
|
|
scriptsSection.appendChild(scriptItem); |
|
|
} |
|
|
} |
|
|
}); |
|
|
|
|
|
fileExplorer.appendChild(scriptsSection); |
|
|
|
|
|
|
|
|
const artifactsSection = document.createElement('div'); |
|
|
artifactsSection.className = 'file-explorer-section'; |
|
|
|
|
|
const artifactsTitle = document.createElement('div'); |
|
|
artifactsTitle.className = 'file-explorer-section-title'; |
|
|
artifactsTitle.textContent = 'artifacts'; |
|
|
artifactsSection.appendChild(artifactsTitle); |
|
|
|
|
|
|
|
|
const artifactsRoot = document.querySelector('.main-content') || document; |
|
|
const artifacts = artifactsRoot.querySelectorAll('.artifact'); |
|
|
if (artifacts.length === 0) { |
|
|
const noArtifacts = document.createElement('div'); |
|
|
noArtifacts.className = 'file-explorer-item artifact'; |
|
|
noArtifacts.textContent = '(none)'; |
|
|
noArtifacts.style.opacity = '0.5'; |
|
|
artifactsSection.appendChild(noArtifacts); |
|
|
} else { |
|
|
artifacts.forEach(artifact => { |
|
|
const artifactItem = document.createElement('div'); |
|
|
artifactItem.className = 'file-explorer-item artifact'; |
|
|
artifactItem.textContent = artifact.textContent; |
|
|
artifactItem.onclick = function() { |
|
|
artifact.click(); |
|
|
}; |
|
|
artifactsSection.appendChild(artifactItem); |
|
|
}); |
|
|
} |
|
|
|
|
|
fileExplorer.appendChild(artifactsSection); |
|
|
|
|
|
return fileExplorer; |
|
|
} |
|
|
|
|
|
|
|
|
let _cursorX = 0; |
|
|
let _cursorY = 0; |
|
|
let _cursorVisible = false; |
|
|
|
|
|
function setActiveTool(tool) { |
|
|
if (!tool || tool === 'none') { |
|
|
document.body.dataset.tool = 'none'; |
|
|
localStorage.setItem('uvnote-active-tool', 'none'); |
|
|
setOverlayActive(false); |
|
|
_cursorVisible = false; |
|
|
|
|
|
const toolButtons = document.querySelectorAll('.tools-widget .tool-button'); |
|
|
toolButtons.forEach(btn => btn.classList.remove('active')); |
|
|
return; |
|
|
} |
|
|
document.body.dataset.tool = tool; |
|
|
localStorage.setItem('uvnote-active-tool', tool); |
|
|
setOverlayActive(true); |
|
|
_cursorVisible = true; |
|
|
} |
|
|
|
|
|
|
|
|
window.setActiveTool = setActiveTool; |
|
|
|
|
|
function getArrowColor() { |
|
|
const saved = localStorage.getItem('uvnote-arrow-color'); |
|
|
if (saved) return saved; |
|
|
return '#e53935'; |
|
|
} |
|
|
|
|
|
function setStoredArrowColor(color) { |
|
|
try { localStorage.setItem('uvnote-arrow-color', color); } catch (_) {} |
|
|
} |
|
|
|
|
|
function getLineThickness() { |
|
|
const saved = localStorage.getItem('uvnote-line-thickness'); |
|
|
if (saved) return parseInt(saved, 10); |
|
|
return 4; |
|
|
} |
|
|
|
|
|
function setStoredLineThickness(thickness) { |
|
|
try { localStorage.setItem('uvnote-line-thickness', thickness); } catch (_) {} |
|
|
} |
|
|
|
|
|
function getFadeoutTime() { |
|
|
const saved = localStorage.getItem('uvnote-fadeout-time'); |
|
|
if (saved) return parseInt(saved, 10); |
|
|
return 5; |
|
|
} |
|
|
|
|
|
function setStoredFadeoutTime(seconds) { |
|
|
try { localStorage.setItem('uvnote-fadeout-time', seconds); } catch (_) {} |
|
|
} |
|
|
|
|
|
function createToolsWidget() { |
|
|
const tools = document.createElement('div'); |
|
|
tools.className = 'tools-widget'; |
|
|
|
|
|
const title = document.createElement('div'); |
|
|
title.className = 'tools-title'; |
|
|
title.textContent = 'tools'; |
|
|
tools.appendChild(title); |
|
|
|
|
|
const row = document.createElement('div'); |
|
|
row.className = 'tools-row'; |
|
|
tools.appendChild(row); |
|
|
|
|
|
|
|
|
const arrowBtn = document.createElement('div'); |
|
|
arrowBtn.className = 'tool-button'; |
|
|
arrowBtn.textContent = 'arrow'; |
|
|
arrowBtn.onclick = function() { |
|
|
const isActive = arrowBtn.classList.contains('active'); |
|
|
if (isActive) { |
|
|
arrowBtn.classList.remove('active'); |
|
|
setActiveTool('none'); |
|
|
} else { |
|
|
tools.querySelectorAll('.tool-button').forEach(b => b.classList.remove('active')); |
|
|
arrowBtn.classList.add('active'); |
|
|
setActiveTool('arrow'); |
|
|
} |
|
|
}; |
|
|
row.appendChild(arrowBtn); |
|
|
|
|
|
|
|
|
const penBtn = document.createElement('div'); |
|
|
penBtn.className = 'tool-button'; |
|
|
penBtn.textContent = 'pen'; |
|
|
penBtn.onclick = function() { |
|
|
const isActive = penBtn.classList.contains('active'); |
|
|
if (isActive) { |
|
|
penBtn.classList.remove('active'); |
|
|
setActiveTool('none'); |
|
|
} else { |
|
|
tools.querySelectorAll('.tool-button').forEach(b => b.classList.remove('active')); |
|
|
penBtn.classList.add('active'); |
|
|
setActiveTool('pen'); |
|
|
} |
|
|
}; |
|
|
row.appendChild(penBtn); |
|
|
|
|
|
|
|
|
const eraseBtn = document.createElement('div'); |
|
|
eraseBtn.className = 'tool-button'; |
|
|
eraseBtn.textContent = 'eraser'; |
|
|
eraseBtn.onclick = function() { |
|
|
const isActive = eraseBtn.classList.contains('active'); |
|
|
if (isActive) { |
|
|
eraseBtn.classList.remove('active'); |
|
|
setActiveTool('none'); |
|
|
} else { |
|
|
tools.querySelectorAll('.tool-button').forEach(b => b.classList.remove('active')); |
|
|
eraseBtn.classList.add('active'); |
|
|
setActiveTool('eraser'); |
|
|
} |
|
|
}; |
|
|
row.appendChild(eraseBtn); |
|
|
|
|
|
|
|
|
const spotlightBtn = document.createElement('div'); |
|
|
spotlightBtn.className = 'tool-button'; |
|
|
spotlightBtn.textContent = 'spotlight'; |
|
|
spotlightBtn.onclick = function() { |
|
|
const isActive = spotlightBtn.classList.contains('active'); |
|
|
if (isActive) { |
|
|
spotlightBtn.classList.remove('active'); |
|
|
setActiveTool('none'); |
|
|
} else { |
|
|
tools.querySelectorAll('.tool-button').forEach(b => b.classList.remove('active')); |
|
|
spotlightBtn.classList.add('active'); |
|
|
setActiveTool('spotlight'); |
|
|
} |
|
|
}; |
|
|
row.appendChild(spotlightBtn); |
|
|
|
|
|
|
|
|
const clearBtn = document.createElement('div'); |
|
|
clearBtn.className = 'tool-button'; |
|
|
clearBtn.textContent = 'clear'; |
|
|
clearBtn.onclick = function() { |
|
|
_shapes = []; |
|
|
saveShapes(); |
|
|
renderOverlay(); |
|
|
}; |
|
|
row.appendChild(clearBtn); |
|
|
|
|
|
|
|
|
const saved = localStorage.getItem('uvnote-active-tool') || 'none'; |
|
|
if (saved === 'arrow') { |
|
|
arrowBtn.classList.add('active'); |
|
|
setActiveTool('arrow'); |
|
|
} else if (saved === 'pen') { |
|
|
penBtn.classList.add('active'); |
|
|
setActiveTool('pen'); |
|
|
} else if (saved === 'eraser') { |
|
|
eraseBtn.classList.add('active'); |
|
|
setActiveTool('eraser'); |
|
|
} else if (saved === 'spotlight') { |
|
|
spotlightBtn.classList.add('active'); |
|
|
setActiveTool('spotlight'); |
|
|
} |
|
|
|
|
|
|
|
|
const colorTitle = document.createElement('div'); |
|
|
colorTitle.className = 'tools-section-title'; |
|
|
colorTitle.textContent = 'color'; |
|
|
tools.appendChild(colorTitle); |
|
|
|
|
|
const colorRow = document.createElement('div'); |
|
|
colorRow.className = 'tools-row color-row'; |
|
|
tools.appendChild(colorRow); |
|
|
|
|
|
const swatchColors = [ |
|
|
|
|
|
'#e53935', '#fb8c00', '#fdd835', '#43a047', '#1e88e5', '#8e24aa', |
|
|
|
|
|
'#ff5722', '#795548', '#607d8b', '#9c27b0', |
|
|
|
|
|
'#000000', '#424242', '#9e9e9e', '#ffffff' |
|
|
]; |
|
|
const swatches = []; |
|
|
swatchColors.forEach(c => { |
|
|
const s = document.createElement('div'); |
|
|
s.className = 'color-swatch'; |
|
|
s.style.backgroundColor = c; |
|
|
s.title = c; |
|
|
s.onclick = () => { |
|
|
setStoredArrowColor(c); |
|
|
refreshColorUI(c); |
|
|
if (_cursorVisible) renderOverlay(); |
|
|
}; |
|
|
colorRow.appendChild(s); |
|
|
swatches.push(s); |
|
|
}); |
|
|
|
|
|
const colorInput = document.createElement('input'); |
|
|
colorInput.type = 'color'; |
|
|
colorInput.className = 'color-input'; |
|
|
colorInput.oninput = () => { |
|
|
setStoredArrowColor(colorInput.value); |
|
|
refreshColorUI(colorInput.value); |
|
|
if (_cursorVisible) renderOverlay(); |
|
|
}; |
|
|
colorRow.appendChild(colorInput); |
|
|
|
|
|
function refreshColorUI(selected) { |
|
|
const selectedHex = selected.startsWith('#') ? selected.toLowerCase() : rgbToHex(selected); |
|
|
|
|
|
swatches.forEach((s, i) => { |
|
|
const swatchHex = swatchColors[i].toLowerCase(); |
|
|
if (swatchHex === selectedHex) { |
|
|
s.classList.add('selected'); |
|
|
} else { |
|
|
s.classList.remove('selected'); |
|
|
} |
|
|
}); |
|
|
|
|
|
try { |
|
|
colorInput.value = selectedHex; |
|
|
} catch (_) {} |
|
|
} |
|
|
|
|
|
function rgbToHex(rgb) { |
|
|
const m = rgb.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/i); |
|
|
if (!m) return '#000000'; |
|
|
const r = parseInt(m[1]).toString(16).padStart(2, '0'); |
|
|
const g = parseInt(m[2]).toString(16).padStart(2, '0'); |
|
|
const b = parseInt(m[3]).toString(16).padStart(2, '0'); |
|
|
return `#${r}${g}${b}`; |
|
|
} |
|
|
|
|
|
|
|
|
refreshColorUI(getArrowColor()); |
|
|
|
|
|
|
|
|
const thicknessTitle = document.createElement('div'); |
|
|
thicknessTitle.className = 'tools-section-title'; |
|
|
thicknessTitle.textContent = 'thickness'; |
|
|
tools.appendChild(thicknessTitle); |
|
|
|
|
|
const thicknessRow = document.createElement('div'); |
|
|
thicknessRow.className = 'thickness-row'; |
|
|
tools.appendChild(thicknessRow); |
|
|
|
|
|
const thicknessSlider = document.createElement('input'); |
|
|
thicknessSlider.type = 'range'; |
|
|
thicknessSlider.className = 'thickness-slider'; |
|
|
thicknessSlider.min = '1'; |
|
|
thicknessSlider.max = '10'; |
|
|
thicknessSlider.value = getLineThickness(); |
|
|
|
|
|
const thicknessValue = document.createElement('span'); |
|
|
thicknessValue.className = 'thickness-value'; |
|
|
thicknessValue.textContent = thicknessSlider.value + 'px'; |
|
|
|
|
|
thicknessSlider.oninput = function() { |
|
|
const value = parseInt(thicknessSlider.value, 10); |
|
|
setStoredLineThickness(value); |
|
|
thicknessValue.textContent = value + 'px'; |
|
|
if (_cursorVisible) renderOverlay(); |
|
|
}; |
|
|
|
|
|
thicknessRow.appendChild(thicknessSlider); |
|
|
thicknessRow.appendChild(thicknessValue); |
|
|
|
|
|
|
|
|
const fadeoutTitle = document.createElement('div'); |
|
|
fadeoutTitle.className = 'tools-section-title'; |
|
|
fadeoutTitle.textContent = 'fadeout time'; |
|
|
tools.appendChild(fadeoutTitle); |
|
|
|
|
|
const fadeoutRow = document.createElement('div'); |
|
|
fadeoutRow.className = 'thickness-row'; |
|
|
tools.appendChild(fadeoutRow); |
|
|
|
|
|
const fadeoutSlider = document.createElement('input'); |
|
|
fadeoutSlider.type = 'range'; |
|
|
fadeoutSlider.className = 'thickness-slider'; |
|
|
fadeoutSlider.min = '0'; |
|
|
fadeoutSlider.max = '30'; |
|
|
fadeoutSlider.value = getFadeoutTime(); |
|
|
|
|
|
const fadeoutValue = document.createElement('span'); |
|
|
fadeoutValue.className = 'thickness-value'; |
|
|
fadeoutValue.textContent = fadeoutSlider.value === '0' ? 'never' : fadeoutSlider.value + 's'; |
|
|
|
|
|
fadeoutSlider.oninput = function() { |
|
|
const value = parseInt(fadeoutSlider.value, 10); |
|
|
setStoredFadeoutTime(value); |
|
|
fadeoutValue.textContent = value === 0 ? 'never' : value + 's'; |
|
|
}; |
|
|
|
|
|
fadeoutRow.appendChild(fadeoutSlider); |
|
|
fadeoutRow.appendChild(fadeoutValue); |
|
|
|
|
|
|
|
|
makeDraggable(tools, 'uvnote-tools-pos', title); |
|
|
|
|
|
return tools; |
|
|
} |
|
|
|
|
|
function initTools() { |
|
|
const widget = createToolsWidget(); |
|
|
document.body.appendChild(widget); |
|
|
} |
|
|
|
|
|
function teardownTools() { |
|
|
const w = document.querySelector('.tools-widget'); |
|
|
if (w && w.parentNode) w.parentNode.removeChild(w); |
|
|
} |
|
|
|
|
|
|
|
|
let _overlay = null; |
|
|
let _overlayCtx = null; |
|
|
let _overlayContainer = null; |
|
|
let _overlayMode = 'single'; |
|
|
let _overlayResizeHandler = null; |
|
|
let _overlayScrollHandler = null; |
|
|
let _drawing = null; |
|
|
let _shapes = []; |
|
|
let _fadeTimer = null; |
|
|
|
|
|
function getOverlayStorageKey() { return 'uvnote-shapes'; } |
|
|
|
|
|
function loadShapes() { |
|
|
try { |
|
|
const raw = localStorage.getItem(getOverlayStorageKey()); |
|
|
_shapes = raw ? JSON.parse(raw) : []; |
|
|
} catch (_) { _shapes = []; } |
|
|
} |
|
|
|
|
|
function saveShapes() { |
|
|
try { localStorage.setItem(getOverlayStorageKey(), JSON.stringify(_shapes)); } catch (_) {} |
|
|
} |
|
|
|
|
|
function updateShapesFade() { |
|
|
const now = Date.now(); |
|
|
const fadeoutSeconds = getFadeoutTime(); |
|
|
|
|
|
|
|
|
if (fadeoutSeconds === 0) return; |
|
|
|
|
|
const fadeStartTime = Math.max(0, (fadeoutSeconds - 2) * 1000); |
|
|
const fadeEndTime = fadeoutSeconds * 1000; |
|
|
let needsUpdate = false; |
|
|
|
|
|
for (let i = _shapes.length - 1; i >= 0; i--) { |
|
|
const shape = _shapes[i]; |
|
|
if (!shape.createdAt) continue; |
|
|
|
|
|
const age = now - shape.createdAt; |
|
|
|
|
|
if (age >= fadeEndTime) { |
|
|
|
|
|
_shapes.splice(i, 1); |
|
|
needsUpdate = true; |
|
|
} else if (age >= fadeStartTime) { |
|
|
|
|
|
const fadeProgress = (age - fadeStartTime) / (fadeEndTime - fadeStartTime); |
|
|
const newOpacity = 1 - fadeProgress; |
|
|
if (Math.abs(shape.opacity - newOpacity) > 0.01) { |
|
|
shape.opacity = newOpacity; |
|
|
needsUpdate = true; |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
if (needsUpdate) { |
|
|
saveShapes(); |
|
|
renderOverlay(); |
|
|
} |
|
|
} |
|
|
|
|
|
function getContentContainer() { return window; } |
|
|
|
|
|
function updateOverlayModeAndContainer() { |
|
|
_overlayContainer = window; |
|
|
_overlayMode = 'single'; |
|
|
} |
|
|
|
|
|
function updateOverlayBounds() { |
|
|
if (!_overlay) return; |
|
|
if (_overlayContainer === window) { |
|
|
_overlay.style.position = 'fixed'; |
|
|
_overlay.style.left = '0px'; |
|
|
_overlay.style.top = '0px'; |
|
|
_overlay.width = window.innerWidth; |
|
|
_overlay.height = window.innerHeight; |
|
|
} else { |
|
|
const rect = _overlayContainer.getBoundingClientRect(); |
|
|
_overlay.style.position = 'fixed'; |
|
|
_overlay.style.left = rect.left + 'px'; |
|
|
_overlay.style.top = rect.top + 'px'; |
|
|
_overlay.width = Math.max(0, Math.floor(rect.width)); |
|
|
_overlay.height = Math.max(0, Math.floor(rect.height)); |
|
|
} |
|
|
renderOverlay(); |
|
|
} |
|
|
|
|
|
function containerScrollLeft() { |
|
|
return (_overlayContainer === window) ? (window.scrollX || 0) : (_overlayContainer.scrollLeft || 0); |
|
|
} |
|
|
function containerScrollTop() { |
|
|
return (_overlayContainer === window) ? (window.scrollY || 0) : (_overlayContainer.scrollTop || 0); |
|
|
} |
|
|
|
|
|
function toCanvasCoords(clientX, clientY) { |
|
|
const rect = _overlay.getBoundingClientRect(); |
|
|
return { x: clientX - rect.left, y: clientY - rect.top }; |
|
|
} |
|
|
|
|
|
function onPointerDown(e) { |
|
|
const tool = document.body.dataset.tool; |
|
|
if (tool === 'arrow') { |
|
|
startDrawArrow(e); |
|
|
} else if (tool === 'pen') { |
|
|
startDrawPen(e); |
|
|
} else if (tool === 'eraser') { |
|
|
eraseAt(e); |
|
|
} else if (tool === 'spotlight') { |
|
|
startDrawSpotlight(e); |
|
|
} |
|
|
} |
|
|
|
|
|
function onPointerMove(e) { |
|
|
|
|
|
const pt = toCanvasCoords(e.touches ? e.touches[0].clientX : e.clientX, e.touches ? e.touches[0].clientY : e.clientY); |
|
|
_cursorX = pt.x; |
|
|
_cursorY = pt.y; |
|
|
|
|
|
if (!_drawing) { |
|
|
|
|
|
if (_cursorVisible) { |
|
|
renderOverlay(); |
|
|
} |
|
|
return; |
|
|
} |
|
|
|
|
|
if (_drawing.type === 'pen') { |
|
|
moveDrawPen(e); |
|
|
} else if (_drawing.type === 'spotlight') { |
|
|
moveDrawSpotlight(e); |
|
|
} else { |
|
|
moveDrawArrow(e); |
|
|
} |
|
|
} |
|
|
|
|
|
function onPointerEnter(e) { |
|
|
_cursorVisible = document.body.dataset.tool !== 'none'; |
|
|
if (_cursorVisible) { |
|
|
renderOverlay(); |
|
|
} |
|
|
} |
|
|
|
|
|
function onPointerLeave(e) { |
|
|
_cursorVisible = false; |
|
|
renderOverlay(); |
|
|
} |
|
|
|
|
|
function onPointerUp(e) { |
|
|
if (!_drawing) return; |
|
|
if (_drawing.type === 'pen') { |
|
|
endDrawPen(); |
|
|
} else if (_drawing.type === 'spotlight') { |
|
|
endDrawSpotlight(); |
|
|
} else { |
|
|
endDrawArrow(); |
|
|
} |
|
|
} |
|
|
|
|
|
function startDrawArrow(e) { |
|
|
if (document.body.dataset.tool !== 'arrow') return; |
|
|
const pt = toCanvasCoords(e.touches ? e.touches[0].clientX : e.clientX, e.touches ? e.touches[0].clientY : e.clientY); |
|
|
_drawing = { |
|
|
x1: pt.x + containerScrollLeft(), |
|
|
y1: pt.y + containerScrollTop(), |
|
|
x2: pt.x + containerScrollLeft(), |
|
|
y2: pt.y + containerScrollTop(), |
|
|
color: getArrowColor(), |
|
|
width: getLineThickness() |
|
|
}; |
|
|
renderOverlay(); |
|
|
e.preventDefault(); |
|
|
} |
|
|
|
|
|
function moveDrawArrow(e) { |
|
|
if (!_drawing) return; |
|
|
const pt = toCanvasCoords(e.touches ? e.touches[0].clientX : e.clientX, e.touches ? e.touches[0].clientY : e.clientY); |
|
|
_drawing.x2 = pt.x + containerScrollLeft(); |
|
|
_drawing.y2 = pt.y + containerScrollTop(); |
|
|
renderOverlay(); |
|
|
e.preventDefault(); |
|
|
} |
|
|
|
|
|
function endDrawArrow() { |
|
|
if (!_drawing) return; |
|
|
_shapes.push({ |
|
|
type: 'arrow', |
|
|
..._drawing, |
|
|
createdAt: Date.now(), |
|
|
opacity: 1.0 |
|
|
}); |
|
|
_drawing = null; |
|
|
saveShapes(); |
|
|
renderOverlay(); |
|
|
} |
|
|
|
|
|
function startDrawPen(e) { |
|
|
if (document.body.dataset.tool !== 'pen') return; |
|
|
const pt = toCanvasCoords(e.touches ? e.touches[0].clientX : e.clientX, e.touches ? e.touches[0].clientY : e.clientY); |
|
|
_drawing = { |
|
|
type: 'pen', |
|
|
points: [{ |
|
|
x: pt.x + containerScrollLeft(), |
|
|
y: pt.y + containerScrollTop() |
|
|
}], |
|
|
color: getArrowColor(), |
|
|
width: getLineThickness() |
|
|
}; |
|
|
renderOverlay(); |
|
|
e.preventDefault(); |
|
|
} |
|
|
|
|
|
function moveDrawPen(e) { |
|
|
if (!_drawing || _drawing.type !== 'pen') return; |
|
|
const pt = toCanvasCoords(e.touches ? e.touches[0].clientX : e.clientX, e.touches ? e.touches[0].clientY : e.clientY); |
|
|
_drawing.points.push({ |
|
|
x: pt.x + containerScrollLeft(), |
|
|
y: pt.y + containerScrollTop() |
|
|
}); |
|
|
renderOverlay(); |
|
|
e.preventDefault(); |
|
|
} |
|
|
|
|
|
function endDrawPen() { |
|
|
if (!_drawing || _drawing.type !== 'pen') return; |
|
|
if (_drawing.points.length > 1) { |
|
|
_shapes.push({ |
|
|
..._drawing, |
|
|
createdAt: Date.now(), |
|
|
opacity: 1.0 |
|
|
}); |
|
|
} |
|
|
_drawing = null; |
|
|
saveShapes(); |
|
|
renderOverlay(); |
|
|
} |
|
|
|
|
|
function startDrawSpotlight(e) { |
|
|
if (document.body.dataset.tool !== 'spotlight') return; |
|
|
const pt = toCanvasCoords(e.touches ? e.touches[0].clientX : e.clientX, e.touches ? e.touches[0].clientY : e.clientY); |
|
|
_drawing = { |
|
|
type: 'spotlight', |
|
|
x: pt.x + containerScrollLeft(), |
|
|
y: pt.y + containerScrollTop(), |
|
|
radius: getLineThickness() * 20, |
|
|
color: getArrowColor() |
|
|
}; |
|
|
renderOverlay(); |
|
|
e.preventDefault(); |
|
|
} |
|
|
|
|
|
function moveDrawSpotlight(e) { |
|
|
if (!_drawing || _drawing.type !== 'spotlight') return; |
|
|
const pt = toCanvasCoords(e.touches ? e.touches[0].clientX : e.clientX, e.touches ? e.touches[0].clientY : e.clientY); |
|
|
const dx = pt.x + containerScrollLeft() - _drawing.x; |
|
|
const dy = pt.y + containerScrollTop() - _drawing.y; |
|
|
_drawing.radius = Math.max(20, Math.sqrt(dx * dx + dy * dy)); |
|
|
renderOverlay(); |
|
|
e.preventDefault(); |
|
|
} |
|
|
|
|
|
function endDrawSpotlight() { |
|
|
if (!_drawing || _drawing.type !== 'spotlight') return; |
|
|
_shapes.push({ |
|
|
..._drawing, |
|
|
createdAt: Date.now(), |
|
|
opacity: 1.0 |
|
|
}); |
|
|
_drawing = null; |
|
|
saveShapes(); |
|
|
renderOverlay(); |
|
|
} |
|
|
|
|
|
function distPointToSegment(px, py, x1, y1, x2, y2) { |
|
|
const dx = x2 - x1, dy = y2 - y1; |
|
|
if (dx === 0 && dy === 0) return Math.hypot(px - x1, py - y1); |
|
|
const t = Math.max(0, Math.min(1, ((px - x1) * dx + (py - y1) * dy) / (dx*dx + dy*dy))); |
|
|
const cx = x1 + t * dx, cy = y1 + t * dy; |
|
|
return Math.hypot(px - cx, py - cy); |
|
|
} |
|
|
|
|
|
function eraseAt(e) { |
|
|
const pt = toCanvasCoords(e.touches ? e.touches[0].clientX : e.clientX, e.touches ? e.touches[0].clientY : e.clientY); |
|
|
const x = pt.x + containerScrollLeft(); |
|
|
const y = pt.y + containerScrollTop(); |
|
|
const threshold = 10; |
|
|
for (let i = _shapes.length - 1; i >= 0; i--) { |
|
|
const s = _shapes[i]; |
|
|
if (s.type === 'arrow') { |
|
|
const d = distPointToSegment(x, y, s.x1, s.y1, s.x2, s.y2); |
|
|
if (d <= threshold) { |
|
|
_shapes.splice(i, 1); |
|
|
saveShapes(); |
|
|
renderOverlay(); |
|
|
break; |
|
|
} |
|
|
} else if (s.type === 'pen' && s.points) { |
|
|
|
|
|
let minDist = Infinity; |
|
|
for (let j = 1; j < s.points.length; j++) { |
|
|
const d = distPointToSegment(x, y, s.points[j-1].x, s.points[j-1].y, s.points[j].x, s.points[j].y); |
|
|
minDist = Math.min(minDist, d); |
|
|
} |
|
|
if (minDist <= threshold) { |
|
|
_shapes.splice(i, 1); |
|
|
saveShapes(); |
|
|
renderOverlay(); |
|
|
break; |
|
|
} |
|
|
} |
|
|
} |
|
|
e.preventDefault(); |
|
|
} |
|
|
|
|
|
function drawArrow(ctx, x1, y1, x2, y2, color, width, opacity = 1.0) { |
|
|
|
|
|
const oldAlpha = ctx.globalAlpha; |
|
|
ctx.globalAlpha = opacity; |
|
|
|
|
|
ctx.strokeStyle = color; |
|
|
ctx.fillStyle = color; |
|
|
ctx.lineWidth = width; |
|
|
ctx.lineCap = 'round'; |
|
|
ctx.lineJoin = 'round'; |
|
|
|
|
|
|
|
|
const dx = x2 - x1; |
|
|
const dy = y2 - y1; |
|
|
const distance = Math.sqrt(dx * dx + dy * dy); |
|
|
|
|
|
if (distance < 5) { |
|
|
|
|
|
const defaultAngle = Math.PI / 4; |
|
|
const headLength = Math.min(15 + width * 1.5, 25); |
|
|
const headAngle = Math.PI / 6; |
|
|
|
|
|
|
|
|
const hx1 = x1 + headLength * Math.cos(defaultAngle - headAngle); |
|
|
const hy1 = y1 + headLength * Math.sin(defaultAngle - headAngle); |
|
|
const hx2 = x1 + headLength * Math.cos(defaultAngle + headAngle); |
|
|
const hy2 = y1 + headLength * Math.sin(defaultAngle + headAngle); |
|
|
|
|
|
|
|
|
ctx.beginPath(); |
|
|
ctx.moveTo(x1, y1); |
|
|
ctx.lineTo(hx1, hy1); |
|
|
ctx.lineTo(hx2, hy2); |
|
|
ctx.closePath(); |
|
|
ctx.fill(); |
|
|
} else { |
|
|
|
|
|
const angle = Math.atan2(y1 - y2, x1 - x2); |
|
|
const headLength = Math.min(15 + width * 1.5, 25); |
|
|
const headAngle = Math.PI / 6; |
|
|
|
|
|
|
|
|
const lineEndX = x1 - headLength * 0.8 * Math.cos(angle); |
|
|
const lineEndY = y1 - headLength * 0.8 * Math.sin(angle); |
|
|
|
|
|
|
|
|
ctx.beginPath(); |
|
|
ctx.moveTo(x2, y2); |
|
|
ctx.lineTo(lineEndX, lineEndY); |
|
|
ctx.stroke(); |
|
|
|
|
|
|
|
|
const hx1 = x1 - headLength * Math.cos(angle - headAngle); |
|
|
const hy1 = y1 - headLength * Math.sin(angle - headAngle); |
|
|
const hx2 = x1 - headLength * Math.cos(angle + headAngle); |
|
|
const hy2 = y1 - headLength * Math.sin(angle + headAngle); |
|
|
|
|
|
|
|
|
ctx.beginPath(); |
|
|
ctx.moveTo(x1, y1); |
|
|
ctx.lineTo(hx1, hy1); |
|
|
ctx.lineTo(hx2, hy2); |
|
|
ctx.closePath(); |
|
|
ctx.fill(); |
|
|
} |
|
|
|
|
|
|
|
|
ctx.globalAlpha = oldAlpha; |
|
|
} |
|
|
|
|
|
function drawPen(ctx, points, color, width, offX, offY, opacity = 1.0) { |
|
|
if (!points || points.length < 2) return; |
|
|
|
|
|
|
|
|
const oldAlpha = ctx.globalAlpha; |
|
|
ctx.globalAlpha = opacity; |
|
|
|
|
|
ctx.strokeStyle = color; |
|
|
ctx.lineWidth = width; |
|
|
ctx.lineCap = 'round'; |
|
|
ctx.lineJoin = 'round'; |
|
|
ctx.beginPath(); |
|
|
ctx.moveTo(points[0].x - offX, points[0].y - offY); |
|
|
for (let i = 1; i < points.length; i++) { |
|
|
ctx.lineTo(points[i].x - offX, points[i].y - offY); |
|
|
} |
|
|
ctx.stroke(); |
|
|
|
|
|
|
|
|
ctx.globalAlpha = oldAlpha; |
|
|
} |
|
|
|
|
|
function drawAllSpotlights(ctx, spotlights, offX, offY) { |
|
|
if (!spotlights || spotlights.length === 0) return; |
|
|
|
|
|
ctx.save(); |
|
|
|
|
|
|
|
|
const maxOpacity = Math.max(...spotlights.map(s => s.opacity || 1.0)); |
|
|
|
|
|
|
|
|
ctx.fillStyle = `rgba(0, 0, 0, ${0.7 * maxOpacity})`; |
|
|
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height); |
|
|
|
|
|
|
|
|
ctx.globalCompositeOperation = 'destination-out'; |
|
|
ctx.fillStyle = 'rgba(0, 0, 0, 1)'; |
|
|
for (const spotlight of spotlights) { |
|
|
ctx.beginPath(); |
|
|
ctx.arc(spotlight.x - offX, spotlight.y - offY, spotlight.radius, 0, 2 * Math.PI); |
|
|
ctx.fill(); |
|
|
} |
|
|
|
|
|
ctx.restore(); |
|
|
} |
|
|
|
|
|
function renderOverlay() { |
|
|
if (!_overlay || !_overlayCtx) return; |
|
|
_overlayCtx.clearRect(0, 0, _overlay.width, _overlay.height); |
|
|
const offX = containerScrollLeft(); |
|
|
const offY = containerScrollTop(); |
|
|
|
|
|
for (const s of _shapes) { |
|
|
const opacity = s.opacity !== undefined ? s.opacity : 1.0; |
|
|
if (s.type === 'arrow') { |
|
|
drawArrow(_overlayCtx, s.x1 - offX, s.y1 - offY, s.x2 - offX, s.y2 - offY, s.color || '#f00', s.width || 2, opacity); |
|
|
} else if (s.type === 'pen') { |
|
|
drawPen(_overlayCtx, s.points, s.color || '#f00', s.width || 2, offX, offY, opacity); |
|
|
} |
|
|
} |
|
|
|
|
|
if (_drawing) { |
|
|
if (_drawing.type === 'pen') { |
|
|
drawPen(_overlayCtx, _drawing.points, _drawing.color, _drawing.width, offX, offY); |
|
|
} else if (_drawing.type !== 'spotlight') { |
|
|
drawArrow(_overlayCtx, _drawing.x1 - offX, _drawing.y1 - offY, _drawing.x2 - offX, _drawing.y2 - offY, _drawing.color, _drawing.width); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
const spotlights = []; |
|
|
|
|
|
|
|
|
for (const s of _shapes) { |
|
|
if (s.type === 'spotlight') { |
|
|
spotlights.push({ |
|
|
x: s.x, |
|
|
y: s.y, |
|
|
radius: s.radius, |
|
|
opacity: s.opacity !== undefined ? s.opacity : 1.0 |
|
|
}); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if (_drawing && _drawing.type === 'spotlight') { |
|
|
spotlights.push({ |
|
|
x: _drawing.x, |
|
|
y: _drawing.y, |
|
|
radius: _drawing.radius, |
|
|
opacity: 1.0 |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
if (_cursorVisible && !_drawing) { |
|
|
const tool = document.body.dataset.tool; |
|
|
if (tool === 'spotlight') { |
|
|
const thickness = getLineThickness(); |
|
|
const radius = thickness * 20; |
|
|
const cursorWorldX = _cursorX + containerScrollLeft(); |
|
|
const cursorWorldY = _cursorY + containerScrollTop(); |
|
|
spotlights.push({ |
|
|
x: cursorWorldX, |
|
|
y: cursorWorldY, |
|
|
radius: radius, |
|
|
opacity: 0.8 |
|
|
}); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
drawAllSpotlights(_overlayCtx, spotlights, offX, offY); |
|
|
|
|
|
|
|
|
if (_cursorVisible && !_drawing) { |
|
|
const tool = document.body.dataset.tool; |
|
|
const color = getArrowColor(); |
|
|
const thickness = getLineThickness(); |
|
|
|
|
|
if (tool !== 'spotlight') { |
|
|
_overlayCtx.save(); |
|
|
_overlayCtx.fillStyle = color; |
|
|
_overlayCtx.globalAlpha = 0.7; |
|
|
|
|
|
if (tool === 'eraser') { |
|
|
|
|
|
_overlayCtx.strokeStyle = color; |
|
|
_overlayCtx.lineWidth = 2; |
|
|
_overlayCtx.beginPath(); |
|
|
_overlayCtx.arc(_cursorX, _cursorY, 10, 0, 2 * Math.PI); |
|
|
_overlayCtx.stroke(); |
|
|
} else { |
|
|
|
|
|
_overlayCtx.beginPath(); |
|
|
_overlayCtx.arc(_cursorX, _cursorY, thickness / 2, 0, 2 * Math.PI); |
|
|
_overlayCtx.fill(); |
|
|
} |
|
|
|
|
|
_overlayCtx.restore(); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
function setOverlayActive(active) { |
|
|
if (!_overlay) initOverlay(); |
|
|
_overlay.style.pointerEvents = active ? 'auto' : 'none'; |
|
|
_overlay.style.cursor = active ? 'none' : 'auto'; |
|
|
|
|
|
renderOverlay(); |
|
|
} |
|
|
|
|
|
function initOverlay() { |
|
|
if (_overlay) return; |
|
|
updateOverlayModeAndContainer(); |
|
|
_overlay = document.createElement('canvas'); |
|
|
_overlay.className = 'draw-overlay'; |
|
|
_overlayCtx = _overlay.getContext('2d'); |
|
|
document.body.appendChild(_overlay); |
|
|
updateOverlayBounds(); |
|
|
loadShapes(); |
|
|
renderOverlay(); |
|
|
|
|
|
|
|
|
_overlay.addEventListener('mousedown', onPointerDown); |
|
|
_overlay.addEventListener('mousemove', onPointerMove); |
|
|
_overlay.addEventListener('mouseenter', onPointerEnter); |
|
|
_overlay.addEventListener('mouseleave', onPointerLeave); |
|
|
document.addEventListener('mouseup', onPointerUp); |
|
|
_overlay.addEventListener('touchstart', onPointerDown, { passive: false }); |
|
|
_overlay.addEventListener('touchmove', onPointerMove, { passive: false }); |
|
|
document.addEventListener('touchend', onPointerUp); |
|
|
|
|
|
_overlayResizeHandler = () => updateOverlayBounds(); |
|
|
window.addEventListener('resize', _overlayResizeHandler); |
|
|
|
|
|
_overlayScrollHandler = () => renderOverlay(); |
|
|
window.addEventListener('scroll', _overlayScrollHandler); |
|
|
|
|
|
|
|
|
_fadeTimer = setInterval(updateShapesFade, 100); |
|
|
} |
|
|
|
|
|
function rebindOverlayContainer() { |
|
|
if (!_overlay) return; |
|
|
|
|
|
if (_overlayScrollHandler) { window.removeEventListener('scroll', _overlayScrollHandler); } |
|
|
updateOverlayModeAndContainer(); |
|
|
updateOverlayBounds(); |
|
|
loadShapes(); |
|
|
renderOverlay(); |
|
|
_overlayScrollHandler = () => renderOverlay(); |
|
|
window.addEventListener('scroll', _overlayScrollHandler); |
|
|
} |
|
|
|
|
|
function teardownOverlay() { |
|
|
if (!_overlay) return; |
|
|
_overlay.removeEventListener('mousedown', onPointerDown); |
|
|
_overlay.removeEventListener('mousemove', onPointerMove); |
|
|
_overlay.removeEventListener('mouseenter', onPointerEnter); |
|
|
_overlay.removeEventListener('mouseleave', onPointerLeave); |
|
|
document.removeEventListener('mouseup', onPointerUp); |
|
|
_overlay.removeEventListener('touchstart', onPointerDown); |
|
|
_overlay.removeEventListener('touchmove', onPointerMove); |
|
|
document.removeEventListener('touchend', onPointerUp); |
|
|
if (_overlayResizeHandler) window.removeEventListener('resize', _overlayResizeHandler); |
|
|
if (_overlayScrollHandler) { |
|
|
if (_overlayContainer === window) { |
|
|
window.removeEventListener('scroll', _overlayScrollHandler); |
|
|
} else if (_overlayContainer) { |
|
|
_overlayContainer.removeEventListener('scroll', _overlayScrollHandler); |
|
|
} |
|
|
} |
|
|
if (_fadeTimer) { |
|
|
clearInterval(_fadeTimer); |
|
|
_fadeTimer = null; |
|
|
} |
|
|
if (_overlay.parentNode) _overlay.parentNode.removeChild(_overlay); |
|
|
_overlay = null; _overlayCtx = null; _overlayContainer = null; _overlayResizeHandler = null; _overlayScrollHandler = null; _drawing = null; |
|
|
} |
|
|
|
|
|
function teardownFileExplorer() { |
|
|
const fe = document.querySelector('.file-explorer'); |
|
|
if (fe && fe.parentNode) fe.parentNode.removeChild(fe); |
|
|
} |
|
|
|
|
|
function escapeHtml(text) { |
|
|
const div = document.createElement('div'); |
|
|
div.textContent = text; |
|
|
return div.innerHTML; |
|
|
} |
|
|
|
|
|
function runCell(cellId){ |
|
|
const btn=document.querySelector('.run-btn[onclick*="'+cellId+'"]'); |
|
|
const output=document.getElementById('output-'+cellId); |
|
|
if(btn){btn.textContent='⏳ running...';btn.disabled=true;} |
|
|
if(output){output.classList.add('output-stale');} |
|
|
fetch('/run/'+cellId,{method:'POST'}).then(r=>r.json()).then(data=>{ |
|
|
if(output){ |
|
|
output.classList.remove('output-stale'); |
|
|
let html=''; |
|
|
if(data.stdout) html+='<div class="cell-stdout">'+escapeHtml(data.stdout)+'</div>'; |
|
|
console.log('UV Logs:', data); |
|
|
if(data.stderr) { |
|
|
|
|
|
const lines = data.stderr.split('\n'); |
|
|
let uvLogs = []; |
|
|
let regularLogs = []; |
|
|
let inUvSection = true; |
|
|
|
|
|
for (const line of lines) { |
|
|
if (inUvSection) { |
|
|
uvLogs.push(line); |
|
|
if (line.startsWith('Installed ')) { |
|
|
inUvSection = false; |
|
|
} |
|
|
} else { |
|
|
regularLogs.push(line); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (inUvSection) { |
|
|
html+='<div class="cell-stderr">'+escapeHtml(data.stderr)+'</div>'; |
|
|
} else { |
|
|
const uvLogsStr = uvLogs.join('\n'); |
|
|
const regularLogsStr = regularLogs.join('\n').trim(); |
|
|
|
|
|
if (uvLogsStr) { |
|
|
html+='<div class="uv-install-logs">'; |
|
|
html+='<div class="uv-logs-header" onclick="toggleUvLogs(this)">▶ UV Install Logs</div>'; |
|
|
html+='<div class="uv-logs-content" style="display: none;">'+escapeHtml(uvLogsStr)+'</div>'; |
|
|
html+='</div>'; |
|
|
} |
|
|
if (regularLogsStr) { |
|
|
html+='<div class="cell-stderr">'+escapeHtml(regularLogsStr)+'</div>'; |
|
|
} |
|
|
} |
|
|
} |
|
|
output.innerHTML=html; |
|
|
} |
|
|
if(btn){btn.textContent='▶ run';btn.disabled=false;} |
|
|
}).catch(e=>{ |
|
|
console.error('Run failed:',e); |
|
|
if(output){output.classList.remove('output-stale');} |
|
|
if(btn){btn.textContent='▶ run';btn.disabled=false;} |
|
|
}); |
|
|
} |
|
|
|
|
|
function copyCell(cellId){ |
|
|
console.log('copyCell called with cellId:', cellId); |
|
|
|
|
|
|
|
|
let codeElement = document.querySelector('#code-'+cellId+' code'); |
|
|
if (!codeElement) { |
|
|
codeElement = document.querySelector('#code-'+cellId+' pre code'); |
|
|
} |
|
|
if (!codeElement) { |
|
|
codeElement = document.querySelector('#code-'+cellId+' .highlight code'); |
|
|
} |
|
|
if (!codeElement) { |
|
|
|
|
|
const codeDiv = document.getElementById('code-'+cellId); |
|
|
if (codeDiv) { |
|
|
codeElement = codeDiv.querySelector('code'); |
|
|
} |
|
|
} |
|
|
|
|
|
const btn = document.querySelector('.copy-btn[onclick*="'+cellId+'"]'); |
|
|
|
|
|
console.log('Found codeElement:', codeElement); |
|
|
console.log('Found btn:', btn); |
|
|
console.log('Code div structure:', document.getElementById('code-'+cellId)); |
|
|
|
|
|
if (!codeElement) { |
|
|
console.error('Code element not found for cell:', cellId); |
|
|
|
|
|
const codeDiv = document.getElementById('code-'+cellId); |
|
|
if (codeDiv) { |
|
|
console.log('Code div HTML:', codeDiv.innerHTML); |
|
|
} |
|
|
return; |
|
|
} |
|
|
if (!btn) { |
|
|
console.error('Copy button not found for cell:', cellId); |
|
|
return; |
|
|
} |
|
|
|
|
|
const codeText = codeElement.textContent; |
|
|
console.log('Code text to copy:', codeText ? codeText.substring(0, 50) + '...' : 'empty'); |
|
|
|
|
|
if (navigator.clipboard && navigator.clipboard.writeText) { |
|
|
navigator.clipboard.writeText(codeText).then(function() { |
|
|
console.log('Clipboard copy successful'); |
|
|
btn.textContent = '✓ Copied!'; |
|
|
btn.classList.add('copied'); |
|
|
setTimeout(function() { |
|
|
btn.textContent = 'Copy'; |
|
|
btn.classList.remove('copied'); |
|
|
}, 2000); |
|
|
}).catch(function(err) { |
|
|
console.warn('Clipboard copy failed:', err); |
|
|
fallbackCopy(); |
|
|
}); |
|
|
} else { |
|
|
console.log('Using fallback copy method'); |
|
|
fallbackCopy(); |
|
|
} |
|
|
|
|
|
function fallbackCopy() { |
|
|
const textarea = document.createElement('textarea'); |
|
|
textarea.value = codeText; |
|
|
textarea.style.position = 'absolute'; |
|
|
textarea.style.left = '-9999px'; |
|
|
document.body.appendChild(textarea); |
|
|
textarea.select(); |
|
|
try { |
|
|
const success = document.execCommand('copy'); |
|
|
console.log('Fallback copy success:', success); |
|
|
btn.textContent = '✓ Copied!'; |
|
|
btn.classList.add('copied'); |
|
|
setTimeout(function() { |
|
|
btn.textContent = 'Copy'; |
|
|
btn.classList.remove('copied'); |
|
|
}, 2000); |
|
|
} catch (err) { |
|
|
console.error('Fallback copy failed:', err); |
|
|
btn.textContent = 'Copy failed'; |
|
|
setTimeout(function() { |
|
|
btn.textContent = 'Copy'; |
|
|
}, 2000); |
|
|
} |
|
|
document.body.removeChild(textarea); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
(function(){ |
|
|
if (!('EventSource' in window)) { |
|
|
console.warn('SSE not supported in this browser'); |
|
|
return; |
|
|
} |
|
|
let source = new EventSource('/events'); |
|
|
let isOpen = false; |
|
|
source.onopen = function(){ isOpen = true; console.log('SSE connected'); }; |
|
|
source.onmessage = function(e){ |
|
|
const msg=(e.data||'').trim(); if(!msg) return; |
|
|
console.log('SSE message:', msg); |
|
|
if (msg==='reload' || msg==='incremental') { location.reload(); } |
|
|
|
|
|
}; |
|
|
source.onerror = function(e){ |
|
|
|
|
|
if (isOpen) console.warn('SSE error after open, retrying...', e); |
|
|
}; |
|
|
window.addEventListener('beforeunload', function(){ try{source.close();}catch(_){} }); |
|
|
})(); |
|
|
|
|
|
|
|
|
document.addEventListener('DOMContentLoaded', function() { |
|
|
updateThemeIcon(); |
|
|
initMinimap(); |
|
|
initFileExplorer(); |
|
|
initTools(); |
|
|
initOverlay(); |
|
|
initializeWidgetVisibility(); |
|
|
layoutWidgetsStackedBottomRight(); |
|
|
window.addEventListener('resize', layoutWidgetsStackedBottomRight); |
|
|
|
|
|
|
|
|
document.addEventListener('keydown', function(e) { |
|
|
if (e.key === 'Escape' || e.keyCode === 27) { |
|
|
const currentTool = document.body.dataset.tool; |
|
|
if (currentTool && currentTool !== 'none') { |
|
|
|
|
|
window.setActiveTool('none'); |
|
|
} |
|
|
} |
|
|
}); |
|
|
}); |
|
|
</script> |
|
|
</head> |
|
|
<body> |
|
|
<div class="controls"> |
|
|
<div class="theme-toggle" onclick="toggleTheme()">light</div> |
|
|
<div class="reset-toggle" onclick="resetLayout()">reset</div> |
|
|
<div class="menu-button" onclick="toggleMenu()"> |
|
|
menu ▼ |
|
|
<div class="menu-dropdown"> |
|
|
<div class="menu-item" onclick="toggleWidget('tools')"> |
|
|
<span class="menu-checkbox" id="checkbox-tools">☐</span> Tools |
|
|
</div> |
|
|
<div class="menu-item" onclick="toggleWidget('file-explorer')"> |
|
|
<span class="menu-checkbox" id="checkbox-file-explorer">☐</span> File Explorer |
|
|
</div> |
|
|
<div class="menu-item" onclick="toggleWidget('minimap')"> |
|
|
<span class="menu-checkbox" id="checkbox-minimap">☐</span> Table of Contents |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="system-info"> |
|
|
<div class="system-info-header">Generated on:</div> |
|
|
<div class="system-info-content"> |
|
|
Linux x86_64 | Linux-5.15.0-1084-aws-x86_64-with-glibc2.31 |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="main-content"> |
|
|
<div class="cell"> |
|
|
<div class="cell-header"> |
|
|
<span class="collapse-indicators"> |
|
|
<span onclick="toggleCode('setup')" style="cursor: pointer;">▼ code</span> |
|
|
<span onclick="toggleOutput('setup')" style="cursor: pointer;">▼ output</span> |
|
|
<span id="uv-indicator-setup" onclick="toggleUvLogsFromHeader('setup')" style="cursor: pointer;">▶ uv-logs</span> |
|
|
</span> | |
|
|
Cell: setup | 304.89s |
|
|
| <button class="run-btn" onclick="runCell('setup')">▶ run</button> |
|
|
<button class="copy-btn" onclick="copyCell('setup')">Copy</button> |
|
|
<a href="cells/setup.py" target="_blank" class="raw-btn">Raw</a> |
|
|
</div> |
|
|
<div id="code-setup" class="cell-code"> |
|
|
<div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal"> 1</span> |
|
|
<span class="normal"> 2</span> |
|
|
<span class="normal"> 3</span> |
|
|
<span class="normal"> 4</span> |
|
|
<span class="normal"> 5</span> |
|
|
<span class="normal"> 6</span> |
|
|
<span class="normal"> 7</span> |
|
|
<span class="normal"> 8</span> |
|
|
<span class="normal"> 9</span> |
|
|
<span class="normal"> 10</span> |
|
|
<span class="normal"> 11</span> |
|
|
<span class="normal"> 12</span> |
|
|
<span class="normal"> 13</span> |
|
|
<span class="normal"> 14</span> |
|
|
<span class="normal"> 15</span> |
|
|
<span class="normal"> 16</span> |
|
|
<span class="normal"> 17</span> |
|
|
<span class="normal"> 18</span> |
|
|
<span class="normal"> 19</span> |
|
|
<span class="normal"> 20</span> |
|
|
<span class="normal"> 21</span> |
|
|
<span class="normal"> 22</span> |
|
|
<span class="normal"> 23</span> |
|
|
<span class="normal"> 24</span> |
|
|
<span class="normal"> 25</span> |
|
|
<span class="normal"> 26</span> |
|
|
<span class="normal"> 27</span> |
|
|
<span class="normal"> 28</span> |
|
|
<span class="normal"> 29</span> |
|
|
<span class="normal"> 30</span> |
|
|
<span class="normal"> 31</span> |
|
|
<span class="normal"> 32</span> |
|
|
<span class="normal"> 33</span> |
|
|
<span class="normal"> 34</span> |
|
|
<span class="normal"> 35</span> |
|
|
<span class="normal"> 36</span> |
|
|
<span class="normal"> 37</span> |
|
|
<span class="normal"> 38</span> |
|
|
<span class="normal"> 39</span> |
|
|
<span class="normal"> 40</span> |
|
|
<span class="normal"> 41</span> |
|
|
<span class="normal"> 42</span> |
|
|
<span class="normal"> 43</span> |
|
|
<span class="normal"> 44</span> |
|
|
<span class="normal"> 45</span> |
|
|
<span class="normal"> 46</span> |
|
|
<span class="normal"> 47</span> |
|
|
<span class="normal"> 48</span> |
|
|
<span class="normal"> 49</span> |
|
|
<span class="normal"> 50</span> |
|
|
<span class="normal"> 51</span> |
|
|
<span class="normal"> 52</span> |
|
|
<span class="normal"> 53</span> |
|
|
<span class="normal"> 54</span> |
|
|
<span class="normal"> 55</span> |
|
|
<span class="normal"> 56</span> |
|
|
<span class="normal"> 57</span> |
|
|
<span class="normal"> 58</span> |
|
|
<span class="normal"> 59</span> |
|
|
<span class="normal"> 60</span> |
|
|
<span class="normal"> 61</span> |
|
|
<span class="normal"> 62</span> |
|
|
<span class="normal"> 63</span> |
|
|
<span class="normal"> 64</span> |
|
|
<span class="normal"> 65</span> |
|
|
<span class="normal"> 66</span> |
|
|
<span class="normal"> 67</span> |
|
|
<span class="normal"> 68</span> |
|
|
<span class="normal"> 69</span> |
|
|
<span class="normal"> 70</span> |
|
|
<span class="normal"> 71</span> |
|
|
<span class="normal"> 72</span> |
|
|
<span class="normal"> 73</span> |
|
|
<span class="normal"> 74</span> |
|
|
<span class="normal"> 75</span> |
|
|
<span class="normal"> 76</span> |
|
|
<span class="normal"> 77</span> |
|
|
<span class="normal"> 78</span> |
|
|
<span class="normal"> 79</span> |
|
|
<span class="normal"> 80</span> |
|
|
<span class="normal"> 81</span> |
|
|
<span class="normal"> 82</span> |
|
|
<span class="normal"> 83</span> |
|
|
<span class="normal"> 84</span> |
|
|
<span class="normal"> 85</span> |
|
|
<span class="normal"> 86</span> |
|
|
<span class="normal"> 87</span> |
|
|
<span class="normal"> 88</span> |
|
|
<span class="normal"> 89</span> |
|
|
<span class="normal"> 90</span> |
|
|
<span class="normal"> 91</span> |
|
|
<span class="normal"> 92</span> |
|
|
<span class="normal"> 93</span> |
|
|
<span class="normal"> 94</span> |
|
|
<span class="normal"> 95</span> |
|
|
<span class="normal"> 96</span> |
|
|
<span class="normal"> 97</span> |
|
|
<span class="normal"> 98</span> |
|
|
<span class="normal"> 99</span> |
|
|
<span class="normal">100</span> |
|
|
<span class="normal">101</span> |
|
|
<span class="normal">102</span> |
|
|
<span class="normal">103</span> |
|
|
<span class="normal">104</span> |
|
|
<span class="normal">105</span> |
|
|
<span class="normal">106</span> |
|
|
<span class="normal">107</span> |
|
|
<span class="normal">108</span> |
|
|
<span class="normal">109</span> |
|
|
<span class="normal">110</span> |
|
|
<span class="normal">111</span> |
|
|
<span class="normal">112</span> |
|
|
<span class="normal">113</span> |
|
|
<span class="normal">114</span> |
|
|
<span class="normal">115</span> |
|
|
<span class="normal">116</span> |
|
|
<span class="normal">117</span> |
|
|
<span class="normal">118</span> |
|
|
<span class="normal">119</span> |
|
|
<span class="normal">120</span> |
|
|
<span class="normal">121</span> |
|
|
<span class="normal">122</span> |
|
|
<span class="normal">123</span> |
|
|
<span class="normal">124</span> |
|
|
<span class="normal">125</span> |
|
|
<span class="normal">126</span> |
|
|
<span class="normal">127</span> |
|
|
<span class="normal">128</span> |
|
|
<span class="normal">129</span> |
|
|
<span class="normal">130</span> |
|
|
<span class="normal">131</span> |
|
|
<span class="normal">132</span> |
|
|
<span class="normal">133</span> |
|
|
<span class="normal">134</span> |
|
|
<span class="normal">135</span> |
|
|
<span class="normal">136</span> |
|
|
<span class="normal">137</span> |
|
|
<span class="normal">138</span> |
|
|
<span class="normal">139</span> |
|
|
<span class="normal">140</span> |
|
|
<span class="normal">141</span> |
|
|
<span class="normal">142</span> |
|
|
<span class="normal">143</span> |
|
|
<span class="normal">144</span> |
|
|
<span class="normal">145</span> |
|
|
<span class="normal">146</span> |
|
|
<span class="normal">147</span> |
|
|
<span class="normal">148</span> |
|
|
<span class="normal">149</span> |
|
|
<span class="normal">150</span> |
|
|
<span class="normal">151</span> |
|
|
<span class="normal">152</span> |
|
|
<span class="normal">153</span> |
|
|
<span class="normal">154</span> |
|
|
<span class="normal">155</span> |
|
|
<span class="normal">156</span> |
|
|
<span class="normal">157</span> |
|
|
<span class="normal">158</span> |
|
|
<span class="normal">159</span> |
|
|
<span class="normal">160</span> |
|
|
<span class="normal">161</span> |
|
|
<span class="normal">162</span> |
|
|
<span class="normal">163</span> |
|
|
<span class="normal">164</span> |
|
|
<span class="normal">165</span> |
|
|
<span class="normal">166</span> |
|
|
<span class="normal">167</span> |
|
|
<span class="normal">168</span> |
|
|
<span class="normal">169</span> |
|
|
<span class="normal">170</span> |
|
|
<span class="normal">171</span> |
|
|
<span class="normal">172</span> |
|
|
<span class="normal">173</span> |
|
|
<span class="normal">174</span> |
|
|
<span class="normal">175</span> |
|
|
<span class="normal">176</span> |
|
|
<span class="normal">177</span> |
|
|
<span class="normal">178</span> |
|
|
<span class="normal">179</span> |
|
|
<span class="normal">180</span> |
|
|
<span class="normal">181</span> |
|
|
<span class="normal">182</span> |
|
|
<span class="normal">183</span> |
|
|
<span class="normal">184</span> |
|
|
<span class="normal">185</span> |
|
|
<span class="normal">186</span> |
|
|
<span class="normal">187</span> |
|
|
<span class="normal">188</span> |
|
|
<span class="normal">189</span> |
|
|
<span class="normal">190</span> |
|
|
<span class="normal">191</span> |
|
|
<span class="normal">192</span> |
|
|
<span class="normal">193</span> |
|
|
<span class="normal">194</span> |
|
|
<span class="normal">195</span> |
|
|
<span class="normal">196</span> |
|
|
<span class="normal">197</span> |
|
|
<span class="normal">198</span> |
|
|
<span class="normal">199</span> |
|
|
<span class="normal">200</span> |
|
|
<span class="normal">201</span> |
|
|
<span class="normal">202</span> |
|
|
<span class="normal">203</span> |
|
|
<span class="normal">204</span> |
|
|
<span class="normal">205</span> |
|
|
<span class="normal">206</span> |
|
|
<span class="normal">207</span> |
|
|
<span class="normal">208</span> |
|
|
<span class="normal">209</span> |
|
|
<span class="normal">210</span> |
|
|
<span class="normal">211</span> |
|
|
<span class="normal">212</span> |
|
|
<span class="normal">213</span> |
|
|
<span class="normal">214</span> |
|
|
<span class="normal">215</span> |
|
|
<span class="normal">216</span> |
|
|
<span class="normal">217</span> |
|
|
<span class="normal">218</span> |
|
|
<span class="normal">219</span> |
|
|
<span class="normal">220</span></pre></div></td><td class="code"><div><pre><span></span><span class="c1"># /// script</span> |
|
|
<span class="c1"># requires-python = ">=3.12"</span> |
|
|
<span class="c1"># dependencies = [</span> |
|
|
<span class="c1"># "accelerate>=1.10.1",</span> |
|
|
<span class="c1"># "torch>=2.7.0",</span> |
|
|
<span class="c1"># "kernels==0.10.0",</span> |
|
|
<span class="c1"># "transformers@https://github.com/huggingface/transformers.git",</span> |
|
|
<span class="c1"># "ipdb>=0.13.13",</span> |
|
|
<span class="c1"># "matplotlib>=3.7.2",</span> |
|
|
<span class="c1"># "numpy>=1.24.3",</span> |
|
|
<span class="c1"># ]</span> |
|
|
<span class="c1"># ///</span> |
|
|
|
|
|
<span class="kn">import</span><span class="w"> </span><span class="nn">torch</span> |
|
|
<span class="kn">from</span><span class="w"> </span><span class="nn">transformers</span><span class="w"> </span><span class="kn">import</span> <span class="n">GptOssForCausalLM</span><span class="p">,</span> <span class="n">PreTrainedTokenizerFast</span><span class="p">,</span> <span class="n">Mxfp4Config</span> |
|
|
<span class="kn">import</span><span class="w"> </span><span class="nn">time</span> |
|
|
<span class="kn">import</span><span class="w"> </span><span class="nn">torch.nn</span><span class="w"> </span><span class="k">as</span><span class="w"> </span><span class="nn">nn</span> |
|
|
<span class="kn">from</span><span class="w"> </span><span class="nn">kernels</span><span class="w"> </span><span class="kn">import</span> <span class="n">register_kernel_mapping</span><span class="p">,</span> <span class="n">Mode</span><span class="p">,</span> <span class="n">LayerRepository</span> |
|
|
<span class="kn">import</span><span class="w"> </span><span class="nn">sys</span> |
|
|
<span class="kn">import</span><span class="w"> </span><span class="nn">torch.profiler</span> |
|
|
<span class="kn">import</span><span class="w"> </span><span class="nn">gc</span> |
|
|
|
|
|
<span class="k">def</span><span class="w"> </span><span class="nf">reset_peak_memory_stats</span><span class="p">():</span> |
|
|
<span class="w"> </span><span class="sd">"""Clear CUDA cache and reset memory allocation counters."""</span> |
|
|
<span class="n">torch</span><span class="o">.</span><span class="n">cuda</span><span class="o">.</span><span class="n">empty_cache</span><span class="p">()</span> |
|
|
<span class="k">if</span> <span class="n">torch</span><span class="o">.</span><span class="n">cuda</span><span class="o">.</span><span class="n">is_available</span><span class="p">():</span> |
|
|
<span class="n">torch</span><span class="o">.</span><span class="n">cuda</span><span class="o">.</span><span class="n">reset_peak_memory_stats</span><span class="p">()</span> |
|
|
<span class="n">gc</span><span class="o">.</span><span class="n">collect</span><span class="p">()</span> |
|
|
|
|
|
<span class="k">def</span><span class="w"> </span><span class="nf">get_memory_stats</span><span class="p">():</span> |
|
|
<span class="w"> </span><span class="sd">"""Get current and peak CUDA memory usage."""</span> |
|
|
<span class="k">if</span> <span class="ow">not</span> <span class="n">torch</span><span class="o">.</span><span class="n">cuda</span><span class="o">.</span><span class="n">is_available</span><span class="p">():</span> |
|
|
<span class="k">return</span> <span class="p">{</span><span class="s2">"allocated_gb"</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span> <span class="s2">"peak_gb"</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span> <span class="s2">"reserved_gb"</span><span class="p">:</span> <span class="mi">0</span><span class="p">}</span> |
|
|
<span class="k">return</span> <span class="p">{</span> |
|
|
<span class="s2">"allocated_gb"</span><span class="p">:</span> <span class="n">torch</span><span class="o">.</span><span class="n">cuda</span><span class="o">.</span><span class="n">memory_allocated</span><span class="p">()</span> <span class="o">/</span> <span class="mf">1e9</span><span class="p">,</span> |
|
|
<span class="s2">"peak_gb"</span><span class="p">:</span> <span class="n">torch</span><span class="o">.</span><span class="n">cuda</span><span class="o">.</span><span class="n">max_memory_allocated</span><span class="p">()</span> <span class="o">/</span> <span class="mf">1e9</span><span class="p">,</span> |
|
|
<span class="s2">"reserved_gb"</span><span class="p">:</span> <span class="n">torch</span><span class="o">.</span><span class="n">cuda</span><span class="o">.</span><span class="n">memory_reserved</span><span class="p">()</span> <span class="o">/</span> <span class="mf">1e9</span><span class="p">,</span> |
|
|
<span class="p">}</span> |
|
|
|
|
|
<span class="k">def</span><span class="w"> </span><span class="nf">override_kernel_layer_name</span><span class="p">(</span><span class="n">cls_name</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span> <span class="o">-></span> <span class="nb">bool</span><span class="p">:</span> |
|
|
<span class="w"> </span><span class="sd">"""Helper to dynamically override the kernel_layer_name in a model class."""</span> |
|
|
<span class="k">for</span> <span class="n">mod</span> <span class="ow">in</span> <span class="n">sys</span><span class="o">.</span><span class="n">modules</span><span class="o">.</span><span class="n">values</span><span class="p">():</span> |
|
|
<span class="k">if</span> <span class="n">mod</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span> |
|
|
<span class="k">continue</span> |
|
|
<span class="n">obj</span> <span class="o">=</span> <span class="nb">getattr</span><span class="p">(</span><span class="n">mod</span><span class="p">,</span> <span class="n">cls_name</span><span class="p">,</span> <span class="kc">None</span><span class="p">)</span> |
|
|
<span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="nb">type</span><span class="p">)</span> <span class="ow">and</span> <span class="nb">issubclass</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="n">nn</span><span class="o">.</span><span class="n">Module</span><span class="p">):</span> |
|
|
<span class="nb">setattr</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="s2">"kernel_layer_name"</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span> |
|
|
<span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Overrode </span><span class="si">{</span><span class="n">cls_name</span><span class="si">}</span><span class="s2">.kernel_layer_name to </span><span class="si">{</span><span class="n">value</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span> |
|
|
<span class="k">return</span> <span class="kc">True</span> |
|
|
<span class="k">return</span> <span class="kc">False</span> |
|
|
|
|
|
<span class="k">def</span><span class="w"> </span><span class="nf">run_generation</span><span class="p">(</span><span class="n">model</span><span class="p">,</span> <span class="n">inputs</span><span class="p">,</span> <span class="n">max_tokens</span><span class="o">=</span><span class="mi">64</span><span class="p">):</span> |
|
|
<span class="w"> </span><span class="sd">"""Run a single generation pass and measure its duration."""</span> |
|
|
<span class="k">with</span> <span class="n">torch</span><span class="o">.</span><span class="n">inference_mode</span><span class="p">():</span> |
|
|
<span class="n">start_time</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span> |
|
|
<span class="n">generated</span> <span class="o">=</span> <span class="n">model</span><span class="o">.</span><span class="n">generate</span><span class="p">(</span> |
|
|
<span class="o">**</span><span class="n">inputs</span><span class="p">,</span> |
|
|
<span class="n">max_new_tokens</span><span class="o">=</span><span class="n">max_tokens</span><span class="p">,</span> |
|
|
<span class="n">do_sample</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span> |
|
|
<span class="n">temperature</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> |
|
|
<span class="p">)</span> |
|
|
<span class="n">end_time</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span> |
|
|
<span class="k">return</span> <span class="n">generated</span><span class="p">,</span> <span class="n">end_time</span> <span class="o">-</span> <span class="n">start_time</span> |
|
|
|
|
|
|
|
|
<span class="c1"># Init the model the normal way</span> |
|
|
<span class="n">model_id</span> <span class="o">=</span> <span class="s2">"openai/gpt-oss-20b"</span> |
|
|
<span class="n">tokenizer</span> <span class="o">=</span> <span class="n">PreTrainedTokenizerFast</span><span class="o">.</span><span class="n">from_pretrained</span><span class="p">(</span><span class="n">model_id</span><span class="p">)</span> |
|
|
<span class="n">quantization_config</span> <span class="o">=</span> <span class="n">Mxfp4Config</span><span class="p">(</span><span class="n">dequantize</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span> |
|
|
|
|
|
<span class="c1"># Now we want to add some custom kernel mapping</span> |
|
|
<span class="n">custom_mapping</span> <span class="o">=</span> <span class="nb">dict</span><span class="p">(</span> |
|
|
<span class="n">Yamoe</span><span class="o">=</span><span class="nb">dict</span><span class="p">(</span> |
|
|
<span class="n">cuda</span><span class="o">=</span><span class="p">{</span> |
|
|
<span class="n">Mode</span><span class="o">.</span><span class="n">INFERENCE</span><span class="p">:</span> <span class="n">LayerRepository</span><span class="p">(</span> |
|
|
<span class="n">repo_id</span><span class="o">=</span><span class="s2">"drbh/yamoe"</span><span class="p">,</span> |
|
|
<span class="n">layer_name</span><span class="o">=</span><span class="s2">"Yamoe"</span><span class="p">,</span> |
|
|
<span class="n">revision</span><span class="o">=</span><span class="s2">"v0.3.0"</span><span class="p">,</span> |
|
|
<span class="p">),</span> |
|
|
<span class="p">},</span> |
|
|
<span class="p">)</span> |
|
|
<span class="p">)</span> |
|
|
<span class="c1"># First add the mapping</span> |
|
|
<span class="n">register_kernel_mapping</span><span class="p">(</span><span class="n">custom_mapping</span><span class="p">)</span> |
|
|
<span class="c1"># Then override the layer name in the model class</span> |
|
|
<span class="n">override_kernel_layer_name</span><span class="p">(</span><span class="s2">"GptOssMLP"</span><span class="p">,</span> <span class="s2">"Yamoe"</span><span class="p">)</span> |
|
|
|
|
|
<span class="c1"># TODO: remove this line once RMSNorm is working</span> |
|
|
<span class="n">override_kernel_layer_name</span><span class="p">(</span><span class="s2">"GptOssRMSNorm"</span><span class="p">,</span> <span class="kc">None</span><span class="p">)</span> |
|
|
|
|
|
<span class="c1">## Normal model stuff</span> |
|
|
|
|
|
<span class="n">model</span> <span class="o">=</span> <span class="n">GptOssForCausalLM</span><span class="o">.</span><span class="n">from_pretrained</span><span class="p">(</span> |
|
|
<span class="n">model_id</span><span class="p">,</span> |
|
|
<span class="n">dtype</span><span class="o">=</span><span class="s2">"bfloat16"</span><span class="p">,</span> |
|
|
<span class="n">device_map</span><span class="o">=</span><span class="s2">"auto"</span><span class="p">,</span> |
|
|
<span class="n">use_kernels</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> |
|
|
<span class="n">quantization_config</span><span class="o">=</span><span class="n">quantization_config</span><span class="p">,</span> |
|
|
<span class="p">)</span><span class="o">.</span><span class="n">eval</span><span class="p">()</span> |
|
|
|
|
|
|
|
|
<span class="n">messages</span> <span class="o">=</span> <span class="p">[</span> |
|
|
<span class="p">{</span><span class="s2">"role"</span><span class="p">:</span> <span class="s2">"system"</span><span class="p">,</span> <span class="s2">"content"</span><span class="p">:</span> <span class="s2">"What is Tensor Parallelism?"</span><span class="p">},</span> |
|
|
<span class="p">]</span> |
|
|
|
|
|
<span class="n">inputs</span> <span class="o">=</span> <span class="n">tokenizer</span><span class="o">.</span><span class="n">apply_chat_template</span><span class="p">(</span> |
|
|
<span class="n">messages</span><span class="p">,</span> |
|
|
<span class="n">add_generation_prompt</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> |
|
|
<span class="n">return_tensors</span><span class="o">=</span><span class="s2">"pt"</span><span class="p">,</span> |
|
|
<span class="n">return_dict</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> |
|
|
<span class="n">reasoning_effort</span><span class="o">=</span><span class="s2">"low"</span><span class="p">,</span> |
|
|
<span class="p">)</span><span class="o">.</span><span class="n">to</span><span class="p">(</span><span class="s2">"cuda"</span><span class="p">)</span> |
|
|
|
|
|
|
|
|
|
|
|
<span class="k">def</span><span class="w"> </span><span class="nf">run_generation</span><span class="p">(</span><span class="n">model</span><span class="p">,</span> <span class="n">inputs</span><span class="p">,</span> <span class="n">max_tokens</span><span class="o">=</span><span class="mi">64</span><span class="p">):</span> |
|
|
<span class="k">with</span> <span class="n">torch</span><span class="o">.</span><span class="n">inference_mode</span><span class="p">():</span> |
|
|
<span class="n">start_time</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span> |
|
|
<span class="n">generated</span> <span class="o">=</span> <span class="n">model</span><span class="o">.</span><span class="n">generate</span><span class="p">(</span> |
|
|
<span class="o">**</span><span class="n">inputs</span><span class="p">,</span> |
|
|
<span class="n">max_new_tokens</span><span class="o">=</span><span class="n">max_tokens</span><span class="p">,</span> |
|
|
<span class="n">do_sample</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span> |
|
|
<span class="n">temperature</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> |
|
|
<span class="p">)</span> |
|
|
<span class="n">end_time</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">perf_counter</span><span class="p">()</span> |
|
|
<span class="k">return</span> <span class="n">generated</span><span class="p">,</span> <span class="n">end_time</span> <span class="o">-</span> <span class="n">start_time</span> |
|
|
|
|
|
|
|
|
<span class="nb">print</span><span class="p">(</span><span class="s2">"</span><span class="se">\n</span><span class="s2">=== Running Benchmarks ==="</span><span class="p">)</span> |
|
|
<span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Model: </span><span class="si">{</span><span class="n">model_id</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span> |
|
|
<span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Device: </span><span class="si">{</span><span class="n">torch</span><span class="o">.</span><span class="n">cuda</span><span class="o">.</span><span class="n">get_device_name</span><span class="p">()</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span> |
|
|
<span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Initial memory: </span><span class="si">{</span><span class="n">get_memory_stats</span><span class="p">()</span><span class="si">}</span><span class="se">\n</span><span class="s2">"</span><span class="p">)</span> |
|
|
|
|
|
<span class="c1"># Warmup</span> |
|
|
<span class="nb">print</span><span class="p">(</span><span class="s2">"Running warmup..."</span><span class="p">)</span> |
|
|
<span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">2</span><span class="p">):</span> |
|
|
<span class="n">_</span> <span class="o">=</span> <span class="n">run_generation</span><span class="p">(</span><span class="n">model</span><span class="p">,</span> <span class="n">inputs</span><span class="p">,</span> <span class="n">max_tokens</span><span class="o">=</span><span class="mi">16</span><span class="p">)</span> |
|
|
|
|
|
<span class="n">reset_peak_memory_stats</span><span class="p">()</span> |
|
|
|
|
|
<span class="c1"># Benchmark runs</span> |
|
|
<span class="n">num_runs</span> <span class="o">=</span> <span class="mi">5</span> |
|
|
<span class="n">max_tokens</span> <span class="o">=</span> <span class="mi">64</span> |
|
|
<span class="n">times</span> <span class="o">=</span> <span class="p">[]</span> |
|
|
|
|
|
<span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">"</span><span class="se">\n</span><span class="s2">Running </span><span class="si">{</span><span class="n">num_runs</span><span class="si">}</span><span class="s2"> benchmark iterations with </span><span class="si">{</span><span class="n">max_tokens</span><span class="si">}</span><span class="s2"> tokens..."</span><span class="p">)</span> |
|
|
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">num_runs</span><span class="p">):</span> |
|
|
<span class="n">reset_peak_memory_stats</span><span class="p">()</span> |
|
|
<span class="n">generated</span><span class="p">,</span> <span class="n">elapsed</span> <span class="o">=</span> <span class="n">run_generation</span><span class="p">(</span><span class="n">model</span><span class="p">,</span> <span class="n">inputs</span><span class="p">,</span> <span class="n">max_tokens</span><span class="p">)</span> |
|
|
<span class="n">times</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">elapsed</span><span class="p">)</span> |
|
|
<span class="n">mem_stats</span> <span class="o">=</span> <span class="n">get_memory_stats</span><span class="p">()</span> |
|
|
<span class="n">tokens_per_sec</span> <span class="o">=</span> <span class="n">max_tokens</span> <span class="o">/</span> <span class="n">elapsed</span> |
|
|
<span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Run </span><span class="si">{</span><span class="n">i</span><span class="o">+</span><span class="mi">1</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="n">elapsed</span><span class="si">:</span><span class="s2">.3f</span><span class="si">}</span><span class="s2">s (</span><span class="si">{</span><span class="n">tokens_per_sec</span><span class="si">:</span><span class="s2">.1f</span><span class="si">}</span><span class="s2"> tok/s) | Peak: </span><span class="si">{</span><span class="n">mem_stats</span><span class="p">[</span><span class="s1">'peak_gb'</span><span class="p">]</span><span class="si">:</span><span class="s2">.2f</span><span class="si">}</span><span class="s2">GB"</span><span class="p">)</span> |
|
|
|
|
|
<span class="c1"># Statistics</span> |
|
|
<span class="n">avg_time</span> <span class="o">=</span> <span class="nb">sum</span><span class="p">(</span><span class="n">times</span><span class="p">)</span> <span class="o">/</span> <span class="nb">len</span><span class="p">(</span><span class="n">times</span><span class="p">)</span> |
|
|
<span class="n">min_time</span> <span class="o">=</span> <span class="nb">min</span><span class="p">(</span><span class="n">times</span><span class="p">)</span> |
|
|
<span class="n">max_time</span> <span class="o">=</span> <span class="nb">max</span><span class="p">(</span><span class="n">times</span><span class="p">)</span> |
|
|
<span class="n">avg_tokens_per_sec</span> <span class="o">=</span> <span class="n">max_tokens</span> <span class="o">/</span> <span class="n">avg_time</span> |
|
|
|
|
|
<span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">"</span><span class="se">\n</span><span class="s2">=== Benchmark Results ==="</span><span class="p">)</span> |
|
|
<span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Average: </span><span class="si">{</span><span class="n">avg_time</span><span class="si">:</span><span class="s2">.3f</span><span class="si">}</span><span class="s2">s (</span><span class="si">{</span><span class="n">avg_tokens_per_sec</span><span class="si">:</span><span class="s2">.1f</span><span class="si">}</span><span class="s2"> tok/s)"</span><span class="p">)</span> |
|
|
<span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Min: </span><span class="si">{</span><span class="n">min_time</span><span class="si">:</span><span class="s2">.3f</span><span class="si">}</span><span class="s2">s | Max: </span><span class="si">{</span><span class="n">max_time</span><span class="si">:</span><span class="s2">.3f</span><span class="si">}</span><span class="s2">s"</span><span class="p">)</span> |
|
|
|
|
|
<span class="c1"># Final memory stats</span> |
|
|
<span class="n">final_mem</span> <span class="o">=</span> <span class="n">get_memory_stats</span><span class="p">()</span> |
|
|
<span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">"</span><span class="se">\n</span><span class="s2">Final Memory:"</span><span class="p">)</span> |
|
|
<span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">" Allocated: </span><span class="si">{</span><span class="n">final_mem</span><span class="p">[</span><span class="s1">'allocated_gb'</span><span class="p">]</span><span class="si">:</span><span class="s2">.2f</span><span class="si">}</span><span class="s2">GB"</span><span class="p">)</span> |
|
|
<span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">" Peak: </span><span class="si">{</span><span class="n">final_mem</span><span class="p">[</span><span class="s1">'peak_gb'</span><span class="p">]</span><span class="si">:</span><span class="s2">.2f</span><span class="si">}</span><span class="s2">GB"</span><span class="p">)</span> |
|
|
<span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">" Reserved: </span><span class="si">{</span><span class="n">final_mem</span><span class="p">[</span><span class="s1">'reserved_gb'</span><span class="p">]</span><span class="si">:</span><span class="s2">.2f</span><span class="si">}</span><span class="s2">GB"</span><span class="p">)</span> |
|
|
|
|
|
|
|
|
<span class="nb">print</span><span class="p">(</span><span class="s2">"</span><span class="se">\n</span><span class="s2">=== Running with Profiler ==="</span><span class="p">)</span> |
|
|
<span class="n">reset_peak_memory_stats</span><span class="p">()</span> |
|
|
|
|
|
<span class="k">with</span> <span class="n">torch</span><span class="o">.</span><span class="n">profiler</span><span class="o">.</span><span class="n">profile</span><span class="p">(</span> |
|
|
<span class="n">activities</span><span class="o">=</span><span class="p">[</span> |
|
|
<span class="n">torch</span><span class="o">.</span><span class="n">profiler</span><span class="o">.</span><span class="n">ProfilerActivity</span><span class="o">.</span><span class="n">CPU</span><span class="p">,</span> |
|
|
<span class="n">torch</span><span class="o">.</span><span class="n">profiler</span><span class="o">.</span><span class="n">ProfilerActivity</span><span class="o">.</span><span class="n">CUDA</span><span class="p">,</span> |
|
|
<span class="p">],</span> |
|
|
<span class="n">record_shapes</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> |
|
|
<span class="n">profile_memory</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> |
|
|
<span class="n">with_stack</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> |
|
|
<span class="p">)</span> <span class="k">as</span> <span class="n">prof</span><span class="p">:</span> |
|
|
<span class="n">generated</span><span class="p">,</span> <span class="n">elapsed</span> <span class="o">=</span> <span class="n">run_generation</span><span class="p">(</span><span class="n">model</span><span class="p">,</span> <span class="n">inputs</span><span class="p">,</span> <span class="n">max_tokens</span><span class="o">=</span><span class="mi">64</span><span class="p">)</span> |
|
|
|
|
|
<span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Generation time: </span><span class="si">{</span><span class="n">elapsed</span><span class="si">:</span><span class="s2">.2f</span><span class="si">}</span><span class="s2"> seconds"</span><span class="p">)</span> |
|
|
|
|
|
<span class="c1"># Print profiler results</span> |
|
|
<span class="nb">print</span><span class="p">(</span><span class="s2">"</span><span class="se">\n</span><span class="s2">=== Top 10 CUDA operations by time ==="</span><span class="p">)</span> |
|
|
<span class="nb">print</span><span class="p">(</span><span class="n">prof</span><span class="o">.</span><span class="n">key_averages</span><span class="p">()</span><span class="o">.</span><span class="n">table</span><span class="p">(</span><span class="n">sort_by</span><span class="o">=</span><span class="s2">"cuda_time_total"</span><span class="p">,</span> <span class="n">row_limit</span><span class="o">=</span><span class="mi">10</span><span class="p">))</span> |
|
|
|
|
|
<span class="nb">print</span><span class="p">(</span><span class="s2">"</span><span class="se">\n</span><span class="s2">=== Top 10 operations by memory ==="</span><span class="p">)</span> |
|
|
<span class="nb">print</span><span class="p">(</span><span class="n">prof</span><span class="o">.</span><span class="n">key_averages</span><span class="p">()</span><span class="o">.</span><span class="n">table</span><span class="p">(</span><span class="n">sort_by</span><span class="o">=</span><span class="s2">"self_cuda_memory_usage"</span><span class="p">,</span> <span class="n">row_limit</span><span class="o">=</span><span class="mi">10</span><span class="p">))</span> |
|
|
|
|
|
<span class="c1"># Memory stats</span> |
|
|
<span class="n">mem_stats</span> <span class="o">=</span> <span class="n">get_memory_stats</span><span class="p">()</span> |
|
|
<span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">"</span><span class="se">\n</span><span class="s2">Peak Memory: </span><span class="si">{</span><span class="n">mem_stats</span><span class="p">[</span><span class="s1">'peak_gb'</span><span class="p">]</span><span class="si">:</span><span class="s2">.2f</span><span class="si">}</span><span class="s2">GB"</span><span class="p">)</span> |
|
|
|
|
|
<span class="c1"># Save trace if needed</span> |
|
|
<span class="n">prof</span><span class="o">.</span><span class="n">export_chrome_trace</span><span class="p">(</span><span class="s2">"trace.json"</span><span class="p">)</span> |
|
|
<span class="nb">print</span><span class="p">(</span><span class="s2">"</span><span class="se">\n</span><span class="s2">Profile trace saved to trace.json"</span><span class="p">)</span> |
|
|
|
|
|
|
|
|
<span class="c1"># Decode and print output</span> |
|
|
<span class="nb">print</span><span class="p">(</span><span class="s2">"</span><span class="se">\n</span><span class="s2">Generated text:"</span><span class="p">)</span> |
|
|
<span class="nb">print</span><span class="p">(</span><span class="n">tokenizer</span><span class="o">.</span><span class="n">decode</span><span class="p">(</span><span class="n">generated</span><span class="p">[</span><span class="mi">0</span><span class="p">][</span><span class="n">inputs</span><span class="p">[</span><span class="s2">"input_ids"</span><span class="p">]</span><span class="o">.</span><span class="n">shape</span><span class="p">[</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span> <span class="p">:]))</span> |
|
|
|
|
|
|
|
|
<span class="c1"># save times and memory stats for charting</span> |
|
|
<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s2">"benchmark_times.txt"</span><span class="p">,</span> <span class="s2">"w"</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span> |
|
|
<span class="k">for</span> <span class="n">t</span> <span class="ow">in</span> <span class="n">times</span><span class="p">:</span> |
|
|
<span class="n">f</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="sa">f</span><span class="s2">"</span><span class="si">{</span><span class="n">t</span><span class="si">}</span><span class="se">\n</span><span class="s2">"</span><span class="p">)</span> |
|
|
<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s2">"benchmark_memory.txt"</span><span class="p">,</span> <span class="s2">"w"</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span> |
|
|
<span class="n">f</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="sa">f</span><span class="s2">"</span><span class="si">{</span><span class="n">final_mem</span><span class="p">[</span><span class="s1">'allocated_gb'</span><span class="p">]</span><span class="si">}</span><span class="s2">,</span><span class="si">{</span><span class="n">final_mem</span><span class="p">[</span><span class="s1">'peak_gb'</span><span class="p">]</span><span class="si">}</span><span class="s2">,</span><span class="si">{</span><span class="n">final_mem</span><span class="p">[</span><span class="s1">'reserved_gb'</span><span class="p">]</span><span class="si">}</span><span class="se">\n</span><span class="s2">"</span><span class="p">)</span> |
|
|
|
|
|
<span class="c1"># save avg_tokens_per_sec for charting</span> |
|
|
<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s2">"benchmark_avg_tokens_per_sec.txt"</span><span class="p">,</span> <span class="s2">"w"</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span> |
|
|
<span class="n">f</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="sa">f</span><span class="s2">"</span><span class="si">{</span><span class="n">avg_tokens_per_sec</span><span class="si">}</span><span class="se">\n</span><span class="s2">"</span><span class="p">)</span> |
|
|
|
|
|
</pre></div></td></tr></table></div> |
|
|
|
|
|
</div> |
|
|
<div id="output-setup" class="cell-output"> |
|
|
<div class="cell-stdout">Overrode GptOssMLP.kernel_layer_name to Yamoe |
|
|
Overrode GptOssRMSNorm.kernel_layer_name to None |
|
|
|
|
|
=== Running Benchmarks === |
|
|
Model: openai/gpt-oss-20b |
|
|
Device: NVIDIA L4 |
|
|
Initial memory: {'allocated_gb': 9.390148608, 'peak_gb': 15.5643264, 'reserved_gb': 17.177772032} |
|
|
|
|
|
Running warmup... |
|
|
|
|
|
Running 5 benchmark iterations with 64 tokens... |
|
|
Run 1: 12.075s (5.3 tok/s) | Peak: 9.41GB |
|
|
Run 2: 12.071s (5.3 tok/s) | Peak: 9.41GB |
|
|
Run 3: 12.070s (5.3 tok/s) | Peak: 9.41GB |
|
|
Run 4: 12.071s (5.3 tok/s) | Peak: 9.41GB |
|
|
Run 5: 12.071s (5.3 tok/s) | Peak: 9.41GB |
|
|
|
|
|
=== Benchmark Results === |
|
|
Average: 12.072s (5.3 tok/s) |
|
|
Min: 12.070s | Max: 12.075s |
|
|
|
|
|
Final Memory: |
|
|
Allocated: 9.40GB |
|
|
Peak: 9.41GB |
|
|
Reserved: 10.33GB |
|
|
|
|
|
=== Running with Profiler === |
|
|
Generation time: 12.73 seconds |
|
|
|
|
|
=== Top 10 CUDA operations by time === |
|
|
------------------------------------------------------- ------------ ------------ ------------ ------------ ------------ ------------ ------------ ------------ ------------ ------------ ------------ ------------ ------------ ------------ |
|
|
Name Self CPU % Self CPU CPU total % CPU total CPU time avg Self CUDA Self CUDA % CUDA total CUDA time avg CPU Mem Self CPU Mem CUDA Mem Self CUDA Mem # of Calls |
|
|
------------------------------------------------------- ------------ ------------ ------------ ------------ ------------ ------------ ------------ ------------ ------------ ------------ ------------ ------------ ------------ ------------ |
|
|
_yamoe_74a2acb_dirty::experts 1.40% 148.156ms 66.87% 7.074s 4.606ms 52.388ms 0.46% 10.583s 6.890ms 0 B -2.98 KB 18.88 MB -2.11 GB 1536 |
|
|
aten::bmm 1.25% 132.560ms 1.75% 185.015ms 29.803us 10.486s 91.79% 10.486s 1.689ms 0 B 0 B 63.12 MB 63.12 MB 6208 |
|
|
void cutlass::Kernel2<cutlass_80_wmma_tensorop_bf16_... 0.00% 0.000us 0.00% 0.000us 0.000us 10.319s 90.32% 10.319s 3.412ms 0 B 0 B 0 B 0 B 3024 |
|
|
aten::linear 0.54% 57.566ms 3.78% 399.802ms 51.627us 0.000us 0.00% 645.165ms 83.312us 0 B 0 B 76.88 MB 0 B 7744 |
|
|
aten::addmm 1.81% 191.354ms 2.57% 272.095ms 35.429us 352.039ms 3.08% 352.151ms 45.853us 0 B 0 B 52.31 MB 52.31 MB 7680 |
|
|
std::enable_if<!(false), void>::type internal::gemvx... 0.00% 0.000us 0.00% 0.000us 0.000us 344.917ms 3.02% 344.917ms 74.982us 0 B 0 B 0 B 0 B 4600 |
|
|
aten::matmul 0.31% 32.441ms 1.72% 181.712ms 56.785us 0.000us 0.00% 303.821ms 94.944us 0 B 0 B 87.68 MB 0 B 3200 |
|
|
std::enable_if<!(false), void>::type internal::gemvx... 0.00% 0.000us 0.00% 0.000us 0.000us 293.850ms 2.57% 293.850ms 97.173us 0 B 0 B 0 B 0 B 3024 |
|
|
aten::mm 0.01% 1.506ms 0.02% 2.161ms 33.768us 293.014ms 2.56% 293.014ms 4.578ms 0 B 0 B 24.56 MB 24.56 MB 64 |
|
|
ampere_bf16_s16816gemm_bf16_128x64_ldg8_f2f_nn 0.00% 0.000us 0.00% 0.000us 0.000us 102.278ms 0.90% 102.278ms 4.262ms 0 B 0 B 0 B 0 B 24 |
|
|
------------------------------------------------------- ------------ ------------ ------------ ------------ ------------ ------------ ------------ ------------ ------------ ------------ ------------ ------------ ------------ ------------ |
|
|
Self CPU time total: 10.579s |
|
|
Self CUDA time total: 11.424s |
|
|
|
|
|
|
|
|
=== Top 10 operations by memory === |
|
|
------------------------------------------------------- ------------ ------------ ------------ ------------ ------------ ------------ ------------ ------------ ------------ ------------ ------------ ------------ ------------ ------------ |
|
|
Name Self CPU % Self CPU CPU total % CPU total CPU time avg Self CUDA Self CUDA % CUDA total CUDA time avg CPU Mem Self CPU Mem CUDA Mem Self CUDA Mem # of Calls |
|
|
------------------------------------------------------- ------------ ------------ ------------ ------------ ------------ ------------ ------------ ------------ ------------ ------------ ------------ ------------ ------------ ------------ |
|
|
aten::empty 0.68% 72.026ms 0.68% 72.026ms 4.244us 0.000us 0.00% 0.000us 0.000us 296 B 296 B 3.49 GB 3.49 GB 16973 |
|
|
aten::clamp 0.46% 48.185ms 0.69% 72.630ms 15.762us 10.269ms 0.09% 10.269ms 2.229us 0 B 0 B 616.69 MB 616.69 MB 4608 |
|
|
aten::mul 1.76% 186.048ms 2.93% 310.383ms 14.181us 47.780ms 0.42% 47.792ms 2.184us 784 B 784 B 554.93 MB 554.93 MB 21888 |
|
|
aten::cat 0.78% 82.030ms 1.22% 129.113ms 16.536us 17.028ms 0.15% 17.030ms 2.181us 0 B 0 B 387.88 MB 387.88 MB 7808 |
|
|
aten::sigmoid 0.09% 9.855ms 0.16% 16.652ms 10.841us 2.889ms 0.03% 2.889ms 1.881us 0 B 0 B 307.97 MB 307.97 MB 1536 |
|
|
aten::empty_strided 1.08% 114.498ms 1.10% 116.720ms 5.564us 0.000us 0.00% 0.000us 0.000us 0 B 0 B 216.60 MB 216.60 MB 20979 |
|
|
aten::add 0.93% 97.861ms 1.56% 164.673ms 15.047us 16.394ms 0.14% 16.395ms 1.498us 0 B 0 B 91.03 MB 91.03 MB 10944 |
|
|
aten::pow 0.36% 38.271ms 0.55% 58.020ms 18.501us 4.117ms 0.04% 4.117ms 1.313us 0 B 0 B 75.58 MB 75.58 MB 3136 |
|
|
aten::bmm 1.25% 132.560ms 1.75% 185.015ms 29.803us 10.486s 91.79% 10.486s 1.689ms 0 B 0 B 63.12 MB 63.12 MB 6208 |
|
|
aten::sub 0.51% 53.869ms 0.82% 87.218ms 13.626us 9.277ms 0.08% 9.355ms 1.461us 0 B 0 B 53.04 MB 53.01 MB 6401 |
|
|
------------------------------------------------------- ------------ ------------ ------------ ------------ ------------ ------------ ------------ ------------ ------------ ------------ ------------ ------------ ------------ ------------ |
|
|
Self CPU time total: 10.579s |
|
|
Self CUDA time total: 11.424s |
|
|
|
|
|
|
|
|
Peak Memory: 9.41GB |
|
|
|
|
|
Profile trace saved to trace.json |
|
|
|
|
|
Generated text: |
|
|
<|channel|>analysis<|message|>We need to explain what Tensor Parallelism is. It's a concept in distributed training of large language models. It refers to splitting the weight matrices (tensors) across multiple devices, so each device holds a slice of the matrix. During forward/backward passes, each device computes partial results and then they are |
|
|
</div> |
|
|
<div class="uv-install-logs" id="uv-logs-setup"> |
|
|
<div class="uv-logs-header" onclick="toggleUvLogs(this)">▶ UV Install Logs</div> |
|
|
<div class="uv-logs-content" style="display: none;"> |
|
|
Updating https://github.com/huggingface/transformers.git (HEAD) |
|
|
Updated https://github.com/huggingface/transformers.git (cbb290ec23ccd9b5c1d1ff4d333477449891debb) |
|
|
Downloading nvidia-cufft-cu12 (184.2MiB) |
|
|
Downloading matplotlib (8.3MiB) |
|
|
Downloading tokenizers (3.1MiB) |
|
|
Downloading kiwisolver (1.4MiB) |
|
|
Building transformers @ git+https://github.com/huggingface/transformers.git@cbb290ec23ccd9b5c1d1ff4d333477449891debb |
|
|
Downloading nvidia-cufile-cu12 (1.1MiB) |
|
|
Downloading nvidia-cuda-nvrtc-cu12 (84.0MiB) |
|
|
Downloading hf-xet (3.0MiB) |
|
|
Downloading fonttools (4.7MiB) |
|
|
Downloading numpy (15.9MiB) |
|
|
Downloading nvidia-cusparse-cu12 (274.9MiB) |
|
|
Downloading pygments (1.2MiB) |
|
|
Downloading nvidia-cublas-cu12 (566.8MiB) |
|
|
Downloading nvidia-cusolver-cu12 (255.1MiB) |
|
|
Downloading pillow (6.3MiB) |
|
|
Downloading nvidia-cusparselt-cu12 (273.9MiB) |
|
|
Downloading nvidia-nvjitlink-cu12 (37.4MiB) |
|
|
Downloading networkx (1.9MiB) |
|
|
Downloading nvidia-nccl-cu12 (307.4MiB) |
|
|
Downloading nvidia-curand-cu12 (60.7MiB) |
|
|
Downloading jedi (1.5MiB) |
|
|
Downloading sympy (6.0MiB) |
|
|
Downloading nvidia-cuda-cupti-cu12 (9.8MiB) |
|
|
Downloading triton (148.4MiB) |
|
|
Downloading nvidia-cudnn-cu12 (674.0MiB) |
|
|
Downloading torch (846.8MiB) |
|
|
Downloading nvidia-cufile-cu12 |
|
|
Downloading kiwisolver |
|
|
Downloading pygments |
|
|
Downloading hf-xet |
|
|
Downloading tokenizers |
|
|
Downloading networkx |
|
|
Downloading fonttools |
|
|
Downloading pillow |
|
|
Downloading matplotlib |
|
|
Downloading nvidia-cuda-cupti-cu12 |
|
|
Downloading numpy |
|
|
Downloading sympy |
|
|
Downloading nvidia-nvjitlink-cu12 |
|
|
Built transformers @ git+https://github.com/huggingface/transformers.git@cbb290ec23ccd9b5c1d1ff4d333477449891debb |
|
|
Downloading jedi |
|
|
Downloading nvidia-curand-cu12 |
|
|
Downloading nvidia-cuda-nvrtc-cu12 |
|
|
Downloading triton |
|
|
Downloading nvidia-cufft-cu12 |
|
|
Downloading nvidia-cusolver-cu12 |
|
|
Downloading nvidia-cusparse-cu12 |
|
|
Downloading nvidia-cusparselt-cu12 |
|
|
Downloading nvidia-nccl-cu12 |
|
|
Downloading nvidia-cublas-cu12 |
|
|
Downloading nvidia-cudnn-cu12 |
|
|
Downloading torch |
|
|
Installed 69 packages in 220ms |
|
|
</div> |
|
|
</div> |
|
|
<div class="cell-stderr">Fetching 3 files: 0%| | 0/3 [00:00<?, ?it/s] |
|
|
Fetching 3 files: 33%|███▎ | 1/3 [00:11<00:23, 11.59s/it] |
|
|
Fetching 3 files: 67%|██████▋ | 2/3 [00:16<00:07, 7.73s/it] |
|
|
Fetching 3 files: 100%|██████████| 3/3 [00:16<00:00, 5.54s/it] |
|
|
You are using full precision kernels, we will dequantize the model to bf16. To use the quantized model with quantization kernels, please set use_kernels=False |
|
|
|
|
|
Loading checkpoint shards: 0%| | 0/3 [00:00<?, ?it/s] |
|
|
Loading checkpoint shards: 33%|███▎ | 1/3 [00:03<00:06, 3.23s/it] |
|
|
Loading checkpoint shards: 67%|██████▋ | 2/3 [00:06<00:03, 3.15s/it] |
|
|
Loading checkpoint shards: 100%|██████████| 3/3 [00:08<00:00, 2.50s/it] |
|
|
Loading checkpoint shards: 100%|██████████| 3/3 [00:08<00:00, 2.68s/it] |
|
|
|
|
|
Fetching 6 files: 0%| | 0/6 [00:00<?, ?it/s] |
|
|
Fetching 6 files: 17%|█▋ | 1/6 [00:00<00:00, 5.23it/s] |
|
|
Fetching 6 files: 50%|█████ | 3/6 [00:00<00:00, 6.19it/s] |
|
|
Fetching 6 files: 100%|██████████| 6/6 [00:00<00:00, 12.15it/s] |
|
|
/tmp/uvnote-run-hjgpkuq6/home/.cache/uv/environments-v2/setup-30bb029f3f83f37d/lib/python3.12/site-packages/kernels/layer.py:868: UserWarning: |
|
|
No kernel mapping found for layer `None`. Check if the layer name matches one of the kernels in the mapping or add the kernel you want to use to the mapping. Defaulting to original forward implementation. |
|
|
warnings.warn( |
|
|
/tmp/uvnote-run-hjgpkuq6/home/.cache/uv/environments-v2/setup-30bb029f3f83f37d/lib/python3.12/site-packages/kernels/layer.py:868: UserWarning: |
|
|
No kernel mapping found for layer `None`. Check if the layer name matches one of the kernels in the mapping or add the kernel you want to use to the mapping. Defaulting to original forward implementation. |
|
|
warnings.warn(</div> |
|
|
<div class="cell-artifacts"> |
|
|
<h4>Artifacts:</h4> |
|
|
<a href="artifacts/setup/benchmark_avg_tokens_per_sec.txt" class="artifact" target="_blank">benchmark_avg_tokens_per_sec.txt</a> |
|
|
<a href="artifacts/setup/benchmark_memory.txt" class="artifact" target="_blank">benchmark_memory.txt</a> |
|
|
<a href="artifacts/setup/benchmark_times.txt" class="artifact" target="_blank">benchmark_times.txt</a> |
|
|
<a href="artifacts/setup/trace.json" class="artifact" target="_blank">trace.json</a> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="cell"> |
|
|
<div class="cell-header"> |
|
|
<span class="collapse-indicators"> |
|
|
<span onclick="toggleCode('charts')" style="cursor: pointer;">▼ code</span> |
|
|
<span onclick="toggleOutput('charts')" style="cursor: pointer;">▼ output</span> |
|
|
<span id="uv-indicator-charts" onclick="toggleUvLogsFromHeader('charts')" style="cursor: pointer;">▶ uv-logs</span> |
|
|
</span> | |
|
|
Cell: charts | deps: matplotlib, numpy | 3.51s |
|
|
| <button class="run-btn" onclick="runCell('charts')">▶ run</button> |
|
|
<button class="copy-btn" onclick="copyCell('charts')">Copy</button> |
|
|
<a href="cells/charts.py" target="_blank" class="raw-btn">Raw</a> |
|
|
</div> |
|
|
<div id="code-charts" class="cell-code"> |
|
|
<div class="highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal"> 1</span> |
|
|
<span class="normal"> 2</span> |
|
|
<span class="normal"> 3</span> |
|
|
<span class="normal"> 4</span> |
|
|
<span class="normal"> 5</span> |
|
|
<span class="normal"> 6</span> |
|
|
<span class="normal"> 7</span> |
|
|
<span class="normal"> 8</span> |
|
|
<span class="normal"> 9</span> |
|
|
<span class="normal"> 10</span> |
|
|
<span class="normal"> 11</span> |
|
|
<span class="normal"> 12</span> |
|
|
<span class="normal"> 13</span> |
|
|
<span class="normal"> 14</span> |
|
|
<span class="normal"> 15</span> |
|
|
<span class="normal"> 16</span> |
|
|
<span class="normal"> 17</span> |
|
|
<span class="normal"> 18</span> |
|
|
<span class="normal"> 19</span> |
|
|
<span class="normal"> 20</span> |
|
|
<span class="normal"> 21</span> |
|
|
<span class="normal"> 22</span> |
|
|
<span class="normal"> 23</span> |
|
|
<span class="normal"> 24</span> |
|
|
<span class="normal"> 25</span> |
|
|
<span class="normal"> 26</span> |
|
|
<span class="normal"> 27</span> |
|
|
<span class="normal"> 28</span> |
|
|
<span class="normal"> 29</span> |
|
|
<span class="normal"> 30</span> |
|
|
<span class="normal"> 31</span> |
|
|
<span class="normal"> 32</span> |
|
|
<span class="normal"> 33</span> |
|
|
<span class="normal"> 34</span> |
|
|
<span class="normal"> 35</span> |
|
|
<span class="normal"> 36</span> |
|
|
<span class="normal"> 37</span> |
|
|
<span class="normal"> 38</span> |
|
|
<span class="normal"> 39</span> |
|
|
<span class="normal"> 40</span> |
|
|
<span class="normal"> 41</span> |
|
|
<span class="normal"> 42</span> |
|
|
<span class="normal"> 43</span> |
|
|
<span class="normal"> 44</span> |
|
|
<span class="normal"> 45</span> |
|
|
<span class="normal"> 46</span> |
|
|
<span class="normal"> 47</span> |
|
|
<span class="normal"> 48</span> |
|
|
<span class="normal"> 49</span> |
|
|
<span class="normal"> 50</span> |
|
|
<span class="normal"> 51</span> |
|
|
<span class="normal"> 52</span> |
|
|
<span class="normal"> 53</span> |
|
|
<span class="normal"> 54</span> |
|
|
<span class="normal"> 55</span> |
|
|
<span class="normal"> 56</span> |
|
|
<span class="normal"> 57</span> |
|
|
<span class="normal"> 58</span> |
|
|
<span class="normal"> 59</span> |
|
|
<span class="normal"> 60</span> |
|
|
<span class="normal"> 61</span> |
|
|
<span class="normal"> 62</span> |
|
|
<span class="normal"> 63</span> |
|
|
<span class="normal"> 64</span> |
|
|
<span class="normal"> 65</span> |
|
|
<span class="normal"> 66</span> |
|
|
<span class="normal"> 67</span> |
|
|
<span class="normal"> 68</span> |
|
|
<span class="normal"> 69</span> |
|
|
<span class="normal"> 70</span> |
|
|
<span class="normal"> 71</span> |
|
|
<span class="normal"> 72</span> |
|
|
<span class="normal"> 73</span> |
|
|
<span class="normal"> 74</span> |
|
|
<span class="normal"> 75</span> |
|
|
<span class="normal"> 76</span> |
|
|
<span class="normal"> 77</span> |
|
|
<span class="normal"> 78</span> |
|
|
<span class="normal"> 79</span> |
|
|
<span class="normal"> 80</span> |
|
|
<span class="normal"> 81</span> |
|
|
<span class="normal"> 82</span> |
|
|
<span class="normal"> 83</span> |
|
|
<span class="normal"> 84</span> |
|
|
<span class="normal"> 85</span> |
|
|
<span class="normal"> 86</span> |
|
|
<span class="normal"> 87</span> |
|
|
<span class="normal"> 88</span> |
|
|
<span class="normal"> 89</span> |
|
|
<span class="normal"> 90</span> |
|
|
<span class="normal"> 91</span> |
|
|
<span class="normal"> 92</span> |
|
|
<span class="normal"> 93</span> |
|
|
<span class="normal"> 94</span> |
|
|
<span class="normal"> 95</span> |
|
|
<span class="normal"> 96</span> |
|
|
<span class="normal"> 97</span> |
|
|
<span class="normal"> 98</span> |
|
|
<span class="normal"> 99</span> |
|
|
<span class="normal">100</span> |
|
|
<span class="normal">101</span> |
|
|
<span class="normal">102</span> |
|
|
<span class="normal">103</span> |
|
|
<span class="normal">104</span> |
|
|
<span class="normal">105</span> |
|
|
<span class="normal">106</span> |
|
|
<span class="normal">107</span> |
|
|
<span class="normal">108</span> |
|
|
<span class="normal">109</span> |
|
|
<span class="normal">110</span> |
|
|
<span class="normal">111</span> |
|
|
<span class="normal">112</span> |
|
|
<span class="normal">113</span> |
|
|
<span class="normal">114</span> |
|
|
<span class="normal">115</span> |
|
|
<span class="normal">116</span> |
|
|
<span class="normal">117</span> |
|
|
<span class="normal">118</span> |
|
|
<span class="normal">119</span> |
|
|
<span class="normal">120</span> |
|
|
<span class="normal">121</span> |
|
|
<span class="normal">122</span> |
|
|
<span class="normal">123</span> |
|
|
<span class="normal">124</span> |
|
|
<span class="normal">125</span> |
|
|
<span class="normal">126</span> |
|
|
<span class="normal">127</span> |
|
|
<span class="normal">128</span> |
|
|
<span class="normal">129</span> |
|
|
<span class="normal">130</span> |
|
|
<span class="normal">131</span> |
|
|
<span class="normal">132</span> |
|
|
<span class="normal">133</span></pre></div></td><td class="code"><div><pre><span></span><span class="kn">import</span><span class="w"> </span><span class="nn">matplotlib.pyplot</span><span class="w"> </span><span class="k">as</span><span class="w"> </span><span class="nn">plt</span> |
|
|
<span class="kn">import</span><span class="w"> </span><span class="nn">numpy</span><span class="w"> </span><span class="k">as</span><span class="w"> </span><span class="nn">np</span> |
|
|
<span class="kn">import</span><span class="w"> </span><span class="nn">os</span> |
|
|
|
|
|
<span class="c1"># get the pathf rom UVNOTE_SETUP env var</span> |
|
|
<span class="n">setup_path</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">getenv</span><span class="p">(</span><span class="s2">"UVNOTE_INPUT_SETUP"</span><span class="p">,</span> <span class="s2">"."</span><span class="p">)</span> |
|
|
<span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Reading benchmark data from: </span><span class="si">{</span><span class="n">setup_path</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span> |
|
|
|
|
|
<span class="n">num_runs</span> <span class="o">=</span> <span class="mi">5</span> |
|
|
<span class="n">max_tokens</span> <span class="o">=</span> <span class="mi">64</span> |
|
|
<span class="n">times</span> <span class="o">=</span> <span class="p">[]</span> |
|
|
<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">setup_path</span><span class="p">,</span> <span class="s2">"benchmark_times.txt"</span><span class="p">),</span> <span class="s2">"r"</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span> |
|
|
<span class="k">for</span> <span class="n">line</span> <span class="ow">in</span> <span class="n">f</span><span class="p">:</span> |
|
|
<span class="n">times</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="nb">float</span><span class="p">(</span><span class="n">line</span><span class="o">.</span><span class="n">strip</span><span class="p">()))</span> |
|
|
|
|
|
|
|
|
<span class="n">avg_time</span> <span class="o">=</span> <span class="mf">0.0</span> |
|
|
<span class="n">min_time</span> <span class="o">=</span> <span class="mf">0.0</span> |
|
|
<span class="n">max_time</span> <span class="o">=</span> <span class="mf">0.0</span> |
|
|
<span class="n">final_mem</span> <span class="o">=</span> <span class="p">{</span><span class="s2">"allocated_gb"</span><span class="p">:</span> <span class="mf">0.0</span><span class="p">,</span> <span class="s2">"peak_gb"</span><span class="p">:</span> <span class="mf">0.0</span><span class="p">,</span> <span class="s2">"reserved_gb"</span><span class="p">:</span> <span class="mf">0.0</span><span class="p">}</span> |
|
|
|
|
|
<span class="n">avg_tokens_per_sec</span> <span class="o">=</span> <span class="mf">0.0</span> |
|
|
<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">setup_path</span><span class="p">,</span> <span class="s2">"benchmark_avg_tokens_per_sec.txt"</span><span class="p">),</span> <span class="s2">"r"</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span> |
|
|
<span class="n">avg_tokens_per_sec</span> <span class="o">=</span> <span class="nb">float</span><span class="p">(</span><span class="n">f</span><span class="o">.</span><span class="n">read</span><span class="p">()</span><span class="o">.</span><span class="n">strip</span><span class="p">())</span> |
|
|
|
|
|
<span class="n">times_file</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">setup_path</span><span class="p">,</span> <span class="s2">"benchmark_times.txt"</span><span class="p">)</span> |
|
|
<span class="n">memory_file</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">setup_path</span><span class="p">,</span> <span class="s2">"benchmark_memory.txt"</span><span class="p">)</span> |
|
|
|
|
|
|
|
|
<span class="c1"># Minimal brutalist palette (dark theme): grayscale + 1 accent</span> |
|
|
<span class="n">ACCENT</span> <span class="o">=</span> <span class="s1">'#5ec8f8'</span> <span class="c1"># calm cyan-blue accent</span> |
|
|
<span class="n">FG</span> <span class="o">=</span> <span class="s1">'#e6e6e6'</span> <span class="c1"># light gray text/lines</span> |
|
|
<span class="n">MUTED</span> <span class="o">=</span> <span class="s1">'#9aa0a6'</span> <span class="c1"># muted gray for secondary</span> |
|
|
<span class="n">GRID</span> <span class="o">=</span> <span class="s1">'#333333'</span> <span class="c1"># grid lines</span> |
|
|
|
|
|
<span class="c1"># Styling tuned for clarity, high contrast, few colors</span> |
|
|
<span class="n">plt</span><span class="o">.</span><span class="n">style</span><span class="o">.</span><span class="n">use</span><span class="p">(</span><span class="s1">'dark_background'</span><span class="p">)</span> |
|
|
<span class="n">plt</span><span class="o">.</span><span class="n">rcParams</span><span class="p">[</span><span class="s1">'figure.facecolor'</span><span class="p">]</span> <span class="o">=</span> <span class="s1">'none'</span> |
|
|
<span class="n">plt</span><span class="o">.</span><span class="n">rcParams</span><span class="p">[</span><span class="s1">'axes.facecolor'</span><span class="p">]</span> <span class="o">=</span> <span class="s1">'none'</span> |
|
|
<span class="n">plt</span><span class="o">.</span><span class="n">rcParams</span><span class="p">[</span><span class="s1">'savefig.facecolor'</span><span class="p">]</span> <span class="o">=</span> <span class="s1">'none'</span> |
|
|
<span class="n">plt</span><span class="o">.</span><span class="n">rcParams</span><span class="p">[</span><span class="s1">'savefig.transparent'</span><span class="p">]</span> <span class="o">=</span> <span class="kc">True</span> |
|
|
<span class="n">plt</span><span class="o">.</span><span class="n">rcParams</span><span class="p">[</span><span class="s1">'font.family'</span><span class="p">]</span> <span class="o">=</span> <span class="s1">'monospace'</span> |
|
|
<span class="n">plt</span><span class="o">.</span><span class="n">rcParams</span><span class="p">[</span><span class="s1">'font.weight'</span><span class="p">]</span> <span class="o">=</span> <span class="s1">'bold'</span> |
|
|
<span class="n">plt</span><span class="o">.</span><span class="n">rcParams</span><span class="p">[</span><span class="s1">'axes.linewidth'</span><span class="p">]</span> <span class="o">=</span> <span class="mi">3</span> |
|
|
<span class="n">plt</span><span class="o">.</span><span class="n">rcParams</span><span class="p">[</span><span class="s1">'grid.linewidth'</span><span class="p">]</span> <span class="o">=</span> <span class="mi">2</span> |
|
|
<span class="n">plt</span><span class="o">.</span><span class="n">rcParams</span><span class="p">[</span><span class="s1">'lines.linewidth'</span><span class="p">]</span> <span class="o">=</span> <span class="mi">3</span> |
|
|
<span class="n">plt</span><span class="o">.</span><span class="n">rcParams</span><span class="p">[</span><span class="s1">'patch.linewidth'</span><span class="p">]</span> <span class="o">=</span> <span class="mi">2</span> |
|
|
|
|
|
<span class="c1"># Prepare data</span> |
|
|
<span class="n">runs</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="nb">range</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="nb">len</span><span class="p">(</span><span class="n">times</span><span class="p">)</span> <span class="o">+</span> <span class="mi">1</span><span class="p">))</span> |
|
|
<span class="n">tokens_per_sec_all</span> <span class="o">=</span> <span class="p">[</span><span class="n">max_tokens</span> <span class="o">/</span> <span class="n">t</span> <span class="k">for</span> <span class="n">t</span> <span class="ow">in</span> <span class="n">times</span><span class="p">]</span> |
|
|
|
|
|
<span class="c1"># Chart 1: Throughput Performance</span> |
|
|
<span class="n">fig1</span><span class="p">,</span> <span class="n">ax1</span> <span class="o">=</span> <span class="n">plt</span><span class="o">.</span><span class="n">subplots</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="n">figsize</span><span class="o">=</span><span class="p">(</span><span class="mi">12</span><span class="p">,</span> <span class="mi">6</span><span class="p">))</span> |
|
|
<span class="n">fig1</span><span class="o">.</span><span class="n">patch</span><span class="o">.</span><span class="n">set_alpha</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span> |
|
|
<span class="n">ax1</span><span class="o">.</span><span class="n">patch</span><span class="o">.</span><span class="n">set_alpha</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span> |
|
|
|
|
|
<span class="n">ax1</span><span class="o">.</span><span class="n">plot</span><span class="p">(</span><span class="n">runs</span><span class="p">,</span> <span class="n">tokens_per_sec_all</span><span class="p">,</span> <span class="n">color</span><span class="o">=</span><span class="n">ACCENT</span><span class="p">,</span> <span class="n">marker</span><span class="o">=</span><span class="s1">'o'</span><span class="p">,</span> <span class="n">markersize</span><span class="o">=</span><span class="mi">12</span><span class="p">,</span> |
|
|
<span class="n">markerfacecolor</span><span class="o">=</span><span class="n">ACCENT</span><span class="p">,</span> <span class="n">markeredgecolor</span><span class="o">=</span><span class="n">FG</span><span class="p">,</span> <span class="n">markeredgewidth</span><span class="o">=</span><span class="mi">3</span><span class="p">,</span> <span class="n">linewidth</span><span class="o">=</span><span class="mi">5</span><span class="p">,</span> <span class="n">label</span><span class="o">=</span><span class="s1">'tok/s'</span><span class="p">)</span> |
|
|
<span class="n">ax1</span><span class="o">.</span><span class="n">fill_between</span><span class="p">(</span><span class="n">runs</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">tokens_per_sec_all</span><span class="p">,</span> <span class="n">alpha</span><span class="o">=</span><span class="mf">0.2</span><span class="p">,</span> <span class="n">color</span><span class="o">=</span><span class="n">ACCENT</span><span class="p">)</span> |
|
|
<span class="n">ax1</span><span class="o">.</span><span class="n">axhline</span><span class="p">(</span><span class="n">y</span><span class="o">=</span><span class="n">avg_tokens_per_sec</span><span class="p">,</span> <span class="n">color</span><span class="o">=</span><span class="n">FG</span><span class="p">,</span> <span class="n">linestyle</span><span class="o">=</span><span class="s1">'--'</span><span class="p">,</span> <span class="n">linewidth</span><span class="o">=</span><span class="mi">3</span><span class="p">,</span> |
|
|
<span class="n">label</span><span class="o">=</span><span class="sa">f</span><span class="s1">'AVG: </span><span class="si">{</span><span class="n">avg_tokens_per_sec</span><span class="si">:</span><span class="s1">.1f</span><span class="si">}</span><span class="s1">'</span><span class="p">)</span> |
|
|
<span class="n">ax1</span><span class="o">.</span><span class="n">set_title</span><span class="p">(</span><span class="s1">'THROUGHPUT PERFORMANCE'</span><span class="p">,</span> <span class="n">color</span><span class="o">=</span><span class="n">FG</span><span class="p">,</span> <span class="n">fontsize</span><span class="o">=</span><span class="mi">18</span><span class="p">,</span> <span class="n">pad</span><span class="o">=</span><span class="mi">20</span><span class="p">,</span> <span class="n">fontweight</span><span class="o">=</span><span class="s1">'bold'</span><span class="p">)</span> |
|
|
<span class="n">ax1</span><span class="o">.</span><span class="n">set_xlabel</span><span class="p">(</span><span class="s1">'RUN NUMBER'</span><span class="p">,</span> <span class="n">color</span><span class="o">=</span><span class="n">FG</span><span class="p">,</span> <span class="n">fontsize</span><span class="o">=</span><span class="mi">14</span><span class="p">,</span> <span class="n">fontweight</span><span class="o">=</span><span class="s1">'bold'</span><span class="p">)</span> |
|
|
<span class="n">ax1</span><span class="o">.</span><span class="n">set_ylabel</span><span class="p">(</span><span class="s1">'TOKENS/SEC'</span><span class="p">,</span> <span class="n">color</span><span class="o">=</span><span class="n">FG</span><span class="p">,</span> <span class="n">fontsize</span><span class="o">=</span><span class="mi">14</span><span class="p">,</span> <span class="n">fontweight</span><span class="o">=</span><span class="s1">'bold'</span><span class="p">)</span> |
|
|
<span class="n">ax1</span><span class="o">.</span><span class="n">grid</span><span class="p">(</span><span class="kc">True</span><span class="p">,</span> <span class="n">color</span><span class="o">=</span><span class="n">GRID</span><span class="p">,</span> <span class="n">alpha</span><span class="o">=</span><span class="mf">0.5</span><span class="p">,</span> <span class="n">linewidth</span><span class="o">=</span><span class="mi">2</span><span class="p">)</span> |
|
|
<span class="n">ax1</span><span class="o">.</span><span class="n">tick_params</span><span class="p">(</span><span class="n">colors</span><span class="o">=</span><span class="n">FG</span><span class="p">,</span> <span class="n">labelsize</span><span class="o">=</span><span class="mi">12</span><span class="p">)</span> |
|
|
<span class="n">legend1</span> <span class="o">=</span> <span class="n">ax1</span><span class="o">.</span><span class="n">legend</span><span class="p">(</span><span class="n">frameon</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span> <span class="n">loc</span><span class="o">=</span><span class="s1">'lower right'</span><span class="p">)</span> |
|
|
<span class="k">for</span> <span class="n">text</span> <span class="ow">in</span> <span class="n">legend1</span><span class="o">.</span><span class="n">get_texts</span><span class="p">():</span> |
|
|
<span class="n">text</span><span class="o">.</span><span class="n">set_color</span><span class="p">(</span><span class="n">FG</span><span class="p">)</span> |
|
|
<span class="n">text</span><span class="o">.</span><span class="n">set_fontweight</span><span class="p">(</span><span class="s1">'bold'</span><span class="p">)</span> |
|
|
<span class="n">plt</span><span class="o">.</span><span class="n">tight_layout</span><span class="p">()</span> |
|
|
<span class="n">plt</span><span class="o">.</span><span class="n">savefig</span><span class="p">(</span><span class="s1">'throughput.png'</span><span class="p">,</span> <span class="n">dpi</span><span class="o">=</span><span class="mi">150</span><span class="p">,</span> <span class="n">bbox_inches</span><span class="o">=</span><span class="s1">'tight'</span><span class="p">,</span> <span class="n">transparent</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span> |
|
|
<span class="n">plt</span><span class="o">.</span><span class="n">show</span><span class="p">()</span> |
|
|
|
|
|
<span class="c1"># Chart 2: Generation Latency</span> |
|
|
<span class="n">fig2</span><span class="p">,</span> <span class="n">ax2</span> <span class="o">=</span> <span class="n">plt</span><span class="o">.</span><span class="n">subplots</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="n">figsize</span><span class="o">=</span><span class="p">(</span><span class="mi">12</span><span class="p">,</span> <span class="mi">6</span><span class="p">))</span> |
|
|
<span class="n">fig2</span><span class="o">.</span><span class="n">patch</span><span class="o">.</span><span class="n">set_alpha</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span> |
|
|
<span class="n">ax2</span><span class="o">.</span><span class="n">patch</span><span class="o">.</span><span class="n">set_alpha</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span> |
|
|
|
|
|
<span class="n">bar_colors</span> <span class="o">=</span> <span class="p">[</span><span class="n">ACCENT</span> <span class="k">if</span> <span class="n">i</span> <span class="o">%</span> <span class="mi">2</span> <span class="o">==</span> <span class="mi">0</span> <span class="k">else</span> <span class="n">MUTED</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">times</span><span class="p">))]</span> |
|
|
<span class="n">bars</span> <span class="o">=</span> <span class="n">ax2</span><span class="o">.</span><span class="n">bar</span><span class="p">(</span><span class="n">runs</span><span class="p">,</span> <span class="n">times</span><span class="p">,</span> <span class="n">color</span><span class="o">=</span><span class="n">bar_colors</span><span class="p">,</span> <span class="n">edgecolor</span><span class="o">=</span><span class="n">FG</span><span class="p">,</span> <span class="n">linewidth</span><span class="o">=</span><span class="mi">3</span><span class="p">,</span> <span class="n">width</span><span class="o">=</span><span class="mf">0.6</span><span class="p">)</span> |
|
|
<span class="n">ax2</span><span class="o">.</span><span class="n">axhline</span><span class="p">(</span><span class="n">y</span><span class="o">=</span><span class="n">avg_time</span><span class="p">,</span> <span class="n">color</span><span class="o">=</span><span class="n">FG</span><span class="p">,</span> <span class="n">linestyle</span><span class="o">=</span><span class="s1">'--'</span><span class="p">,</span> <span class="n">linewidth</span><span class="o">=</span><span class="mi">3</span><span class="p">,</span> |
|
|
<span class="n">label</span><span class="o">=</span><span class="sa">f</span><span class="s1">'AVG: </span><span class="si">{</span><span class="n">avg_time</span><span class="si">:</span><span class="s1">.2f</span><span class="si">}</span><span class="s1">s'</span><span class="p">)</span> |
|
|
<span class="k">for</span> <span class="n">i</span><span class="p">,</span> <span class="p">(</span><span class="n">run</span><span class="p">,</span> <span class="n">time</span><span class="p">,</span> <span class="n">bar</span><span class="p">)</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="nb">zip</span><span class="p">(</span><span class="n">runs</span><span class="p">,</span> <span class="n">times</span><span class="p">,</span> <span class="n">bars</span><span class="p">)):</span> |
|
|
<span class="n">ax2</span><span class="o">.</span><span class="n">text</span><span class="p">(</span><span class="n">run</span><span class="p">,</span> <span class="n">time</span> <span class="o">+</span> <span class="mf">0.02</span><span class="p">,</span> <span class="sa">f</span><span class="s1">'</span><span class="si">{</span><span class="n">time</span><span class="si">:</span><span class="s1">.2f</span><span class="si">}</span><span class="s1">s'</span><span class="p">,</span> <span class="n">ha</span><span class="o">=</span><span class="s1">'center'</span><span class="p">,</span> <span class="n">va</span><span class="o">=</span><span class="s1">'bottom'</span><span class="p">,</span> |
|
|
<span class="n">color</span><span class="o">=</span><span class="n">FG</span><span class="p">,</span> <span class="n">fontweight</span><span class="o">=</span><span class="s1">'bold'</span><span class="p">,</span> <span class="n">fontsize</span><span class="o">=</span><span class="mi">11</span><span class="p">)</span> |
|
|
<span class="n">ax2</span><span class="o">.</span><span class="n">set_title</span><span class="p">(</span><span class="s1">'GENERATION LATENCY'</span><span class="p">,</span> <span class="n">color</span><span class="o">=</span><span class="n">FG</span><span class="p">,</span> <span class="n">fontsize</span><span class="o">=</span><span class="mi">18</span><span class="p">,</span> <span class="n">pad</span><span class="o">=</span><span class="mi">20</span><span class="p">,</span> <span class="n">fontweight</span><span class="o">=</span><span class="s1">'bold'</span><span class="p">)</span> |
|
|
<span class="n">ax2</span><span class="o">.</span><span class="n">set_xlabel</span><span class="p">(</span><span class="s1">'RUN NUMBER'</span><span class="p">,</span> <span class="n">color</span><span class="o">=</span><span class="n">FG</span><span class="p">,</span> <span class="n">fontsize</span><span class="o">=</span><span class="mi">14</span><span class="p">,</span> <span class="n">fontweight</span><span class="o">=</span><span class="s1">'bold'</span><span class="p">)</span> |
|
|
<span class="n">ax2</span><span class="o">.</span><span class="n">set_ylabel</span><span class="p">(</span><span class="s1">'TIME (SECONDS)'</span><span class="p">,</span> <span class="n">color</span><span class="o">=</span><span class="n">FG</span><span class="p">,</span> <span class="n">fontsize</span><span class="o">=</span><span class="mi">14</span><span class="p">,</span> <span class="n">fontweight</span><span class="o">=</span><span class="s1">'bold'</span><span class="p">)</span> |
|
|
<span class="n">ax2</span><span class="o">.</span><span class="n">grid</span><span class="p">(</span><span class="kc">True</span><span class="p">,</span> <span class="n">axis</span><span class="o">=</span><span class="s1">'y'</span><span class="p">,</span> <span class="n">color</span><span class="o">=</span><span class="n">GRID</span><span class="p">,</span> <span class="n">alpha</span><span class="o">=</span><span class="mf">0.5</span><span class="p">,</span> <span class="n">linewidth</span><span class="o">=</span><span class="mi">2</span><span class="p">)</span> |
|
|
<span class="n">ax2</span><span class="o">.</span><span class="n">tick_params</span><span class="p">(</span><span class="n">colors</span><span class="o">=</span><span class="n">FG</span><span class="p">,</span> <span class="n">labelsize</span><span class="o">=</span><span class="mi">12</span><span class="p">)</span> |
|
|
<span class="n">ax2</span><span class="o">.</span><span class="n">set_ylim</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="nb">max</span><span class="p">(</span><span class="n">times</span><span class="p">)</span> <span class="o">*</span> <span class="mf">1.15</span><span class="p">)</span> |
|
|
<span class="n">legend2</span> <span class="o">=</span> <span class="n">ax2</span><span class="o">.</span><span class="n">legend</span><span class="p">(</span><span class="n">frameon</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span> <span class="n">loc</span><span class="o">=</span><span class="s1">'upper right'</span><span class="p">)</span> |
|
|
<span class="k">for</span> <span class="n">text</span> <span class="ow">in</span> <span class="n">legend2</span><span class="o">.</span><span class="n">get_texts</span><span class="p">():</span> |
|
|
<span class="n">text</span><span class="o">.</span><span class="n">set_color</span><span class="p">(</span><span class="n">FG</span><span class="p">)</span> |
|
|
<span class="n">text</span><span class="o">.</span><span class="n">set_fontweight</span><span class="p">(</span><span class="s1">'bold'</span><span class="p">)</span> |
|
|
<span class="n">plt</span><span class="o">.</span><span class="n">tight_layout</span><span class="p">()</span> |
|
|
<span class="n">plt</span><span class="o">.</span><span class="n">savefig</span><span class="p">(</span><span class="s1">'latency.png'</span><span class="p">,</span> <span class="n">dpi</span><span class="o">=</span><span class="mi">150</span><span class="p">,</span> <span class="n">bbox_inches</span><span class="o">=</span><span class="s1">'tight'</span><span class="p">,</span> <span class="n">transparent</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span> |
|
|
<span class="n">plt</span><span class="o">.</span><span class="n">show</span><span class="p">()</span> |
|
|
|
|
|
<span class="c1"># Chart 3: Memory Usage</span> |
|
|
<span class="n">fig3</span><span class="p">,</span> <span class="n">ax3</span> <span class="o">=</span> <span class="n">plt</span><span class="o">.</span><span class="n">subplots</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="n">figsize</span><span class="o">=</span><span class="p">(</span><span class="mi">12</span><span class="p">,</span> <span class="mi">6</span><span class="p">))</span> |
|
|
<span class="n">fig3</span><span class="o">.</span><span class="n">patch</span><span class="o">.</span><span class="n">set_alpha</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span> |
|
|
<span class="n">ax3</span><span class="o">.</span><span class="n">patch</span><span class="o">.</span><span class="n">set_alpha</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span> |
|
|
|
|
|
<span class="n">memory_labels</span> <span class="o">=</span> <span class="p">[</span><span class="s1">'ALLOCATED'</span><span class="p">,</span> <span class="s1">'PEAK'</span><span class="p">,</span> <span class="s1">'RESERVED'</span><span class="p">]</span> |
|
|
<span class="n">memory_values</span> <span class="o">=</span> <span class="p">[</span><span class="n">final_mem</span><span class="p">[</span><span class="s1">'allocated_gb'</span><span class="p">],</span> <span class="n">final_mem</span><span class="p">[</span><span class="s1">'peak_gb'</span><span class="p">],</span> <span class="n">final_mem</span><span class="p">[</span><span class="s1">'reserved_gb'</span><span class="p">]]</span> |
|
|
<span class="n">colors_mem</span> <span class="o">=</span> <span class="p">[</span><span class="n">MUTED</span><span class="p">,</span> <span class="n">ACCENT</span><span class="p">,</span> <span class="n">FG</span><span class="p">]</span> |
|
|
<span class="n">bars</span> <span class="o">=</span> <span class="n">ax3</span><span class="o">.</span><span class="n">barh</span><span class="p">(</span><span class="n">memory_labels</span><span class="p">,</span> <span class="n">memory_values</span><span class="p">,</span> <span class="n">color</span><span class="o">=</span><span class="n">colors_mem</span><span class="p">,</span> <span class="n">edgecolor</span><span class="o">=</span><span class="n">FG</span><span class="p">,</span> <span class="n">linewidth</span><span class="o">=</span><span class="mi">3</span><span class="p">,</span> <span class="n">height</span><span class="o">=</span><span class="mf">0.5</span><span class="p">)</span> |
|
|
<span class="k">for</span> <span class="n">i</span><span class="p">,</span> <span class="p">(</span><span class="n">label</span><span class="p">,</span> <span class="n">value</span><span class="p">,</span> <span class="n">bar</span><span class="p">)</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="nb">zip</span><span class="p">(</span><span class="n">memory_labels</span><span class="p">,</span> <span class="n">memory_values</span><span class="p">,</span> <span class="n">bars</span><span class="p">)):</span> |
|
|
<span class="n">ax3</span><span class="o">.</span><span class="n">text</span><span class="p">(</span><span class="n">value</span> <span class="o">+</span> <span class="mf">0.5</span><span class="p">,</span> <span class="n">i</span><span class="p">,</span> <span class="sa">f</span><span class="s1">'</span><span class="si">{</span><span class="n">value</span><span class="si">:</span><span class="s1">.1f</span><span class="si">}</span><span class="s1"> GB'</span><span class="p">,</span> <span class="n">va</span><span class="o">=</span><span class="s1">'center'</span><span class="p">,</span> |
|
|
<span class="n">color</span><span class="o">=</span><span class="n">FG</span><span class="p">,</span> <span class="n">fontweight</span><span class="o">=</span><span class="s1">'bold'</span><span class="p">,</span> <span class="n">fontsize</span><span class="o">=</span><span class="mi">13</span><span class="p">)</span> |
|
|
<span class="n">ax3</span><span class="o">.</span><span class="n">set_title</span><span class="p">(</span><span class="s1">'MEMORY USAGE'</span><span class="p">,</span> <span class="n">color</span><span class="o">=</span><span class="n">FG</span><span class="p">,</span> <span class="n">fontsize</span><span class="o">=</span><span class="mi">18</span><span class="p">,</span> <span class="n">pad</span><span class="o">=</span><span class="mi">20</span><span class="p">,</span> <span class="n">fontweight</span><span class="o">=</span><span class="s1">'bold'</span><span class="p">)</span> |
|
|
<span class="n">ax3</span><span class="o">.</span><span class="n">set_xlabel</span><span class="p">(</span><span class="s1">'GIGABYTES'</span><span class="p">,</span> <span class="n">color</span><span class="o">=</span><span class="n">FG</span><span class="p">,</span> <span class="n">fontsize</span><span class="o">=</span><span class="mi">14</span><span class="p">,</span> <span class="n">fontweight</span><span class="o">=</span><span class="s1">'bold'</span><span class="p">)</span> |
|
|
<span class="n">ax3</span><span class="o">.</span><span class="n">set_xlim</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="nb">max</span><span class="p">(</span><span class="n">memory_values</span><span class="p">)</span> <span class="o">*</span> <span class="mf">1.3</span><span class="p">)</span> |
|
|
<span class="n">ax3</span><span class="o">.</span><span class="n">grid</span><span class="p">(</span><span class="kc">True</span><span class="p">,</span> <span class="n">axis</span><span class="o">=</span><span class="s1">'x'</span><span class="p">,</span> <span class="n">color</span><span class="o">=</span><span class="n">GRID</span><span class="p">,</span> <span class="n">alpha</span><span class="o">=</span><span class="mf">0.5</span><span class="p">,</span> <span class="n">linewidth</span><span class="o">=</span><span class="mi">2</span><span class="p">)</span> |
|
|
<span class="n">ax3</span><span class="o">.</span><span class="n">tick_params</span><span class="p">(</span><span class="n">colors</span><span class="o">=</span><span class="n">FG</span><span class="p">,</span> <span class="n">labelsize</span><span class="o">=</span><span class="mi">12</span><span class="p">)</span> |
|
|
<span class="n">ax3</span><span class="o">.</span><span class="n">set_yticks</span><span class="p">(</span><span class="nb">range</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">memory_labels</span><span class="p">)))</span> |
|
|
<span class="n">ax3</span><span class="o">.</span><span class="n">set_yticklabels</span><span class="p">(</span><span class="n">memory_labels</span><span class="p">,</span> <span class="n">fontweight</span><span class="o">=</span><span class="s1">'bold'</span><span class="p">)</span> |
|
|
<span class="n">plt</span><span class="o">.</span><span class="n">tight_layout</span><span class="p">()</span> |
|
|
<span class="n">plt</span><span class="o">.</span><span class="n">savefig</span><span class="p">(</span><span class="s1">'memory.png'</span><span class="p">,</span> <span class="n">dpi</span><span class="o">=</span><span class="mi">150</span><span class="p">,</span> <span class="n">bbox_inches</span><span class="o">=</span><span class="s1">'tight'</span><span class="p">,</span> <span class="n">transparent</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span> |
|
|
<span class="n">plt</span><span class="o">.</span><span class="n">show</span><span class="p">()</span> |
|
|
|
|
|
<span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">"</span><span class="se">\n</span><span class="s2">📊 Charts saved as:"</span><span class="p">)</span> |
|
|
<span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">" • throughput.png"</span><span class="p">)</span> |
|
|
<span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">" • latency.png"</span><span class="p">)</span> |
|
|
<span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">" • memory.png"</span><span class="p">)</span> |
|
|
<span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">"</span><span class="se">\n</span><span class="s2">Benchmark Summary:"</span><span class="p">)</span> |
|
|
<span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">" avg tokens/sec: </span><span class="si">{</span><span class="n">avg_tokens_per_sec</span><span class="si">:</span><span class="s2">.1f</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span> |
|
|
<span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">" min time: </span><span class="si">{</span><span class="n">min_time</span><span class="si">:</span><span class="s2">.3f</span><span class="si">}</span><span class="s2">s"</span><span class="p">)</span> |
|
|
<span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">" max time: </span><span class="si">{</span><span class="n">max_time</span><span class="si">:</span><span class="s2">.3f</span><span class="si">}</span><span class="s2">s"</span><span class="p">)</span> |
|
|
<span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">" peak memory: </span><span class="si">{</span><span class="n">final_mem</span><span class="p">[</span><span class="s1">'peak_gb'</span><span class="p">]</span><span class="si">:</span><span class="s2">.2f</span><span class="si">}</span><span class="s2">GB"</span><span class="p">)</span> |
|
|
</pre></div></td></tr></table></div> |
|
|
|
|
|
</div> |
|
|
<div id="output-charts" class="cell-output"> |
|
|
<div class="cell-stdout">Reading benchmark data from: /home/ubuntu/Projects/yamoe-gpt-integration/.uvnote/cache/0e89c413a25ded7b4d6fab2a010f0538ba2b35fb5f619a0dfced3121d3ccf879 |
|
|
|
|
|
📊 Charts saved as: |
|
|
• throughput.png |
|
|
• latency.png |
|
|
• memory.png |
|
|
|
|
|
Benchmark Summary: |
|
|
avg tokens/sec: 5.3 |
|
|
min time: 0.000s |
|
|
max time: 0.000s |
|
|
peak memory: 0.00GB |
|
|
</div> |
|
|
<div class="uv-install-logs" id="uv-logs-charts"> |
|
|
<div class="uv-logs-header" onclick="toggleUvLogs(this)">▶ UV Install Logs</div> |
|
|
<div class="uv-logs-content" style="display: none;"> |
|
|
Downloading pillow (6.3MiB) |
|
|
Downloading kiwisolver (1.4MiB) |
|
|
Downloading matplotlib (8.3MiB) |
|
|
Downloading fonttools (4.7MiB) |
|
|
Downloading numpy (15.9MiB) |
|
|
Downloading kiwisolver |
|
|
Downloading pillow |
|
|
Downloading fonttools |
|
|
Downloading matplotlib |
|
|
Downloading numpy |
|
|
Installed 11 packages in 24ms |
|
|
</div> |
|
|
</div> |
|
|
<div class="cell-stderr">/home/ubuntu/Projects/yamoe-gpt-integration/.uvnote/cells/charts.py:123: UserWarning: Attempting to set identical low and high xlims makes transformation singular; automatically expanding. |
|
|
ax3.set_xlim(0, max(memory_values) * 1.3) |
|
|
/home/ubuntu/Projects/yamoe-gpt-integration/.uvnote/cells/charts.py:128: UserWarning: Tight layout not applied. The left and right margins cannot be made large enough to accommodate all Axes decorations. |
|
|
plt.tight_layout()</div> |
|
|
<div class="cell-artifacts"> |
|
|
<h4>Artifacts:</h4> |
|
|
<a href="artifacts/charts/latency.png" class="artifact" target="_blank">latency.png</a> |
|
|
<a href="artifacts/charts/memory.png" class="artifact" target="_blank">memory.png</a> |
|
|
<a href="artifacts/charts/throughput.png" class="artifact" target="_blank">throughput.png</a> |
|
|
<div class="artifact-preview"> |
|
|
<img src="artifacts/charts/latency.png" alt="latency.png"> |
|
|
</div> |
|
|
<div class="artifact-preview"> |
|
|
<img src="artifacts/charts/memory.png" alt="memory.png"> |
|
|
</div> |
|
|
<div class="artifact-preview"> |
|
|
<img src="artifacts/charts/throughput.png" alt="throughput.png"> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
</body> |
|
|
</html> |