|
|
<!DOCTYPE html> |
|
|
<html lang="en"> |
|
|
<head> |
|
|
<meta charset="UTF-8"> |
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
|
<title>Megablocks Only Test</title> |
|
|
<script> |
|
|
|
|
|
(function() { |
|
|
const configTheme = 'light'; |
|
|
const hasConfigUi = false; |
|
|
const configUi = hasConfigUi ? 'None' : null; |
|
|
const hasWidgetsConfig = false; |
|
|
const widgetsOn = hasWidgetsConfig ? false : true; |
|
|
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); |
|
|
|
|
|
|
|
|
let ui = hasConfigUi ? configUi : (localStorage.getItem('uvnote-ui') || 'default'); |
|
|
if (ui !== 'default' && ui !== 'none' && ui !== 'monocolor') { ui = 'default'; } |
|
|
document.documentElement.setAttribute('data-ui', ui); |
|
|
|
|
|
|
|
|
document.documentElement.setAttribute('data-widgets', widgetsOn ? 'on' : 'off'); |
|
|
})(); |
|
|
</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); |
|
|
} |
|
|
|
|
|
:root[data-ui="monocolor"] { --mono-color: #0a66ff; } |
|
|
:root[data-ui="monocolor"][data-theme="light"] { |
|
|
--bg-primary: #ffffff; |
|
|
} |
|
|
:root[data-ui="monocolor"][data-theme="dark"] { |
|
|
--bg-primary: #000000; |
|
|
} |
|
|
:root[data-ui="monocolor"] { |
|
|
--bg-secondary: var(--bg-primary); |
|
|
--bg-tertiary: var(--bg-primary); |
|
|
--bg-code: var(--bg-primary); |
|
|
--bg-error: var(--bg-primary); |
|
|
--bg-artifact: var(--bg-primary); |
|
|
--bg-artifact-hover: var(--bg-primary); |
|
|
|
|
|
--text-primary: var(--mono-color); |
|
|
--text-secondary: var(--mono-color); |
|
|
--text-error: var(--mono-color); |
|
|
--text-link: var(--mono-color); |
|
|
|
|
|
--border-primary: var(--mono-color); |
|
|
--border-error: var(--mono-color); |
|
|
--border-cell-failed: var(--mono-color); |
|
|
|
|
|
--shadow: none; |
|
|
} |
|
|
:root[data-ui="monocolor"] a { color: var(--mono-color); } |
|
|
:root[data-ui="monocolor"] .menu-button, |
|
|
:root[data-ui="monocolor"] .theme-toggle, |
|
|
:root[data-ui="monocolor"] .reset-toggle { background: var(--bg-primary); color: var(--mono-color); border-color: var(--mono-color); } |
|
|
:root[data-ui="monocolor"] .menu-button:hover, |
|
|
:root[data-ui="monocolor"] .theme-toggle:hover, |
|
|
:root[data-ui="monocolor"] .reset-toggle:hover { background: var(--bg-primary); color: var(--mono-color); border-color: var(--mono-color); } |
|
|
:root[data-ui="monocolor"] .menu-dropdown { background: var(--bg-primary); border-color: var(--mono-color); box-shadow: none; } |
|
|
:root[data-ui="monocolor"] .menu-item { color: var(--mono-color); border-bottom-color: var(--mono-color); } |
|
|
:root[data-ui="monocolor"] .system-info { background: var(--bg-primary); border-color: var(--mono-color); } |
|
|
:root[data-ui="monocolor"] .cell { border-color: var(--mono-color); background: var(--bg-primary); } |
|
|
:root[data-ui="monocolor"] .cell-header { background: var(--bg-primary); border-bottom-color: var(--mono-color); } |
|
|
:root[data-ui="monocolor"] .artifact { background: var(--bg-primary); border-color: var(--mono-color); color: var(--mono-color); } |
|
|
:root[data-ui="monocolor"] .artifact:hover { background: var(--bg-primary); } |
|
|
:root[data-ui="monocolor"] .artifact-preview img, |
|
|
:root[data-ui="monocolor"] .artifact-preview svg { border-color: var(--mono-color); } |
|
|
:root[data-ui="monocolor"] .status-widget { background: var(--bg-primary); border-color: var(--mono-color); color: var(--mono-color); } |
|
|
:root[data-ui="monocolor"] .minimap, |
|
|
:root[data-ui="monocolor"] .file-explorer, |
|
|
:root[data-ui="monocolor"] .tools-widget { |
|
|
background: var(--bg-primary); |
|
|
border-color: var(--mono-color); |
|
|
color: var(--mono-color); |
|
|
} |
|
|
:root[data-ui="monocolor"] .cell-code { |
|
|
background: var(--bg-primary); |
|
|
border-bottom-color: var(--mono-color); |
|
|
} |
|
|
:root[data-ui="monocolor"] .tools-title, |
|
|
:root[data-ui="monocolor"] .file-explorer-section-title, |
|
|
:root[data-ui="monocolor"] .minimap-title { color: var(--mono-color); border-bottom-color: var(--mono-color); } |
|
|
:root[data-ui="monocolor"] .tool-button { background: var(--bg-primary); border-color: var(--mono-color); color: var(--mono-color); } |
|
|
:root[data-ui="monocolor"] .tool-button.active { border-color: var(--mono-color); } |
|
|
:root[data-ui="monocolor"] .file-explorer-item, |
|
|
:root[data-ui="monocolor"] .minimap-item { color: var(--mono-color); } |
|
|
|
|
|
:root[data-ui="monocolor"] .highlight { background: var(--bg-primary) !important; color: var(--mono-color) !important; } |
|
|
:root[data-ui="monocolor"] .highlight *, |
|
|
:root[data-ui="monocolor"] .highlight .hll { color: var(--mono-color) !important; background: transparent !important; border-color: var(--mono-color) !important; } |
|
|
|
|
|
:root { --code-font-size: 0.95rem; --code-line-height: 1.5; --code-pad-y: 0.75rem; } |
|
|
|
|
|
:root[data-ui="none"] { |
|
|
--bg-primary: #ffffff; |
|
|
--bg-secondary: transparent; |
|
|
--bg-tertiary: transparent; |
|
|
--bg-code: #f9f9f9; |
|
|
--bg-error: #fff0f0; |
|
|
--bg-artifact: #f0f7ff; |
|
|
--bg-artifact-hover: #e5f1ff; |
|
|
|
|
|
--text-primary: #000000; |
|
|
--text-secondary: #222222; |
|
|
--text-error: #a00000; |
|
|
--text-link: #0000ee; |
|
|
|
|
|
--border-primary: #cccccc; |
|
|
--border-error: #cc0000; |
|
|
--border-cell-failed: #cc0000; |
|
|
|
|
|
--shadow: none; |
|
|
} |
|
|
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; |
|
|
} |
|
|
|
|
|
:root[data-ui="none"] body { |
|
|
font-family: 'Times New Roman', Times, serif; |
|
|
line-height: 1.5; |
|
|
max-width: 860px; |
|
|
padding: 12px; |
|
|
background: #ffffff; |
|
|
color: #000000; |
|
|
transition: none; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
.controls { |
|
|
position: fixed; |
|
|
top: 20px; |
|
|
right: 20px; |
|
|
display: flex; |
|
|
flex-direction: column; |
|
|
align-items: flex-end; |
|
|
gap: 0.25rem; |
|
|
z-index: 1000; |
|
|
} |
|
|
.controls-buttons { display: flex; gap: 0.5rem; } |
|
|
|
|
|
.menu-button { |
|
|
position: relative; |
|
|
background: var(--bg-secondary); |
|
|
border: 1px solid var(--border-primary); |
|
|
padding: 8px 12px; |
|
|
border-radius: 2px; |
|
|
color: var(--text-secondary); |
|
|
cursor: pointer; |
|
|
font-family: inherit; |
|
|
font-size: 0.9rem; |
|
|
user-select: none; |
|
|
} |
|
|
|
|
|
:root[data-ui="none"][data-widgets="on"] .menu-button, |
|
|
:root[data-ui="none"][data-widgets="on"] .theme-toggle, |
|
|
:root[data-ui="none"][data-widgets="on"] .reset-toggle { |
|
|
background: #f6f6f6; |
|
|
border: 1px solid #cccccc; |
|
|
color: #222222; |
|
|
} |
|
|
|
|
|
.menu-button:hover { |
|
|
color: var(--text-primary); |
|
|
background: var(--bg-tertiary); |
|
|
} |
|
|
|
|
|
|
|
|
.status-widget { |
|
|
position: fixed; |
|
|
right: 20px; |
|
|
bottom: 20px; |
|
|
width: auto; |
|
|
max-width: 260px; |
|
|
background: var(--bg-secondary); |
|
|
border: 1px solid var(--border-primary); |
|
|
border-radius: 2px; |
|
|
padding: 6px 8px; |
|
|
font-size: 0.8rem; |
|
|
color: var(--text-secondary); |
|
|
z-index: 100; |
|
|
} |
|
|
.status-widget strong { color: var(--text-primary); } |
|
|
:root[data-ui="none"][data-widgets="on"] .status-widget { background: #f6f6f6; border-color: #ccc; color: #222; } |
|
|
:root[data-ui="none"][data-widgets="on"] .menu-button:hover, |
|
|
:root[data-ui="none"][data-widgets="on"] .theme-toggle:hover, |
|
|
:root[data-ui="none"][data-widgets="on"] .reset-toggle:hover { |
|
|
background: #ededed; |
|
|
border-color: #bbbbbb; |
|
|
color: #000000; |
|
|
} |
|
|
|
|
|
.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; |
|
|
} |
|
|
:root[data-ui="none"][data-widgets="on"] .menu-dropdown { background: #ffffff; border: 1px solid #cccccc; box-shadow: none; } |
|
|
|
|
|
.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; |
|
|
} |
|
|
:root[data-ui="none"] .menu-item { color: #000; border-bottom: 1px solid #eee; } |
|
|
|
|
|
.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; |
|
|
|
|
|
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; |
|
|
} |
|
|
|
|
|
:root[data-widgets="off"] .controls, |
|
|
:root[data-widgets="off"] .minimap, |
|
|
:root[data-widgets="off"] .file-explorer, |
|
|
:root[data-widgets="off"] .tools-widget, |
|
|
:root[data-widgets="off"] .status-widget { display: none !important; } |
|
|
|
|
|
.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); |
|
|
} |
|
|
:root[data-ui="none"] .cell { margin: 1em 0; border: none; background: transparent; } |
|
|
.cell-header { |
|
|
background: var(--bg-secondary); |
|
|
padding: 0.5rem 1rem; |
|
|
border-bottom: 1px solid var(--border-primary); |
|
|
font-family: inherit; |
|
|
font-size: 0.85rem; |
|
|
} |
|
|
:root[data-ui="none"] .cell-header { background: transparent; border: none; padding: 0; font-weight: bold; } |
|
|
:root[data-ui="none"] .cell-content { padding: 0; } |
|
|
:root[data-ui="none"] .copy-button, |
|
|
:root[data-ui="none"] .collapse-indicators, |
|
|
:root[data-ui="none"] .cell-meta, |
|
|
:root[data-ui="none"] .cell-outputs-header { display: none !important; } |
|
|
:root[data-ui="none"] pre, :root[data-ui="none"] code { font-family: Menlo, Monaco, 'Courier New', monospace; } |
|
|
:root[data-ui="none"] .code-content pre { background: #f9f9f9; border: 1px solid #ddd; padding: 8px; } |
|
|
:root[data-ui="none"] .output { background: transparent; border: none; padding: 0.25em 0; } |
|
|
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-secondary); |
|
|
} |
|
|
.cell-output.collapsed { |
|
|
display: none; |
|
|
} |
|
|
.cell-stdout { |
|
|
background: var(--bg-tertiary); |
|
|
padding: 0.75rem; |
|
|
border-radius: 1px; |
|
|
|
|
|
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; |
|
|
font-size: var(--code-font-size); |
|
|
} |
|
|
.code-wrap { position: relative; } |
|
|
.code-line-highlight { display: none; position: absolute; left: 0; right: 0; height: 1.5em; background: rgba(255, 235, 170, 0.35); pointer-events: none; border-left: 3px solid #f4c542; } |
|
|
.line-number { cursor: pointer; text-decoration: none; color: var(--text-secondary); padding: 0 0.25rem; } |
|
|
.line-number.selected { background: rgba(255, 235, 170, 0.4); color: var(--text-primary); } |
|
|
|
|
|
|
|
|
.highlight-with-lines { |
|
|
display: flex; |
|
|
} |
|
|
.line-numbers { |
|
|
background: var(--bg-tertiary); |
|
|
padding: var(--code-pad-y) 0.5rem; |
|
|
font-family: 'Cascadia Mono', 'Cascadia Code', 'JetBrains Mono', 'SF Mono', Monaco, 'Consolas', monospace; |
|
|
font-size: var(--code-font-size); |
|
|
line-height: var(--code-line-height); |
|
|
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: var(--code-line-height); |
|
|
} |
|
|
.highlight-with-lines .highlight { |
|
|
flex: 1; |
|
|
} |
|
|
.highlight .hll { background-color: transparent; } |
|
|
.highlight pre { |
|
|
white-space: pre; |
|
|
margin: 0; |
|
|
padding: var(--code-pad-y) 0.75rem; |
|
|
line-height: var(--code-line-height); |
|
|
} |
|
|
|
|
|
|
|
|
.cell-code.collapsed { |
|
|
display: none; |
|
|
} |
|
|
.cell-code.expanded { |
|
|
display: block; |
|
|
} |
|
|
|
|
|
.cell-code { |
|
|
display: block; |
|
|
border-bottom: 1px solid var(--border-primary); |
|
|
} |
|
|
|
|
|
|
|
|
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 } |
|
|
|
|
|
|
|
|
.highlight pre { |
|
|
white-space: pre; |
|
|
margin: 0; |
|
|
padding: var(--code-pad-y) 0.75rem !important; |
|
|
line-height: var(--code-line-height) !important; |
|
|
font-size: var(--code-font-size) !important; |
|
|
font-family: 'Cascadia Mono', 'Cascadia Code', 'JetBrains Mono', 'SF Mono', Monaco, 'Consolas', monospace !important; |
|
|
border: none; |
|
|
} |
|
|
.line-numbers { line-height: var(--code-line-height) !important; } |
|
|
.line-numbers .line-number { line-height: var(--code-line-height) !important; } |
|
|
|
|
|
|
|
|
#output-setup { |
|
|
overflow-x: auto; |
|
|
} |
|
|
.cell-output { |
|
|
overflow: scroll; |
|
|
} |
|
|
.cell-stdout { |
|
|
width: max-content; |
|
|
overflow: scroll; |
|
|
} |
|
|
.cell-stderr { |
|
|
width: max-content; |
|
|
overflow: scroll; |
|
|
max-height: 300px; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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); |
|
|
|
|
|
} |
|
|
</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); |
|
|
encodeToolStateToUrl(); |
|
|
} |
|
|
|
|
|
function toggleCode(cellId) { |
|
|
const codeElement = document.getElementById('code-' + cellId); |
|
|
if (codeElement) { |
|
|
codeElement.classList.toggle('collapsed'); |
|
|
updateIndicators(cellId); |
|
|
encodeToolStateToUrl(); |
|
|
} |
|
|
} |
|
|
|
|
|
function toggleOutput(cellId) { |
|
|
const outputElement = document.getElementById('output-' + cellId); |
|
|
if (outputElement) { |
|
|
outputElement.classList.toggle('collapsed'); |
|
|
updateIndicators(cellId); |
|
|
encodeToolStateToUrl(); |
|
|
} |
|
|
} |
|
|
|
|
|
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(); |
|
|
updateUiDebug(); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
function updateThemeIcon() { |
|
|
const theme = document.documentElement.getAttribute('data-theme'); |
|
|
const toggle = document.querySelector('.theme-toggle'); |
|
|
if (toggle) { |
|
|
toggle.textContent = theme === 'dark' ? 'light' : 'dark'; |
|
|
} |
|
|
} |
|
|
function setUiTheme(newUi) { |
|
|
if (newUi !== 'default' && newUi !== 'none' && newUi !== 'monocolor') return; |
|
|
const html = document.documentElement; |
|
|
html.setAttribute('data-ui', newUi); |
|
|
try { localStorage.setItem('uvnote-ui', newUi); } catch (_) {} |
|
|
updateUiMenu(); |
|
|
updateUiDebug(); |
|
|
} |
|
|
function updateUiMenu() { |
|
|
const ui = document.documentElement.getAttribute('data-ui') || 'default'; |
|
|
const checks = { |
|
|
default: document.getElementById('checkbox-ui-default'), |
|
|
none: document.getElementById('checkbox-ui-none'), |
|
|
monocolor: document.getElementById('checkbox-ui-monocolor') |
|
|
}; |
|
|
if (checks.default) checks.default.textContent = ui === 'default' ? '☑' : '☐'; |
|
|
if (checks.none) checks.none.textContent = ui === 'none' ? '☑' : '☐'; |
|
|
if (checks.monocolor) checks.monocolor.textContent = ui === 'monocolor' ? '☑' : '☐'; |
|
|
} |
|
|
|
|
|
function updateUiDebug() { |
|
|
const ui = document.documentElement.getAttribute('data-ui') || 'default'; |
|
|
const color = document.documentElement.getAttribute('data-theme') || 'light'; |
|
|
const el = document.getElementById('ui-debug'); |
|
|
if (el) { |
|
|
el.textContent = `UI: ${ui} | Color: ${color}`; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function clearLineSelections() { |
|
|
try { |
|
|
document.querySelectorAll('.code-line-highlight').forEach(el => { el.style.display = 'none'; }); |
|
|
document.querySelectorAll('.line-number.selected').forEach(el => el.classList.remove('selected')); |
|
|
} catch (_) {} |
|
|
} |
|
|
|
|
|
let _selection = null; |
|
|
|
|
|
function clearSelection(updateUrl) { |
|
|
clearLineSelections(); |
|
|
_selection = null; |
|
|
if (updateUrl) { |
|
|
try { |
|
|
const url = new URL(window.location.href); |
|
|
url.searchParams.delete('cell'); |
|
|
url.searchParams.delete('line'); |
|
|
history.replaceState(null, '', url.toString()); |
|
|
} catch (_) {} |
|
|
} |
|
|
updateStateIndicator(); |
|
|
} |
|
|
|
|
|
function selectCellLine(cellId, line, updateUrl) { |
|
|
try { |
|
|
|
|
|
clearLineSelections(); |
|
|
const codeBox = document.getElementById(`code-${cellId}`); |
|
|
if (!codeBox) return; |
|
|
const pre = codeBox.querySelector('.highlight pre'); |
|
|
const overlay = document.getElementById(`line-highlight-${cellId}`); |
|
|
const numbers = document.getElementById(`lines-${cellId}`); |
|
|
if (!pre || !overlay) return; |
|
|
|
|
|
|
|
|
const preStyle = getComputedStyle(pre); |
|
|
const padTop = parseFloat(preStyle.paddingTop || '0'); |
|
|
const lh = parseFloat(preStyle.lineHeight || '20'); |
|
|
|
|
|
|
|
|
overlay.style.display = 'block'; |
|
|
overlay.style.height = `${lh}px`; |
|
|
overlay.style.top = `${pre.offsetTop + padTop + (line - 1) * lh}px`; |
|
|
|
|
|
|
|
|
if (numbers) { |
|
|
numbers.querySelectorAll('.line-number').forEach(a => a.classList.remove('selected')); |
|
|
const sel = numbers.querySelector(`.line-number[data-line="${line}"]`); |
|
|
if (sel) sel.classList.add('selected'); |
|
|
} |
|
|
|
|
|
if (updateUrl) { |
|
|
const url = new URL(window.location.href); |
|
|
url.searchParams.set('cell', cellId); |
|
|
url.searchParams.set('line', String(line)); |
|
|
history.replaceState(null, '', url.toString()); |
|
|
} |
|
|
_selection = { cellId, a: line, b: line }; |
|
|
updateStateIndicator(); |
|
|
} catch (e) { console.warn('selectCellLine error', e); } |
|
|
} |
|
|
|
|
|
function selectCellLines(cellId, startLine, endLine, updateUrl) { |
|
|
try { |
|
|
|
|
|
const a = Math.min(startLine, endLine); |
|
|
const b = Math.max(startLine, endLine); |
|
|
clearLineSelections(); |
|
|
const codeBox = document.getElementById(`code-${cellId}`); |
|
|
if (!codeBox) return; |
|
|
const pre = codeBox.querySelector('.highlight pre'); |
|
|
const overlay = document.getElementById(`line-highlight-${cellId}`); |
|
|
const numbers = document.getElementById(`lines-${cellId}`); |
|
|
if (!pre || !overlay) return; |
|
|
|
|
|
const preStyle = getComputedStyle(pre); |
|
|
const padTop = parseFloat(preStyle.paddingTop || '0'); |
|
|
const lh = parseFloat(preStyle.lineHeight || '20'); |
|
|
|
|
|
overlay.style.display = 'block'; |
|
|
overlay.style.top = `${pre.offsetTop + padTop + (a - 1) * lh}px`; |
|
|
overlay.style.height = `${(b - a + 1) * lh}px`; |
|
|
|
|
|
if (numbers) { |
|
|
numbers.querySelectorAll('.line-number').forEach(a => a.classList.remove('selected')); |
|
|
for (let i = a; i <= b; i++) { |
|
|
const el = numbers.querySelector(`.line-number[data-line="${i}"]`); |
|
|
if (el) el.classList.add('selected'); |
|
|
} |
|
|
} |
|
|
|
|
|
if (updateUrl) { |
|
|
const url = new URL(window.location.href); |
|
|
url.searchParams.set('cell', cellId); |
|
|
if (a === b) url.searchParams.set('line', String(a)); |
|
|
else url.searchParams.set('line', `${a}-${b}`); |
|
|
history.replaceState(null, '', url.toString()); |
|
|
} |
|
|
_selection = { cellId, a, b }; |
|
|
updateStateIndicator(); |
|
|
} catch (e) { console.warn('selectCellLines error', e); } |
|
|
} |
|
|
|
|
|
|
|
|
let _lineDrag = { active: false, cellId: null, start: 0 }; |
|
|
function onLineNumberMouseDown(e) { |
|
|
const a = e.target.closest('.line-number'); |
|
|
if (!a) return; |
|
|
e.preventDefault(); |
|
|
const cellId = a.dataset.cell; |
|
|
const line = parseInt(a.dataset.line || '1', 10) || 1; |
|
|
|
|
|
const numbers = document.getElementById(`lines-${cellId}`); |
|
|
if (numbers) { |
|
|
const selected = Array.from(numbers.querySelectorAll('.line-number.selected')).map(n => parseInt(n.dataset.line||'0',10)).filter(Boolean); |
|
|
if (selected.length === 1 && selected[0] === line) { |
|
|
clearSelection(true); |
|
|
return; |
|
|
} |
|
|
} |
|
|
_lineDrag.active = true; |
|
|
_lineDrag.cellId = cellId; |
|
|
_lineDrag.start = line; |
|
|
selectCellLines(_lineDrag.cellId, _lineDrag.start, _lineDrag.start, false); |
|
|
} |
|
|
function onDocMouseMove(e) { |
|
|
if (!_lineDrag.active) return; |
|
|
const el = document.elementFromPoint(e.clientX, e.clientY); |
|
|
if (!el) return; |
|
|
const a = el.closest && el.closest('.line-number'); |
|
|
if (!a) return; |
|
|
if (a.dataset.cell !== _lineDrag.cellId) return; |
|
|
const cur = parseInt(a.dataset.line || '1', 10) || 1; |
|
|
selectCellLines(_lineDrag.cellId, _lineDrag.start, cur, false); |
|
|
} |
|
|
function onDocMouseUp(e) { |
|
|
if (!_lineDrag.active) return; |
|
|
const last = document.querySelector('.line-number.selected:last-of-type'); |
|
|
|
|
|
const numbers = document.getElementById(`lines-${_lineDrag.cellId}`); |
|
|
if (numbers) { |
|
|
const selected = Array.from(numbers.querySelectorAll('.line-number.selected')).map(n => parseInt(n.dataset.line||'0',10)).filter(Boolean); |
|
|
if (selected.length) { |
|
|
const a = Math.min(...selected); const b = Math.max(...selected); |
|
|
selectCellLines(_lineDrag.cellId, a, b, true); |
|
|
} |
|
|
} |
|
|
_lineDrag.active = false; _lineDrag.cellId = null; _lineDrag.start = 0; |
|
|
} |
|
|
|
|
|
function applyLocationFromUrl() { |
|
|
try { |
|
|
const url = new URL(window.location.href); |
|
|
const cell = url.searchParams.get('cell'); |
|
|
const lineParam = url.searchParams.get('line'); |
|
|
if (cell && lineParam) { |
|
|
if (lineParam.includes('-')) { |
|
|
const [a, b] = lineParam.split('-').map(x => parseInt(x, 10)); |
|
|
if (!Number.isNaN(a) && !Number.isNaN(b)) selectCellLines(cell, a, b, false); |
|
|
} else { |
|
|
const l = parseInt(lineParam, 10); |
|
|
if (!Number.isNaN(l)) selectCellLine(cell, l, false); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
applyToolsFromUrl(url.searchParams); |
|
|
|
|
|
|
|
|
const encodedCellStates = url.searchParams.get('cells'); |
|
|
console.log('Encoded cell states from URL:', encodedCellStates); |
|
|
} catch (_) {} |
|
|
} |
|
|
|
|
|
function applyToolsFromUrl(params) { |
|
|
try { |
|
|
|
|
|
const showTools = params.get('tools'); |
|
|
if (showTools === '1') { |
|
|
|
|
|
_urlLoadedTool = true; |
|
|
|
|
|
|
|
|
const color = params.get('color'); |
|
|
if (color && /^[0-9a-fA-F]{6}$/.test(color)) { |
|
|
setStoredArrowColor('#' + color); |
|
|
} |
|
|
|
|
|
|
|
|
const thickness = params.get('thickness'); |
|
|
if (thickness) { |
|
|
const value = parseInt(thickness, 10); |
|
|
if (value >= 1 && value <= 10) { |
|
|
setStoredLineThickness(value); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const encodedShapes = params.get('shapes'); |
|
|
if (encodedShapes) { |
|
|
const decodedShapes = decodeShapesFromUrl(encodedShapes); |
|
|
if (decodedShapes.length > 0) { |
|
|
_shapes = decodedShapes; |
|
|
saveShapes(); |
|
|
|
|
|
setTimeout(() => { |
|
|
renderOverlay(); |
|
|
}, 300); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
setTimeout(() => { |
|
|
const toolsWidget = document.querySelector('.tools-widget'); |
|
|
const checkbox = document.getElementById('checkbox-tools'); |
|
|
if (toolsWidget && checkbox) { |
|
|
toolsWidget.style.display = 'block'; |
|
|
checkbox.textContent = '☑'; |
|
|
localStorage.setItem('uvnote-widget-tools', 'visible'); |
|
|
} |
|
|
|
|
|
|
|
|
const activeTool = params.get('tool'); |
|
|
if (activeTool && ['arrow', 'pen', 'eraser', 'spotlight'].includes(activeTool)) { |
|
|
const toolBtn = Array.from(document.querySelectorAll('.tool-button')).find(btn => btn.textContent === activeTool); |
|
|
if (toolBtn) { |
|
|
toolBtn.click(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
layoutWidgetsStackedBottomRight(); |
|
|
}, 200); |
|
|
} |
|
|
} catch (_) {} |
|
|
} |
|
|
|
|
|
function captureInitialCellStates() { |
|
|
const cells = document.querySelectorAll('.cell'); |
|
|
cells.forEach(cell => { |
|
|
const cellId = cell.id.replace('cell-', ''); |
|
|
const codeEl = document.getElementById('code-' + cellId); |
|
|
const outputEl = document.getElementById('output-' + cellId); |
|
|
|
|
|
if (codeEl || outputEl) { |
|
|
const state = {}; |
|
|
if (codeEl) { |
|
|
state.c = codeEl.classList.contains('collapsed') ? 0 : 1; |
|
|
} |
|
|
if (outputEl) { |
|
|
state.o = outputEl.classList.contains('collapsed') ? 0 : 1; |
|
|
} |
|
|
_initialCellStates[cellId] = state; |
|
|
} |
|
|
}); |
|
|
console.log('Captured initial cell states:', _initialCellStates); |
|
|
} |
|
|
|
|
|
function encodeCellStatesToUrl() { |
|
|
|
|
|
const cells = document.querySelectorAll('.cell'); |
|
|
const cellStates = {}; |
|
|
|
|
|
console.log('Found cells:', cells.length); |
|
|
|
|
|
cells.forEach(cell => { |
|
|
const cellId = cell.id.replace('cell-', ''); |
|
|
const codeEl = document.getElementById('code-' + cellId); |
|
|
const outputEl = document.getElementById('output-' + cellId); |
|
|
const initialState = _initialCellStates[cellId] || {}; |
|
|
|
|
|
console.log(`Encoding cell ${cellId}:`, { |
|
|
codeEl: !!codeEl, |
|
|
outputEl: !!outputEl, |
|
|
codeCollapsed: codeEl ? codeEl.classList.contains('collapsed') : 'N/A', |
|
|
outputCollapsed: outputEl ? outputEl.classList.contains('collapsed') : 'N/A', |
|
|
initialState: initialState |
|
|
}); |
|
|
|
|
|
if (codeEl || outputEl) { |
|
|
const state = {}; |
|
|
let hasChanges = false; |
|
|
|
|
|
if (codeEl) { |
|
|
const currentCodeState = codeEl.classList.contains('collapsed') ? 0 : 1; |
|
|
const initialCodeState = initialState.c; |
|
|
|
|
|
if (initialCodeState !== undefined && currentCodeState !== initialCodeState) { |
|
|
state.c = currentCodeState; |
|
|
hasChanges = true; |
|
|
} |
|
|
} |
|
|
|
|
|
if (outputEl) { |
|
|
const currentOutputState = outputEl.classList.contains('collapsed') ? 0 : 1; |
|
|
const initialOutputState = initialState.o; |
|
|
|
|
|
if (initialOutputState !== undefined && currentOutputState !== initialOutputState) { |
|
|
state.o = currentOutputState; |
|
|
hasChanges = true; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if (hasChanges) { |
|
|
cellStates[cellId] = state; |
|
|
console.log(`Added cell ${cellId}:`, state); |
|
|
} |
|
|
} |
|
|
}); |
|
|
|
|
|
console.log('Final cell states to encode:', cellStates); |
|
|
|
|
|
|
|
|
if (Object.keys(cellStates).length === 0) return ''; |
|
|
|
|
|
|
|
|
const encoded = btoa(JSON.stringify(cellStates)); |
|
|
console.log('Encoded cell states:', encoded); |
|
|
return encoded; |
|
|
} |
|
|
|
|
|
function decodeCellStatesFromUrl(encodedStates) { |
|
|
if (!encodedStates) return {}; |
|
|
|
|
|
try { |
|
|
return JSON.parse(atob(encodedStates)); |
|
|
} catch (e) { |
|
|
console.error('Failed to decode cell states:', e); |
|
|
return {}; |
|
|
} |
|
|
} |
|
|
|
|
|
function applyCellStatesFromUrl(cellStates) { |
|
|
console.log('Applying cell states from URL:', cellStates); |
|
|
Object.entries(cellStates).forEach(([cellId, state]) => { |
|
|
const codeEl = document.getElementById('code-' + cellId); |
|
|
const outputEl = document.getElementById('output-' + cellId); |
|
|
|
|
|
console.log(`Cell ${cellId}:`, { |
|
|
codeEl: !!codeEl, |
|
|
outputEl: !!outputEl, |
|
|
state: state |
|
|
}); |
|
|
|
|
|
if (codeEl && state.c !== undefined) { |
|
|
if (state.c === 0) { |
|
|
codeEl.classList.add('collapsed'); |
|
|
console.log(`Collapsed code for cell ${cellId}`, { |
|
|
hasCollapsedClass: codeEl.classList.contains('collapsed'), |
|
|
computedDisplay: getComputedStyle(codeEl).display, |
|
|
classList: Array.from(codeEl.classList), |
|
|
elementId: codeEl.id |
|
|
}); |
|
|
} else { |
|
|
codeEl.classList.remove('collapsed'); |
|
|
codeEl.classList.add('expanded'); |
|
|
console.log(`Expanded code for cell ${cellId}`, { |
|
|
hasCollapsedClass: codeEl.classList.contains('collapsed'), |
|
|
hasExpandedClass: codeEl.classList.contains('expanded'), |
|
|
computedDisplay: getComputedStyle(codeEl).display, |
|
|
classList: Array.from(codeEl.classList), |
|
|
elementId: codeEl.id |
|
|
}); |
|
|
} |
|
|
} |
|
|
|
|
|
if (outputEl && state.o !== undefined) { |
|
|
if (state.o === 0) { |
|
|
outputEl.classList.add('collapsed'); |
|
|
console.log(`Collapsed output for cell ${cellId}`); |
|
|
} else { |
|
|
outputEl.classList.remove('collapsed'); |
|
|
console.log(`Expanded output for cell ${cellId}`); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
try { |
|
|
updateIndicators(cellId); |
|
|
|
|
|
if (codeEl) { |
|
|
codeEl.offsetHeight; |
|
|
console.log(`After indicators update - code visible: ${getComputedStyle(codeEl).display !== 'none'}`); |
|
|
} |
|
|
if (outputEl) { |
|
|
outputEl.offsetHeight; |
|
|
console.log(`After indicators update - output visible: ${getComputedStyle(outputEl).display !== 'none'}`); |
|
|
} |
|
|
} catch (e) { |
|
|
console.error(`Error updating indicators for cell ${cellId}:`, e); |
|
|
} |
|
|
}); |
|
|
} |
|
|
|
|
|
function encodeShapesToUrl() { |
|
|
|
|
|
if (_shapes.length === 0) return ''; |
|
|
|
|
|
const shapeData = _shapes.map(shape => { |
|
|
const baseData = { |
|
|
ct: shape.createdAt, |
|
|
fo: shape.fadeoutTime || getFadeoutTime() |
|
|
}; |
|
|
|
|
|
if (shape.type === 'arrow') { |
|
|
return { |
|
|
...baseData, |
|
|
t: 'a', |
|
|
x1: Math.round(shape.x1), |
|
|
y1: Math.round(shape.y1), |
|
|
x2: Math.round(shape.x2), |
|
|
y2: Math.round(shape.y2), |
|
|
c: shape.color.substring(1), |
|
|
w: shape.width |
|
|
}; |
|
|
} else if (shape.type === 'pen') { |
|
|
return { |
|
|
...baseData, |
|
|
t: 'p', |
|
|
pts: shape.points.map(p => [Math.round(p.x), Math.round(p.y)]), |
|
|
c: shape.color.substring(1), |
|
|
w: shape.width |
|
|
}; |
|
|
} else if (shape.type === 'spotlight') { |
|
|
return { |
|
|
...baseData, |
|
|
t: 's', |
|
|
x: Math.round(shape.x), |
|
|
y: Math.round(shape.y), |
|
|
r: Math.round(shape.radius) |
|
|
}; |
|
|
} |
|
|
}).filter(Boolean); |
|
|
|
|
|
return btoa(JSON.stringify(shapeData)); |
|
|
} |
|
|
|
|
|
function decodeShapesFromUrl(encodedShapes) { |
|
|
if (!encodedShapes) return []; |
|
|
|
|
|
try { |
|
|
const shapeData = JSON.parse(atob(encodedShapes)); |
|
|
return shapeData.map(data => { |
|
|
const base = { |
|
|
createdAt: data.ct || Date.now(), |
|
|
fadeoutTime: data.fo || 0, |
|
|
opacity: 1.0 |
|
|
}; |
|
|
|
|
|
if (data.t === 'a') { |
|
|
return { |
|
|
...base, |
|
|
type: 'arrow', |
|
|
x1: data.x1, |
|
|
y1: data.y1, |
|
|
x2: data.x2, |
|
|
y2: data.y2, |
|
|
color: '#' + data.c, |
|
|
width: data.w |
|
|
}; |
|
|
} else if (data.t === 'p') { |
|
|
return { |
|
|
...base, |
|
|
type: 'pen', |
|
|
points: data.pts.map(([x, y]) => ({ x, y })), |
|
|
color: '#' + data.c, |
|
|
width: data.w |
|
|
}; |
|
|
} else if (data.t === 's') { |
|
|
return { |
|
|
...base, |
|
|
type: 'spotlight', |
|
|
x: data.x, |
|
|
y: data.y, |
|
|
radius: data.r, |
|
|
color: '#000000' |
|
|
}; |
|
|
} |
|
|
}).filter(Boolean); |
|
|
} catch (e) { |
|
|
console.error('Failed to decode shapes:', e); |
|
|
return []; |
|
|
} |
|
|
} |
|
|
|
|
|
function encodeToolStateToUrl() { |
|
|
|
|
|
if (_isInitializing) { |
|
|
return window.location.href; |
|
|
} |
|
|
|
|
|
const params = new URLSearchParams(window.location.search); |
|
|
|
|
|
|
|
|
const toolsWidget = document.querySelector('.tools-widget'); |
|
|
const activeTool = document.body.dataset.tool; |
|
|
const hasActiveTool = activeTool && activeTool !== 'none'; |
|
|
const toolsWidgetVisible = toolsWidget && getComputedStyle(toolsWidget).display !== 'none'; |
|
|
|
|
|
|
|
|
const encodedShapes = encodeShapesToUrl(); |
|
|
if (encodedShapes) { |
|
|
params.set('shapes', encodedShapes); |
|
|
} else { |
|
|
params.delete('shapes'); |
|
|
} |
|
|
|
|
|
|
|
|
const existingCellStates = params.get('cells'); |
|
|
if (existingCellStates) { |
|
|
|
|
|
params.set('cells', existingCellStates); |
|
|
} else { |
|
|
|
|
|
const encodedCellStates = encodeCellStatesToUrl(); |
|
|
if (encodedCellStates) { |
|
|
params.set('cells', encodedCellStates); |
|
|
} |
|
|
} |
|
|
|
|
|
if (toolsWidgetVisible && hasActiveTool) { |
|
|
|
|
|
params.set('tools', '1'); |
|
|
params.set('tool', activeTool); |
|
|
|
|
|
|
|
|
const color = getArrowColor(); |
|
|
if (color && color.startsWith('#')) { |
|
|
params.set('color', color.substring(1)); |
|
|
} |
|
|
|
|
|
|
|
|
const thickness = getLineThickness(); |
|
|
params.set('thickness', thickness.toString()); |
|
|
} else { |
|
|
|
|
|
params.delete('tools'); |
|
|
params.delete('tool'); |
|
|
params.delete('color'); |
|
|
params.delete('thickness'); |
|
|
params.delete('fadeout'); |
|
|
} |
|
|
|
|
|
|
|
|
const newUrl = window.location.pathname + (params.toString() ? '?' + params.toString() : '') + window.location.hash; |
|
|
window.history.replaceState(null, '', newUrl); |
|
|
|
|
|
return window.location.href; |
|
|
} |
|
|
|
|
|
function resetLayout() { |
|
|
try { |
|
|
|
|
|
const allKeys = Object.keys(localStorage); |
|
|
const uvnoteKeys = allKeys.filter(key => key.startsWith('uvnote-')); |
|
|
uvnoteKeys.forEach(k => localStorage.removeItem(k)); |
|
|
} catch (_) {} |
|
|
|
|
|
|
|
|
try { clearSelection(true); } catch(_) {} |
|
|
|
|
|
try { window.setActiveTool('none'); } catch(_) {} |
|
|
|
|
|
try { _shapes = []; saveShapes(); } catch(_) {} |
|
|
|
|
|
try { _urlLoadedTool = false; } catch(_) {} |
|
|
|
|
|
try { |
|
|
const cells = document.querySelectorAll('.cell'); |
|
|
cells.forEach(cell => { |
|
|
const cellId = cell.id.replace('cell-', ''); |
|
|
const codeEl = document.getElementById('code-' + cellId); |
|
|
const outputEl = document.getElementById('output-' + cellId); |
|
|
if (codeEl) codeEl.classList.remove('collapsed'); |
|
|
if (outputEl) outputEl.classList.remove('collapsed'); |
|
|
updateIndicators(cellId); |
|
|
}); |
|
|
} catch(_) {} |
|
|
|
|
|
|
|
|
try { |
|
|
const cleanUrl = window.location.pathname + window.location.hash; |
|
|
window.location.href = cleanUrl; |
|
|
} 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; |
|
|
case 'status': |
|
|
widget = document.querySelector('.status-widget'); |
|
|
checkbox = document.getElementById('checkbox-status'); |
|
|
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 (_) {} |
|
|
|
|
|
|
|
|
if (widgetName === 'tools') { |
|
|
encodeToolStateToUrl(); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
function initializeWidgetVisibility() { |
|
|
const widgets = [ |
|
|
{ name: 'tools', selector: '.tools-widget' }, |
|
|
{ name: 'file-explorer', selector: '.file-explorer' }, |
|
|
{ name: 'minimap', selector: '.minimap' }, |
|
|
{ name: 'status', selector: '.status-widget' } |
|
|
]; |
|
|
|
|
|
widgets.forEach(({ name, selector }) => { |
|
|
const defaultState = name === 'status' ? 'visible' : 'hidden'; |
|
|
const savedState = localStorage.getItem(`uvnote-widget-${name}`) || defaultState; |
|
|
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 status = document.querySelector('.status-widget'); |
|
|
const widgets = [minimap, fileExplorer, tools, status].filter(el => el && getComputedStyle(el).display !== 'none'); |
|
|
if (!widgets.length) return; |
|
|
|
|
|
const order = [minimap, fileExplorer, tools, status].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; |
|
|
} |
|
|
|
|
|
function initStatusWidget() { |
|
|
let el = document.querySelector('.status-widget'); |
|
|
if (!el) { |
|
|
el = document.createElement('div'); |
|
|
el.className = 'status-widget'; |
|
|
el.id = 'status-widget'; |
|
|
el.textContent = 'ready — Esc'; |
|
|
document.body.appendChild(el); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
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')); |
|
|
updateStateIndicator(); |
|
|
encodeToolStateToUrl(); |
|
|
return; |
|
|
} |
|
|
document.body.dataset.tool = tool; |
|
|
localStorage.setItem('uvnote-active-tool', tool); |
|
|
setOverlayActive(true); |
|
|
_cursorVisible = true; |
|
|
updateStateIndicator(); |
|
|
encodeToolStateToUrl(); |
|
|
} |
|
|
|
|
|
|
|
|
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 6; |
|
|
} |
|
|
|
|
|
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(); |
|
|
encodeToolStateToUrl(); |
|
|
}; |
|
|
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(); |
|
|
encodeToolStateToUrl(); |
|
|
}; |
|
|
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(); |
|
|
encodeToolStateToUrl(); |
|
|
}; |
|
|
|
|
|
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'; |
|
|
encodeToolStateToUrl(); |
|
|
}; |
|
|
|
|
|
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; |
|
|
let _urlLoadedTool = false; |
|
|
let _isInitializing = true; |
|
|
let _initialCellStates = {}; |
|
|
|
|
|
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)); |
|
|
|
|
|
encodeToolStateToUrl(); |
|
|
} catch (_) {} |
|
|
} |
|
|
|
|
|
function updateShapesFade() { |
|
|
const now = Date.now(); |
|
|
let needsUpdate = false; |
|
|
|
|
|
for (let i = _shapes.length - 1; i >= 0; i--) { |
|
|
const shape = _shapes[i]; |
|
|
if (!shape.createdAt) continue; |
|
|
|
|
|
|
|
|
const shapesFadeoutSeconds = shape.fadeoutTime !== undefined ? shape.fadeoutTime : getFadeoutTime(); |
|
|
|
|
|
|
|
|
if (shapesFadeoutSeconds === 0) continue; |
|
|
|
|
|
const fadeStartTime = Math.max(0, (shapesFadeoutSeconds - 2) * 1000); |
|
|
const fadeEndTime = shapesFadeoutSeconds * 1000; |
|
|
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(); |
|
|
|
|
|
encodeToolStateToUrl(); |
|
|
} |
|
|
} |
|
|
|
|
|
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(), |
|
|
fadeoutTime: getFadeoutTime(), |
|
|
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(), |
|
|
fadeoutTime: getFadeoutTime(), |
|
|
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(), |
|
|
fadeoutTime: getFadeoutTime(), |
|
|
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() { |
|
|
|
|
|
captureInitialCellStates(); |
|
|
|
|
|
updateThemeIcon(); |
|
|
updateUiMenu(); |
|
|
updateUiDebug(); |
|
|
const widgetsEnabled = (document.documentElement.getAttribute('data-widgets') || 'on') === 'on'; |
|
|
if (widgetsEnabled) { |
|
|
initMinimap(); |
|
|
initFileExplorer(); |
|
|
initTools(); |
|
|
initOverlay(); |
|
|
initStatusWidget(); |
|
|
initializeWidgetVisibility(); |
|
|
layoutWidgetsStackedBottomRight(); |
|
|
window.addEventListener('resize', layoutWidgetsStackedBottomRight); |
|
|
} |
|
|
|
|
|
|
|
|
applyLocationFromUrl(); |
|
|
updateStateIndicator(); |
|
|
|
|
|
|
|
|
const url = new URL(window.location.href); |
|
|
const encodedCellStates = url.searchParams.get('cells'); |
|
|
if (encodedCellStates) { |
|
|
console.log('Applying cell states from URL...'); |
|
|
const cellStates = decodeCellStatesFromUrl(encodedCellStates); |
|
|
|
|
|
|
|
|
requestAnimationFrame(() => { |
|
|
applyCellStatesFromUrl(cellStates); |
|
|
|
|
|
|
|
|
if (typeof _isInitializing !== 'undefined') { |
|
|
_isInitializing = false; |
|
|
} |
|
|
}); |
|
|
} else { |
|
|
|
|
|
if (typeof _isInitializing !== 'undefined') { |
|
|
requestAnimationFrame(() => { |
|
|
_isInitializing = false; |
|
|
}); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
document.addEventListener('mousedown', onLineNumberMouseDown); |
|
|
document.addEventListener('mousemove', onDocMouseMove); |
|
|
document.addEventListener('mouseup', onDocMouseUp); |
|
|
|
|
|
|
|
|
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'); |
|
|
} |
|
|
|
|
|
clearSelection(true); |
|
|
} |
|
|
}); |
|
|
}); |
|
|
|
|
|
function updateStateIndicator() { |
|
|
try { |
|
|
const el = document.getElementById('status-widget'); |
|
|
if (!el) return; |
|
|
const tool = document.body.dataset.tool || 'none'; |
|
|
if (tool && tool !== 'none') { |
|
|
el.textContent = `tool: ${tool} — Esc`; |
|
|
return; |
|
|
} |
|
|
if (_selection) { |
|
|
const t = _selection.a === _selection.b ? `L${_selection.a}` : `L${_selection.a}-${_selection.b}`; |
|
|
el.textContent = `selected: ${t} — Esc`; |
|
|
return; |
|
|
} |
|
|
el.textContent = 'ready — Esc'; |
|
|
} catch (_) {} |
|
|
} |
|
|
</script> |
|
|
</head> |
|
|
|
|
|
|
|
|
<body> |
|
|
<div class="controls"> |
|
|
<div class="controls-buttons"> |
|
|
<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="setUiTheme('default')"> |
|
|
<span class="menu-checkbox" id="checkbox-ui-default">☑</span> Theme: default |
|
|
</div> |
|
|
<div class="menu-item" onclick="setUiTheme('none')"> |
|
|
<span class="menu-checkbox" id="checkbox-ui-none">☐</span> Theme: none |
|
|
</div> |
|
|
<div class="menu-item" onclick="setUiTheme('monocolor')"> |
|
|
<span class="menu-checkbox" id="checkbox-ui-monocolor">☐</span> Theme: monocolor |
|
|
</div> |
|
|
<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 class="menu-item" onclick="toggleWidget('status')"> |
|
|
<span class="menu-checkbox" id="checkbox-status">☑</span> Status Indicator |
|
|
</div> |
|
|
</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-6.12.40-64.114.amzn2023.x86_64-x86_64-with-glibc2.36 |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="main-content"> |
|
|
<h1>No Kernels</h1> |
|
|
<p>First, we run the model without any custom kernels to get a reference point.</p> |
|
|
<h2>Forward</h2> |
|
|
<h2>Forward and Backward</h2> |
|
|
<p>Next, we'll attempt to run a forward and backward pass without any custom kernels. This will likely run out of memory since the default implementation is not optimized for memory usage.</p> |
|
|
<div class="cell cell-failed" id="cell-forward_and_backward_no_kernel"> |
|
|
<div class="cell-header"> |
|
|
<span class="collapse-indicators"> |
|
|
<span onclick="toggleCode('forward_and_backward_no_kernel')" style="cursor: pointer;">▼ code</span> |
|
|
<span onclick="toggleOutput('forward_and_backward_no_kernel')" style="cursor: pointer;">▼ output</span> |
|
|
<span id="uv-indicator-forward_and_backward_no_kernel" onclick="toggleUvLogsFromHeader('forward_and_backward_no_kernel')" style="cursor: pointer;">▶ uv-logs</span> |
|
|
</span> | |
|
|
Cell: forward_and_backward_no_kernel | 99.38s | FAILED |
|
|
| <button class="run-btn" onclick="runCell('forward_and_backward_no_kernel')">▶ run</button> |
|
|
<button class="copy-btn" onclick="copyCell('forward_and_backward_no_kernel')">Copy</button> |
|
|
<a href="cells/forward_and_backward_no_kernel.py" target="_blank" class="raw-btn">Raw</a> |
|
|
</div> |
|
|
<div id="code-forward_and_backward_no_kernel" class="cell-code" data-lines="196"> |
|
|
<div class="highlight-with-lines"> |
|
|
<div class="line-numbers" id="lines-forward_and_backward_no_kernel"> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="1" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 1, true);">1</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="2" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 2, true);">2</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="3" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 3, true);">3</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="4" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 4, true);">4</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="5" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 5, true);">5</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="6" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 6, true);">6</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="7" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 7, true);">7</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="8" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 8, true);">8</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="9" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 9, true);">9</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="10" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 10, true);">10</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="11" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 11, true);">11</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="12" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 12, true);">12</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="13" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 13, true);">13</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="14" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 14, true);">14</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="15" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 15, true);">15</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="16" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 16, true);">16</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="17" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 17, true);">17</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="18" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 18, true);">18</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="19" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 19, true);">19</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="20" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 20, true);">20</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="21" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 21, true);">21</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="22" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 22, true);">22</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="23" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 23, true);">23</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="24" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 24, true);">24</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="25" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 25, true);">25</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="26" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 26, true);">26</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="27" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 27, true);">27</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="28" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 28, true);">28</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="29" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 29, true);">29</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="30" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 30, true);">30</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="31" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 31, true);">31</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="32" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 32, true);">32</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="33" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 33, true);">33</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="34" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 34, true);">34</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="35" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 35, true);">35</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="36" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 36, true);">36</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="37" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 37, true);">37</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="38" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 38, true);">38</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="39" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 39, true);">39</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="40" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 40, true);">40</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="41" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 41, true);">41</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="42" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 42, true);">42</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="43" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 43, true);">43</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="44" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 44, true);">44</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="45" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 45, true);">45</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="46" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 46, true);">46</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="47" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 47, true);">47</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="48" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 48, true);">48</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="49" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 49, true);">49</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="50" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 50, true);">50</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="51" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 51, true);">51</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="52" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 52, true);">52</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="53" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 53, true);">53</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="54" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 54, true);">54</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="55" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 55, true);">55</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="56" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 56, true);">56</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="57" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 57, true);">57</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="58" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 58, true);">58</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="59" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 59, true);">59</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="60" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 60, true);">60</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="61" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 61, true);">61</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="62" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 62, true);">62</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="63" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 63, true);">63</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="64" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 64, true);">64</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="65" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 65, true);">65</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="66" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 66, true);">66</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="67" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 67, true);">67</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="68" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 68, true);">68</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="69" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 69, true);">69</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="70" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 70, true);">70</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="71" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 71, true);">71</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="72" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 72, true);">72</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="73" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 73, true);">73</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="74" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 74, true);">74</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="75" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 75, true);">75</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="76" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 76, true);">76</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="77" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 77, true);">77</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="78" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 78, true);">78</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="79" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 79, true);">79</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="80" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 80, true);">80</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="81" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 81, true);">81</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="82" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 82, true);">82</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="83" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 83, true);">83</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="84" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 84, true);">84</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="85" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 85, true);">85</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="86" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 86, true);">86</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="87" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 87, true);">87</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="88" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 88, true);">88</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="89" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 89, true);">89</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="90" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 90, true);">90</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="91" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 91, true);">91</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="92" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 92, true);">92</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="93" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 93, true);">93</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="94" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 94, true);">94</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="95" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 95, true);">95</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="96" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 96, true);">96</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="97" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 97, true);">97</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="98" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 98, true);">98</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="99" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 99, true);">99</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="100" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 100, true);">100</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="101" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 101, true);">101</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="102" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 102, true);">102</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="103" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 103, true);">103</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="104" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 104, true);">104</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="105" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 105, true);">105</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="106" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 106, true);">106</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="107" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 107, true);">107</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="108" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 108, true);">108</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="109" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 109, true);">109</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="110" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 110, true);">110</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="111" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 111, true);">111</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="112" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 112, true);">112</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="113" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 113, true);">113</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="114" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 114, true);">114</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="115" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 115, true);">115</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="116" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 116, true);">116</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="117" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 117, true);">117</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="118" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 118, true);">118</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="119" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 119, true);">119</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="120" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 120, true);">120</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="121" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 121, true);">121</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="122" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 122, true);">122</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="123" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 123, true);">123</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="124" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 124, true);">124</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="125" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 125, true);">125</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="126" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 126, true);">126</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="127" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 127, true);">127</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="128" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 128, true);">128</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="129" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 129, true);">129</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="130" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 130, true);">130</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="131" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 131, true);">131</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="132" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 132, true);">132</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="133" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 133, true);">133</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="134" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 134, true);">134</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="135" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 135, true);">135</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="136" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 136, true);">136</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="137" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 137, true);">137</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="138" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 138, true);">138</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="139" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 139, true);">139</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="140" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 140, true);">140</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="141" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 141, true);">141</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="142" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 142, true);">142</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="143" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 143, true);">143</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="144" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 144, true);">144</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="145" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 145, true);">145</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="146" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 146, true);">146</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="147" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 147, true);">147</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="148" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 148, true);">148</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="149" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 149, true);">149</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="150" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 150, true);">150</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="151" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 151, true);">151</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="152" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 152, true);">152</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="153" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 153, true);">153</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="154" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 154, true);">154</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="155" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 155, true);">155</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="156" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 156, true);">156</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="157" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 157, true);">157</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="158" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 158, true);">158</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="159" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 159, true);">159</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="160" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 160, true);">160</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="161" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 161, true);">161</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="162" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 162, true);">162</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="163" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 163, true);">163</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="164" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 164, true);">164</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="165" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 165, true);">165</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="166" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 166, true);">166</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="167" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 167, true);">167</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="168" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 168, true);">168</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="169" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 169, true);">169</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="170" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 170, true);">170</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="171" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 171, true);">171</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="172" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 172, true);">172</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="173" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 173, true);">173</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="174" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 174, true);">174</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="175" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 175, true);">175</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="176" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 176, true);">176</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="177" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 177, true);">177</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="178" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 178, true);">178</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="179" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 179, true);">179</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="180" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 180, true);">180</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="181" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 181, true);">181</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="182" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 182, true);">182</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="183" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 183, true);">183</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="184" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 184, true);">184</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="185" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 185, true);">185</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="186" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 186, true);">186</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="187" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 187, true);">187</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="188" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 188, true);">188</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="189" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 189, true);">189</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="190" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 190, true);">190</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="191" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 191, true);">191</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="192" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 192, true);">192</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="193" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 193, true);">193</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="194" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 194, true);">194</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="195" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 195, true);">195</a> |
|
|
<a class="line-number" data-cell="forward_and_backward_no_kernel" data-line="196" href="#cell-forward_and_backward_no_kernel" onclick="event.preventDefault(); selectCellLine('forward_and_backward_no_kernel', 196, true);">196</a> |
|
|
</div> |
|
|
<div class="code-wrap"> |
|
|
<div class="highlight"><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="p">,</span> <span class="n">replace_kernel_forward_from_hub</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="kn">import</span><span class="w"> </span><span class="nn">logging</span> |
|
|
<span class="kn">from</span><span class="w"> </span><span class="nn">transformers.models.gpt_oss.modeling_gpt_oss</span><span class="w"> </span><span class="kn">import</span> <span class="n">GptOssRMSNorm</span> |
|
|
|
|
|
<span class="c1"># remove liger kernel for testing </span> |
|
|
<span class="n">replace_kernel_forward_from_hub</span><span class="p">(</span><span class="n">GptOssRMSNorm</span><span class="p">,</span> <span class="kc">None</span><span class="p">)</span> |
|
|
|
|
|
<span class="c1"># set to debug logging</span> |
|
|
<span class="n">logging</span><span class="o">.</span><span class="n">basicConfig</span><span class="p">(</span><span class="n">level</span><span class="o">=</span><span class="n">logging</span><span class="o">.</span><span class="n">INFO</span><span class="p">)</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="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="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">False</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="n">max_tokens</span> <span class="o">=</span> <span class="mi">128</span> <span class="c1"># Reduced to help with memory usage</span> |
|
|
|
|
|
<span class="c1"># Clear memory before backward pass</span> |
|
|
<span class="n">reset_peak_memory_stats</span><span class="p">()</span> |
|
|
<span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Pre-generation memory: </span><span class="si">{</span><span class="n">get_memory_stats</span><span class="p">()</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span> |
|
|
|
|
|
<span class="c1"># forward and backward pass</span> |
|
|
<span class="k">with</span> <span class="n">torch</span><span class="o">.</span><span class="n">autograd</span><span class="o">.</span><span class="n">set_grad_enabled</span><span class="p">(</span><span class="kc">True</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="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">skip_special_tokens</span><span class="o">=</span><span class="kc">False</span><span class="p">))</span> |
|
|
<span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Generation took </span><span class="si">{</span><span class="n">end_time</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="n">start_time</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="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Post-generation memory: </span><span class="si">{</span><span class="n">get_memory_stats</span><span class="p">()</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span> |
|
|
|
|
|
<span class="c1"># Use gradient checkpointing to reduce memory usage</span> |
|
|
<span class="k">if</span> <span class="nb">hasattr</span><span class="p">(</span><span class="n">model</span><span class="p">,</span> <span class="s1">'gradient_checkpointing_enable'</span><span class="p">):</span> |
|
|
<span class="n">model</span><span class="o">.</span><span class="n">gradient_checkpointing_enable</span><span class="p">()</span> |
|
|
<span class="nb">print</span><span class="p">(</span><span class="s2">"Enabled gradient checkpointing"</span><span class="p">)</span> |
|
|
|
|
|
<span class="c1"># Reduce sequence length if needed for memory</span> |
|
|
<span class="n">max_seq_len</span> <span class="o">=</span> <span class="mi">512</span> <span class="c1"># Limit sequence length for backward pass</span> |
|
|
<span class="k">if</span> <span class="n">generated</span><span class="o">.</span><span class="n">size</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="o">></span> <span class="n">max_seq_len</span><span class="p">:</span> |
|
|
<span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Truncating sequence from </span><span class="si">{</span><span class="n">generated</span><span class="o">.</span><span class="n">size</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span><span class="si">}</span><span class="s2"> to </span><span class="si">{</span><span class="n">max_seq_len</span><span class="si">}</span><span class="s2"> tokens"</span><span class="p">)</span> |
|
|
<span class="n">full_sequence</span> <span class="o">=</span> <span class="n">generated</span><span class="p">[:,</span> <span class="o">-</span><span class="n">max_seq_len</span><span class="p">:]</span> |
|
|
<span class="k">else</span><span class="p">:</span> |
|
|
<span class="n">full_sequence</span> <span class="o">=</span> <span class="n">generated</span> |
|
|
|
|
|
<span class="c1"># Get model outputs for the full sequence</span> |
|
|
<span class="n">model</span><span class="o">.</span><span class="n">train</span><span class="p">()</span> <span class="c1"># Enable dropout and other training behaviors</span> |
|
|
|
|
|
<span class="k">try</span><span class="p">:</span> |
|
|
<span class="n">outputs</span> <span class="o">=</span> <span class="n">model</span><span class="p">(</span> |
|
|
<span class="n">input_ids</span><span class="o">=</span><span class="n">full_sequence</span><span class="p">,</span> |
|
|
<span class="n">labels</span><span class="o">=</span><span class="n">full_sequence</span><span class="p">,</span> <span class="c1"># This will compute loss internally</span> |
|
|
<span class="n">return_dict</span><span class="o">=</span><span class="kc">True</span> |
|
|
<span class="p">)</span> |
|
|
<span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Post-forward memory: </span><span class="si">{</span><span class="n">get_memory_stats</span><span class="p">()</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span> |
|
|
|
|
|
<span class="c1"># If model doesn't compute loss, compute it manually</span> |
|
|
<span class="k">if</span> <span class="n">outputs</span><span class="o">.</span><span class="n">loss</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span> |
|
|
<span class="n">shift_logits</span> <span class="o">=</span> <span class="n">outputs</span><span class="o">.</span><span class="n">logits</span><span class="p">[</span><span class="o">...</span><span class="p">,</span> <span class="p">:</span><span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="p">:]</span><span class="o">.</span><span class="n">contiguous</span><span class="p">()</span> |
|
|
<span class="n">shift_labels</span> <span class="o">=</span> <span class="n">full_sequence</span><span class="p">[</span><span class="o">...</span><span class="p">,</span> <span class="mi">1</span><span class="p">:]</span><span class="o">.</span><span class="n">contiguous</span><span class="p">()</span> |
|
|
|
|
|
<span class="c1"># Use CrossEntropyLoss with ignore_index for padding tokens</span> |
|
|
<span class="n">loss_fct</span> <span class="o">=</span> <span class="n">torch</span><span class="o">.</span><span class="n">nn</span><span class="o">.</span><span class="n">CrossEntropyLoss</span><span class="p">(</span><span class="n">ignore_index</span><span class="o">=</span><span class="n">tokenizer</span><span class="o">.</span><span class="n">pad_token_id</span> <span class="k">if</span> <span class="n">tokenizer</span><span class="o">.</span><span class="n">pad_token_id</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span> <span class="k">else</span> <span class="o">-</span><span class="mi">100</span><span class="p">)</span> |
|
|
<span class="n">loss</span> <span class="o">=</span> <span class="n">loss_fct</span><span class="p">(</span> |
|
|
<span class="n">shift_logits</span><span class="o">.</span><span class="n">view</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="n">shift_logits</span><span class="o">.</span><span class="n">size</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">)),</span> |
|
|
<span class="n">shift_labels</span><span class="o">.</span><span class="n">view</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">)</span> |
|
|
<span class="p">)</span> |
|
|
<span class="k">else</span><span class="p">:</span> |
|
|
<span class="n">loss</span> <span class="o">=</span> <span class="n">outputs</span><span class="o">.</span><span class="n">loss</span> |
|
|
|
|
|
<span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Loss: </span><span class="si">{</span><span class="n">loss</span><span class="o">.</span><span class="n">item</span><span class="p">()</span><span class="si">:</span><span class="s2">.4f</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span> |
|
|
|
|
|
<span class="c1"># Clear intermediate tensors to save memory</span> |
|
|
<span class="k">del</span> <span class="n">outputs</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="c1"># Perform backward pass with memory management</span> |
|
|
<span class="nb">print</span><span class="p">(</span><span class="s2">"Running backward pass..."</span><span class="p">)</span> |
|
|
<span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Pre-backward memory: </span><span class="si">{</span><span class="n">get_memory_stats</span><span class="p">()</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span> |
|
|
|
|
|
<span class="n">loss</span><span class="o">.</span><span class="n">backward</span><span class="p">()</span> |
|
|
<span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Post-backward memory: </span><span class="si">{</span><span class="n">get_memory_stats</span><span class="p">()</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span> |
|
|
|
|
|
<span class="k">except</span> <span class="n">torch</span><span class="o">.</span><span class="n">cuda</span><span class="o">.</span><span class="n">OutOfMemoryError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span> |
|
|
<span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">"OOM during forward/backward pass: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span> |
|
|
<span class="nb">print</span><span class="p">(</span><span class="s2">"Try reducing max_tokens or max_seq_len"</span><span class="p">)</span> |
|
|
<span class="k">raise</span> |
|
|
|
|
|
<span class="c1"># Calculate gradient statistics and print sample gradients</span> |
|
|
<span class="n">total_norm</span> <span class="o">=</span> <span class="mf">0.0</span> |
|
|
<span class="n">param_count</span> <span class="o">=</span> <span class="mi">0</span> |
|
|
<span class="n">grad_samples</span> <span class="o">=</span> <span class="p">{}</span> |
|
|
|
|
|
<span class="k">for</span> <span class="n">name</span><span class="p">,</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">model</span><span class="o">.</span><span class="n">named_parameters</span><span class="p">():</span> |
|
|
<span class="k">if</span> <span class="n">p</span><span class="o">.</span><span class="n">grad</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span> |
|
|
<span class="n">param_count</span> <span class="o">+=</span> <span class="mi">1</span> |
|
|
<span class="n">grad_norm</span> <span class="o">=</span> <span class="n">p</span><span class="o">.</span><span class="n">grad</span><span class="o">.</span><span class="n">data</span><span class="o">.</span><span class="n">norm</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span><span class="o">.</span><span class="n">item</span><span class="p">()</span> |
|
|
<span class="n">total_norm</span> <span class="o">+=</span> <span class="n">grad_norm</span> <span class="o">**</span> <span class="mi">2</span> |
|
|
|
|
|
<span class="c1"># Collect gradient statistics for key layers</span> |
|
|
<span class="k">if</span> <span class="nb">any</span><span class="p">(</span><span class="n">key</span> <span class="ow">in</span> <span class="n">name</span> <span class="k">for</span> <span class="n">key</span> <span class="ow">in</span> <span class="p">[</span><span class="s1">'embed'</span><span class="p">,</span> <span class="s1">'lm_head'</span><span class="p">,</span> <span class="s1">'mlp.up'</span><span class="p">,</span> <span class="s1">'mlp.down'</span><span class="p">,</span> <span class="s1">'self_attn.q_proj'</span><span class="p">,</span> <span class="s1">'norm'</span><span class="p">]):</span> |
|
|
<span class="n">grad_samples</span><span class="p">[</span><span class="n">name</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span> |
|
|
<span class="s1">'norm'</span><span class="p">:</span> <span class="n">grad_norm</span><span class="p">,</span> |
|
|
<span class="s1">'mean'</span><span class="p">:</span> <span class="n">p</span><span class="o">.</span><span class="n">grad</span><span class="o">.</span><span class="n">data</span><span class="o">.</span><span class="n">mean</span><span class="p">()</span><span class="o">.</span><span class="n">item</span><span class="p">(),</span> |
|
|
<span class="s1">'std'</span><span class="p">:</span> <span class="n">p</span><span class="o">.</span><span class="n">grad</span><span class="o">.</span><span class="n">data</span><span class="o">.</span><span class="n">std</span><span class="p">()</span><span class="o">.</span><span class="n">item</span><span class="p">(),</span> |
|
|
<span class="s1">'max'</span><span class="p">:</span> <span class="n">p</span><span class="o">.</span><span class="n">grad</span><span class="o">.</span><span class="n">data</span><span class="o">.</span><span class="n">max</span><span class="p">()</span><span class="o">.</span><span class="n">item</span><span class="p">(),</span> |
|
|
<span class="s1">'min'</span><span class="p">:</span> <span class="n">p</span><span class="o">.</span><span class="n">grad</span><span class="o">.</span><span class="n">data</span><span class="o">.</span><span class="n">min</span><span class="p">()</span><span class="o">.</span><span class="n">item</span><span class="p">(),</span> |
|
|
<span class="p">}</span> |
|
|
|
|
|
<span class="n">total_norm</span> <span class="o">=</span> <span class="n">total_norm</span> <span class="o">**</span> <span class="mf">0.5</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">Gradient norm: </span><span class="si">{</span><span class="n">total_norm</span><span class="si">:</span><span class="s2">.4f</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">"Parameters with gradients: </span><span class="si">{</span><span class="n">param_count</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span> |
|
|
|
|
|
<span class="c1"># Print sample gradients from important layers</span> |
|
|
<span class="nb">print</span><span class="p">(</span><span class="s2">"</span><span class="se">\n</span><span class="s2">Sample gradient statistics:"</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">name</span><span class="p">,</span> <span class="n">stats</span><span class="p">)</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="nb">list</span><span class="p">(</span><span class="n">grad_samples</span><span class="o">.</span><span class="n">items</span><span class="p">())[:</span><span class="mi">10</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="si">{</span><span class="n">name</span><span class="p">[:</span><span class="mi">60</span><span class="p">]</span><span class="si">:</span><span class="s2"><60</span><span class="si">}</span><span class="s2"> | norm: </span><span class="si">{</span><span class="n">stats</span><span class="p">[</span><span class="s1">'norm'</span><span class="p">]</span><span class="si">:</span><span class="s2">.4e</span><span class="si">}</span><span class="s2"> | mean: </span><span class="si">{</span><span class="n">stats</span><span class="p">[</span><span class="s1">'mean'</span><span class="p">]</span><span class="si">:</span><span class="s2">.4e</span><span class="si">}</span><span class="s2"> | std: </span><span class="si">{</span><span class="n">stats</span><span class="p">[</span><span class="s1">'std'</span><span class="p">]</span><span class="si">:</span><span class="s2">.4e</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span> |
|
|
|
|
|
<span class="c1"># Optional: zero gradients for next iteration</span> |
|
|
<span class="n">model</span><span class="o">.</span><span class="n">zero_grad</span><span class="p">()</span> |
|
|
<span class="n">model</span><span class="o">.</span><span class="n">eval</span><span class="p">()</span> <span class="c1"># Switch back to eval mode</span> |
|
|
</pre></div> |
|
|
|
|
|
<div class="code-line-highlight" id="line-highlight-forward_and_backward_no_kernel"></div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
<div id="output-forward_and_backward_no_kernel" class="cell-output"> |
|
|
<div class="cell-stdout">Pre-generation memory: {'allocated_gb': 9.390148608, 'peak_gb': 9.390148608, 'reserved_gb': 17.177772032} |
|
|
<|start|>system<|message|>You are ChatGPT, a large language model trained by OpenAI. |
|
|
Knowledge cutoff: 2024-06 |
|
|
Current date: 2025-09-24 |
|
|
|
|
|
Reasoning: low |
|
|
|
|
|
# Valid channels: analysis, commentary, final. Channel must be included for every message.<|end|><|start|>developer<|message|># Instructions |
|
|
|
|
|
What is Tensor Parallelism? |
|
|
|
|
|
<|end|><|start|>assistant<|channel|>analysis<|message|>Need to explain tensor parallelism.<|end|><|start|>assistant<|channel|>final<|message|>**Tensor Parallelism** is a technique used to distribute the computation of a single tensor (or a set of tensors) across multiple devices (GPUs, TPUs, or other accelerators) in order to speed up large‑scale deep‑learning training and inference. It is one of several parallelism strategies—alongside data parallelism, pipeline parallelism, and model parallelism—designed to scale models that exceed the memory or compute capacity of a single device. |
|
|
|
|
|
--- |
|
|
|
|
|
## 1. Why Tensor Parallelism? |
|
|
|
|
|
- **Memory constraints**: Modern |
|
|
Generation took 13.15 seconds |
|
|
Post-generation memory: {'allocated_gb': 9.398670336, 'peak_gb': 9.514059776, 'reserved_gb': 17.188257792} |
|
|
Enabled gradient checkpointing |
|
|
Post-forward memory: {'allocated_gb': 9.487933952, 'peak_gb': 9.514059776, 'reserved_gb': 17.188257792} |
|
|
Loss: 1.9761 |
|
|
Running backward pass... |
|
|
Pre-backward memory: {'allocated_gb': 9.405890048, 'peak_gb': 9.514059776, 'reserved_gb': 17.177772032} |
|
|
OOM during forward/backward pass: CUDA out of memory. Tried to allocate 508.00 MiB. GPU 2 has a total capacity of 22.30 GiB of which 118.69 MiB is free. Process 25557 has 22.18 GiB memory in use. Of the allocated memory 21.52 GiB is allocated by PyTorch, and 357.89 MiB is reserved by PyTorch but unallocated. If reserved but unallocated memory is large try setting PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True to avoid fragmentation. See documentation for Memory Management (https://pytorch.org/docs/stable/notes/cuda.html#environment-variables) |
|
|
Try reducing max_tokens or max_seq_len |
|
|
</div> |
|
|
<div class="uv-install-logs" id="uv-logs-forward_and_backward_no_kernel"> |
|
|
<div class="uv-logs-header" onclick="toggleUvLogs(this)">▶ UV Install Logs</div> |
|
|
<div class="uv-logs-content" style="display: none;"> |
|
|
Downloading cpython-3.13.7-linux-x86_64-gnu (download) (32.0MiB) |
|
|
Downloading cpython-3.13.7-linux-x86_64-gnu (download) |
|
|
Updating https://github.com/huggingface/transformers.git (HEAD) |
|
|
Updated https://github.com/huggingface/transformers.git (7258ea44bc0c0a425a468f66f8559d1de8c4126d) |
|
|
Downloading nvidia-cuda-cupti-cu12 (9.8MiB) |
|
|
Downloading networkx (1.9MiB) |
|
|
Downloading jedi (1.5MiB) |
|
|
Building transformers @ git+https://github.com/huggingface/transformers.git@7258ea44bc0c0a425a468f66f8559d1de8c4126d |
|
|
Downloading kiwisolver (1.4MiB) |
|
|
Downloading nvidia-cuda-nvrtc-cu12 (84.0MiB) |
|
|
Downloading nvidia-nccl-cu12 (307.4MiB) |
|
|
Downloading nvidia-cublas-cu12 (566.8MiB) |
|
|
Downloading nvidia-cudnn-cu12 (674.0MiB) |
|
|
Downloading nvidia-cufft-cu12 (184.2MiB) |
|
|
Downloading nvidia-curand-cu12 (60.7MiB) |
|
|
Downloading nvidia-cusparse-cu12 (274.9MiB) |
|
|
Downloading hf-xet (3.0MiB) |
|
|
Downloading triton (148.4MiB) |
|
|
Downloading nvidia-cufile-cu12 (1.1MiB) |
|
|
Downloading nvidia-nvjitlink-cu12 (37.4MiB) |
|
|
Downloading tokenizers (3.1MiB) |
|
|
Downloading matplotlib (8.3MiB) |
|
|
Downloading sympy (6.0MiB) |
|
|
Downloading pillow (6.3MiB) |
|
|
Downloading nvidia-cusparselt-cu12 (273.9MiB) |
|
|
Downloading pygments (1.2MiB) |
|
|
Downloading nvidia-cusolver-cu12 (255.1MiB) |
|
|
Downloading numpy (15.9MiB) |
|
|
Downloading torch (846.8MiB) |
|
|
Downloading fonttools (4.7MiB) |
|
|
Downloading nvidia-cufile-cu12 |
|
|
Downloading kiwisolver |
|
|
Downloading pygments |
|
|
Downloading tokenizers |
|
|
Downloading hf-xet |
|
|
Downloading networkx |
|
|
Downloading fonttools |
|
|
Downloading pillow |
|
|
Downloading matplotlib |
|
|
Downloading nvidia-cuda-cupti-cu12 |
|
|
Downloading numpy |
|
|
Downloading sympy |
|
|
Built transformers @ git+https://github.com/huggingface/transformers.git@7258ea44bc0c0a425a468f66f8559d1de8c4126d |
|
|
Downloading nvidia-nvjitlink-cu12 |
|
|
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 579ms |
|
|
</div> |
|
|
</div> |
|
|
<div class="cell-stderr">Fetching 3 files: 0%| | 0/3 [00:00<?, ?it/s] |
|
|
Fetching 3 files: 33%|███▎ | 1/3 [00:07<00:15, 7.84s/it] |
|
|
Fetching 3 files: 67%|██████▋ | 2/3 [00:08<00:03, 3.40s/it] |
|
|
Fetching 3 files: 100%|██████████| 3/3 [00:08<00:00, 2.71s/it] |
|
|
|
|
|
Loading checkpoint shards: 0%| | 0/3 [00:00<?, ?it/s] |
|
|
Loading checkpoint shards: 33%|███▎ | 1/3 [00:02<00:04, 2.34s/it] |
|
|
Loading checkpoint shards: 67%|██████▋ | 2/3 [00:04<00:02, 2.25s/it] |
|
|
Loading checkpoint shards: 100%|██████████| 3/3 [00:05<00:00, 1.80s/it] |
|
|
Loading checkpoint shards: 100%|██████████| 3/3 [00:05<00:00, 1.93s/it] |
|
|
`use_cache=True` is incompatible with gradient checkpointing. Setting `use_cache=False`. |
|
|
Traceback (most recent call last): |
|
|
File "/repo/moe_benchmarks/megablocks/.uvnote/cells/forward_and_backward_no_kernel.py", line 154, in <module> |
|
|
loss.backward() |
|
|
~~~~~~~~~~~~~^^ |
|
|
File "/tmp/uvnote-run-yr7p57do/home/.cache/uv/environments-v2/forward-and-backward-no-kernel-349948fac2e1b63b/lib/python3.13/site-packages/torch/_tensor.py", line 647, in backward |
|
|
torch.autograd.backward( |
|
|
~~~~~~~~~~~~~~~~~~~~~~~^ |
|
|
self, gradient, retain_graph, create_graph, inputs=inputs |
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ |
|
|
) |
|
|
^ |
|
|
File "/tmp/uvnote-run-yr7p57do/home/.cache/uv/environments-v2/forward-and-backward-no-kernel-349948fac2e1b63b/lib/python3.13/site-packages/torch/autograd/__init__.py", line 354, in backward |
|
|
_engine_run_backward( |
|
|
~~~~~~~~~~~~~~~~~~~~^ |
|
|
tensors, |
|
|
^^^^^^^^ |
|
|
...<5 lines>... |
|
|
accumulate_grad=True, |
|
|
^^^^^^^^^^^^^^^^^^^^^ |
|
|
) |
|
|
^ |
|
|
File "/tmp/uvnote-run-yr7p57do/home/.cache/uv/environments-v2/forward-and-backward-no-kernel-349948fac2e1b63b/lib/python3.13/site-packages/torch/autograd/graph.py", line 829, in _engine_run_backward |
|
|
return Variable._execution_engine.run_backward( # Calls into the C++ engine to run the backward pass |
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ |
|
|
t_outputs, *args, **kwargs |
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^ |
|
|
) # Calls into the C++ engine to run the backward pass |
|
|
^ |
|
|
File "/tmp/uvnote-run-yr7p57do/home/.cache/uv/environments-v2/forward-and-backward-no-kernel-349948fac2e1b63b/lib/python3.13/site-packages/torch/autograd/function.py", line 311, in apply |
|
|
return user_fn(self, *args) |
|
|
File "/tmp/uvnote-run-yr7p57do/home/.cache/uv/environments-v2/forward-and-backward-no-kernel-349948fac2e1b63b/lib/python3.13/site-packages/torch/utils/checkpoint.py", line 319, in backward |
|
|
torch.autograd.backward(outputs_with_grad, args_with_grad) |
|
|
~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ |
|
|
File "/tmp/uvnote-run-yr7p57do/home/.cache/uv/environments-v2/forward-and-backward-no-kernel-349948fac2e1b63b/lib/python3.13/site-packages/torch/autograd/__init__.py", line 354, in backward |
|
|
_engine_run_backward( |
|
|
~~~~~~~~~~~~~~~~~~~~^ |
|
|
tensors, |
|
|
^^^^^^^^ |
|
|
...<5 lines>... |
|
|
accumulate_grad=True, |
|
|
^^^^^^^^^^^^^^^^^^^^^ |
|
|
) |
|
|
^ |
|
|
File "/tmp/uvnote-run-yr7p57do/home/.cache/uv/environments-v2/forward-and-backward-no-kernel-349948fac2e1b63b/lib/python3.13/site-packages/torch/autograd/graph.py", line 829, in _engine_run_backward |
|
|
return Variable._execution_engine.run_backward( # Calls into the C++ engine to run the backward pass |
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ |
|
|
t_outputs, *args, **kwargs |
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^ |
|
|
) # Calls into the C++ engine to run the backward pass |
|
|
^ |
|
|
torch.OutOfMemoryError: CUDA out of memory. Tried to allocate 508.00 MiB. GPU 2 has a total capacity of 22.30 GiB of which 118.69 MiB is free. Process 25557 has 22.18 GiB memory in use. Of the allocated memory 21.52 GiB is allocated by PyTorch, and 357.89 MiB is reserved by PyTorch but unallocated. If reserved but unallocated memory is large try setting PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True to avoid fragmentation. See documentation for Memory Management (https://pytorch.org/docs/stable/notes/cuda.html#environment-variables)</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<h1>Kernels</h1> |
|
|
<p>Next we can run with Megablocks kernels enabled.</p> |
|
|
<h3>Forward</h3> |
|
|
<p>First, we run a forward pass with Megablocks kernels.</p> |
|
|
<h2>Forward and Backward</h2> |
|
|
<p>Next, we run a forward and backward pass with Megablocks kernels enabled. This should be more memory efficient and allow us to complete the backward pass without running out of memory.</p> |
|
|
</div> |
|
|
|
|
|
</body> |
|
|
</html> |