1628 lines
95 KiB
Plaintext
1628 lines
95 KiB
Plaintext
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Memory Palace | Letta Code</title>
|
|
<style>
|
|
@import url('https://fonts.googleapis.com/css2?family=Manrope:wght@300;400;500;600;700&display=swap');
|
|
@import url('https://fonts.googleapis.com/css2?family=Fira+Code:wght@400;500&display=swap');
|
|
:root {
|
|
--accent: hsl(240, 93%, 35%);
|
|
--accent-light: hsl(240, 67%, 98%);
|
|
--accent-light-border: hsl(240, 62%, 94%);
|
|
--bg: #ffffff;
|
|
--panel: #ffffff;
|
|
--border: hsl(210, 10%, 92.2%);
|
|
--text: hsl(0, 0%, 8%);
|
|
--text-muted: hsl(210, 3%, 28%);
|
|
--text-dim: hsl(210, 3%, 66%);
|
|
--mono: 'Fira Code', Menlo, Courier, monospace;
|
|
--sans: 'Manrope', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Helvetica Neue', sans-serif;
|
|
--serif: 'Iowan Old Style', 'Palatino Linotype', 'Book Antiqua', Georgia, serif;
|
|
--radius: 6px;
|
|
--max-w: 1180px;
|
|
--surface: hsl(0, 0%, 98%);
|
|
--surface-2: hsl(0, 0%, 96%);
|
|
--hover: hsl(0, 0%, 96%);
|
|
--hover-accent: hsl(240, 67%, 98%);
|
|
--diff-add-bg: #dafbe1;
|
|
--diff-del-bg: #ffebe9;
|
|
--diff-hunk-bg: #ddf4ff;
|
|
}
|
|
|
|
html.dark {
|
|
--accent: hsl(240, 80%, 68%);
|
|
--accent-light: hsl(240, 20%, 18%);
|
|
--accent-light-border: hsl(240, 15%, 25%);
|
|
--bg: hsl(0, 0%, 11%);
|
|
--panel: hsl(0, 0%, 13%);
|
|
--surface: hsl(0, 0%, 15%);
|
|
--surface-2: hsl(0, 0%, 18%);
|
|
--hover: hsl(0, 0%, 18%);
|
|
--hover-accent: hsl(240, 15%, 20%);
|
|
--border: hsl(210, 3%, 20%);
|
|
--text: hsl(210, 7%, 84%);
|
|
--text-muted: hsl(210, 3%, 60%);
|
|
--text-dim: hsl(210, 3%, 42%);
|
|
--diff-add-bg: hsl(140, 30%, 14%);
|
|
--diff-del-bg: hsl(0, 40%, 16%);
|
|
--diff-hunk-bg: hsl(210, 30%, 16%);
|
|
}
|
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
body {
|
|
font-family: var(--mono);
|
|
color: var(--text);
|
|
background: var(--bg);
|
|
line-height: 1.55;
|
|
font-weight: 400;
|
|
font-size: 13px;
|
|
}
|
|
a { color: var(--accent); text-decoration: none; }
|
|
a:hover { text-decoration: underline; }
|
|
|
|
.app-shell {
|
|
max-width: var(--max-w);
|
|
margin: 22px auto;
|
|
border: 1px solid var(--border);
|
|
border-radius: 8px;
|
|
overflow: hidden;
|
|
background: var(--panel);
|
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04), 0 6px 16px rgba(0, 0, 0, 0.04);
|
|
}
|
|
|
|
.header {
|
|
padding: 14px 20px;
|
|
border-bottom: 1px solid var(--border);
|
|
background: var(--panel);
|
|
display: flex;
|
|
gap: 12px;
|
|
align-items: center;
|
|
}
|
|
.header h1 {
|
|
display: none;
|
|
}
|
|
.header-brand {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 3px;
|
|
}
|
|
.header-brand svg {
|
|
height: 26px;
|
|
width: auto;
|
|
fill: var(--text);
|
|
}
|
|
|
|
.header .agent-meta {
|
|
margin-left: auto;
|
|
text-align: right;
|
|
}
|
|
.header .agent-name {
|
|
font-family: var(--sans);
|
|
font-size: 13px;
|
|
font-weight: 500;
|
|
color: var(--text-muted);
|
|
}
|
|
.header .agent-id {
|
|
font-family: var(--mono);
|
|
font-size: 11px;
|
|
color: var(--text-dim);
|
|
}
|
|
|
|
.tabs {
|
|
display: flex;
|
|
border-bottom: 1px solid var(--border);
|
|
background: var(--surface);
|
|
}
|
|
.tab {
|
|
padding: 10px 16px;
|
|
cursor: pointer;
|
|
font-size: 10px;
|
|
font-weight: 400;
|
|
letter-spacing: 1.5px;
|
|
text-transform: uppercase;
|
|
color: var(--text-muted);
|
|
user-select: none;
|
|
}
|
|
.tab:hover { color: var(--text); }
|
|
.tab.active {
|
|
color: var(--accent);
|
|
background: var(--panel);
|
|
box-shadow: inset 0 -2px 0 var(--accent);
|
|
}
|
|
|
|
.main {
|
|
padding: 16px;
|
|
}
|
|
|
|
/* Files view */
|
|
.files-layout {
|
|
display: grid;
|
|
grid-template-columns: 320px 1fr;
|
|
gap: 16px;
|
|
height: 74vh;
|
|
}
|
|
.panel {
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius);
|
|
overflow: hidden;
|
|
background: var(--panel);
|
|
}
|
|
.file-chars {
|
|
color: var(--text-dim);
|
|
font-size: 11px;
|
|
opacity: 0.6;
|
|
}
|
|
.tree-desc {
|
|
padding: 8px 12px;
|
|
font-size: 10px;
|
|
letter-spacing: 1px;
|
|
text-transform: uppercase;
|
|
color: var(--text-dim);
|
|
border-bottom: 1px solid var(--border);
|
|
position: sticky;
|
|
top: 0;
|
|
background: var(--surface-2);
|
|
z-index: 1;
|
|
}
|
|
.tree-panel {
|
|
overflow: auto;
|
|
}
|
|
.file-panel {
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
.tree-item {
|
|
padding: 4px 10px;
|
|
font-size: 13px;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
white-space: nowrap;
|
|
}
|
|
.tree-item.dir {
|
|
padding-left: 10px;
|
|
color: var(--text-muted);
|
|
font-weight: 400;
|
|
cursor: pointer;
|
|
user-select: none;
|
|
border-left: 2px solid transparent;
|
|
margin-top: 2px;
|
|
}
|
|
.tree-item.dir:not(.collapsed) { border-left-color: var(--accent); }
|
|
.tree-item.dir:hover { background: var(--surface-2); }
|
|
.tree-item.dir.collapsed ~ .tree-children { display: none; }
|
|
.tree-children { padding-left: 12px; }
|
|
.tree-children.hidden { display: none; }
|
|
.tree-item.file { cursor: pointer; padding-left: 24px; }
|
|
.tree-item.file:hover { background: var(--hover-accent); }
|
|
.tree-item.file.active {
|
|
background: var(--accent-light);
|
|
color: var(--accent);
|
|
font-weight: 600;
|
|
}
|
|
|
|
.diff-bar {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
font-family: var(--mono);
|
|
font-size: 11px;
|
|
}
|
|
.diff-bar .db-plus { color: #1a7f37; }
|
|
.diff-bar .db-minus { color: #cf222e; }
|
|
.diff-bar .db-blocks {
|
|
display: inline-flex;
|
|
gap: 1px;
|
|
}
|
|
.diff-bar .db-block {
|
|
width: 8px;
|
|
height: 8px;
|
|
border-radius: 1px;
|
|
}
|
|
.diff-bar .db-block.add { background: #2da44e; }
|
|
.diff-bar .db-block.del { background: #cf222e; }
|
|
.diff-bar .db-block.neutral { background: #d1d5db; }
|
|
.badge {
|
|
margin-left: 6px;
|
|
font-size: 10px;
|
|
color: var(--accent);
|
|
background: var(--accent-light);
|
|
border: 1px solid var(--accent-light-border);
|
|
border-radius: 999px;
|
|
padding: 0 6px;
|
|
}
|
|
.dir-count {
|
|
margin-left: auto;
|
|
font-size: 11px;
|
|
color: var(--text-dim);
|
|
font-weight: 400;
|
|
padding-right: 4px;
|
|
}
|
|
|
|
.file-last-commit {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
padding: 8px 14px;
|
|
background: var(--surface);
|
|
border-bottom: 1px solid var(--border);
|
|
font-size: 12px;
|
|
cursor: pointer;
|
|
}
|
|
.file-last-commit:hover { background: var(--hover-accent); }
|
|
.file-last-commit .lc-hash {
|
|
font-family: var(--mono);
|
|
font-size: 11px;
|
|
color: var(--accent);
|
|
background: var(--accent-light);
|
|
border-radius: 3px;
|
|
padding: 1px 5px;
|
|
flex-shrink: 0;
|
|
}
|
|
.file-last-commit .lc-msg {
|
|
flex: 1;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
color: var(--text);
|
|
}
|
|
.file-last-commit .lc-meta {
|
|
color: var(--text-dim);
|
|
white-space: nowrap;
|
|
flex-shrink: 0;
|
|
}
|
|
.file-meta {
|
|
border-bottom: 1px solid var(--border);
|
|
background: var(--surface);
|
|
padding: 10px 14px;
|
|
}
|
|
.file-path {
|
|
font-family: var(--mono);
|
|
font-size: 12px;
|
|
color: var(--text-muted);
|
|
margin-bottom: 6px;
|
|
}
|
|
.meta-row {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 10px;
|
|
}
|
|
.meta-pill {
|
|
font-size: 12px;
|
|
color: var(--text-muted);
|
|
background: var(--panel);
|
|
border: 1px solid var(--border);
|
|
border-radius: 999px;
|
|
padding: 2px 8px;
|
|
}
|
|
.meta-pill strong { color: var(--text); }
|
|
|
|
.raw-toggle {
|
|
position: absolute;
|
|
top: 8px;
|
|
right: 10px;
|
|
font-size: 11px;
|
|
padding: 3px 8px;
|
|
border: 1px solid var(--border);
|
|
border-radius: 4px;
|
|
background: var(--panel);
|
|
color: var(--text-muted);
|
|
cursor: pointer;
|
|
z-index: 2;
|
|
}
|
|
.raw-toggle:hover { background: var(--accent-light); color: var(--accent); }
|
|
.raw-toggle.active { background: var(--accent); color: #fff; border-color: var(--accent); }
|
|
.file-last-commit {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
padding: 8px 14px;
|
|
background: var(--surface);
|
|
border-bottom: 1px solid var(--border);
|
|
font-size: 12px;
|
|
cursor: pointer;
|
|
}
|
|
.file-last-commit:hover { background: var(--hover-accent); }
|
|
.file-last-commit .lc-hash {
|
|
font-family: var(--mono);
|
|
font-size: 11px;
|
|
color: var(--accent);
|
|
background: var(--accent-light);
|
|
border-radius: 3px;
|
|
padding: 1px 5px;
|
|
flex-shrink: 0;
|
|
}
|
|
.file-last-commit .lc-msg {
|
|
flex: 1;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
color: var(--text);
|
|
}
|
|
.file-last-commit .lc-meta {
|
|
color: var(--text-dim);
|
|
white-space: nowrap;
|
|
flex-shrink: 0;
|
|
}
|
|
.file-meta { position: relative; }
|
|
.file-body-raw {
|
|
padding: 20px 28px;
|
|
flex: 1;
|
|
overflow: auto;
|
|
min-height: 0;
|
|
font-family: var(--mono);
|
|
font-size: 13px;
|
|
white-space: pre-wrap;
|
|
word-break: break-word;
|
|
line-height: 1.6;
|
|
color: var(--text);
|
|
display: none;
|
|
}
|
|
.file-body {
|
|
padding: 20px 28px;
|
|
flex: 1;
|
|
overflow: auto;
|
|
min-height: 0;
|
|
font-family: var(--serif);
|
|
font-size: 16px;
|
|
}
|
|
.file-body h1, .file-body h2, .file-body h3 {
|
|
margin-top: 18px;
|
|
margin-bottom: 8px;
|
|
}
|
|
.file-body h1 { font-size: 30px; }
|
|
.file-body h2 { font-size: 23px; border-bottom: 1px solid var(--border); padding-bottom: 3px; }
|
|
.file-body h3 { font-size: 18px; }
|
|
.file-body p, .file-body ul, .file-body ol, .file-body pre, .file-body table, .file-body blockquote {
|
|
margin-bottom: 12px;
|
|
}
|
|
.file-body ul, .file-body ol {
|
|
padding-left: 28px;
|
|
}
|
|
.file-body code {
|
|
font-family: var(--mono);
|
|
background: var(--surface-2);
|
|
border: 1px solid var(--border);
|
|
border-radius: 4px;
|
|
padding: 1px 5px;
|
|
font-size: 13px;
|
|
}
|
|
.file-body pre {
|
|
background: var(--surface-2);
|
|
border: 1px solid var(--border);
|
|
border-radius: 8px;
|
|
padding: 12px;
|
|
overflow: auto;
|
|
}
|
|
.file-body pre code {
|
|
border: none;
|
|
background: transparent;
|
|
padding: 0;
|
|
}
|
|
.file-body table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
}
|
|
.file-body th, .file-body td {
|
|
border: 1px solid #ddd;
|
|
padding: 6px 10px;
|
|
}
|
|
.file-body th { background: var(--surface); text-align: left; }
|
|
.file-body blockquote {
|
|
border-left: 3px solid var(--accent);
|
|
padding-left: 10px;
|
|
color: var(--text-muted);
|
|
}
|
|
|
|
/* History view */
|
|
.commit-list { list-style: none; }
|
|
.commit-item {
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius);
|
|
margin-bottom: 10px;
|
|
overflow: hidden;
|
|
background: var(--panel);
|
|
}
|
|
.commit-head {
|
|
padding: 11px 13px;
|
|
cursor: pointer;
|
|
display: grid;
|
|
grid-template-columns: 110px 1fr auto;
|
|
align-items: start;
|
|
column-gap: 10px;
|
|
row-gap: 0;
|
|
}
|
|
.commit-head:hover { background: var(--hover-accent); }
|
|
.commit-hash {
|
|
font-family: var(--mono);
|
|
font-size: 11px;
|
|
color: var(--accent);
|
|
background: var(--accent-light);
|
|
border-radius: 4px;
|
|
padding: 2px 6px;
|
|
}
|
|
.commit-center {
|
|
display: flex;
|
|
flex-direction: column;
|
|
min-width: 0;
|
|
}
|
|
.commit-title {
|
|
font-size: 13px;
|
|
font-weight: 400;
|
|
word-break: break-word;
|
|
}
|
|
.commit-meta {
|
|
text-align: right;
|
|
font-size: 11px;
|
|
color: var(--text-dim);
|
|
}
|
|
.reflection {
|
|
margin-left: 6px;
|
|
font-size: 10px;
|
|
background: #fef1d4;
|
|
color: #9a5d07;
|
|
border-radius: 999px;
|
|
padding: 1px 6px;
|
|
}
|
|
.commit-preview {
|
|
font-size: 12px;
|
|
color: var(--text-muted);
|
|
line-height: 1.4;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
.commit-item.expanded .commit-preview { display: none; }
|
|
.commit-full-body {
|
|
padding: 10px 14px;
|
|
background: var(--surface);
|
|
border-bottom: 1px solid var(--border);
|
|
font-size: 13px;
|
|
line-height: 1.6;
|
|
white-space: pre-wrap;
|
|
word-break: break-word;
|
|
color: var(--text);
|
|
}
|
|
.commit-body {
|
|
display: none;
|
|
border-top: 1px solid var(--border);
|
|
}
|
|
.commit-item.expanded .commit-body { display: block; }
|
|
.commit-stats {
|
|
padding: 10px 14px;
|
|
background: var(--surface);
|
|
border-bottom: 1px solid var(--border);
|
|
font-family: var(--mono);
|
|
font-size: 12px;
|
|
line-height: 1.7;
|
|
}
|
|
.stat-row {
|
|
display: flex;
|
|
gap: 8px;
|
|
align-items: center;
|
|
}
|
|
.stat-file {
|
|
color: var(--text);
|
|
cursor: pointer;
|
|
}
|
|
.stat-file:hover { color: var(--accent); text-decoration: underline; }
|
|
.stat-bar { display: inline-flex; gap: 1px; }
|
|
.stat-plus { color: #1a7f37; }
|
|
.stat-minus { color: #cf222e; }
|
|
.stat-summary {
|
|
margin-bottom: 6px;
|
|
padding-bottom: 6px;
|
|
border-bottom: 1px solid var(--border);
|
|
font-size: 11px;
|
|
color: var(--text-muted);
|
|
}
|
|
.diff-file-header {
|
|
padding: 6px 12px;
|
|
background: var(--surface);
|
|
font-family: var(--mono);
|
|
font-size: 12px;
|
|
border-top: 1px solid var(--border);
|
|
border-bottom: 1px solid var(--border);
|
|
}
|
|
.diff-file-header .file-link {
|
|
cursor: pointer;
|
|
color: var(--text);
|
|
}
|
|
.diff-file-header .file-link:hover {
|
|
color: var(--accent);
|
|
text-decoration: underline;
|
|
}
|
|
.diff-line {
|
|
padding: 0 12px;
|
|
font-family: var(--mono);
|
|
font-size: 12px;
|
|
white-space: pre-wrap;
|
|
word-break: break-all;
|
|
}
|
|
.diff-line.add { background: var(--diff-add-bg); }
|
|
.diff-line.del { background: var(--diff-del-bg); }
|
|
.diff-line.hunk { background: var(--diff-hunk-bg); color: var(--text-muted); }
|
|
.note {
|
|
padding: 8px 12px;
|
|
font-size: 12px;
|
|
color: var(--text-dim);
|
|
|
|
}
|
|
.show-more {
|
|
margin-top: 8px;
|
|
width: 100%;
|
|
border: 1px dashed var(--border);
|
|
background: var(--accent-light);
|
|
color: var(--accent);
|
|
border-radius: 8px;
|
|
padding: 10px;
|
|
cursor: pointer;
|
|
}
|
|
.show-more:hover { background: hsl(240, 62%, 94%); }
|
|
|
|
.empty {
|
|
padding: 24px;
|
|
text-align: center;
|
|
color: var(--text-dim);
|
|
}
|
|
.generated {
|
|
margin-top: 14px;
|
|
border-top: 1px solid var(--border);
|
|
padding-top: 10px;
|
|
font-size: 12px;
|
|
color: var(--text-dim);
|
|
text-align: center;
|
|
}
|
|
|
|
@media (max-width: 900px) {
|
|
.files-layout { grid-template-columns: 1fr; }
|
|
}
|
|
|
|
/* Dark mode overrides */
|
|
html.dark .app-shell { box-shadow: 0 1px 3px rgba(0,0,0,0.2), 0 6px 16px rgba(0,0,0,0.3); }
|
|
html.dark .tab:hover { color: var(--text); }
|
|
html.dark .show-more:hover { background: hsl(240, 15%, 22%); }
|
|
html.dark .diff-line.add { background: var(--diff-add-bg); color: hsl(140, 60%, 70%); }
|
|
html.dark .diff-line.del { background: var(--diff-del-bg); color: hsl(0, 70%, 72%); }
|
|
html.dark .diff-line.hunk { background: var(--diff-hunk-bg); color: hsl(210, 40%, 70%); }
|
|
html.dark .stat-plus { color: hsl(140, 55%, 55%); }
|
|
html.dark .stat-minus { color: hsl(0, 65%, 60%); }
|
|
html.dark .db-block.add { background: hsl(140, 50%, 45%); }
|
|
html.dark .db-block.del { background: hsl(0, 60%, 50%); }
|
|
html.dark .db-block.neutral { background: hsl(210, 3%, 30%); }
|
|
html.dark .badge { background: var(--surface); border-color: var(--border); }
|
|
html.dark code { background: var(--surface); border-color: var(--border); }
|
|
html.dark pre { background: var(--surface-2); border-color: var(--border); }
|
|
html.dark .diff-header { background: var(--surface-2); }
|
|
html.dark .stat-row { background: var(--surface-2); }
|
|
|
|
|
|
html.dark .tabs { background: var(--surface-2); }
|
|
html.dark .file-chars {
|
|
color: var(--text-dim);
|
|
font-size: 11px;
|
|
opacity: 0.6;
|
|
}
|
|
.tree-desc {
|
|
padding: 8px 12px;
|
|
font-size: 10px;
|
|
letter-spacing: 1.5px;
|
|
text-transform: uppercase;
|
|
color: var(--text-dim);
|
|
border-bottom: 1px solid var(--border);
|
|
position: sticky;
|
|
top: 0;
|
|
background: var(--surface-2);
|
|
z-index: 1;
|
|
}
|
|
.tree-panel { background: var(--surface-2); }
|
|
html.dark .file-last-commit { background: var(--surface); }
|
|
html.dark .file-meta { background: var(--surface); }
|
|
html.dark .commit-full-body { background: var(--surface); }
|
|
html.dark .commit-body { border-color: var(--border); }
|
|
html.dark .commit-preview { color: var(--text-dim); }
|
|
html.dark .commit-meta { color: var(--text-dim); }
|
|
html.dark .commit-title { color: var(--text); }
|
|
html.dark .file-body { color: var(--text); }
|
|
html.dark .file-body h1, html.dark .file-body h2, html.dark .file-body h3 { color: var(--text); border-color: var(--border); }
|
|
html.dark .file-body th { background: var(--surface); color: var(--text); }
|
|
html.dark .file-body td { border-color: var(--border); }
|
|
html.dark .file-body th { border-color: var(--border); }
|
|
html.dark .file-body table { border-color: var(--border); }
|
|
html.dark .file-body a { color: var(--accent); }
|
|
html.dark .file-body-raw { background: var(--surface-2); color: var(--text); }
|
|
html.dark .tree-item { color: var(--text); }
|
|
html.dark .tree-item.active { background: var(--accent-light); }
|
|
html.dark .tree-badge-system { background: var(--accent-light); color: var(--accent); }
|
|
html.dark .reflection { background: hsl(280, 20%, 18%); color: hsl(280, 60%, 72%); }
|
|
html.dark .footer { color: var(--text-dim); }
|
|
html.dark .empty { color: var(--text-dim); }
|
|
html.dark .history-note { color: var(--text-dim); }
|
|
html.dark .file-path { color: var(--text); }
|
|
html.dark .agent-name { color: var(--text-muted); }
|
|
html.dark .agent-id { color: var(--text-dim); }
|
|
html.dark h1 { color: var(--text); }
|
|
html.dark a { color: var(--accent); }
|
|
html.dark .warning-badge { background: hsl(42, 30%, 18%); color: hsl(42, 80%, 70%); }
|
|
|
|
/* Context tab */
|
|
.context-layout {
|
|
display: flex;
|
|
gap: 0;
|
|
min-height: 400px;
|
|
}
|
|
.context-grid-area {
|
|
flex: 1;
|
|
padding: 20px;
|
|
display: flex;
|
|
align-items: flex-start;
|
|
justify-content: center;
|
|
}
|
|
.context-grid {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 4px;
|
|
max-width: 700px;
|
|
padding: 8px;
|
|
}
|
|
.context-grid .block {
|
|
width: 18px;
|
|
height: 18px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 14px;
|
|
line-height: 1;
|
|
opacity: 0;
|
|
animation: blockIn 0.3s ease forwards;
|
|
cursor: default;
|
|
user-select: none;
|
|
}
|
|
.context-grid .block.alive {
|
|
animation: blockIn 0.3s ease forwards, idlePulse 3s ease-in-out infinite;
|
|
animation-delay: var(--enter-delay, 0ms), var(--pulse-delay, 0s);
|
|
}
|
|
.context-grid .block.dim {
|
|
opacity: 0.15 !important;
|
|
transition: opacity 0.12s;
|
|
}
|
|
.context-grid .block.glow {
|
|
filter: brightness(1.8) drop-shadow(0 0 3px currentColor);
|
|
transform: scale(1.3);
|
|
transition: filter 0.1s, transform 0.1s;
|
|
}
|
|
@keyframes blockIn {
|
|
from { opacity: 0; transform: scale(0); }
|
|
to { opacity: var(--target-opacity, 1); transform: scale(1); }
|
|
}
|
|
@keyframes idlePulse {
|
|
0%, 100% { filter: brightness(1); }
|
|
50% { filter: brightness(0.6); }
|
|
}
|
|
.context-sidebar {
|
|
width: 280px;
|
|
min-width: 280px;
|
|
padding: 20px;
|
|
border-left: 1px solid var(--border);
|
|
background: var(--surface);
|
|
}
|
|
.ctx-model {
|
|
font-size: 14px;
|
|
font-weight: 400;
|
|
color: var(--text);
|
|
margin-bottom: 2px;
|
|
}
|
|
.ctx-model-sub {
|
|
font-size: 11px;
|
|
color: var(--text-dim);
|
|
margin-bottom: 12px;
|
|
}
|
|
.ctx-usage-label {
|
|
font-size: 13px;
|
|
color: var(--text-muted);
|
|
margin-bottom: 6px;
|
|
}
|
|
.ctx-bar-track {
|
|
height: 6px;
|
|
background: var(--surface-2);
|
|
border-radius: 3px;
|
|
overflow: hidden;
|
|
margin-bottom: 4px;
|
|
}
|
|
.ctx-bar-fill {
|
|
height: 100%;
|
|
border-radius: 3px;
|
|
transition: width 1.5s ease;
|
|
}
|
|
.ctx-bar-text {
|
|
font-size: 11px;
|
|
color: var(--text-dim);
|
|
margin-bottom: 20px;
|
|
}
|
|
.ctx-legend-title {
|
|
font-size: 10px;
|
|
letter-spacing: 1px;
|
|
text-transform: uppercase;
|
|
color: var(--text-dim);
|
|
margin-bottom: 10px;
|
|
}
|
|
.ctx-legend-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
padding: 4px 0;
|
|
font-size: 12px;
|
|
color: var(--text-muted);
|
|
}
|
|
.ctx-legend-sym {
|
|
font-size: 14px;
|
|
width: 18px;
|
|
text-align: center;
|
|
flex-shrink: 0;
|
|
}
|
|
.ctx-legend-name { flex: 1; }
|
|
.ctx-legend-item { transition: opacity 0.15s; }
|
|
.ctx-legend-item.legend-dim { opacity: 0.25; }
|
|
.ctx-legend-item.legend-glow { opacity: 1; }
|
|
.ctx-legend-item[data-tab-link] { cursor: pointer; }
|
|
.ctx-legend-item[data-tab-link] .ctx-legend-arrow {
|
|
font-size: 10px;
|
|
color: var(--text-dim);
|
|
margin-left: 4px;
|
|
}
|
|
.ctx-legend-value { text-align: right; color: var(--text-dim); }
|
|
.context-legend {
|
|
padding: 0 20px 20px;
|
|
}
|
|
.ctx-unavailable {
|
|
padding: 40px 20px;
|
|
text-align: center;
|
|
color: var(--text-dim);
|
|
font-size: 13px;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="app-shell">
|
|
<header class="header">
|
|
<h1>Memory Palace</h1>
|
|
<div class="header-brand">
|
|
<svg viewBox="0 0 1020 183" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#lc)"><path d="M109.63 73.09H73.09V109.63H109.63V73.09Z"/><path d="M146.18 24.9V0H36.54V24.9C36.54 31.34 31.33 36.55 24.89 36.55H0V146.19H24.9C31.34 146.19 36.55 151.4 36.55 157.84V182.73H146.19V157.84C146.19 151.4 151.4 146.19 157.84 146.19H182.74V36.54H157.84C151.4 36.54 146.19 31.33 146.19 24.89L146.18 24.9ZM146.18 134.53C146.18 140.96 140.97 146.18 134.53 146.18H48.2C41.76 146.18 36.55 140.97 36.55 134.53V48.19C36.55 41.75 41.76 36.54 48.2 36.54H134.54C140.98 36.54 146.19 41.75 146.19 48.19V134.53H146.18Z"/><path d="M290.2 36.54H274.08V146.18H345.7V130.67H290.2V36.54Z"/><path d="M392.16 68.82H386.43C369.2 68.82 351.83 79.38 351.83 102.97V114.44C351.83 135.82 364.2 148.59 384.93 148.59H393.68C411.17 148.59 423.61 138.3 426.16 121.72L426.38 120.26H409.78L409.51 121.16C407.05 129.29 400.63 133.08 389.3 133.08C374.6 133.08 367.66 126.55 367.49 112.53H426.77V102.96C426.77 79.37 409.39 68.81 392.17 68.81L392.16 68.82ZM367.97 98.84C369.61 88.56 375.9 84.33 389.3 84.33C402.7 84.33 408.98 88.56 410.62 98.84H367.97Z"/><path d="M458.92 36.55H442.81V68.83H432.1V84.34H442.81V115.05C442.81 142.14 459.29 146.18 469.12 146.18H479.29V130.67H473.5C463.15 130.67 458.93 125.44 458.93 112.63V84.33H479.29V68.82H458.93V36.54L458.92 36.55Z"/><path d="M513.36 36.55H497.25V68.83H486.54V84.34H497.25V115.05C497.25 142.14 513.73 146.18 523.56 146.18H533.73V130.67H527.94C517.59 130.67 513.37 125.44 513.37 112.63V84.33H533.73V68.82H513.37V36.54L513.36 36.55Z"/><path d="M616.73 130.67C613.54 130.67 612.12 129.34 612.12 126.36V99.96C612.12 72.87 595.64 68.83 585.81 68.83H571.03C558.25 68.83 544.57 78.19 544.57 92.12V93.39H560.68V92.12C560.68 87.83 565.93 84.34 572.38 84.34H581.43C592.99 84.34 595.49 88.5 595.95 97.8H572.68C554.69 97.8 542.6 107.4 542.6 121.7V123.36C542.6 130.91 545.53 148.46 572.68 148.46C579.61 148.46 591.73 147.25 598.25 139.51C602.2 146.19 610.52 146.19 616.73 146.19H618V130.68H616.73V130.67ZM596 111.78V122.43C596 131.04 582.33 132.93 577.05 132.93C561.71 132.93 558.71 129.19 558.71 122.73C558.71 115.56 565.06 111.78 577.05 111.78H596Z"/><path d="M755.83 109.7C754.29 116.63 750.78 122.3 745.39 126.53C739.99 130.77 732.78 132.92 723.95 132.92C711.01 132.92 701.57 129.46 695.91 122.64C690.28 115.75 687.43 106.62 687.43 95.49V87.66C687.43 76.43 690.29 67.2 695.91 60.21C701.58 53.29 711.02 49.78 723.95 49.78C732.1 49.78 738.92 51.73 744.23 55.6C749.52 59.35 753.12 64.42 754.95 70.66L755.18 71.45H771.8L771.52 70.13C769.99 62.78 767.13 56.35 763.04 51.02C758.94 45.69 753.95 41.58 748.2 38.81C742.46 36.04 736.37 34.64 730.11 34.64H717.77C708.99 34.64 701.01 36.66 694.05 40.66C687.07 44.66 681.51 50.64 677.51 58.42C673.64 66.07 671.67 75.5 671.67 86.46V96.69C671.67 107.65 673.63 117.13 677.51 124.89C681.5 132.57 687.07 138.49 694.04 142.49C701 146.48 708.98 148.5 717.76 148.5H730.1C736.76 148.5 743.1 147.04 748.95 144.17C754.91 141.19 759.96 136.77 763.96 131.03C768.05 125.2 770.75 118.16 771.97 110.1L772.16 108.84H756L755.81 109.7H755.83Z"/><path d="M836.22 72.98C831.27 70.51 825.74 69.25 819.77 69.25H811.04C805.08 69.25 799.54 70.5 794.59 72.98C789.6 75.47 785.55 79.31 782.54 84.4C779.65 89.46 778.18 95.77 778.18 103.16V114.6C778.18 121.98 779.65 128.29 782.55 133.37C785.55 138.45 789.61 142.29 794.59 144.78C799.54 147.25 805.07 148.51 811.04 148.51H819.77C825.73 148.51 831.27 147.26 836.22 144.78C841.21 142.28 845.22 138.44 848.11 133.37C851.11 128.3 852.63 121.99 852.63 114.6V103.16C852.63 95.77 851.11 89.45 848.12 84.4C845.21 79.31 841.21 75.47 836.22 72.98ZM836.89 105.57V112.19C836.89 119.1 835.22 124.41 831.9 128.01C828.72 131.56 823.18 133.36 815.41 133.36C807.64 133.36 802.04 131.56 798.77 128.01C795.56 124.42 793.93 119.1 793.93 112.19V105.57C793.93 98.66 795.56 93.34 798.76 89.76C802.04 86.2 807.64 84.4 815.41 84.4C823.18 84.4 828.72 86.2 831.91 89.76C835.21 93.35 836.89 98.66 836.89 105.57Z"/><path d="M917.7 81.85C915.43 78.73 912.47 76.02 908.85 73.79C904.08 70.78 899.17 69.26 894.27 69.26C884.79 69.26 876.85 72.2 870.64 77.98C864.52 83.69 861.41 92.16 861.41 103.17V114.61C861.41 125.61 864.51 134.13 870.65 139.95C876.85 145.63 884.8 148.52 894.27 148.52C899.17 148.52 904.08 146.99 908.85 143.99C912.47 141.75 915.44 139.05 917.71 135.92V146.1H933.45V36.54H917.7V81.84V81.85ZM914.55 93.8C916.64 97.22 917.7 101.74 917.7 107.22V110.53C917.7 116.02 916.64 120.53 914.55 123.95C912.47 127.35 909.85 129.79 906.76 131.2C903.61 132.63 900.37 133.36 897.13 133.36C890.36 133.36 885.27 131.51 882.01 127.87C878.79 124.18 877.16 118.91 877.16 112.19V105.57C877.16 98.85 878.79 93.58 882 89.9C885.28 86.25 890.37 84.4 897.13 84.4C900.37 84.4 903.61 85.13 906.76 86.56C909.85 87.96 912.47 90.4 914.55 93.81V93.8Z"/><path d="M1019.23 103.16C1019.23 95.85 1017.61 89.58 1014.41 84.53C1011.3 79.35 1007.04 75.46 1001.75 72.97C996.6 70.5 990.92 69.25 984.87 69.25H979.15C973.1 69.25 967.37 70.5 962.12 72.97C956.94 75.46 952.68 79.34 949.47 84.52C946.37 89.59 944.79 95.86 944.79 103.15V114.59C944.79 125.69 947.84 134.21 953.87 139.93C959.97 145.62 967.97 148.5 977.65 148.5H986.38C994.52 148.5 1001.63 146.25 1007.51 141.81C1013.55 137.23 1017.28 130.51 1018.62 121.84L1018.82 120.57H1002.57L1002.33 121.35C999.86 129.43 993.21 133.36 982.01 133.36C974.13 133.36 968.43 131.55 965.06 128C961.81 124.48 960.14 119.27 960.09 112.54H1019.23V103.16ZM960.54 99.2C961.25 94.5 963.09 90.91 966.01 88.5C969.3 85.77 974.69 84.39 982.01 84.39C989.33 84.39 994.66 85.77 997.88 88.51C1000.89 90.92 1002.77 94.51 1003.49 99.19H960.54V99.2Z"/></g><defs><clipPath id="lc"><rect width="1019.23" height="182.72" fill="none"/></clipPath></defs></svg>
|
|
</div>
|
|
<div class="agent-meta">
|
|
<div class="agent-name" id="agent-name"></div>
|
|
<a class="agent-id" id="agent-id" rel="noopener"></a>
|
|
</div>
|
|
</header>
|
|
|
|
<nav class="tabs">
|
|
<div class="tab active" data-tab="context">Context</div>
|
|
<div class="tab" data-tab="core" id="tab-core-label">Core Memory</div>
|
|
<div class="tab" data-tab="external" id="tab-external-label">External Memory</div>
|
|
<div class="tab" data-tab="history">History</div>
|
|
</nav>
|
|
|
|
<main class="main">
|
|
<section id="tab-context">
|
|
<div class="context-layout">
|
|
<div class="context-grid-area">
|
|
<div class="context-grid" id="context-grid"></div>
|
|
</div>
|
|
<div class="context-sidebar" id="context-sidebar"></div>
|
|
</div>
|
|
<div class="context-legend" id="context-legend"></div>
|
|
</section>
|
|
|
|
<section id="tab-core" style="display:none">
|
|
<div class="files-layout">
|
|
<div class="panel tree-panel" id="tree-panel-core"></div>
|
|
<div class="panel file-panel" id="file-panel-core">
|
|
<div class="empty">Select a file from the tree</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section id="tab-external" style="display:none">
|
|
<div class="files-layout">
|
|
<div class="panel tree-panel" id="tree-panel-external"></div>
|
|
<div class="panel file-panel" id="file-panel-external">
|
|
<div class="empty">Select a file from the tree</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section id="tab-history" style="display:none">
|
|
<div id="history-cap" class="note" style="display:none"></div>
|
|
<ul class="commit-list" id="commit-list"></ul>
|
|
<button class="show-more" id="show-more" style="display:none">Show older commits</button>
|
|
<div class="empty" id="history-empty" style="display:none">No history yet</div>
|
|
</section>
|
|
|
|
<div class="generated" id="generated-at"></div>
|
|
</main>
|
|
</div>
|
|
|
|
<script type="application/json" id="letta-data"><!--LETTA_DATA_PLACEHOLDER--></script>
|
|
<script>
|
|
/**
|
|
* marked v15.0.4 - a markdown parser
|
|
* Copyright (c) 2011-2024, Christopher Jeffrey. (MIT Licensed)
|
|
* https://github.com/markedjs/marked
|
|
*/
|
|
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).marked={})}(this,(function(e){"use strict";function t(){return{async:!1,breaks:!1,extensions:null,gfm:!0,hooks:null,pedantic:!1,renderer:null,silent:!1,tokenizer:null,walkTokens:null}}function n(t){e.defaults=t}e.defaults={async:!1,breaks:!1,extensions:null,gfm:!0,hooks:null,pedantic:!1,renderer:null,silent:!1,tokenizer:null,walkTokens:null};const s={exec:()=>null};function r(e,t=""){let n="string"==typeof e?e:e.source;const s={replace:(e,t)=>{let r="string"==typeof t?t:t.source;return r=r.replace(i.caret,"$1"),n=n.replace(e,r),s},getRegex:()=>new RegExp(n,t)};return s}const i={codeRemoveIndent:/^(?: {1,4}| {0,3}\t)/gm,outputLinkReplace:/\\([\[\]])/g,indentCodeCompensation:/^(\s+)(?:```)/,beginningSpace:/^\s+/,endingHash:/#$/,startingSpaceChar:/^ /,endingSpaceChar:/ $/,nonSpaceChar:/[^ ]/,newLineCharGlobal:/\n/g,tabCharGlobal:/\t/g,multipleSpaceGlobal:/\s+/g,blankLine:/^[ \t]*$/,doubleBlankLine:/\n[ \t]*\n[ \t]*$/,blockquoteStart:/^ {0,3}>/,blockquoteSetextReplace:/\n {0,3}((?:=+|-+) *)(?=\n|$)/g,blockquoteSetextReplace2:/^ {0,3}>[ \t]?/gm,listReplaceTabs:/^\t+/,listReplaceNesting:/^ {1,4}(?=( {4})*[^ ])/g,listIsTask:/^\[[ xX]\] /,listReplaceTask:/^\[[ xX]\] +/,anyLine:/\n.*\n/,hrefBrackets:/^<(.*)>$/,tableDelimiter:/[:|]/,tableAlignChars:/^\||\| *$/g,tableRowBlankLine:/\n[ \t]*$/,tableAlignRight:/^ *-+: *$/,tableAlignCenter:/^ *:-+: *$/,tableAlignLeft:/^ *:-+ *$/,startATag:/^<a /i,endATag:/^<\/a>/i,startPreScriptTag:/^<(pre|code|kbd|script)(\s|>)/i,endPreScriptTag:/^<\/(pre|code|kbd|script)(\s|>)/i,startAngleBracket:/^</,endAngleBracket:/>$/,pedanticHrefTitle:/^([^'"]*[^\s])\s+(['"])(.*)\2/,unicodeAlphaNumeric:/[\p{L}\p{N}]/u,escapeTest:/[&<>"']/,escapeReplace:/[&<>"']/g,escapeTestNoEncode:/[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/,escapeReplaceNoEncode:/[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/g,unescapeTest:/&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/gi,caret:/(^|[^\[])\^/g,percentDecode:/%25/g,findPipe:/\|/g,splitPipe:/ \|/,slashPipe:/\\\|/g,carriageReturn:/\r\n|\r/g,spaceLine:/^ +$/gm,notSpaceStart:/^\S*/,endingNewline:/\n$/,listItemRegex:e=>new RegExp(`^( {0,3}${e})((?:[\t ][^\\n]*)?(?:\\n|$))`),nextBulletRegex:e=>new RegExp(`^ {0,${Math.min(3,e-1)}}(?:[*+-]|\\d{1,9}[.)])((?:[ \t][^\\n]*)?(?:\\n|$))`),hrRegex:e=>new RegExp(`^ {0,${Math.min(3,e-1)}}((?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$)`),fencesBeginRegex:e=>new RegExp(`^ {0,${Math.min(3,e-1)}}(?:\`\`\`|~~~)`),headingBeginRegex:e=>new RegExp(`^ {0,${Math.min(3,e-1)}}#`),htmlBeginRegex:e=>new RegExp(`^ {0,${Math.min(3,e-1)}}<(?:[a-z].*>|!--)`,"i")},l=/^ {0,3}((?:-[\t ]*){3,}|(?:_[ \t]*){3,}|(?:\*[ \t]*){3,})(?:\n+|$)/,o=/(?:[*+-]|\d{1,9}[.)])/,a=r(/^(?!bull |blockCode|fences|blockquote|heading|html)((?:.|\n(?!\s*?\n|bull |blockCode|fences|blockquote|heading|html))+?)\n {0,3}(=+|-+) *(?:\n+|$)/).replace(/bull/g,o).replace(/blockCode/g,/(?: {4}| {0,3}\t)/).replace(/fences/g,/ {0,3}(?:`{3,}|~{3,})/).replace(/blockquote/g,/ {0,3}>/).replace(/heading/g,/ {0,3}#{1,6}/).replace(/html/g,/ {0,3}<[^\n>]+>\n/).getRegex(),c=/^([^\n]+(?:\n(?!hr|heading|lheading|blockquote|fences|list|html|table| +\n)[^\n]+)*)/,h=/(?!\s*\])(?:\\.|[^\[\]\\])+/,p=r(/^ {0,3}\[(label)\]: *(?:\n[ \t]*)?([^<\s][^\s]*|<.*?>)(?:(?: +(?:\n[ \t]*)?| *\n[ \t]*)(title))? *(?:\n+|$)/).replace("label",h).replace("title",/(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/).getRegex(),u=r(/^( {0,3}bull)([ \t][^\n]+?)?(?:\n|$)/).replace(/bull/g,o).getRegex(),g="address|article|aside|base|basefont|blockquote|body|caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option|p|param|search|section|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul",k=/<!--(?:-?>|[\s\S]*?(?:-->|$))/,f=r("^ {0,3}(?:<(script|pre|style|textarea)[\\s>][\\s\\S]*?(?:</\\1>[^\\n]*\\n+|$)|comment[^\\n]*(\\n+|$)|<\\?[\\s\\S]*?(?:\\?>\\n*|$)|<![A-Z][\\s\\S]*?(?:>\\n*|$)|<!\\[CDATA\\[[\\s\\S]*?(?:\\]\\]>\\n*|$)|</?(tag)(?: +|\\n|/?>)[\\s\\S]*?(?:(?:\\n[ \t]*)+\\n|$)|<(?!script|pre|style|textarea)([a-z][\\w-]*)(?:attribute)*? */?>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n[ \t]*)+\\n|$)|</(?!script|pre|style|textarea)[a-z][\\w-]*\\s*>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n[ \t]*)+\\n|$))","i").replace("comment",k).replace("tag",g).replace("attribute",/ +[a-zA-Z:_][\w.:-]*(?: *= *"[^"\n]*"| *= *'[^'\n]*'| *= *[^\s"'=<>`]+)?/).getRegex(),d=r(c).replace("hr",l).replace("heading"," {0,3}#{1,6}(?:\\s|$)").replace("|lheading","").replace("|table","").replace("blockquote"," {0,3}>").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)]) ").replace("html","</?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)").replace("tag",g).getRegex(),x={blockquote:r(/^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/).replace("paragraph",d).getRegex(),code:/^((?: {4}| {0,3}\t)[^\n]+(?:\n(?:[ \t]*(?:\n|$))*)?)+/,def:p,fences:/^ {0,3}(`{3,}(?=[^`\n]*(?:\n|$))|~{3,})([^\n]*)(?:\n|$)(?:|([\s\S]*?)(?:\n|$))(?: {0,3}\1[~`]* *(?=\n|$)|$)/,heading:/^ {0,3}(#{1,6})(?=\s|$)(.*)(?:\n+|$)/,hr:l,html:f,lheading:a,list:u,newline:/^(?:[ \t]*(?:\n|$))+/,paragraph:d,table:s,text:/^[^\n]+/},b=r("^ *([^\\n ].*)\\n {0,3}((?:\\| *)?:?-+:? *(?:\\| *:?-+:? *)*(?:\\| *)?)(?:\\n((?:(?! *\\n|hr|heading|blockquote|code|fences|list|html).*(?:\\n|$))*)\\n*|$)").replace("hr",l).replace("heading"," {0,3}#{1,6}(?:\\s|$)").replace("blockquote"," {0,3}>").replace("code","(?: {4}| {0,3}\t)[^\\n]").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)]) ").replace("html","</?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)").replace("tag",g).getRegex(),w={...x,table:b,paragraph:r(c).replace("hr",l).replace("heading"," {0,3}#{1,6}(?:\\s|$)").replace("|lheading","").replace("table",b).replace("blockquote"," {0,3}>").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)]) ").replace("html","</?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)").replace("tag",g).getRegex()},m={...x,html:r("^ *(?:comment *(?:\\n|\\s*$)|<(tag)[\\s\\S]+?</\\1> *(?:\\n{2,}|\\s*$)|<tag(?:\"[^\"]*\"|'[^']*'|\\s[^'\"/>\\s]*)*?/?> *(?:\\n{2,}|\\s*$))").replace("comment",k).replace(/tag/g,"(?!(?:a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)\\b)\\w+(?!:|[^\\w\\s@]*@)\\b").getRegex(),def:/^ *\[([^\]]+)\]: *<?([^\s>]+)>?(?: +(["(][^\n]+[")]))? *(?:\n+|$)/,heading:/^(#{1,6})(.*)(?:\n+|$)/,fences:s,lheading:/^(.+?)\n {0,3}(=+|-+) *(?:\n+|$)/,paragraph:r(c).replace("hr",l).replace("heading"," *#{1,6} *[^\n]").replace("lheading",a).replace("|table","").replace("blockquote"," {0,3}>").replace("|fences","").replace("|list","").replace("|html","").replace("|tag","").getRegex()},y=/^\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/,$=/^( {2,}|\\)\n(?!\s*$)/,R=/[\p{P}\p{S}]/u,S=/[\s\p{P}\p{S}]/u,T=/[^\s\p{P}\p{S}]/u,z=r(/^((?![*_])punctSpace)/,"u").replace(/punctSpace/g,S).getRegex(),A=r(/^(?:\*+(?:((?!\*)punct)|[^\s*]))|^_+(?:((?!_)punct)|([^\s_]))/,"u").replace(/punct/g,R).getRegex(),_=r("^[^_*]*?__[^_*]*?\\*[^_*]*?(?=__)|[^*]+(?=[^*])|(?!\\*)punct(\\*+)(?=[\\s]|$)|notPunctSpace(\\*+)(?!\\*)(?=punctSpace|$)|(?!\\*)punctSpace(\\*+)(?=notPunctSpace)|[\\s](\\*+)(?!\\*)(?=punct)|(?!\\*)punct(\\*+)(?!\\*)(?=punct)|notPunctSpace(\\*+)(?=notPunctSpace)","gu").replace(/notPunctSpace/g,T).replace(/punctSpace/g,S).replace(/punct/g,R).getRegex(),P=r("^[^_*]*?\\*\\*[^_*]*?_[^_*]*?(?=\\*\\*)|[^_]+(?=[^_])|(?!_)punct(_+)(?=[\\s]|$)|notPunctSpace(_+)(?!_)(?=punctSpace|$)|(?!_)punctSpace(_+)(?=notPunctSpace)|[\\s](_+)(?!_)(?=punct)|(?!_)punct(_+)(?!_)(?=punct)","gu").replace(/notPunctSpace/g,T).replace(/punctSpace/g,S).replace(/punct/g,R).getRegex(),I=r(/\\(punct)/,"gu").replace(/punct/g,R).getRegex(),L=r(/^<(scheme:[^\s\x00-\x1f<>]*|email)>/).replace("scheme",/[a-zA-Z][a-zA-Z0-9+.-]{1,31}/).replace("email",/[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+(@)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?![-_])/).getRegex(),B=r(k).replace("(?:--\x3e|$)","--\x3e").getRegex(),C=r("^comment|^</[a-zA-Z][\\w:-]*\\s*>|^<[a-zA-Z][\\w-]*(?:attribute)*?\\s*/?>|^<\\?[\\s\\S]*?\\?>|^<![a-zA-Z]+\\s[\\s\\S]*?>|^<!\\[CDATA\\[[\\s\\S]*?\\]\\]>").replace("comment",B).replace("attribute",/\s+[a-zA-Z:_][\w.:-]*(?:\s*=\s*"[^"]*"|\s*=\s*'[^']*'|\s*=\s*[^\s"'=<>`]+)?/).getRegex(),E=/(?:\[(?:\\.|[^\[\]\\])*\]|\\.|`[^`]*`|[^\[\]\\`])*?/,q=r(/^!?\[(label)\]\(\s*(href)(?:\s+(title))?\s*\)/).replace("label",E).replace("href",/<(?:\\.|[^\n<>\\])+>|[^\s\x00-\x1f]*/).replace("title",/"(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)/).getRegex(),Z=r(/^!?\[(label)\]\[(ref)\]/).replace("label",E).replace("ref",h).getRegex(),v=r(/^!?\[(ref)\](?:\[\])?/).replace("ref",h).getRegex(),D={_backpedal:s,anyPunctuation:I,autolink:L,blockSkip:/\[[^[\]]*?\]\((?:\\.|[^\\\(\)]|\((?:\\.|[^\\\(\)])*\))*\)|`[^`]*?`|<[^<>]*?>/g,br:$,code:/^(`+)([^`]|[^`][\s\S]*?[^`])\1(?!`)/,del:s,emStrongLDelim:A,emStrongRDelimAst:_,emStrongRDelimUnd:P,escape:y,link:q,nolink:v,punctuation:z,reflink:Z,reflinkSearch:r("reflink|nolink(?!\\()","g").replace("reflink",Z).replace("nolink",v).getRegex(),tag:C,text:/^(`+|[^`])(?:(?= {2,}\n)|[\s\S]*?(?:(?=[\\<!\[`*_]|\b_|$)|[^ ](?= {2,}\n)))/,url:s},M={...D,link:r(/^!?\[(label)\]\((.*?)\)/).replace("label",E).getRegex(),reflink:r(/^!?\[(label)\]\s*\[([^\]]*)\]/).replace("label",E).getRegex()},O={...D,escape:r(y).replace("])","~|])").getRegex(),url:r(/^((?:ftp|https?):\/\/|www\.)(?:[a-zA-Z0-9\-]+\.?)+[^\s<]*|^email/,"i").replace("email",/[A-Za-z0-9._+-]+(@)[a-zA-Z0-9-_]+(?:\.[a-zA-Z0-9-_]*[a-zA-Z0-9])+(?![-_])/).getRegex(),_backpedal:/(?:[^?!.,:;*_'"~()&]+|\([^)]*\)|&(?![a-zA-Z0-9]+;$)|[?!.,:;*_'"~)]+(?!$))+/,del:/^(~~?)(?=[^\s~])((?:\\.|[^\\])*?(?:\\.|[^\s~\\]))\1(?=[^~]|$)/,text:/^([`~]+|[^`~])(?:(?= {2,}\n)|(?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@)|[\s\S]*?(?:(?=[\\<!\[`*~_]|\b_|https?:\/\/|ftp:\/\/|www\.|$)|[^ ](?= {2,}\n)|[^a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-](?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@)))/},Q={...O,br:r($).replace("{2,}","*").getRegex(),text:r(O.text).replace("\\b_","\\b_| {2,}\\n").replace(/\{2,\}/g,"*").getRegex()},j={normal:x,gfm:w,pedantic:m},N={normal:D,gfm:O,breaks:Q,pedantic:M},G={"&":"&","<":"<",">":">",'"':""","'":"'"},H=e=>G[e];function X(e,t){if(t){if(i.escapeTest.test(e))return e.replace(i.escapeReplace,H)}else if(i.escapeTestNoEncode.test(e))return e.replace(i.escapeReplaceNoEncode,H);return e}function F(e){try{e=encodeURI(e).replace(i.percentDecode,"%")}catch{return null}return e}function U(e,t){const n=e.replace(i.findPipe,((e,t,n)=>{let s=!1,r=t;for(;--r>=0&&"\\"===n[r];)s=!s;return s?"|":" |"})).split(i.splitPipe);let s=0;if(n[0].trim()||n.shift(),n.length>0&&!n.at(-1)?.trim()&&n.pop(),t)if(n.length>t)n.splice(t);else for(;n.length<t;)n.push("");for(;s<n.length;s++)n[s]=n[s].trim().replace(i.slashPipe,"|");return n}function J(e,t,n){const s=e.length;if(0===s)return"";let r=0;for(;r<s;){const i=e.charAt(s-r-1);if(i!==t||n){if(i===t||!n)break;r++}else r++}return e.slice(0,s-r)}function K(e,t,n,s,r){const i=t.href,l=t.title||null,o=e[1].replace(r.other.outputLinkReplace,"$1");if("!"!==e[0].charAt(0)){s.state.inLink=!0;const e={type:"link",raw:n,href:i,title:l,text:o,tokens:s.inlineTokens(o)};return s.state.inLink=!1,e}return{type:"image",raw:n,href:i,title:l,text:o}}class V{options;rules;lexer;constructor(t){this.options=t||e.defaults}space(e){const t=this.rules.block.newline.exec(e);if(t&&t[0].length>0)return{type:"space",raw:t[0]}}code(e){const t=this.rules.block.code.exec(e);if(t){const e=t[0].replace(this.rules.other.codeRemoveIndent,"");return{type:"code",raw:t[0],codeBlockStyle:"indented",text:this.options.pedantic?e:J(e,"\n")}}}fences(e){const t=this.rules.block.fences.exec(e);if(t){const e=t[0],n=function(e,t,n){const s=e.match(n.other.indentCodeCompensation);if(null===s)return t;const r=s[1];return t.split("\n").map((e=>{const t=e.match(n.other.beginningSpace);if(null===t)return e;const[s]=t;return s.length>=r.length?e.slice(r.length):e})).join("\n")}(e,t[3]||"",this.rules);return{type:"code",raw:e,lang:t[2]?t[2].trim().replace(this.rules.inline.anyPunctuation,"$1"):t[2],text:n}}}heading(e){const t=this.rules.block.heading.exec(e);if(t){let e=t[2].trim();if(this.rules.other.endingHash.test(e)){const t=J(e,"#");this.options.pedantic?e=t.trim():t&&!this.rules.other.endingSpaceChar.test(t)||(e=t.trim())}return{type:"heading",raw:t[0],depth:t[1].length,text:e,tokens:this.lexer.inline(e)}}}hr(e){const t=this.rules.block.hr.exec(e);if(t)return{type:"hr",raw:J(t[0],"\n")}}blockquote(e){const t=this.rules.block.blockquote.exec(e);if(t){let e=J(t[0],"\n").split("\n"),n="",s="";const r=[];for(;e.length>0;){let t=!1;const i=[];let l;for(l=0;l<e.length;l++)if(this.rules.other.blockquoteStart.test(e[l]))i.push(e[l]),t=!0;else{if(t)break;i.push(e[l])}e=e.slice(l);const o=i.join("\n"),a=o.replace(this.rules.other.blockquoteSetextReplace,"\n $1").replace(this.rules.other.blockquoteSetextReplace2,"");n=n?`${n}\n${o}`:o,s=s?`${s}\n${a}`:a;const c=this.lexer.state.top;if(this.lexer.state.top=!0,this.lexer.blockTokens(a,r,!0),this.lexer.state.top=c,0===e.length)break;const h=r.at(-1);if("code"===h?.type)break;if("blockquote"===h?.type){const t=h,i=t.raw+"\n"+e.join("\n"),l=this.blockquote(i);r[r.length-1]=l,n=n.substring(0,n.length-t.raw.length)+l.raw,s=s.substring(0,s.length-t.text.length)+l.text;break}if("list"!==h?.type);else{const t=h,i=t.raw+"\n"+e.join("\n"),l=this.list(i);r[r.length-1]=l,n=n.substring(0,n.length-h.raw.length)+l.raw,s=s.substring(0,s.length-t.raw.length)+l.raw,e=i.substring(r.at(-1).raw.length).split("\n")}}return{type:"blockquote",raw:n,tokens:r,text:s}}}list(e){let t=this.rules.block.list.exec(e);if(t){let n=t[1].trim();const s=n.length>1,r={type:"list",raw:"",ordered:s,start:s?+n.slice(0,-1):"",loose:!1,items:[]};n=s?`\\d{1,9}\\${n.slice(-1)}`:`\\${n}`,this.options.pedantic&&(n=s?n:"[*+-]");const i=this.rules.other.listItemRegex(n);let l=!1;for(;e;){let n=!1,s="",o="";if(!(t=i.exec(e)))break;if(this.rules.block.hr.test(e))break;s=t[0],e=e.substring(s.length);let a=t[2].split("\n",1)[0].replace(this.rules.other.listReplaceTabs,(e=>" ".repeat(3*e.length))),c=e.split("\n",1)[0],h=!a.trim(),p=0;if(this.options.pedantic?(p=2,o=a.trimStart()):h?p=t[1].length+1:(p=t[2].search(this.rules.other.nonSpaceChar),p=p>4?1:p,o=a.slice(p),p+=t[1].length),h&&this.rules.other.blankLine.test(c)&&(s+=c+"\n",e=e.substring(c.length+1),n=!0),!n){const t=this.rules.other.nextBulletRegex(p),n=this.rules.other.hrRegex(p),r=this.rules.other.fencesBeginRegex(p),i=this.rules.other.headingBeginRegex(p),l=this.rules.other.htmlBeginRegex(p);for(;e;){const u=e.split("\n",1)[0];let g;if(c=u,this.options.pedantic?(c=c.replace(this.rules.other.listReplaceNesting," "),g=c):g=c.replace(this.rules.other.tabCharGlobal," "),r.test(c))break;if(i.test(c))break;if(l.test(c))break;if(t.test(c))break;if(n.test(c))break;if(g.search(this.rules.other.nonSpaceChar)>=p||!c.trim())o+="\n"+g.slice(p);else{if(h)break;if(a.replace(this.rules.other.tabCharGlobal," ").search(this.rules.other.nonSpaceChar)>=4)break;if(r.test(a))break;if(i.test(a))break;if(n.test(a))break;o+="\n"+c}h||c.trim()||(h=!0),s+=u+"\n",e=e.substring(u.length+1),a=g.slice(p)}}r.loose||(l?r.loose=!0:this.rules.other.doubleBlankLine.test(s)&&(l=!0));let u,g=null;this.options.gfm&&(g=this.rules.other.listIsTask.exec(o),g&&(u="[ ] "!==g[0],o=o.replace(this.rules.other.listReplaceTask,""))),r.items.push({type:"list_item",raw:s,task:!!g,checked:u,loose:!1,text:o,tokens:[]}),r.raw+=s}const o=r.items.at(-1);if(!o)return;o.raw=o.raw.trimEnd(),o.text=o.text.trimEnd(),r.raw=r.raw.trimEnd();for(let e=0;e<r.items.length;e++)if(this.lexer.state.top=!1,r.items[e].tokens=this.lexer.blockTokens(r.items[e].text,[]),!r.loose){const t=r.items[e].tokens.filter((e=>"space"===e.type)),n=t.length>0&&t.some((e=>this.rules.other.anyLine.test(e.raw)));r.loose=n}if(r.loose)for(let e=0;e<r.items.length;e++)r.items[e].loose=!0;return r}}html(e){const t=this.rules.block.html.exec(e);if(t){return{type:"html",block:!0,raw:t[0],pre:"pre"===t[1]||"script"===t[1]||"style"===t[1],text:t[0]}}}def(e){const t=this.rules.block.def.exec(e);if(t){const e=t[1].toLowerCase().replace(this.rules.other.multipleSpaceGlobal," "),n=t[2]?t[2].replace(this.rules.other.hrefBrackets,"$1").replace(this.rules.inline.anyPunctuation,"$1"):"",s=t[3]?t[3].substring(1,t[3].length-1).replace(this.rules.inline.anyPunctuation,"$1"):t[3];return{type:"def",tag:e,raw:t[0],href:n,title:s}}}table(e){const t=this.rules.block.table.exec(e);if(!t)return;if(!this.rules.other.tableDelimiter.test(t[2]))return;const n=U(t[1]),s=t[2].replace(this.rules.other.tableAlignChars,"").split("|"),r=t[3]?.trim()?t[3].replace(this.rules.other.tableRowBlankLine,"").split("\n"):[],i={type:"table",raw:t[0],header:[],align:[],rows:[]};if(n.length===s.length){for(const e of s)this.rules.other.tableAlignRight.test(e)?i.align.push("right"):this.rules.other.tableAlignCenter.test(e)?i.align.push("center"):this.rules.other.tableAlignLeft.test(e)?i.align.push("left"):i.align.push(null);for(let e=0;e<n.length;e++)i.header.push({text:n[e],tokens:this.lexer.inline(n[e]),header:!0,align:i.align[e]});for(const e of r)i.rows.push(U(e,i.header.length).map(((e,t)=>({text:e,tokens:this.lexer.inline(e),header:!1,align:i.align[t]}))));return i}}lheading(e){const t=this.rules.block.lheading.exec(e);if(t)return{type:"heading",raw:t[0],depth:"="===t[2].charAt(0)?1:2,text:t[1],tokens:this.lexer.inline(t[1])}}paragraph(e){const t=this.rules.block.paragraph.exec(e);if(t){const e="\n"===t[1].charAt(t[1].length-1)?t[1].slice(0,-1):t[1];return{type:"paragraph",raw:t[0],text:e,tokens:this.lexer.inline(e)}}}text(e){const t=this.rules.block.text.exec(e);if(t)return{type:"text",raw:t[0],text:t[0],tokens:this.lexer.inline(t[0])}}escape(e){const t=this.rules.inline.escape.exec(e);if(t)return{type:"escape",raw:t[0],text:t[1]}}tag(e){const t=this.rules.inline.tag.exec(e);if(t)return!this.lexer.state.inLink&&this.rules.other.startATag.test(t[0])?this.lexer.state.inLink=!0:this.lexer.state.inLink&&this.rules.other.endATag.test(t[0])&&(this.lexer.state.inLink=!1),!this.lexer.state.inRawBlock&&this.rules.other.startPreScriptTag.test(t[0])?this.lexer.state.inRawBlock=!0:this.lexer.state.inRawBlock&&this.rules.other.endPreScriptTag.test(t[0])&&(this.lexer.state.inRawBlock=!1),{type:"html",raw:t[0],inLink:this.lexer.state.inLink,inRawBlock:this.lexer.state.inRawBlock,block:!1,text:t[0]}}link(e){const t=this.rules.inline.link.exec(e);if(t){const e=t[2].trim();if(!this.options.pedantic&&this.rules.other.startAngleBracket.test(e)){if(!this.rules.other.endAngleBracket.test(e))return;const t=J(e.slice(0,-1),"\\");if((e.length-t.length)%2==0)return}else{const e=function(e,t){if(-1===e.indexOf(t[1]))return-1;let n=0;for(let s=0;s<e.length;s++)if("\\"===e[s])s++;else if(e[s]===t[0])n++;else if(e[s]===t[1]&&(n--,n<0))return s;return-1}(t[2],"()");if(e>-1){const n=(0===t[0].indexOf("!")?5:4)+t[1].length+e;t[2]=t[2].substring(0,e),t[0]=t[0].substring(0,n).trim(),t[3]=""}}let n=t[2],s="";if(this.options.pedantic){const e=this.rules.other.pedanticHrefTitle.exec(n);e&&(n=e[1],s=e[3])}else s=t[3]?t[3].slice(1,-1):"";return n=n.trim(),this.rules.other.startAngleBracket.test(n)&&(n=this.options.pedantic&&!this.rules.other.endAngleBracket.test(e)?n.slice(1):n.slice(1,-1)),K(t,{href:n?n.replace(this.rules.inline.anyPunctuation,"$1"):n,title:s?s.replace(this.rules.inline.anyPunctuation,"$1"):s},t[0],this.lexer,this.rules)}}reflink(e,t){let n;if((n=this.rules.inline.reflink.exec(e))||(n=this.rules.inline.nolink.exec(e))){const e=t[(n[2]||n[1]).replace(this.rules.other.multipleSpaceGlobal," ").toLowerCase()];if(!e){const e=n[0].charAt(0);return{type:"text",raw:e,text:e}}return K(n,e,n[0],this.lexer,this.rules)}}emStrong(e,t,n=""){let s=this.rules.inline.emStrongLDelim.exec(e);if(!s)return;if(s[3]&&n.match(this.rules.other.unicodeAlphaNumeric))return;if(!(s[1]||s[2]||"")||!n||this.rules.inline.punctuation.exec(n)){const n=[...s[0]].length-1;let r,i,l=n,o=0;const a="*"===s[0][0]?this.rules.inline.emStrongRDelimAst:this.rules.inline.emStrongRDelimUnd;for(a.lastIndex=0,t=t.slice(-1*e.length+n);null!=(s=a.exec(t));){if(r=s[1]||s[2]||s[3]||s[4]||s[5]||s[6],!r)continue;if(i=[...r].length,s[3]||s[4]){l+=i;continue}if((s[5]||s[6])&&n%3&&!((n+i)%3)){o+=i;continue}if(l-=i,l>0)continue;i=Math.min(i,i+l+o);const t=[...s[0]][0].length,a=e.slice(0,n+s.index+t+i);if(Math.min(n,i)%2){const e=a.slice(1,-1);return{type:"em",raw:a,text:e,tokens:this.lexer.inlineTokens(e)}}const c=a.slice(2,-2);return{type:"strong",raw:a,text:c,tokens:this.lexer.inlineTokens(c)}}}}codespan(e){const t=this.rules.inline.code.exec(e);if(t){let e=t[2].replace(this.rules.other.newLineCharGlobal," ");const n=this.rules.other.nonSpaceChar.test(e),s=this.rules.other.startingSpaceChar.test(e)&&this.rules.other.endingSpaceChar.test(e);return n&&s&&(e=e.substring(1,e.length-1)),{type:"codespan",raw:t[0],text:e}}}br(e){const t=this.rules.inline.br.exec(e);if(t)return{type:"br",raw:t[0]}}del(e){const t=this.rules.inline.del.exec(e);if(t)return{type:"del",raw:t[0],text:t[2],tokens:this.lexer.inlineTokens(t[2])}}autolink(e){const t=this.rules.inline.autolink.exec(e);if(t){let e,n;return"@"===t[2]?(e=t[1],n="mailto:"+e):(e=t[1],n=e),{type:"link",raw:t[0],text:e,href:n,tokens:[{type:"text",raw:e,text:e}]}}}url(e){let t;if(t=this.rules.inline.url.exec(e)){let e,n;if("@"===t[2])e=t[0],n="mailto:"+e;else{let s;do{s=t[0],t[0]=this.rules.inline._backpedal.exec(t[0])?.[0]??""}while(s!==t[0]);e=t[0],n="www."===t[1]?"http://"+t[0]:t[0]}return{type:"link",raw:t[0],text:e,href:n,tokens:[{type:"text",raw:e,text:e}]}}}inlineText(e){const t=this.rules.inline.text.exec(e);if(t){const e=this.lexer.state.inRawBlock;return{type:"text",raw:t[0],text:t[0],escaped:e}}}}class W{tokens;options;state;tokenizer;inlineQueue;constructor(t){this.tokens=[],this.tokens.links=Object.create(null),this.options=t||e.defaults,this.options.tokenizer=this.options.tokenizer||new V,this.tokenizer=this.options.tokenizer,this.tokenizer.options=this.options,this.tokenizer.lexer=this,this.inlineQueue=[],this.state={inLink:!1,inRawBlock:!1,top:!0};const n={other:i,block:j.normal,inline:N.normal};this.options.pedantic?(n.block=j.pedantic,n.inline=N.pedantic):this.options.gfm&&(n.block=j.gfm,this.options.breaks?n.inline=N.breaks:n.inline=N.gfm),this.tokenizer.rules=n}static get rules(){return{block:j,inline:N}}static lex(e,t){return new W(t).lex(e)}static lexInline(e,t){return new W(t).inlineTokens(e)}lex(e){e=e.replace(i.carriageReturn,"\n"),this.blockTokens(e,this.tokens);for(let e=0;e<this.inlineQueue.length;e++){const t=this.inlineQueue[e];this.inlineTokens(t.src,t.tokens)}return this.inlineQueue=[],this.tokens}blockTokens(e,t=[],n=!1){for(this.options.pedantic&&(e=e.replace(i.tabCharGlobal," ").replace(i.spaceLine,""));e;){let s;if(this.options.extensions?.block?.some((n=>!!(s=n.call({lexer:this},e,t))&&(e=e.substring(s.raw.length),t.push(s),!0))))continue;if(s=this.tokenizer.space(e)){e=e.substring(s.raw.length);const n=t.at(-1);1===s.raw.length&&void 0!==n?n.raw+="\n":t.push(s);continue}if(s=this.tokenizer.code(e)){e=e.substring(s.raw.length);const n=t.at(-1);"paragraph"===n?.type||"text"===n?.type?(n.raw+="\n"+s.raw,n.text+="\n"+s.text,this.inlineQueue.at(-1).src=n.text):t.push(s);continue}if(s=this.tokenizer.fences(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.heading(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.hr(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.blockquote(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.list(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.html(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.def(e)){e=e.substring(s.raw.length);const n=t.at(-1);"paragraph"===n?.type||"text"===n?.type?(n.raw+="\n"+s.raw,n.text+="\n"+s.raw,this.inlineQueue.at(-1).src=n.text):this.tokens.links[s.tag]||(this.tokens.links[s.tag]={href:s.href,title:s.title});continue}if(s=this.tokenizer.table(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.lheading(e)){e=e.substring(s.raw.length),t.push(s);continue}let r=e;if(this.options.extensions?.startBlock){let t=1/0;const n=e.slice(1);let s;this.options.extensions.startBlock.forEach((e=>{s=e.call({lexer:this},n),"number"==typeof s&&s>=0&&(t=Math.min(t,s))})),t<1/0&&t>=0&&(r=e.substring(0,t+1))}if(this.state.top&&(s=this.tokenizer.paragraph(r))){const i=t.at(-1);n&&"paragraph"===i?.type?(i.raw+="\n"+s.raw,i.text+="\n"+s.text,this.inlineQueue.pop(),this.inlineQueue.at(-1).src=i.text):t.push(s),n=r.length!==e.length,e=e.substring(s.raw.length)}else if(s=this.tokenizer.text(e)){e=e.substring(s.raw.length);const n=t.at(-1);"text"===n?.type?(n.raw+="\n"+s.raw,n.text+="\n"+s.text,this.inlineQueue.pop(),this.inlineQueue.at(-1).src=n.text):t.push(s)}else if(e){const t="Infinite loop on byte: "+e.charCodeAt(0);if(this.options.silent){console.error(t);break}throw new Error(t)}}return this.state.top=!0,t}inline(e,t=[]){return this.inlineQueue.push({src:e,tokens:t}),t}inlineTokens(e,t=[]){let n=e,s=null;if(this.tokens.links){const e=Object.keys(this.tokens.links);if(e.length>0)for(;null!=(s=this.tokenizer.rules.inline.reflinkSearch.exec(n));)e.includes(s[0].slice(s[0].lastIndexOf("[")+1,-1))&&(n=n.slice(0,s.index)+"["+"a".repeat(s[0].length-2)+"]"+n.slice(this.tokenizer.rules.inline.reflinkSearch.lastIndex))}for(;null!=(s=this.tokenizer.rules.inline.blockSkip.exec(n));)n=n.slice(0,s.index)+"["+"a".repeat(s[0].length-2)+"]"+n.slice(this.tokenizer.rules.inline.blockSkip.lastIndex);for(;null!=(s=this.tokenizer.rules.inline.anyPunctuation.exec(n));)n=n.slice(0,s.index)+"++"+n.slice(this.tokenizer.rules.inline.anyPunctuation.lastIndex);let r=!1,i="";for(;e;){let s;if(r||(i=""),r=!1,this.options.extensions?.inline?.some((n=>!!(s=n.call({lexer:this},e,t))&&(e=e.substring(s.raw.length),t.push(s),!0))))continue;if(s=this.tokenizer.escape(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.tag(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.link(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.reflink(e,this.tokens.links)){e=e.substring(s.raw.length);const n=t.at(-1);"text"===s.type&&"text"===n?.type?(n.raw+=s.raw,n.text+=s.text):t.push(s);continue}if(s=this.tokenizer.emStrong(e,n,i)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.codespan(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.br(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.del(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.autolink(e)){e=e.substring(s.raw.length),t.push(s);continue}if(!this.state.inLink&&(s=this.tokenizer.url(e))){e=e.substring(s.raw.length),t.push(s);continue}let l=e;if(this.options.extensions?.startInline){let t=1/0;const n=e.slice(1);let s;this.options.extensions.startInline.forEach((e=>{s=e.call({lexer:this},n),"number"==typeof s&&s>=0&&(t=Math.min(t,s))})),t<1/0&&t>=0&&(l=e.substring(0,t+1))}if(s=this.tokenizer.inlineText(l)){e=e.substring(s.raw.length),"_"!==s.raw.slice(-1)&&(i=s.raw.slice(-1)),r=!0;const n=t.at(-1);"text"===n?.type?(n.raw+=s.raw,n.text+=s.text):t.push(s)}else if(e){const t="Infinite loop on byte: "+e.charCodeAt(0);if(this.options.silent){console.error(t);break}throw new Error(t)}}return t}}class Y{options;parser;constructor(t){this.options=t||e.defaults}space(e){return""}code({text:e,lang:t,escaped:n}){const s=(t||"").match(i.notSpaceStart)?.[0],r=e.replace(i.endingNewline,"")+"\n";return s?'<pre><code class="language-'+X(s)+'">'+(n?r:X(r,!0))+"</code></pre>\n":"<pre><code>"+(n?r:X(r,!0))+"</code></pre>\n"}blockquote({tokens:e}){return`<blockquote>\n${this.parser.parse(e)}</blockquote>\n`}html({text:e}){return e}heading({tokens:e,depth:t}){return`<h${t}>${this.parser.parseInline(e)}</h${t}>\n`}hr(e){return"<hr>\n"}list(e){const t=e.ordered,n=e.start;let s="";for(let t=0;t<e.items.length;t++){const n=e.items[t];s+=this.listitem(n)}const r=t?"ol":"ul";return"<"+r+(t&&1!==n?' start="'+n+'"':"")+">\n"+s+"</"+r+">\n"}listitem(e){let t="";if(e.task){const n=this.checkbox({checked:!!e.checked});e.loose?"paragraph"===e.tokens[0]?.type?(e.tokens[0].text=n+" "+e.tokens[0].text,e.tokens[0].tokens&&e.tokens[0].tokens.length>0&&"text"===e.tokens[0].tokens[0].type&&(e.tokens[0].tokens[0].text=n+" "+X(e.tokens[0].tokens[0].text),e.tokens[0].tokens[0].escaped=!0)):e.tokens.unshift({type:"text",raw:n+" ",text:n+" ",escaped:!0}):t+=n+" "}return t+=this.parser.parse(e.tokens,!!e.loose),`<li>${t}</li>\n`}checkbox({checked:e}){return"<input "+(e?'checked="" ':"")+'disabled="" type="checkbox">'}paragraph({tokens:e}){return`<p>${this.parser.parseInline(e)}</p>\n`}table(e){let t="",n="";for(let t=0;t<e.header.length;t++)n+=this.tablecell(e.header[t]);t+=this.tablerow({text:n});let s="";for(let t=0;t<e.rows.length;t++){const r=e.rows[t];n="";for(let e=0;e<r.length;e++)n+=this.tablecell(r[e]);s+=this.tablerow({text:n})}return s&&(s=`<tbody>${s}</tbody>`),"<table>\n<thead>\n"+t+"</thead>\n"+s+"</table>\n"}tablerow({text:e}){return`<tr>\n${e}</tr>\n`}tablecell(e){const t=this.parser.parseInline(e.tokens),n=e.header?"th":"td";return(e.align?`<${n} align="${e.align}">`:`<${n}>`)+t+`</${n}>\n`}strong({tokens:e}){return`<strong>${this.parser.parseInline(e)}</strong>`}em({tokens:e}){return`<em>${this.parser.parseInline(e)}</em>`}codespan({text:e}){return`<code>${X(e,!0)}</code>`}br(e){return"<br>"}del({tokens:e}){return`<del>${this.parser.parseInline(e)}</del>`}link({href:e,title:t,tokens:n}){const s=this.parser.parseInline(n),r=F(e);if(null===r)return s;let i='<a href="'+(e=r)+'"';return t&&(i+=' title="'+X(t)+'"'),i+=">"+s+"</a>",i}image({href:e,title:t,text:n}){const s=F(e);if(null===s)return X(n);let r=`<img src="${e=s}" alt="${n}"`;return t&&(r+=` title="${X(t)}"`),r+=">",r}text(e){return"tokens"in e&&e.tokens?this.parser.parseInline(e.tokens):"escaped"in e&&e.escaped?e.text:X(e.text)}}class ee{strong({text:e}){return e}em({text:e}){return e}codespan({text:e}){return e}del({text:e}){return e}html({text:e}){return e}text({text:e}){return e}link({text:e}){return""+e}image({text:e}){return""+e}br(){return""}}class te{options;renderer;textRenderer;constructor(t){this.options=t||e.defaults,this.options.renderer=this.options.renderer||new Y,this.renderer=this.options.renderer,this.renderer.options=this.options,this.renderer.parser=this,this.textRenderer=new ee}static parse(e,t){return new te(t).parse(e)}static parseInline(e,t){return new te(t).parseInline(e)}parse(e,t=!0){let n="";for(let s=0;s<e.length;s++){const r=e[s];if(this.options.extensions?.renderers?.[r.type]){const e=r,t=this.options.extensions.renderers[e.type].call({parser:this},e);if(!1!==t||!["space","hr","heading","code","table","blockquote","list","html","paragraph","text"].includes(e.type)){n+=t||"";continue}}const i=r;switch(i.type){case"space":n+=this.renderer.space(i);continue;case"hr":n+=this.renderer.hr(i);continue;case"heading":n+=this.renderer.heading(i);continue;case"code":n+=this.renderer.code(i);continue;case"table":n+=this.renderer.table(i);continue;case"blockquote":n+=this.renderer.blockquote(i);continue;case"list":n+=this.renderer.list(i);continue;case"html":n+=this.renderer.html(i);continue;case"paragraph":n+=this.renderer.paragraph(i);continue;case"text":{let r=i,l=this.renderer.text(r);for(;s+1<e.length&&"text"===e[s+1].type;)r=e[++s],l+="\n"+this.renderer.text(r);n+=t?this.renderer.paragraph({type:"paragraph",raw:l,text:l,tokens:[{type:"text",raw:l,text:l,escaped:!0}]}):l;continue}default:{const e='Token with "'+i.type+'" type was not found.';if(this.options.silent)return console.error(e),"";throw new Error(e)}}}return n}parseInline(e,t=this.renderer){let n="";for(let s=0;s<e.length;s++){const r=e[s];if(this.options.extensions?.renderers?.[r.type]){const e=this.options.extensions.renderers[r.type].call({parser:this},r);if(!1!==e||!["escape","html","link","image","strong","em","codespan","br","del","text"].includes(r.type)){n+=e||"";continue}}const i=r;switch(i.type){case"escape":case"text":n+=t.text(i);break;case"html":n+=t.html(i);break;case"link":n+=t.link(i);break;case"image":n+=t.image(i);break;case"strong":n+=t.strong(i);break;case"em":n+=t.em(i);break;case"codespan":n+=t.codespan(i);break;case"br":n+=t.br(i);break;case"del":n+=t.del(i);break;default:{const e='Token with "'+i.type+'" type was not found.';if(this.options.silent)return console.error(e),"";throw new Error(e)}}}return n}}class ne{options;block;constructor(t){this.options=t||e.defaults}static passThroughHooks=new Set(["preprocess","postprocess","processAllTokens"]);preprocess(e){return e}postprocess(e){return e}processAllTokens(e){return e}provideLexer(){return this.block?W.lex:W.lexInline}provideParser(){return this.block?te.parse:te.parseInline}}class se{defaults={async:!1,breaks:!1,extensions:null,gfm:!0,hooks:null,pedantic:!1,renderer:null,silent:!1,tokenizer:null,walkTokens:null};options=this.setOptions;parse=this.parseMarkdown(!0);parseInline=this.parseMarkdown(!1);Parser=te;Renderer=Y;TextRenderer=ee;Lexer=W;Tokenizer=V;Hooks=ne;constructor(...e){this.use(...e)}walkTokens(e,t){let n=[];for(const s of e)switch(n=n.concat(t.call(this,s)),s.type){case"table":{const e=s;for(const s of e.header)n=n.concat(this.walkTokens(s.tokens,t));for(const s of e.rows)for(const e of s)n=n.concat(this.walkTokens(e.tokens,t));break}case"list":{const e=s;n=n.concat(this.walkTokens(e.items,t));break}default:{const e=s;this.defaults.extensions?.childTokens?.[e.type]?this.defaults.extensions.childTokens[e.type].forEach((s=>{const r=e[s].flat(1/0);n=n.concat(this.walkTokens(r,t))})):e.tokens&&(n=n.concat(this.walkTokens(e.tokens,t)))}}return n}use(...e){const t=this.defaults.extensions||{renderers:{},childTokens:{}};return e.forEach((e=>{const n={...e};if(n.async=this.defaults.async||n.async||!1,e.extensions&&(e.extensions.forEach((e=>{if(!e.name)throw new Error("extension name required");if("renderer"in e){const n=t.renderers[e.name];t.renderers[e.name]=n?function(...t){let s=e.renderer.apply(this,t);return!1===s&&(s=n.apply(this,t)),s}:e.renderer}if("tokenizer"in e){if(!e.level||"block"!==e.level&&"inline"!==e.level)throw new Error("extension level must be 'block' or 'inline'");const n=t[e.level];n?n.unshift(e.tokenizer):t[e.level]=[e.tokenizer],e.start&&("block"===e.level?t.startBlock?t.startBlock.push(e.start):t.startBlock=[e.start]:"inline"===e.level&&(t.startInline?t.startInline.push(e.start):t.startInline=[e.start]))}"childTokens"in e&&e.childTokens&&(t.childTokens[e.name]=e.childTokens)})),n.extensions=t),e.renderer){const t=this.defaults.renderer||new Y(this.defaults);for(const n in e.renderer){if(!(n in t))throw new Error(`renderer '${n}' does not exist`);if(["options","parser"].includes(n))continue;const s=n,r=e.renderer[s],i=t[s];t[s]=(...e)=>{let n=r.apply(t,e);return!1===n&&(n=i.apply(t,e)),n||""}}n.renderer=t}if(e.tokenizer){const t=this.defaults.tokenizer||new V(this.defaults);for(const n in e.tokenizer){if(!(n in t))throw new Error(`tokenizer '${n}' does not exist`);if(["options","rules","lexer"].includes(n))continue;const s=n,r=e.tokenizer[s],i=t[s];t[s]=(...e)=>{let n=r.apply(t,e);return!1===n&&(n=i.apply(t,e)),n}}n.tokenizer=t}if(e.hooks){const t=this.defaults.hooks||new ne;for(const n in e.hooks){if(!(n in t))throw new Error(`hook '${n}' does not exist`);if(["options","block"].includes(n))continue;const s=n,r=e.hooks[s],i=t[s];ne.passThroughHooks.has(n)?t[s]=e=>{if(this.defaults.async)return Promise.resolve(r.call(t,e)).then((e=>i.call(t,e)));const n=r.call(t,e);return i.call(t,n)}:t[s]=(...e)=>{let n=r.apply(t,e);return!1===n&&(n=i.apply(t,e)),n}}n.hooks=t}if(e.walkTokens){const t=this.defaults.walkTokens,s=e.walkTokens;n.walkTokens=function(e){let n=[];return n.push(s.call(this,e)),t&&(n=n.concat(t.call(this,e))),n}}this.defaults={...this.defaults,...n}})),this}setOptions(e){return this.defaults={...this.defaults,...e},this}lexer(e,t){return W.lex(e,t??this.defaults)}parser(e,t){return te.parse(e,t??this.defaults)}parseMarkdown(e){return(t,n)=>{const s={...n},r={...this.defaults,...s},i=this.onError(!!r.silent,!!r.async);if(!0===this.defaults.async&&!1===s.async)return i(new Error("marked(): The async option was set to true by an extension. Remove async: false from the parse options object to return a Promise."));if(null==t)return i(new Error("marked(): input parameter is undefined or null"));if("string"!=typeof t)return i(new Error("marked(): input parameter is of type "+Object.prototype.toString.call(t)+", string expected"));r.hooks&&(r.hooks.options=r,r.hooks.block=e);const l=r.hooks?r.hooks.provideLexer():e?W.lex:W.lexInline,o=r.hooks?r.hooks.provideParser():e?te.parse:te.parseInline;if(r.async)return Promise.resolve(r.hooks?r.hooks.preprocess(t):t).then((e=>l(e,r))).then((e=>r.hooks?r.hooks.processAllTokens(e):e)).then((e=>r.walkTokens?Promise.all(this.walkTokens(e,r.walkTokens)).then((()=>e)):e)).then((e=>o(e,r))).then((e=>r.hooks?r.hooks.postprocess(e):e)).catch(i);try{r.hooks&&(t=r.hooks.preprocess(t));let e=l(t,r);r.hooks&&(e=r.hooks.processAllTokens(e)),r.walkTokens&&this.walkTokens(e,r.walkTokens);let n=o(e,r);return r.hooks&&(n=r.hooks.postprocess(n)),n}catch(e){return i(e)}}}onError(e,t){return n=>{if(n.message+="\nPlease report this to https://github.com/markedjs/marked.",e){const e="<p>An error occurred:</p><pre>"+X(n.message+"",!0)+"</pre>";return t?Promise.resolve(e):e}if(t)return Promise.reject(n);throw n}}}const re=new se;function ie(e,t){return re.parse(e,t)}ie.options=ie.setOptions=function(e){return re.setOptions(e),ie.defaults=re.defaults,n(ie.defaults),ie},ie.getDefaults=t,ie.defaults=e.defaults,ie.use=function(...e){return re.use(...e),ie.defaults=re.defaults,n(ie.defaults),ie},ie.walkTokens=function(e,t){return re.walkTokens(e,t)},ie.parseInline=re.parseInline,ie.Parser=te,ie.parser=te.parse,ie.Renderer=Y,ie.TextRenderer=ee,ie.Lexer=W,ie.lexer=W.lex,ie.Tokenizer=V,ie.Hooks=ne,ie.parse=ie;const le=ie.options,oe=ie.setOptions,ae=ie.use,ce=ie.walkTokens,he=ie.parseInline,pe=ie,ue=te.parse,ge=W.lex;e.Hooks=ne,e.Lexer=W,e.Marked=se,e.Parser=te,e.Renderer=Y,e.TextRenderer=ee,e.Tokenizer=V,e.getDefaults=t,e.lexer=ge,e.marked=ie,e.options=le,e.parse=pe,e.parseInline=he,e.parser=ue,e.setOptions=oe,e.use=ae,e.walkTokens=ce}));
|
|
</script>
|
|
<script>
|
|
(function() {
|
|
'use strict';
|
|
|
|
// Auto dark mode from OS preference
|
|
if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
|
document.documentElement.classList.add('dark');
|
|
}
|
|
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', function(e) {
|
|
document.documentElement.classList.toggle('dark', e.matches);
|
|
});
|
|
|
|
var dataEl = document.getElementById('letta-data');
|
|
var DATA;
|
|
try {
|
|
DATA = JSON.parse(dataEl.textContent);
|
|
} catch (err) {
|
|
document.body.innerHTML = '<div class="empty">Failed to parse data: ' + String(err && err.message ? err.message : err) + '</div>';
|
|
return;
|
|
}
|
|
|
|
function escHtml(s) {
|
|
return String(s || '').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
}
|
|
function escAttr(s) {
|
|
return escHtml(s).replace(/"/g, '"').replace(/'/g, ''');
|
|
}
|
|
|
|
function sanitizeUrl(raw) {
|
|
if (!raw) return '';
|
|
return String(raw).replace(/[\x00-\x1f\x7f]/g, '').trim();
|
|
}
|
|
function isSafeUrl(clean) {
|
|
if (!clean) return false;
|
|
if (/^(https?:|mailto:)/i.test(clean)) return true;
|
|
return !clean.startsWith('//') && !/^[a-z][a-z0-9+.-]*:/i.test(clean);
|
|
}
|
|
|
|
// marked renderer hardening
|
|
var renderer = new marked.Renderer();
|
|
renderer.html = function(token) {
|
|
return escHtml(token && token.text ? token.text : '');
|
|
};
|
|
renderer.link = function(opts) {
|
|
var clean = sanitizeUrl(opts && opts.href ? opts.href : '');
|
|
var safeHref = isSafeUrl(clean) ? escAttr(clean) : '#';
|
|
var titleAttr = opts && opts.title ? ' title="' + escAttr(opts.title) + '"' : '';
|
|
var inner = this.parser && opts && opts.tokens ? this.parser.parseInline(opts.tokens) : escHtml('');
|
|
return '<a href="' + safeHref + '"' + titleAttr + ' rel="noopener">' + inner + '</a>';
|
|
};
|
|
renderer.image = function(opts) {
|
|
var clean = sanitizeUrl(opts && opts.href ? opts.href : '');
|
|
var safeHref = isSafeUrl(clean) ? escAttr(clean) : '';
|
|
var titleAttr = opts && opts.title ? ' title="' + escAttr(opts.title) + '"' : '';
|
|
var alt = escAttr(opts && opts.text ? opts.text : '');
|
|
return safeHref
|
|
? '<img src="' + safeHref + '" alt="' + alt + '"' + titleAttr + '>'
|
|
: '[' + escHtml(opts && opts.text ? opts.text : '') + ']';
|
|
};
|
|
marked.use({ renderer: renderer, gfm: true, breaks: true });
|
|
|
|
function renderMarkdown(text) {
|
|
try {
|
|
return marked.parse(String(text || ''));
|
|
} catch (_) {
|
|
return '<pre>' + escHtml(String(text || '')) + '</pre>';
|
|
}
|
|
}
|
|
|
|
function relativeTime(iso) {
|
|
var d = new Date(iso);
|
|
var now = new Date();
|
|
var sec = Math.floor((now - d) / 1000);
|
|
if (!Number.isFinite(sec) || sec < 0) return String(iso || '');
|
|
if (sec < 60) return 'just now';
|
|
if (sec < 3600) return Math.floor(sec / 60) + 'm ago';
|
|
if (sec < 86400) return Math.floor(sec / 3600) + 'h ago';
|
|
if (sec < 2592000) return Math.floor(sec / 86400) + 'd ago';
|
|
return d.toLocaleDateString();
|
|
}
|
|
|
|
// Header
|
|
var agentName = DATA.agent && DATA.agent.name ? DATA.agent.name : '';
|
|
var agentId = DATA.agent && DATA.agent.id ? DATA.agent.id : '';
|
|
var serverUrl = DATA.agent && DATA.agent.serverUrl ? DATA.agent.serverUrl : '';
|
|
var nameEl = document.getElementById('agent-name');
|
|
if (agentName && agentName !== agentId) {
|
|
nameEl.textContent = agentName + "'s Memory Palace";
|
|
} else {
|
|
nameEl.textContent = 'Memory Palace';
|
|
}
|
|
var agentIdEl = document.getElementById('agent-id');
|
|
agentIdEl.textContent = agentId;
|
|
if (agentId) {
|
|
var adeBase;
|
|
if (serverUrl) {
|
|
adeBase = serverUrl.replace(/\/v1\/?$/, '').replace('api.letta.com', 'app.letta.com');
|
|
} else {
|
|
adeBase = 'https://app.letta.com';
|
|
}
|
|
agentIdEl.href = adeBase + '/chat/' + encodeURIComponent(agentId);
|
|
agentIdEl.target = '_blank';
|
|
}
|
|
document.getElementById('generated-at').textContent = 'Generated ' + new Date(DATA.generatedAt).toLocaleString();
|
|
|
|
|
|
// ---- Context tab ----
|
|
var CTX_COLORS = {
|
|
system: 'rgb(224, 112, 80)',
|
|
coreMemory:'rgb(224, 160, 64)',
|
|
tools: 'rgb(32, 178, 170)',
|
|
messages: 'rgb(140, 140, 249)',
|
|
summary: 'rgb(160, 160, 160)',
|
|
free: 'var(--text-dim)'
|
|
};
|
|
var CTX_SYMBOLS = {
|
|
system: '\u25C9',
|
|
coreMemory: '\u25C9',
|
|
tools: '\u25CF',
|
|
messages: '\u25CF',
|
|
summary: '\u29BB',
|
|
free: '\u25A1'
|
|
};
|
|
var CTX_LABELS = {
|
|
system: 'System',
|
|
coreMemory: 'Core Memory',
|
|
tools: 'Tools',
|
|
messages: 'Messages',
|
|
summary: 'Summary',
|
|
free: 'Free'
|
|
};
|
|
|
|
function renderContextTab() {
|
|
var ctx = DATA.context;
|
|
var gridEl = document.getElementById('context-grid');
|
|
var sidebarEl = document.getElementById('context-sidebar');
|
|
|
|
if (!ctx) {
|
|
gridEl.parentElement.parentElement.innerHTML = '<div class="ctx-unavailable">Context data not available. Send a message first to see context usage.</div>';
|
|
return;
|
|
}
|
|
|
|
var bd = ctx.breakdown;
|
|
var total = ctx.contextWindow || 200000;
|
|
var used = ctx.usedTokens || 0;
|
|
var pct = total > 0 ? Math.round(used / total * 100) : 0;
|
|
|
|
var cats = [
|
|
{ key: 'system', tokens: bd.system },
|
|
{ key: 'coreMemory', tokens: bd.coreMemory },
|
|
{ key: 'tools', tokens: bd.tools },
|
|
{ key: 'messages', tokens: bd.messages },
|
|
{ key: 'summary', tokens: bd.summaryMemory }
|
|
].filter(function(c) { return c.tokens > 0; });
|
|
|
|
var freeTokens = Math.max(0, total - used);
|
|
|
|
var BLOCK_COUNT = 480;
|
|
var tokensPerBlock = total / BLOCK_COUNT;
|
|
var blocks = [];
|
|
|
|
cats.forEach(function(cat) {
|
|
var count = Math.round(cat.tokens / tokensPerBlock);
|
|
if (count < 1 && cat.tokens > 0) count = 1;
|
|
for (var i = 0; i < count && blocks.length < BLOCK_COUNT; i++) {
|
|
blocks.push(cat.key);
|
|
}
|
|
});
|
|
while (blocks.length < BLOCK_COUNT) blocks.push('free');
|
|
|
|
var gridHtml = '';
|
|
for (var i = 0; i < blocks.length; i++) {
|
|
var key = blocks[i];
|
|
var color = CTX_COLORS[key] || CTX_COLORS.free;
|
|
var sym = CTX_SYMBOLS[key] || CTX_SYMBOLS.free;
|
|
var enterDelay = (i * 3);
|
|
var isActive = key !== 'free';
|
|
var opacity = isActive ? '1' : '0.25';
|
|
// Stagger idle pulse randomly so they don't sync
|
|
var pulseDelay = isActive ? (Math.random() * 3).toFixed(2) + 's' : '0s';
|
|
var cls = isActive ? 'block alive' : 'block';
|
|
gridHtml += '<div class="' + cls + '" data-cat="' + key + '" style="color:' + color + ';--target-opacity:' + opacity + ';--enter-delay:' + enterDelay + 'ms;--pulse-delay:' + pulseDelay + ';animation-delay:' + enterDelay + 'ms,' + pulseDelay + '">' + sym + '</div>';
|
|
}
|
|
gridEl.innerHTML = gridHtml;
|
|
|
|
// Hover: glow ALL blocks of the same category, dim everything else
|
|
var allBlockEls;
|
|
gridEl.addEventListener('mouseover', function(ev) {
|
|
var block = ev.target.closest('.block');
|
|
if (!block) return;
|
|
var cat = block.dataset.cat;
|
|
if (!cat) return;
|
|
if (!allBlockEls) allBlockEls = Array.prototype.slice.call(gridEl.querySelectorAll('.block'));
|
|
for (var i = 0; i < allBlockEls.length; i++) {
|
|
var b = allBlockEls[i];
|
|
if (b.dataset.cat === cat) {
|
|
b.classList.add('glow');
|
|
b.classList.remove('dim');
|
|
} else {
|
|
b.classList.add('dim');
|
|
b.classList.remove('glow');
|
|
}
|
|
}
|
|
// Highlight matching sidebar legend item
|
|
var legendItems = sidebarEl.querySelectorAll('.ctx-legend-item[data-legend]');
|
|
for (var j = 0; j < legendItems.length; j++) {
|
|
if (legendItems[j].dataset.legend === cat) {
|
|
legendItems[j].classList.add('legend-glow');
|
|
legendItems[j].classList.remove('legend-dim');
|
|
} else {
|
|
legendItems[j].classList.add('legend-dim');
|
|
legendItems[j].classList.remove('legend-glow');
|
|
}
|
|
}
|
|
});
|
|
gridEl.addEventListener('mouseleave', function() {
|
|
if (!allBlockEls) return;
|
|
for (var i = 0; i < allBlockEls.length; i++) {
|
|
allBlockEls[i].classList.remove('glow', 'dim');
|
|
}
|
|
var legendItems = sidebarEl.querySelectorAll('.ctx-legend-item[data-legend]');
|
|
for (var j = 0; j < legendItems.length; j++) {
|
|
legendItems[j].classList.remove('legend-glow', 'legend-dim');
|
|
}
|
|
});
|
|
|
|
// Sidebar
|
|
function fmtK(n) { return n >= 1000 ? Math.round(n / 1000) + 'k' : String(n); }
|
|
var barColor = pct > 90 ? 'rgb(224, 80, 80)' : pct > 70 ? 'rgb(224, 160, 64)' : 'var(--accent)';
|
|
var agentName = DATA.agent.name || 'Agent';
|
|
var modelDisplay = ctx.model && ctx.model !== 'unknown' ? ctx.model : '';
|
|
|
|
var sHtml = '';
|
|
sHtml += '<div class="ctx-model">' + escHtml(agentName) + '</div>';
|
|
if (modelDisplay) sHtml += '<div class="ctx-model-sub">' + escHtml(modelDisplay) + '</div>';
|
|
sHtml += '<div class="ctx-usage-label">' + fmtK(used) + '/' + fmtK(total) + ' tokens (' + pct + '%)</div>';
|
|
sHtml += '<div class="ctx-bar-track"><div class="ctx-bar-fill" id="ctx-bar-fill" style="width:0%;background:' + barColor + '"></div></div>';
|
|
sHtml += '<div class="ctx-bar-text">' + (total - used > 0 ? fmtK(total - used) + ' tokens remaining' : 'Context full') + '</div>';
|
|
|
|
sHtml += '<div class="ctx-legend-title">Estimated usage</div>';
|
|
cats.forEach(function(cat) {
|
|
var cpct = total > 0 ? (cat.tokens / total * 100).toFixed(1) : '0';
|
|
var tabLink = cat.key === 'coreMemory' ? 'core' : '';
|
|
sHtml += '<div class="ctx-legend-item" data-legend="' + cat.key + '"' + (tabLink ? ' data-tab-link="' + tabLink + '"' : '') + '>';
|
|
sHtml += '<span class="ctx-legend-sym" style="color:' + CTX_COLORS[cat.key] + '">' + CTX_SYMBOLS[cat.key] + '</span>';
|
|
sHtml += '<span class="ctx-legend-name">' + CTX_LABELS[cat.key] + (tabLink ? '<span class="ctx-legend-arrow">\u2192</span>' : '') + '</span>';
|
|
sHtml += '<span class="ctx-legend-value">' + fmtK(cat.tokens) + ' (' + cpct + '%)</span>';
|
|
sHtml += '</div>';
|
|
});
|
|
if (freeTokens > 0) {
|
|
var fpct = (freeTokens / total * 100).toFixed(1);
|
|
sHtml += '<div class="ctx-legend-item" data-legend="free">';
|
|
sHtml += '<span class="ctx-legend-sym" style="color:var(--text-dim);opacity:0.4">' + CTX_SYMBOLS.free + '</span>';
|
|
sHtml += '<span class="ctx-legend-name">Free</span>';
|
|
sHtml += '<span class="ctx-legend-value">' + fmtK(freeTokens) + ' (' + fpct + '%)</span>';
|
|
sHtml += '</div>';
|
|
}
|
|
|
|
sidebarEl.innerHTML = sHtml;
|
|
|
|
// Click on legend items with tab links
|
|
sidebarEl.addEventListener('click', function(ev) {
|
|
var item = ev.target.closest('[data-tab-link]');
|
|
if (item) activateTab(item.dataset.tabLink);
|
|
});
|
|
|
|
setTimeout(function() {
|
|
var bar = document.getElementById('ctx-bar-fill');
|
|
if (bar) bar.style.width = pct + '%';
|
|
}, 100);
|
|
}
|
|
|
|
renderContextTab();
|
|
|
|
// Tabs
|
|
var tabs = Array.prototype.slice.call(document.querySelectorAll('.tab'));
|
|
function activateTab(name) {
|
|
tabs.forEach(function(el) {
|
|
el.classList.toggle('active', el.dataset.tab === name);
|
|
});
|
|
document.getElementById('tab-context').style.display = name === 'context' ? '' : 'none';
|
|
document.getElementById('tab-core').style.display = name === 'core' ? '' : 'none';
|
|
document.getElementById('tab-external').style.display = name === 'external' ? '' : 'none';
|
|
document.getElementById('tab-history').style.display = name === 'history' ? '' : 'none';
|
|
location.hash = name;
|
|
}
|
|
tabs.forEach(function(el) {
|
|
el.addEventListener('click', function() { activateTab(el.dataset.tab); });
|
|
});
|
|
|
|
// Build file tree (from path segments)
|
|
function buildTree(files) {
|
|
var root = { dirs: Object.create(null), files: [] };
|
|
files.forEach(function(file) {
|
|
var parts = String(file.path || '').split(/[\\/]/);
|
|
var node = root;
|
|
for (var i = 0; i < parts.length - 1; i++) {
|
|
var dir = parts[i];
|
|
if (!node.dirs[dir]) node.dirs[dir] = { dirs: Object.create(null), files: [] };
|
|
node = node.dirs[dir];
|
|
}
|
|
node.files.push(file);
|
|
});
|
|
return root;
|
|
}
|
|
|
|
var allFiles = DATA.files || [];
|
|
var coreFiles = allFiles.filter(function(f) { return f.isSystem; });
|
|
var externalFiles = allFiles.filter(function(f) { return !f.isSystem; });
|
|
|
|
// Strip system/ prefix for core files display
|
|
var coreFilesStripped = coreFiles.map(function(f) {
|
|
var stripped = String(f.path).replace(/^system[\\/]/, '');
|
|
return { path: stripped, origPath: f.path, isSystem: f.isSystem, frontmatter: f.frontmatter, content: f.content };
|
|
});
|
|
|
|
var coreTreeRoot = buildTree(coreFilesStripped);
|
|
var externalTreeRoot = buildTree(externalFiles);
|
|
|
|
var treePanelCore = document.getElementById('tree-panel-core');
|
|
var filePanelCore = document.getElementById('file-panel-core');
|
|
var treePanelExternal = document.getElementById('tree-panel-external');
|
|
var filePanelExternal = document.getElementById('file-panel-external');
|
|
|
|
// Set tab counts
|
|
document.getElementById('tab-core-label').textContent = 'Core Memory (' + coreFiles.length + ')';
|
|
document.getElementById('tab-external-label').textContent = 'External Memory (' + externalFiles.length + ')';
|
|
var fileMap = Object.create(null);
|
|
(DATA.files || []).forEach(function(f) { fileMap[f.path] = f; });
|
|
|
|
function countFiles(node) {
|
|
var count = node.files.length;
|
|
var dirs = Object.keys(node.dirs);
|
|
for (var i = 0; i < dirs.length; i++) {
|
|
count += countFiles(node.dirs[dirs[i]]);
|
|
}
|
|
return count;
|
|
}
|
|
|
|
function renderTree(node, depth, prefix) {
|
|
var html = '';
|
|
var dirs = Object.keys(node.dirs).sort(function(a, b) {
|
|
if (a === 'system') return -1;
|
|
if (b === 'system') return 1;
|
|
return a.localeCompare(b);
|
|
});
|
|
var inSystem = prefix === 'system' || (prefix && prefix.indexOf('system/') === 0);
|
|
|
|
dirs.forEach(function(dir) {
|
|
var fullDir = prefix ? prefix + '/' + dir : dir;
|
|
var isSystemRoot = fullDir === 'system';
|
|
var isUnderSystem = isSystemRoot || fullDir.indexOf('system/') === 0;
|
|
var sysCls = isUnderSystem ? ' system-child' : '';
|
|
var fileCount = countFiles(node.dirs[dir]);
|
|
html += '<div class="tree-item dir' + sysCls + '" data-dir="' + escAttr(fullDir) + '">';
|
|
html += escHtml(dir) + '/';
|
|
html += '<span class="dir-count">' + fileCount + '</span>';
|
|
html += '</div>';
|
|
html += '<div class="tree-children" data-parent="' + escAttr(fullDir) + '">';
|
|
html += renderTree(node.dirs[dir], depth + 1, fullDir);
|
|
html += '</div>';
|
|
});
|
|
|
|
node.files.sort(function(a, b) { return String(a.path).localeCompare(String(b.path)); });
|
|
node.files.forEach(function(file) {
|
|
var name = String(file.path).split(/[\\/]/).pop();
|
|
var displayName = name.replace(/\.md$/, '');
|
|
html += '<div class="tree-item file" data-path="' + escAttr(file.origPath || file.path) + '">';
|
|
var charCount = (file.content || '').length;
|
|
var charLabel = charCount >= 1000 ? Math.round(charCount / 1000) + 'k' : String(charCount);
|
|
html += escHtml(displayName) + ' <span class="file-chars">' + charLabel + ' chars</span>';
|
|
html += '</div>';
|
|
});
|
|
|
|
return html;
|
|
}
|
|
|
|
treePanelCore.innerHTML = '<div class="tree-desc">Memory stored in-context</div>' + renderTree(coreTreeRoot, 0, '');
|
|
treePanelExternal.innerHTML = '<div class="tree-desc">Memory stored out-of-context</div>' + (externalFiles.length > 0 ? renderTree(externalTreeRoot, 0, '') : '<div class="empty" style="padding:16px">No external memory files</div>');
|
|
|
|
// Collapsible directory toggling (both tree panels)
|
|
function attachTreeHandlers(treePanel, filePanel) {
|
|
treePanel.addEventListener('click', function(ev) {
|
|
var dirItem = ev.target.closest('.tree-item.dir[data-dir]');
|
|
if (dirItem) {
|
|
var dirPath = dirItem.dataset.dir;
|
|
var children = treePanel.querySelector('.tree-children[data-parent="' + dirPath + '"]');
|
|
if (children) {
|
|
var isCollapsed = children.classList.contains('hidden');
|
|
children.classList.toggle('hidden');
|
|
dirItem.classList.toggle('collapsed', !isCollapsed);
|
|
}
|
|
return;
|
|
}
|
|
});
|
|
}
|
|
attachTreeHandlers(treePanelCore, filePanelCore);
|
|
attachTreeHandlers(treePanelExternal, filePanelExternal);
|
|
|
|
// Build per-file last-commit index from commit data
|
|
var fileLastCommit = Object.create(null);
|
|
if (DATA.commits) {
|
|
for (var ci = 0; ci < DATA.commits.length; ci++) {
|
|
var c = DATA.commits[ci];
|
|
if (!c.stats) continue;
|
|
var statLines = c.stats.split('\n');
|
|
for (var si = 0; si < statLines.length; si++) {
|
|
var fm = /^\s*(.+?)\s*\|/.exec(statLines[si]);
|
|
if (fm) {
|
|
var fPath = fm[1].trim();
|
|
// Handle renames: "{old => new}" patterns
|
|
if (fPath.indexOf('=>') !== -1) {
|
|
var rm = /=> (.+?)\}/.exec(fPath);
|
|
if (rm) {
|
|
var afterArrow = rm[1];
|
|
var braceStart = fPath.indexOf('{');
|
|
var braceEnd = fPath.indexOf('}');
|
|
if (braceStart !== -1 && braceEnd !== -1) {
|
|
fPath = fPath.slice(0, braceStart) + afterArrow + fPath.slice(braceEnd + 1);
|
|
}
|
|
}
|
|
}
|
|
fPath = fPath.replace(/^\s+|\s+$/g, '');
|
|
if (!fileLastCommit[fPath]) {
|
|
fileLastCommit[fPath] = c;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
var rawMode = false;
|
|
|
|
var activeFilePanel = null;
|
|
|
|
function renderFile(file, targetPanel) {
|
|
var panel = targetPanel || activeFilePanel;
|
|
if (!panel) return;
|
|
var fm = file.frontmatter || {};
|
|
var metaHtml = '';
|
|
var pills = '';
|
|
if (fm.description) pills += '<span class="meta-pill">description: <strong>' + escHtml(fm.description) + '</strong></span>';
|
|
if (fm.limit) pills += '<span class="meta-pill">limit: <strong>' + escHtml(fm.limit) + '</strong></span>';
|
|
if (fm.read_only) pills += '<span class="meta-pill">read_only: <strong>' + escHtml(fm.read_only) + '</strong></span>';
|
|
// Last commit bar
|
|
var lc = fileLastCommit[file.path];
|
|
var lcHtml = '';
|
|
if (lc) {
|
|
var lcTitle = String(lc.message || '').split('\n')[0];
|
|
var lcDiffStats = parseDiffStats(lc.stats);
|
|
lcHtml = '<div class="file-last-commit" data-commit-hash="' + escAttr(lc.shortHash) + '">';
|
|
lcHtml += '<span class="lc-hash">' + escHtml(lc.shortHash) + '</span>';
|
|
lcHtml += renderDiffBar(lcDiffStats);
|
|
lcHtml += '<span class="lc-msg">' + escHtml(lcTitle) + '</span>';
|
|
lcHtml += '<span class="lc-meta">' + escHtml(relativeTime(lc.date)) + '</span>';
|
|
lcHtml += '</div>';
|
|
}
|
|
|
|
metaHtml += lcHtml;
|
|
metaHtml += '<div class="file-meta">';
|
|
metaHtml += '<button class="raw-toggle' + (rawMode ? ' active' : '') + '">' + (rawMode ? 'Rendered' : 'Raw') + '</button>';
|
|
// file path omitted - already visible from tree selection
|
|
if (pills) metaHtml += '<div class="meta-row">' + pills + '</div>';
|
|
metaHtml += '</div>';
|
|
|
|
panel.innerHTML =
|
|
metaHtml +
|
|
'<article class="file-body" style="' + (rawMode ? 'display:none' : '') + '">' + renderMarkdown(file.content || '') + '</article>' +
|
|
'<pre class="file-body-raw" style="' + (rawMode ? 'display:block' : '') + '">' + escHtml(file.content || '') + '</pre>';
|
|
|
|
panel.currentFile = file;
|
|
panel.querySelector('.raw-toggle').addEventListener('click', function() {
|
|
rawMode = !rawMode;
|
|
renderFile(panel.currentFile, panel);
|
|
});
|
|
}
|
|
|
|
function attachFileClickHandler(treePanel, filePanel) {
|
|
treePanel.addEventListener('click', function(ev) {
|
|
var node = ev.target.closest('.tree-item.file[data-path]');
|
|
if (!node) return;
|
|
var path = node.dataset.path;
|
|
var file = fileMap[path];
|
|
if (!file) return;
|
|
|
|
Array.prototype.forEach.call(treePanel.querySelectorAll('.tree-item.file'), function(el) {
|
|
el.classList.remove('active');
|
|
});
|
|
node.classList.add('active');
|
|
activeFilePanel = filePanel;
|
|
renderFile(file, filePanel);
|
|
});
|
|
}
|
|
attachFileClickHandler(treePanelCore, filePanelCore);
|
|
attachFileClickHandler(treePanelExternal, filePanelExternal);
|
|
|
|
var firstCore = treePanelCore.querySelector('.tree-item.file[data-path]');
|
|
if (firstCore) firstCore.click();
|
|
var firstExternal = treePanelExternal.querySelector('.tree-item.file[data-path]');
|
|
if (firstExternal) firstExternal.click();
|
|
|
|
// History
|
|
var commits = DATA.commits || [];
|
|
var commitList = document.getElementById('commit-list');
|
|
var showMore = document.getElementById('show-more');
|
|
var historyEmpty = document.getElementById('history-empty');
|
|
var historyCap = document.getElementById('history-cap');
|
|
var visible = 0;
|
|
var STEP = 50;
|
|
|
|
if (DATA.totalCommitCount > commits.length) {
|
|
historyCap.style.display = '';
|
|
historyCap.textContent = 'Showing ' + commits.length + ' most recent commits (' + DATA.totalCommitCount + ' total).';
|
|
}
|
|
|
|
function renderDiff(diff) {
|
|
var lines = String(diff || '').split('\n');
|
|
var out = '';
|
|
var inFile = false;
|
|
|
|
for (var i = 0; i < lines.length; i++) {
|
|
var ln = lines[i];
|
|
if (ln.startsWith('diff --git')) {
|
|
var m = / b\/(.+)$/.exec(ln);
|
|
var fileName = m ? m[1] : ln;
|
|
out += '<div class="diff-file-header"><span class="file-link" data-goto="' + escAttr(fileName) + '">' + escHtml(fileName) + '</span></div>';
|
|
inFile = true;
|
|
continue;
|
|
}
|
|
if (!inFile) continue;
|
|
if (ln.startsWith('@@')) {
|
|
out += '<div class="diff-line hunk">' + escHtml(ln) + '</div>';
|
|
} else if (ln.startsWith('+') && !ln.startsWith('+++')) {
|
|
out += '<div class="diff-line add">' + escHtml(ln) + '</div>';
|
|
} else if (ln.startsWith('-') && !ln.startsWith('---')) {
|
|
out += '<div class="diff-line del">' + escHtml(ln) + '</div>';
|
|
} else if (ln.startsWith('index ') || ln.startsWith('---') || ln.startsWith('+++') || ln.startsWith('new file mode') || ln.startsWith('deleted file mode')) {
|
|
// Skip git metadata lines
|
|
} else {
|
|
out += '<div class="diff-line">' + escHtml(ln) + '</div>';
|
|
}
|
|
}
|
|
|
|
return out;
|
|
}
|
|
|
|
function navigateToFile(filePath) {
|
|
if (fileMap[filePath]) {
|
|
var isCore = fileMap[filePath].isSystem;
|
|
var tp = isCore ? treePanelCore : treePanelExternal;
|
|
activateTab(isCore ? 'core' : 'external');
|
|
var treeItem = tp.querySelector('.tree-item.file[data-path="' + filePath + '"]');
|
|
if (treeItem) {
|
|
var parent = treeItem.parentElement;
|
|
while (parent && parent !== tp) {
|
|
if (parent.classList && parent.classList.contains('hidden')) {
|
|
parent.classList.remove('hidden');
|
|
var dirPath = parent.dataset.parent;
|
|
if (dirPath) {
|
|
var dirItem = tp.querySelector('.tree-item.dir[data-dir="' + dirPath + '"]');
|
|
if (dirItem) dirItem.classList.remove('collapsed');
|
|
}
|
|
}
|
|
parent = parent.parentElement;
|
|
}
|
|
treeItem.click();
|
|
treeItem.scrollIntoView({ block: 'nearest' });
|
|
}
|
|
}
|
|
}
|
|
|
|
function renderStats(statsStr) {
|
|
var lines = statsStr.split('\n');
|
|
var summaryHtml = '';
|
|
var rowsHtml = '';
|
|
for (var i = 0; i < lines.length; i++) {
|
|
var line = lines[i].trim();
|
|
if (!line) continue;
|
|
// Summary line (e.g. "2 files changed, 15 insertions(+), 2 deletions(-)")
|
|
var summaryMatch = /^\d+ files? changed/.exec(line);
|
|
if (summaryMatch) {
|
|
var colored = escHtml(line)
|
|
.replace(/(\d+ insertion[s]?\(\+\))/, '<span class="stat-plus">$1</span>')
|
|
.replace(/(\d+ deletion[s]?\(-\))/, '<span class="stat-minus">$1</span>');
|
|
summaryHtml = '<div class="stat-summary">' + colored + '</div>';
|
|
continue;
|
|
}
|
|
// File stat line (e.g. " src/foo.md | 3 +++")
|
|
var fileMatch = /^\s*(.+?)\s*\|\s*(\d+)\s*(.*)$/.exec(line);
|
|
if (fileMatch) {
|
|
var fName = fileMatch[1].trim();
|
|
var count = fileMatch[2];
|
|
var barChars = fileMatch[3] || '';
|
|
var bar = '';
|
|
for (var j = 0; j < barChars.length; j++) {
|
|
if (barChars[j] === '+') bar += '<span class="stat-plus">+</span>';
|
|
else if (barChars[j] === '-') bar += '<span class="stat-minus">-</span>';
|
|
}
|
|
rowsHtml += '<div class="stat-row">';
|
|
rowsHtml += '<span class="stat-file" data-goto="' + escAttr(fName) + '">' + escHtml(fName) + '</span>';
|
|
rowsHtml += ' | ' + escHtml(count) + ' ' + bar;
|
|
rowsHtml += '</div>';
|
|
continue;
|
|
}
|
|
rowsHtml += '<div>' + escHtml(line) + '</div>';
|
|
}
|
|
return '<div class="commit-stats">' + summaryHtml + rowsHtml + '</div>';
|
|
}
|
|
|
|
// Click handler for file links in stats and diffs
|
|
document.addEventListener('click', function(ev) {
|
|
var link = ev.target.closest('[data-goto]');
|
|
if (link) {
|
|
navigateToFile(link.dataset.goto);
|
|
ev.preventDefault();
|
|
return;
|
|
}
|
|
// Click on last-commit bar -> jump to that commit in history
|
|
var lcBar = ev.target.closest('.file-last-commit[data-commit-hash]');
|
|
if (lcBar) {
|
|
var hash = lcBar.dataset.commitHash;
|
|
activateTab('history');
|
|
// Ensure enough commits are rendered, then find and expand
|
|
setTimeout(function() {
|
|
var commitEl = document.querySelector('.commit-item .commit-hash');
|
|
// Search all rendered commit hashes
|
|
var allHashes = commitList.querySelectorAll('.commit-hash');
|
|
for (var k = 0; k < allHashes.length; k++) {
|
|
if (allHashes[k].textContent.trim() === hash) {
|
|
var item = allHashes[k].closest('.commit-item');
|
|
if (item) {
|
|
item.classList.add('expanded');
|
|
item.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}, 50);
|
|
}
|
|
});
|
|
|
|
function parseDiffStats(statsStr) {
|
|
if (!statsStr) return null;
|
|
var lines = statsStr.split('\n');
|
|
var totalAdd = 0, totalDel = 0;
|
|
for (var i = 0; i < lines.length; i++) {
|
|
var m = /(\d+) insertion/.exec(lines[i]);
|
|
if (m) totalAdd = parseInt(m[1], 10);
|
|
var m2 = /(\d+) deletion/.exec(lines[i]);
|
|
if (m2) totalDel = parseInt(m2[1], 10);
|
|
}
|
|
if (totalAdd === 0 && totalDel === 0) {
|
|
// Try per-file lines: " file | 3 +++"
|
|
for (var j = 0; j < lines.length; j++) {
|
|
var fm = /\|\s*\d+\s*([+-]*)/.exec(lines[j]);
|
|
if (fm && fm[1]) {
|
|
for (var k = 0; k < fm[1].length; k++) {
|
|
if (fm[1][k] === '+') totalAdd++;
|
|
else if (fm[1][k] === '-') totalDel++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (totalAdd === 0 && totalDel === 0) return null;
|
|
return { add: totalAdd, del: totalDel };
|
|
}
|
|
|
|
function renderDiffBar(stats) {
|
|
if (!stats) return '';
|
|
var total = stats.add + stats.del;
|
|
var maxBlocks = 5;
|
|
var addBlocks = total > 0 ? Math.round((stats.add / total) * maxBlocks) : 0;
|
|
var delBlocks = total > 0 ? Math.round((stats.del / total) * maxBlocks) : 0;
|
|
// Ensure at least 1 block for non-zero values
|
|
if (stats.add > 0 && addBlocks === 0) { addBlocks = 1; delBlocks = Math.min(delBlocks, maxBlocks - 1); }
|
|
if (stats.del > 0 && delBlocks === 0) { delBlocks = 1; addBlocks = Math.min(addBlocks, maxBlocks - 1); }
|
|
var neutralBlocks = maxBlocks - addBlocks - delBlocks;
|
|
var html = '<span class="diff-bar">';
|
|
if (stats.add > 0) html += '<span class="db-plus">+' + stats.add + '</span>';
|
|
if (stats.del > 0) html += '<span class="db-minus">-' + stats.del + '</span>';
|
|
html += '<span class="db-blocks">';
|
|
for (var i = 0; i < addBlocks; i++) html += '<span class="db-block add"></span>';
|
|
for (var i = 0; i < delBlocks; i++) html += '<span class="db-block del"></span>';
|
|
for (var i = 0; i < neutralBlocks; i++) html += '<span class="db-block neutral"></span>';
|
|
html += '</span></span>';
|
|
return html;
|
|
}
|
|
|
|
function renderCommitRange(start, end) {
|
|
var html = '';
|
|
for (var i = start; i < end; i++) {
|
|
var c = commits[i];
|
|
var title = String(c.message || '').split('\n')[0];
|
|
html += '<li class="commit-item" data-idx="' + i + '">';
|
|
html += '<div class="commit-head">';
|
|
var diffStats = parseDiffStats(c.stats);
|
|
html += '<div style="display:flex;flex-direction:column;align-items:flex-start;gap:4px">';
|
|
html += '<div class="commit-hash">' + escHtml(c.shortHash || '') + '</div>';
|
|
html += renderDiffBar(diffStats);
|
|
html += '</div>';
|
|
html += '<div class="commit-center">';
|
|
html += '<div class="commit-title">' + escHtml(title);
|
|
if (c.isReflection) html += '<span class="reflection">reflection</span>';
|
|
html += '</div>';
|
|
var bodyLines = String(c.message || '').split('\n');
|
|
var bodyText = bodyLines.length > 1 ? bodyLines.slice(1).join(' ').trim() : '';
|
|
if (bodyText) {
|
|
var preview = bodyText.length > 120 ? bodyText.slice(0, 120) + '...' : bodyText;
|
|
html += '<div class="commit-preview">' + escHtml(preview) + '</div>';
|
|
}
|
|
html += '</div>';
|
|
var authorStr = c.author && c.author.length < 60 ? c.author : '';
|
|
html += '<div class="commit-meta">' + escHtml(authorStr) + '<br>' + escHtml(relativeTime(c.date)) + '</div>';
|
|
html += '</div>';
|
|
|
|
html += '<div class="commit-body">';
|
|
if (bodyText) {
|
|
html += '<div class="commit-full-body">' + escHtml(bodyLines.slice(1).join('\n').trim()) + '</div>';
|
|
}
|
|
if (c.stats) {
|
|
html += renderStats(c.stats);
|
|
}
|
|
if (c.diff) {
|
|
html += renderDiff(c.diff);
|
|
if (c.truncated) html += '<div class="note">[diff truncated]</div>';
|
|
} else if (c.truncated) {
|
|
html += '<div class="note">[diff not available due to payload cap]</div>';
|
|
} else if (i >= 50) {
|
|
html += '<div class="note">[diff not loaded for older commits]</div>';
|
|
}
|
|
html += '</div>';
|
|
html += '</li>';
|
|
}
|
|
return html;
|
|
}
|
|
|
|
function renderMore() {
|
|
var next = Math.min(visible + STEP, commits.length);
|
|
commitList.innerHTML += renderCommitRange(visible, next);
|
|
visible = next;
|
|
|
|
if (visible >= commits.length) {
|
|
showMore.style.display = 'none';
|
|
} else {
|
|
showMore.style.display = '';
|
|
showMore.textContent = 'Show older commits (' + (commits.length - visible) + ' remaining)';
|
|
}
|
|
}
|
|
|
|
if (!commits.length) {
|
|
historyEmpty.style.display = '';
|
|
} else {
|
|
renderMore();
|
|
}
|
|
|
|
showMore.addEventListener('click', renderMore);
|
|
|
|
commitList.addEventListener('click', function(ev) {
|
|
var head = ev.target.closest('.commit-head');
|
|
if (!head) return;
|
|
var item = head.closest('.commit-item');
|
|
if (!item) return;
|
|
item.classList.toggle('expanded');
|
|
});
|
|
|
|
var hash = String(location.hash || '').replace('#', '');
|
|
if (hash === 'context') activateTab('context');
|
|
else if (hash === 'history') activateTab('history');
|
|
else if (hash === 'external') activateTab('external');
|
|
})();
|
|
</script>
|
|
</body>
|
|
</html>
|