cdpearlman commited on
Commit
e1f91c2
·
1 Parent(s): 4416500

Tutorial, feedback, and github buttons added

Browse files
.claude/settings.local.json CHANGED
@@ -12,7 +12,12 @@
12
  "Bash(find /c/Users/cdpea/OneDrive/Documents/GradProject/.context -type f -name *.md)",
13
  "Bash(wc:*)",
14
  "Bash(ls -la /c/Users/cdpea/OneDrive/Documents/GradProject/utils/*.py)",
15
- "WebSearch"
 
 
 
 
 
16
  ]
17
  }
18
  }
 
12
  "Bash(find /c/Users/cdpea/OneDrive/Documents/GradProject/.context -type f -name *.md)",
13
  "Bash(wc:*)",
14
  "Bash(ls -la /c/Users/cdpea/OneDrive/Documents/GradProject/utils/*.py)",
15
+ "WebSearch",
16
+ "Bash(curl -sSL -o /tmp/driver.js.iife.js \"https://cdn.jsdelivr.net/npm/driver.js@1.3.1/dist/driver.js.iife.js\")",
17
+ "Bash(curl -sSL -o /tmp/driver.css \"https://cdn.jsdelivr.net/npm/driver.js@1.3.1/dist/driver.css\")",
18
+ "Read(//tmp/**)",
19
+ "Bash(cp /tmp/driver.js.iife.js \"C:/Users/cdpea/OneDrive/Documents/GradProject/assets/driver.js.iife.js\")",
20
+ "Bash(cp /tmp/driver.css \"C:/Users/cdpea/OneDrive/Documents/GradProject/assets/driver.css\")"
21
  ]
22
  }
23
  }
app.py CHANGED
@@ -71,16 +71,37 @@ app.layout = html.Div([
71
  html.Div([
72
  # Header
73
  html.Div([
 
 
 
 
 
74
  html.Div([
75
  html.H1("Transformer Explanation Dashboard", className="header-title"),
76
  html.P("Understand how transformer models process text and make predictions",
77
  className="header-subtitle")
78
  ], className="header-text"),
79
- html.Button(
80
- [html.I(className="fas fa-book", style={'marginRight': '8px'}), "Glossary"],
81
- id="open-glossary-btn",
82
- className="header-glossary-btn",
83
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
  ], className="header"),
85
 
86
  # Main content area
 
71
  html.Div([
72
  # Header
73
  html.Div([
74
+ html.Div([
75
+ html.Span("Not sure where to start?", className="tutorial-banner-text"),
76
+ html.Button("Use this Tutorial", id="start-tutorial-btn",
77
+ className="tutorial-cta-btn"),
78
+ ], id="tutorial-banner", className="tutorial-banner"),
79
  html.Div([
80
  html.H1("Transformer Explanation Dashboard", className="header-title"),
81
  html.P("Understand how transformer models process text and make predictions",
82
  className="header-subtitle")
83
  ], className="header-text"),
84
+ html.Div([
85
+ html.Button(
86
+ [html.I(className="fas fa-book", style={'marginRight': '8px'}), "Glossary"],
87
+ id="open-glossary-btn",
88
+ className="header-action-btn",
89
+ ),
90
+ html.A(
91
+ [html.I(className="fas fa-comment", style={'marginRight': '8px'}), "Feedback"],
92
+ href="https://forms.gle/r4L65XGP1d6tpZyU8",
93
+ target="_blank",
94
+ rel="noopener noreferrer",
95
+ className="header-action-btn",
96
+ ),
97
+ html.A(
98
+ [html.I(className="fab fa-github", style={'marginRight': '8px'}), "GitHub"],
99
+ href="https://github.com/cdpearlman/LLMVis",
100
+ target="_blank",
101
+ rel="noopener noreferrer",
102
+ className="header-action-btn",
103
+ ),
104
+ ], className="header-actions"),
105
  ], className="header"),
106
 
107
  # Main content area
assets/driver.css ADDED
@@ -0,0 +1 @@
 
 
1
+ .driver-active .driver-overlay,.driver-active *{pointer-events:none}.driver-active .driver-active-element,.driver-active .driver-active-element *,.driver-popover,.driver-popover *{pointer-events:auto}@keyframes animate-fade-in{0%{opacity:0}to{opacity:1}}.driver-fade .driver-overlay{animation:animate-fade-in .2s ease-in-out}.driver-fade .driver-popover{animation:animate-fade-in .2s}.driver-popover{all:unset;box-sizing:border-box;color:#2d2d2d;margin:0;padding:15px;border-radius:5px;min-width:250px;max-width:300px;box-shadow:0 1px 10px #0006;z-index:1000000000;position:fixed;top:0;right:0;background-color:#fff}.driver-popover *{font-family:Helvetica Neue,Inter,ui-sans-serif,"Apple Color Emoji",Helvetica,Arial,sans-serif}.driver-popover-title{font:19px/normal sans-serif;font-weight:700;display:block;position:relative;line-height:1.5;zoom:1;margin:0}.driver-popover-close-btn{all:unset;position:absolute;top:0;right:0;width:32px;height:28px;cursor:pointer;font-size:18px;font-weight:500;color:#d2d2d2;z-index:1;text-align:center;transition:color;transition-duration:.2s}.driver-popover-close-btn:hover,.driver-popover-close-btn:focus{color:#2d2d2d}.driver-popover-title[style*=block]+.driver-popover-description{margin-top:5px}.driver-popover-description{margin-bottom:0;font:14px/normal sans-serif;line-height:1.5;font-weight:400;zoom:1}.driver-popover-footer{margin-top:15px;text-align:right;zoom:1;display:flex;align-items:center;justify-content:space-between}.driver-popover-progress-text{font-size:13px;font-weight:400;color:#727272;zoom:1}.driver-popover-footer button{all:unset;display:inline-block;box-sizing:border-box;padding:3px 7px;text-decoration:none;text-shadow:1px 1px 0 #fff;background-color:#fff;color:#2d2d2d;font:12px/normal sans-serif;cursor:pointer;outline:0;zoom:1;line-height:1.3;border:1px solid #ccc;border-radius:3px}.driver-popover-footer .driver-popover-btn-disabled{opacity:.5;pointer-events:none}:not(body):has(>.driver-active-element){overflow:hidden!important}.driver-no-interaction,.driver-no-interaction *{pointer-events:none!important}.driver-popover-footer button:hover,.driver-popover-footer button:focus{background-color:#f7f7f7}.driver-popover-navigation-btns{display:flex;flex-grow:1;justify-content:flex-end}.driver-popover-navigation-btns button+button{margin-left:4px}.driver-popover-arrow{content:"";position:absolute;border:5px solid #fff}.driver-popover-arrow-side-over{display:none}.driver-popover-arrow-side-left{left:100%;border-right-color:transparent;border-bottom-color:transparent;border-top-color:transparent}.driver-popover-arrow-side-right{right:100%;border-left-color:transparent;border-bottom-color:transparent;border-top-color:transparent}.driver-popover-arrow-side-top{top:100%;border-right-color:transparent;border-bottom-color:transparent;border-left-color:transparent}.driver-popover-arrow-side-bottom{bottom:100%;border-left-color:transparent;border-top-color:transparent;border-right-color:transparent}.driver-popover-arrow-side-center{display:none}.driver-popover-arrow-side-left.driver-popover-arrow-align-start,.driver-popover-arrow-side-right.driver-popover-arrow-align-start{top:15px}.driver-popover-arrow-side-top.driver-popover-arrow-align-start,.driver-popover-arrow-side-bottom.driver-popover-arrow-align-start{left:15px}.driver-popover-arrow-align-end.driver-popover-arrow-side-left,.driver-popover-arrow-align-end.driver-popover-arrow-side-right{bottom:15px}.driver-popover-arrow-side-top.driver-popover-arrow-align-end,.driver-popover-arrow-side-bottom.driver-popover-arrow-align-end{right:15px}.driver-popover-arrow-side-left.driver-popover-arrow-align-center,.driver-popover-arrow-side-right.driver-popover-arrow-align-center{top:50%;margin-top:-5px}.driver-popover-arrow-side-top.driver-popover-arrow-align-center,.driver-popover-arrow-side-bottom.driver-popover-arrow-align-center{left:50%;margin-left:-5px}.driver-popover-arrow-none{display:none}
assets/driver.js.iife.js ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ this.driver=this.driver||{};this.driver.js=function(D){"use strict";let F={};function z(e={}){F={animate:!0,allowClose:!0,overlayOpacity:.7,smoothScroll:!1,disableActiveInteraction:!1,showProgress:!1,stagePadding:10,stageRadius:5,popoverOffset:10,showButtons:["next","previous","close"],disableButtons:[],overlayColor:"#000",...e}}function a(e){return e?F[e]:F}function W(e,o,t,i){return(e/=i/2)<1?t/2*e*e+o:-t/2*(--e*(e-2)-1)+o}function q(e){const o='a[href]:not([disabled]), button:not([disabled]), textarea:not([disabled]), input[type="text"]:not([disabled]), input[type="radio"]:not([disabled]), input[type="checkbox"]:not([disabled]), select:not([disabled])';return e.flatMap(t=>{const i=t.matches(o),p=Array.from(t.querySelectorAll(o));return[...i?[t]:[],...p]}).filter(t=>getComputedStyle(t).pointerEvents!=="none"&&ae(t))}function V(e){if(!e||se(e))return;const o=a("smoothScroll");e.scrollIntoView({behavior:!o||re(e)?"auto":"smooth",inline:"center",block:"center"})}function re(e){if(!e||!e.parentElement)return;const o=e.parentElement;return o.scrollHeight>o.clientHeight}function se(e){const o=e.getBoundingClientRect();return o.top>=0&&o.left>=0&&o.bottom<=(window.innerHeight||document.documentElement.clientHeight)&&o.right<=(window.innerWidth||document.documentElement.clientWidth)}function ae(e){return!!(e.offsetWidth||e.offsetHeight||e.getClientRects().length)}let N={};function b(e,o){N[e]=o}function l(e){return e?N[e]:N}function K(){N={}}let E={};function O(e,o){E[e]=o}function _(e){var o;(o=E[e])==null||o.call(E)}function ce(){E={}}function le(e,o,t,i){let p=l("__activeStagePosition");const n=p||t.getBoundingClientRect(),f=i.getBoundingClientRect(),w=W(e,n.x,f.x-n.x,o),r=W(e,n.y,f.y-n.y,o),v=W(e,n.width,f.width-n.width,o),s=W(e,n.height,f.height-n.height,o);p={x:w,y:r,width:v,height:s},Y(p),b("__activeStagePosition",p)}function X(e){if(!e)return;const o=e.getBoundingClientRect(),t={x:o.x,y:o.y,width:o.width,height:o.height};b("__activeStagePosition",t),Y(t)}function de(){const e=l("__activeStagePosition"),o=l("__overlaySvg");if(!e)return;if(!o){console.warn("No stage svg found.");return}const t=window.innerWidth,i=window.innerHeight;o.setAttribute("viewBox",`0 0 ${t} ${i}`)}function pe(e){const o=ue(e);document.body.appendChild(o),G(o,t=>{t.target.tagName==="path"&&_("overlayClick")}),b("__overlaySvg",o)}function Y(e){const o=l("__overlaySvg");if(!o){pe(e);return}const t=o.firstElementChild;if((t==null?void 0:t.tagName)!=="path")throw new Error("no path element found in stage svg");t.setAttribute("d",j(e))}function ue(e){const o=window.innerWidth,t=window.innerHeight,i=document.createElementNS("http://www.w3.org/2000/svg","svg");i.classList.add("driver-overlay","driver-overlay-animated"),i.setAttribute("viewBox",`0 0 ${o} ${t}`),i.setAttribute("xmlSpace","preserve"),i.setAttribute("xmlnsXlink","http://www.w3.org/1999/xlink"),i.setAttribute("version","1.1"),i.setAttribute("preserveAspectRatio","xMinYMin slice"),i.style.fillRule="evenodd",i.style.clipRule="evenodd",i.style.strokeLinejoin="round",i.style.strokeMiterlimit="2",i.style.zIndex="10000",i.style.position="fixed",i.style.top="0",i.style.left="0",i.style.width="100%",i.style.height="100%";const p=document.createElementNS("http://www.w3.org/2000/svg","path");return p.setAttribute("d",j(e)),p.style.fill=a("overlayColor")||"rgb(0,0,0)",p.style.opacity=`${a("overlayOpacity")}`,p.style.pointerEvents="auto",p.style.cursor="auto",i.appendChild(p),i}function j(e){const o=window.innerWidth,t=window.innerHeight,i=a("stagePadding")||0,p=a("stageRadius")||0,n=e.width+i*2,f=e.height+i*2,w=Math.min(p,n/2,f/2),r=Math.floor(Math.max(w,0)),v=e.x-i+r,s=e.y-i,c=n-r*2,d=f-r*2;return`M${o},0L0,0L0,${t}L${o},${t}L${o},0Z
2
+ M${v},${s} h${c} a${r},${r} 0 0 1 ${r},${r} v${d} a${r},${r} 0 0 1 -${r},${r} h-${c} a${r},${r} 0 0 1 -${r},-${r} v-${d} a${r},${r} 0 0 1 ${r},-${r} z`}function ve(){const e=l("__overlaySvg");e&&e.remove()}function fe(){const e=document.getElementById("driver-dummy-element");if(e)return e;let o=document.createElement("div");return o.id="driver-dummy-element",o.style.width="0",o.style.height="0",o.style.pointerEvents="none",o.style.opacity="0",o.style.position="fixed",o.style.top="50%",o.style.left="50%",document.body.appendChild(o),o}function Q(e){const{element:o}=e;let t=typeof o=="string"?document.querySelector(o):o;t||(t=fe()),ge(t,e)}function he(){const e=l("__activeElement"),o=l("__activeStep");e&&(X(e),de(),oe(e,o))}function ge(e,o){const i=Date.now(),p=l("__activeStep"),n=l("__activeElement")||e,f=!n||n===e,w=e.id==="driver-dummy-element",r=n.id==="driver-dummy-element",v=a("animate"),s=o.onHighlightStarted||a("onHighlightStarted"),c=(o==null?void 0:o.onHighlighted)||a("onHighlighted"),d=(p==null?void 0:p.onDeselected)||a("onDeselected"),m=a(),g=l();!f&&d&&d(r?void 0:n,p,{config:m,state:g}),s&&s(w?void 0:e,o,{config:m,state:g});const u=!f&&v;let h=!1;xe(),b("previousStep",p),b("previousElement",n),b("activeStep",o),b("activeElement",e);const P=()=>{if(l("__transitionCallback")!==P)return;const x=Date.now()-i,y=400-x<=400/2;o.popover&&y&&!h&&u&&(J(e,o),h=!0),a("animate")&&x<400?le(x,400,n,e):(X(e),c&&c(w?void 0:e,o,{config:a(),state:l()}),b("__transitionCallback",void 0),b("__previousStep",p),b("__previousElement",n),b("__activeStep",o),b("__activeElement",e)),window.requestAnimationFrame(P)};b("__transitionCallback",P),window.requestAnimationFrame(P),V(e),!u&&o.popover&&J(e,o),n.classList.remove("driver-active-element","driver-no-interaction"),n.removeAttribute("aria-haspopup"),n.removeAttribute("aria-expanded"),n.removeAttribute("aria-controls"),a("disableActiveInteraction")&&e.classList.add("driver-no-interaction"),e.classList.add("driver-active-element"),e.setAttribute("aria-haspopup","dialog"),e.setAttribute("aria-expanded","true"),e.setAttribute("aria-controls","driver-popover-content")}function we(){var e;(e=document.getElementById("driver-dummy-element"))==null||e.remove(),document.querySelectorAll(".driver-active-element").forEach(o=>{o.classList.remove("driver-active-element","driver-no-interaction"),o.removeAttribute("aria-haspopup"),o.removeAttribute("aria-expanded"),o.removeAttribute("aria-controls")})}function A(){const e=l("__resizeTimeout");e&&window.cancelAnimationFrame(e),b("__resizeTimeout",window.requestAnimationFrame(he))}function me(e){var r;if(!l("isInitialized")||!(e.key==="Tab"||e.keyCode===9))return;const i=l("__activeElement"),p=(r=l("popover"))==null?void 0:r.wrapper,n=q([...p?[p]:[],...i?[i]:[]]),f=n[0],w=n[n.length-1];if(e.preventDefault(),e.shiftKey){const v=n[n.indexOf(document.activeElement)-1]||w;v==null||v.focus()}else{const v=n[n.indexOf(document.activeElement)+1]||f;v==null||v.focus()}}function Z(e){var t;((t=a("allowKeyboardControl"))==null||t)&&(e.key==="Escape"?_("escapePress"):e.key==="ArrowRight"?_("arrowRightPress"):e.key==="ArrowLeft"&&_("arrowLeftPress"))}function G(e,o,t){const i=(n,f)=>{const w=n.target;e.contains(w)&&((!t||t(w))&&(n.preventDefault(),n.stopPropagation(),n.stopImmediatePropagation()),f==null||f(n))};document.addEventListener("pointerdown",i,!0),document.addEventListener("mousedown",i,!0),document.addEventListener("pointerup",i,!0),document.addEventListener("mouseup",i,!0),document.addEventListener("click",n=>{i(n,o)},!0)}function ye(){window.addEventListener("keyup",Z,!1),window.addEventListener("keydown",me,!1),window.addEventListener("resize",A),window.addEventListener("scroll",A)}function be(){window.removeEventListener("keyup",Z),window.removeEventListener("resize",A),window.removeEventListener("scroll",A)}function xe(){const e=l("popover");e&&(e.wrapper.style.display="none")}function J(e,o){var C,y;let t=l("popover");t&&document.body.removeChild(t.wrapper),t=Pe(),document.body.appendChild(t.wrapper);const{title:i,description:p,showButtons:n,disableButtons:f,showProgress:w,nextBtnText:r=a("nextBtnText")||"Next &rarr;",prevBtnText:v=a("prevBtnText")||"&larr; Previous",progressText:s=a("progressText")||"{current} of {total}"}=o.popover||{};t.nextButton.innerHTML=r,t.previousButton.innerHTML=v,t.progress.innerHTML=s,i?(t.title.innerHTML=i,t.title.style.display="block"):t.title.style.display="none",p?(t.description.innerHTML=p,t.description.style.display="block"):t.description.style.display="none";const c=n||a("showButtons"),d=w||a("showProgress")||!1,m=(c==null?void 0:c.includes("next"))||(c==null?void 0:c.includes("previous"))||d;t.closeButton.style.display=c.includes("close")?"block":"none",m?(t.footer.style.display="flex",t.progress.style.display=d?"block":"none",t.nextButton.style.display=c.includes("next")?"block":"none",t.previousButton.style.display=c.includes("previous")?"block":"none"):t.footer.style.display="none";const g=f||a("disableButtons")||[];g!=null&&g.includes("next")&&(t.nextButton.disabled=!0,t.nextButton.classList.add("driver-popover-btn-disabled")),g!=null&&g.includes("previous")&&(t.previousButton.disabled=!0,t.previousButton.classList.add("driver-popover-btn-disabled")),g!=null&&g.includes("close")&&(t.closeButton.disabled=!0,t.closeButton.classList.add("driver-popover-btn-disabled"));const u=t.wrapper;u.style.display="block",u.style.left="",u.style.top="",u.style.bottom="",u.style.right="",u.id="driver-popover-content",u.setAttribute("role","dialog"),u.setAttribute("aria-labelledby","driver-popover-title"),u.setAttribute("aria-describedby","driver-popover-description");const h=t.arrow;h.className="driver-popover-arrow";const P=((C=o.popover)==null?void 0:C.popoverClass)||a("popoverClass")||"";u.className=`driver-popover ${P}`.trim(),G(t.wrapper,k=>{var M,R,I;const T=k.target,H=((M=o.popover)==null?void 0:M.onNextClick)||a("onNextClick"),$=((R=o.popover)==null?void 0:R.onPrevClick)||a("onPrevClick"),B=((I=o.popover)==null?void 0:I.onCloseClick)||a("onCloseClick");if(T.classList.contains("driver-popover-next-btn"))return H?H(e,o,{config:a(),state:l()}):_("nextClick");if(T.classList.contains("driver-popover-prev-btn"))return $?$(e,o,{config:a(),state:l()}):_("prevClick");if(T.classList.contains("driver-popover-close-btn"))return B?B(e,o,{config:a(),state:l()}):_("closeClick")},k=>!(t!=null&&t.description.contains(k))&&!(t!=null&&t.title.contains(k))&&typeof k.className=="string"&&k.className.includes("driver-popover")),b("popover",t);const S=((y=o.popover)==null?void 0:y.onPopoverRender)||a("onPopoverRender");S&&S(t,{config:a(),state:l()}),oe(e,o),V(u);const L=e.classList.contains("driver-dummy-element"),x=q([u,...L?[]:[e]]);x.length>0&&x[0].focus()}function U(){const e=l("popover");if(!(e!=null&&e.wrapper))return;const o=e.wrapper.getBoundingClientRect(),t=a("stagePadding")||0,i=a("popoverOffset")||0;return{width:o.width+t+i,height:o.height+t+i,realWidth:o.width,realHeight:o.height}}function ee(e,o){const{elementDimensions:t,popoverDimensions:i,popoverPadding:p,popoverArrowDimensions:n}=o;return e==="start"?Math.max(Math.min(t.top-p,window.innerHeight-i.realHeight-n.width),n.width):e==="end"?Math.max(Math.min(t.top-(i==null?void 0:i.realHeight)+t.height+p,window.innerHeight-(i==null?void 0:i.realHeight)-n.width),n.width):e==="center"?Math.max(Math.min(t.top+t.height/2-(i==null?void 0:i.realHeight)/2,window.innerHeight-(i==null?void 0:i.realHeight)-n.width),n.width):0}function te(e,o){const{elementDimensions:t,popoverDimensions:i,popoverPadding:p,popoverArrowDimensions:n}=o;return e==="start"?Math.max(Math.min(t.left-p,window.innerWidth-i.realWidth-n.width),n.width):e==="end"?Math.max(Math.min(t.left-(i==null?void 0:i.realWidth)+t.width+p,window.innerWidth-(i==null?void 0:i.realWidth)-n.width),n.width):e==="center"?Math.max(Math.min(t.left+t.width/2-(i==null?void 0:i.realWidth)/2,window.innerWidth-(i==null?void 0:i.realWidth)-n.width),n.width):0}function oe(e,o){const t=l("popover");if(!t)return;const{align:i="start",side:p="left"}=(o==null?void 0:o.popover)||{},n=i,f=e.id==="driver-dummy-element"?"over":p,w=a("stagePadding")||0,r=U(),v=t.arrow.getBoundingClientRect(),s=e.getBoundingClientRect(),c=s.top-r.height;let d=c>=0;const m=window.innerHeight-(s.bottom+r.height);let g=m>=0;const u=s.left-r.width;let h=u>=0;const P=window.innerWidth-(s.right+r.width);let S=P>=0;const L=!d&&!g&&!h&&!S;let x=f;if(f==="top"&&d?S=h=g=!1:f==="bottom"&&g?S=h=d=!1:f==="left"&&h?S=d=g=!1:f==="right"&&S&&(h=d=g=!1),f==="over"){const C=window.innerWidth/2-r.realWidth/2,y=window.innerHeight/2-r.realHeight/2;t.wrapper.style.left=`${C}px`,t.wrapper.style.right="auto",t.wrapper.style.top=`${y}px`,t.wrapper.style.bottom="auto"}else if(L){const C=window.innerWidth/2-(r==null?void 0:r.realWidth)/2,y=10;t.wrapper.style.left=`${C}px`,t.wrapper.style.right="auto",t.wrapper.style.bottom=`${y}px`,t.wrapper.style.top="auto"}else if(h){const C=Math.min(u,window.innerWidth-(r==null?void 0:r.realWidth)-v.width),y=ee(n,{elementDimensions:s,popoverDimensions:r,popoverPadding:w,popoverArrowDimensions:v});t.wrapper.style.left=`${C}px`,t.wrapper.style.top=`${y}px`,t.wrapper.style.bottom="auto",t.wrapper.style.right="auto",x="left"}else if(S){const C=Math.min(P,window.innerWidth-(r==null?void 0:r.realWidth)-v.width),y=ee(n,{elementDimensions:s,popoverDimensions:r,popoverPadding:w,popoverArrowDimensions:v});t.wrapper.style.right=`${C}px`,t.wrapper.style.top=`${y}px`,t.wrapper.style.bottom="auto",t.wrapper.style.left="auto",x="right"}else if(d){const C=Math.min(c,window.innerHeight-r.realHeight-v.width);let y=te(n,{elementDimensions:s,popoverDimensions:r,popoverPadding:w,popoverArrowDimensions:v});t.wrapper.style.top=`${C}px`,t.wrapper.style.left=`${y}px`,t.wrapper.style.bottom="auto",t.wrapper.style.right="auto",x="top"}else if(g){const C=Math.min(m,window.innerHeight-(r==null?void 0:r.realHeight)-v.width);let y=te(n,{elementDimensions:s,popoverDimensions:r,popoverPadding:w,popoverArrowDimensions:v});t.wrapper.style.left=`${y}px`,t.wrapper.style.bottom=`${C}px`,t.wrapper.style.top="auto",t.wrapper.style.right="auto",x="bottom"}L?t.arrow.classList.add("driver-popover-arrow-none"):Ce(n,x,e)}function Ce(e,o,t){const i=l("popover");if(!i)return;const p=t.getBoundingClientRect(),n=U(),f=i.arrow,w=n.width,r=window.innerWidth,v=p.width,s=p.left,c=n.height,d=window.innerHeight,m=p.top,g=p.height;f.className="driver-popover-arrow";let u=o,h=e;o==="top"?(s+v<=0?(u="right",h="end"):s+v-w<=0&&(u="top",h="start"),s>=r?(u="left",h="end"):s+w>=r&&(u="top",h="end")):o==="bottom"?(s+v<=0?(u="right",h="start"):s+v-w<=0&&(u="bottom",h="start"),s>=r?(u="left",h="start"):s+w>=r&&(u="bottom",h="end")):o==="left"?(m+g<=0?(u="bottom",h="end"):m+g-c<=0&&(u="left",h="start"),m>=d?(u="top",h="end"):m+c>=d&&(u="left",h="end")):o==="right"&&(m+g<=0?(u="bottom",h="start"):m+g-c<=0&&(u="right",h="start"),m>=d?(u="top",h="start"):m+c>=d&&(u="right",h="end")),u?(f.classList.add(`driver-popover-arrow-side-${u}`),f.classList.add(`driver-popover-arrow-align-${h}`)):f.classList.add("driver-popover-arrow-none")}function Pe(){const e=document.createElement("div");e.classList.add("driver-popover");const o=document.createElement("div");o.classList.add("driver-popover-arrow");const t=document.createElement("header");t.id="driver-popover-title",t.classList.add("driver-popover-title"),t.style.display="none",t.innerText="Popover Title";const i=document.createElement("div");i.id="driver-popover-description",i.classList.add("driver-popover-description"),i.style.display="none",i.innerText="Popover description is here";const p=document.createElement("button");p.type="button",p.classList.add("driver-popover-close-btn"),p.setAttribute("aria-label","Close"),p.innerHTML="&times;";const n=document.createElement("footer");n.classList.add("driver-popover-footer");const f=document.createElement("span");f.classList.add("driver-popover-progress-text"),f.innerText="";const w=document.createElement("span");w.classList.add("driver-popover-navigation-btns");const r=document.createElement("button");r.type="button",r.classList.add("driver-popover-prev-btn"),r.innerHTML="&larr; Previous";const v=document.createElement("button");return v.type="button",v.classList.add("driver-popover-next-btn"),v.innerHTML="Next &rarr;",w.appendChild(r),w.appendChild(v),n.appendChild(f),n.appendChild(w),e.appendChild(p),e.appendChild(o),e.appendChild(t),e.appendChild(i),e.appendChild(n),{wrapper:e,arrow:o,title:t,description:i,footer:n,previousButton:r,nextButton:v,closeButton:p,footerButtons:w,progress:f}}function Se(){var o;const e=l("popover");e&&((o=e.wrapper.parentElement)==null||o.removeChild(e.wrapper))}const Le="";function ke(e={}){z(e);function o(){a("allowClose")&&v()}function t(){const s=l("activeIndex"),c=a("steps")||[];if(typeof s=="undefined")return;const d=s+1;c[d]?r(d):v()}function i(){const s=l("activeIndex"),c=a("steps")||[];if(typeof s=="undefined")return;const d=s-1;c[d]?r(d):v()}function p(s){(a("steps")||[])[s]?r(s):v()}function n(){var h;if(l("__transitionCallback"))return;const c=l("activeIndex"),d=l("__activeStep"),m=l("__activeElement");if(typeof c=="undefined"||typeof d=="undefined"||typeof l("activeIndex")=="undefined")return;const u=((h=d.popover)==null?void 0:h.onPrevClick)||a("onPrevClick");if(u)return u(m,d,{config:a(),state:l()});i()}function f(){var u;if(l("__transitionCallback"))return;const c=l("activeIndex"),d=l("__activeStep"),m=l("__activeElement");if(typeof c=="undefined"||typeof d=="undefined")return;const g=((u=d.popover)==null?void 0:u.onNextClick)||a("onNextClick");if(g)return g(m,d,{config:a(),state:l()});t()}function w(){l("isInitialized")||(b("isInitialized",!0),document.body.classList.add("driver-active",a("animate")?"driver-fade":"driver-simple"),ye(),O("overlayClick",o),O("escapePress",o),O("arrowLeftPress",n),O("arrowRightPress",f))}function r(s=0){var H,$,B,M,R,I,ie,ne;const c=a("steps");if(!c){console.error("No steps to drive through"),v();return}if(!c[s]){v();return}b("__activeOnDestroyed",document.activeElement),b("activeIndex",s);const d=c[s],m=c[s+1],g=c[s-1],u=((H=d.popover)==null?void 0:H.doneBtnText)||a("doneBtnText")||"Done",h=a("allowClose"),P=typeof(($=d.popover)==null?void 0:$.showProgress)!="undefined"?(B=d.popover)==null?void 0:B.showProgress:a("showProgress"),L=(((M=d.popover)==null?void 0:M.progressText)||a("progressText")||"{{current}} of {{total}}").replace("{{current}}",`${s+1}`).replace("{{total}}",`${c.length}`),x=((R=d.popover)==null?void 0:R.showButtons)||a("showButtons"),C=["next","previous",...h?["close"]:[]].filter(_e=>!(x!=null&&x.length)||x.includes(_e)),y=((I=d.popover)==null?void 0:I.onNextClick)||a("onNextClick"),k=((ie=d.popover)==null?void 0:ie.onPrevClick)||a("onPrevClick"),T=((ne=d.popover)==null?void 0:ne.onCloseClick)||a("onCloseClick");Q({...d,popover:{showButtons:C,nextBtnText:m?void 0:u,disableButtons:[...g?[]:["previous"]],showProgress:P,progressText:L,onNextClick:y||(()=>{m?r(s+1):v()}),onPrevClick:k||(()=>{r(s-1)}),onCloseClick:T||(()=>{v()}),...(d==null?void 0:d.popover)||{}}})}function v(s=!0){const c=l("__activeElement"),d=l("__activeStep"),m=l("__activeOnDestroyed"),g=a("onDestroyStarted");if(s&&g){const P=!c||(c==null?void 0:c.id)==="driver-dummy-element";g(P?void 0:c,d,{config:a(),state:l()});return}const u=(d==null?void 0:d.onDeselected)||a("onDeselected"),h=a("onDestroyed");if(document.body.classList.remove("driver-active","driver-fade","driver-simple"),be(),Se(),we(),ve(),ce(),K(),c&&d){const P=c.id==="driver-dummy-element";u&&u(P?void 0:c,d,{config:a(),state:l()}),h&&h(P?void 0:c,d,{config:a(),state:l()})}m&&m.focus()}return{isActive:()=>l("isInitialized")||!1,refresh:A,drive:(s=0)=>{w(),r(s)},setConfig:z,setSteps:s=>{K(),z({...a(),steps:s})},getConfig:a,getState:l,getActiveIndex:()=>l("activeIndex"),isFirstStep:()=>l("activeIndex")===0,isLastStep:()=>{const s=a("steps")||[],c=l("activeIndex");return c!==void 0&&c===s.length-1},getActiveStep:()=>l("activeStep"),getActiveElement:()=>l("activeElement"),getPreviousElement:()=>l("previousElement"),getPreviousStep:()=>l("previousStep"),moveNext:t,movePrevious:i,moveTo:p,hasNextStep:()=>{const s=a("steps")||[],c=l("activeIndex");return c!==void 0&&s[c+1]},hasPreviousStep:()=>{const s=a("steps")||[],c=l("activeIndex");return c!==void 0&&s[c-1]},highlight:s=>{w(),Q({...s,popover:s.popover?{showButtons:[],showProgress:!1,progressText:"",...s.popover}:void 0})},destroy:()=>{v(!1)}}}return D.driver=ke,Object.defineProperty(D,Symbol.toStringTag,{value:"Module"}),D}({});
assets/style.css CHANGED
@@ -52,7 +52,14 @@ body {
52
  opacity: 0.9;
53
  }
54
 
55
- .header-glossary-btn {
 
 
 
 
 
 
 
56
  background: rgba(255, 255, 255, 0.15);
57
  color: white;
58
  border: 1px solid rgba(255, 255, 255, 0.4);
@@ -63,10 +70,16 @@ body {
63
  white-space: nowrap;
64
  transition: background 0.2s;
65
  flex-shrink: 0;
 
 
 
 
66
  }
67
 
68
- .header-glossary-btn:hover {
69
  background: rgba(255, 255, 255, 0.3);
 
 
70
  }
71
 
72
  /* Content container */
@@ -1652,4 +1665,144 @@ details[open].transformer-layers-container .transformer-layers-summary::before {
1652
  .chat-suggestion-chip:hover {
1653
  background-color: #667eea;
1654
  color: white;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1655
  }
 
52
  opacity: 0.9;
53
  }
54
 
55
+ .header-actions {
56
+ display: flex;
57
+ gap: 0.5rem;
58
+ align-items: center;
59
+ flex-shrink: 0;
60
+ }
61
+
62
+ .header-action-btn {
63
  background: rgba(255, 255, 255, 0.15);
64
  color: white;
65
  border: 1px solid rgba(255, 255, 255, 0.4);
 
70
  white-space: nowrap;
71
  transition: background 0.2s;
72
  flex-shrink: 0;
73
+ text-decoration: none;
74
+ display: inline-flex;
75
+ align-items: center;
76
+ line-height: 1;
77
  }
78
 
79
+ .header-action-btn:hover {
80
  background: rgba(255, 255, 255, 0.3);
81
+ color: white;
82
+ text-decoration: none;
83
  }
84
 
85
  /* Content container */
 
1665
  .chat-suggestion-chip:hover {
1666
  background-color: #667eea;
1667
  color: white;
1668
+ }
1669
+
1670
+ /* ============================================================================
1671
+ Tutorial (Driver.js) — banner in header + theme overrides
1672
+ ============================================================================ */
1673
+
1674
+ .tutorial-banner {
1675
+ display: flex;
1676
+ align-items: center;
1677
+ gap: 10px;
1678
+ color: white;
1679
+ font-size: 13px;
1680
+ flex-shrink: 0;
1681
+ }
1682
+
1683
+ .tutorial-banner-text {
1684
+ opacity: 0.9;
1685
+ }
1686
+
1687
+ .tutorial-cta-btn {
1688
+ background: white;
1689
+ color: #667eea;
1690
+ border: none;
1691
+ padding: 6px 14px;
1692
+ border-radius: 6px;
1693
+ font-weight: 600;
1694
+ font-size: 13px;
1695
+ cursor: pointer;
1696
+ transition: transform 0.1s ease, box-shadow 0.2s ease;
1697
+ white-space: nowrap;
1698
+ }
1699
+
1700
+ .tutorial-cta-btn:hover {
1701
+ transform: translateY(-1px);
1702
+ box-shadow: 0 4px 10px rgba(0, 0, 0, 0.15);
1703
+ }
1704
+
1705
+ /* Driver.js disables pointer-events on every element while active, which
1706
+ breaks complex components whose menus/portals render outside the
1707
+ highlighted element (e.g. Dash dcc.Dropdown). The overlay itself is the
1708
+ only thing that actually needs to stay click-through; everything else
1709
+ should work exactly as it does outside the tutorial. */
1710
+ .driver-active * {
1711
+ pointer-events: auto !important;
1712
+ }
1713
+ .driver-active .driver-overlay {
1714
+ pointer-events: none !important;
1715
+ }
1716
+
1717
+ /* Driver.js also forces `overflow: hidden` on the direct parent of the
1718
+ highlighted element, which clips floating children like dropdown menus
1719
+ and slider tooltips. Undo it so those components render normally. */
1720
+ :not(body):has(> .driver-active-element) {
1721
+ overflow: visible !important;
1722
+ }
1723
+
1724
+ /* Driver.js popover — match app theme (blue Continue, gray Dismiss) */
1725
+ .driver-popover-title {
1726
+ color: #667eea;
1727
+ font-weight: 600;
1728
+ }
1729
+
1730
+ .driver-popover-next-btn,
1731
+ .driver-popover-done-btn {
1732
+ background: #667eea !important;
1733
+ color: white !important;
1734
+ text-shadow: none !important;
1735
+ border: 1px solid #667eea !important;
1736
+ border-radius: 6px !important;
1737
+ padding: 6px 14px !important;
1738
+ font-weight: 600 !important;
1739
+ }
1740
+
1741
+ .driver-popover-next-btn:hover,
1742
+ .driver-popover-done-btn:hover {
1743
+ background: #5a6fd8 !important;
1744
+ border-color: #5a6fd8 !important;
1745
+ }
1746
+
1747
+ .driver-popover-close-btn {
1748
+ color: #6c757d !important;
1749
+ }
1750
+
1751
+ /* Driver.js Back (previous) button — match the app theme */
1752
+ .driver-popover-prev-btn {
1753
+ background: white !important;
1754
+ color: #667eea !important;
1755
+ text-shadow: none !important;
1756
+ border: 1px solid #667eea !important;
1757
+ border-radius: 6px !important;
1758
+ padding: 6px 14px !important;
1759
+ font-weight: 600 !important;
1760
+ }
1761
+ .driver-popover-prev-btn:hover {
1762
+ background: #f0f3ff !important;
1763
+ }
1764
+
1765
+ /* Floating "Continue Tutorial" pill — appears between steps when the
1766
+ popover/overlay are closed so the user can freely interact with the app. */
1767
+ .tutorial-float-btn {
1768
+ position: fixed;
1769
+ top: 18px;
1770
+ right: 18px;
1771
+ z-index: 99999999;
1772
+ display: none;
1773
+ align-items: center;
1774
+ gap: 10px;
1775
+ padding: 8px 10px 8px 14px;
1776
+ background: #667eea;
1777
+ color: white;
1778
+ border: none;
1779
+ border-radius: 24px;
1780
+ font-weight: 600;
1781
+ font-size: 13px;
1782
+ cursor: pointer;
1783
+ box-shadow: 0 4px 14px rgba(102, 126, 234, 0.45);
1784
+ transition: transform 0.1s ease, box-shadow 0.2s ease;
1785
+ }
1786
+ .tutorial-float-btn:hover {
1787
+ transform: translateY(-1px);
1788
+ box-shadow: 0 6px 20px rgba(102, 126, 234, 0.55);
1789
+ }
1790
+ .tutorial-float-btn-label {
1791
+ line-height: 1;
1792
+ }
1793
+ .tutorial-float-btn-dismiss {
1794
+ display: inline-flex;
1795
+ align-items: center;
1796
+ justify-content: center;
1797
+ width: 20px;
1798
+ height: 20px;
1799
+ border-radius: 50%;
1800
+ background: rgba(255, 255, 255, 0.22);
1801
+ font-size: 11px;
1802
+ line-height: 1;
1803
+ cursor: pointer;
1804
+ transition: background 0.15s ease;
1805
+ }
1806
+ .tutorial-float-btn-dismiss:hover {
1807
+ background: rgba(255, 255, 255, 0.4);
1808
  }
assets/tutorial.js ADDED
@@ -0,0 +1,287 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ (function () {
2
+ const STEP_KEY = 'transformer_tutorial_step';
3
+ const DONE_KEY = 'transformer_tutorial_done';
4
+
5
+ const FOOTER_NORMAL =
6
+ '<br><br><i>Click <b>Continue</b> to close this window and try it yourself. ' +
7
+ 'When you are ready, click <b>Continue Tutorial</b> in the top right to move on ' +
8
+ '— or use <b>Back</b> to revisit the previous step.</i>';
9
+
10
+ const FOOTER_LAST =
11
+ '<br><br><i>Click <b>Finish</b> to close the tutorial — you have seen the whole ' +
12
+ 'pipeline. <b>Back</b> still works if you want to revisit anything.</i>';
13
+
14
+ let currentStep = 0;
15
+ let driverObj = null;
16
+
17
+ function safeGet(key) {
18
+ try { return localStorage.getItem(key); } catch (e) { return null; }
19
+ }
20
+ function safeSet(key, value) {
21
+ try { localStorage.setItem(key, value); } catch (e) { /* ignore */ }
22
+ }
23
+ function safeRemove(key) {
24
+ try { localStorage.removeItem(key); } catch (e) { /* ignore */ }
25
+ }
26
+
27
+ function buildSteps() {
28
+ const raw = [
29
+ {
30
+ element: '#model-dropdown',
31
+ title: 'Step 1 of 12 — Pick a model',
32
+ body:
33
+ 'Everything starts here. Different transformer models have different ' +
34
+ 'sizes, training data, and quirks, so what you see downstream depends on ' +
35
+ 'which one you load.<br><br>' +
36
+ '<b>Recommended:</b> GPT-2 (124M). It is small enough to run quickly on ' +
37
+ 'any machine and well-studied, so the attention patterns you will see ' +
38
+ 'have known interpretations.'
39
+ },
40
+ {
41
+ element: '#prompt-section',
42
+ title: 'Step 2 of 12 — Give it something to predict',
43
+ body:
44
+ 'The prompt is what the model tries to continue. The four buttons above ' +
45
+ 'load prompts chosen to activate specific, studyable behaviors.<br><br>' +
46
+ '<b>Recommended:</b> click <b>"Understand repetition"</b>. Its prompt ' +
47
+ '("The cat sat on the mat. The cat sat on the") triggers <i>induction ' +
48
+ 'heads</i> — attention detectors that complete repeated patterns. We will ' +
49
+ 'come back to those in the ablation step.'
50
+ },
51
+ {
52
+ element: '#max-new-tokens-slider',
53
+ title: 'Step 3 of 12 — How many words to generate',
54
+ body:
55
+ 'This controls how far the model extends the prompt. One word is enough ' +
56
+ 'to see the whole pipeline in action — every extra word makes the ' +
57
+ 'visualizations busier without adding new concepts.<br><br>' +
58
+ '<b>Leave this at 1</b> for your first run.'
59
+ },
60
+ {
61
+ element: '#beam-width-slider',
62
+ title: 'Step 4 of 12 — Explore alternative completions',
63
+ body:
64
+ 'Language models do not pick just one next word — they assign ' +
65
+ 'probabilities to every word in the vocabulary. <i>Beam search</i> keeps ' +
66
+ 'the top N candidates so you can compare them side-by-side.<br><br>' +
67
+ 'Bumping this to 3 or 5 is a great way to see where the model is ' +
68
+ 'confident vs. uncertain.'
69
+ },
70
+ {
71
+ element: '#generate-btn',
72
+ title: 'Step 5 of 12 — Run the model',
73
+ body:
74
+ 'Clicking Analyze runs the prompt through every layer and records the ' +
75
+ 'internal activations. This is what powers everything below — nothing in ' +
76
+ 'the pipeline or ablation tools is available until this runs.<br><br>' +
77
+ '<b>Close this window, click Analyze, and wait for the pipeline to appear</b> ' +
78
+ 'before moving on.'
79
+ },
80
+ {
81
+ element: '.stage-tokenization > summary',
82
+ title: 'Step 6 of 12 — Stage 1: Tokenization',
83
+ body:
84
+ 'Models do not read words — they read <i>tokens</i>, integer IDs for ' +
85
+ 'chunks of text. This stage shows exactly how your prompt was sliced up. ' +
86
+ 'Surprising splits (like "running" → "run" + "ning") explain a lot of ' +
87
+ 'later model behavior.<br><br>' +
88
+ 'Close this window and click the stage header to expand it, then skim ' +
89
+ 'the tokens.'
90
+ },
91
+ {
92
+ element: '.stage-embedding > summary',
93
+ title: 'Step 7 of 12 — Stage 2: Embedding',
94
+ body:
95
+ 'Each token ID gets converted into a vector of numbers (its "meaning") ' +
96
+ 'plus a second vector encoding its <i>position</i> in the sentence. ' +
97
+ 'Without position encoding, the model could not tell "dog bites man" ' +
98
+ 'from "man bites dog".'
99
+ },
100
+ {
101
+ element: '.stage-attention > summary',
102
+ title: 'Step 8 of 12 — Stage 3: Attention',
103
+ body:
104
+ 'This is the heart of the transformer. Each token looks at every other ' +
105
+ 'token and decides how much to pay attention to it. Different <i>heads</i> ' +
106
+ 'specialize: some track previous tokens, some detect duplicates, some ' +
107
+ 'complete patterns (induction). The bars show which specialties fired ' +
108
+ 'most on your prompt.'
109
+ },
110
+ {
111
+ element: '.stage-mlp > summary',
112
+ title: 'Step 9 of 12 — Stage 4: Knowledge Retrieval (MLP)',
113
+ body:
114
+ 'After attention has shuffled information between tokens, the MLP layer ' +
115
+ 'transforms each token vector independently. It is where the model\'s ' +
116
+ 'factual and associative knowledge lives — expand → nonlinear activation ' +
117
+ '→ compress.'
118
+ },
119
+ {
120
+ element: '.stage-output > summary',
121
+ title: 'Step 10 of 12 — Stage 5: Output Selection',
122
+ body:
123
+ 'The final vector is projected onto the full vocabulary, producing a ' +
124
+ 'probability for every possible next token. The chart shows the top 5 ' +
125
+ 'candidates. This is where the abstract math becomes a concrete word.'
126
+ },
127
+ {
128
+ element: '#ablation-tool',
129
+ title: 'Step 11 of 12 — Test a head\'s importance',
130
+ body:
131
+ 'Now the fun part. <i>Ablation</i> means silencing a specific attention ' +
132
+ 'head to see how much the output changes. If removing a head barely ' +
133
+ 'affects the prediction, it was not doing much here. If it wrecks the ' +
134
+ 'prediction, that head was critical.'
135
+ },
136
+ {
137
+ element: '#ablation-category-buttons',
138
+ title: 'Step 12 of 12 — Ablate the induction heads',
139
+ body:
140
+ 'Because you used the repetition prompt, the <b>Induction</b> quick-' +
141
+ 'select button is the most interesting one to try. Close this window, ' +
142
+ 'click Induction to add every induction head, then hit <b>Run Ablation</b>.' +
143
+ '<br><br>You should see the ablated output diverge from the original — ' +
144
+ 'concrete evidence that induction heads are what make the model complete ' +
145
+ '"The cat sat on the" with "mat". That is mechanistic interpretability ' +
146
+ 'in action.'
147
+ }
148
+ ];
149
+
150
+ const lastIndex = raw.length - 1;
151
+ return raw.map((s, i) => ({
152
+ element: s.element,
153
+ popover: {
154
+ title: s.title,
155
+ description: s.body + (i === lastIndex ? FOOTER_LAST : FOOTER_NORMAL)
156
+ }
157
+ }));
158
+ }
159
+
160
+ function ensureFloatBtn() {
161
+ let btn = document.getElementById('tutorial-float-btn');
162
+ if (btn) return btn;
163
+ btn = document.createElement('button');
164
+ btn.id = 'tutorial-float-btn';
165
+ btn.className = 'tutorial-float-btn';
166
+ btn.type = 'button';
167
+ btn.innerHTML =
168
+ '<span class="tutorial-float-btn-label">▶ Continue Tutorial</span>' +
169
+ '<span class="tutorial-float-btn-dismiss" title="Dismiss tutorial">✕</span>';
170
+ btn.addEventListener('click', (e) => {
171
+ if (e.target.closest('.tutorial-float-btn-dismiss')) {
172
+ e.stopPropagation();
173
+ finishTour();
174
+ } else {
175
+ resumeTour();
176
+ }
177
+ });
178
+ document.body.appendChild(btn);
179
+ return btn;
180
+ }
181
+
182
+ function showFloatBtn() {
183
+ const btn = ensureFloatBtn();
184
+ btn.style.display = 'flex';
185
+ }
186
+
187
+ function hideFloatBtn() {
188
+ const btn = document.getElementById('tutorial-float-btn');
189
+ if (btn) btn.style.display = 'none';
190
+ }
191
+
192
+ function createDriver() {
193
+ if (!window.driver || !window.driver.js || !window.driver.js.driver) {
194
+ console.error('Driver.js not loaded — tutorial cannot start');
195
+ return null;
196
+ }
197
+ return window.driver.js.driver({
198
+ showProgress: false,
199
+ allowClose: true,
200
+ nextBtnText: 'Continue',
201
+ doneBtnText: 'Finish',
202
+ prevBtnText: 'Back',
203
+ showButtons: ['previous', 'next', 'close'],
204
+ steps: buildSteps(),
205
+ onNextClick: () => {
206
+ const steps = buildSteps();
207
+ const nextIndex = currentStep + 1;
208
+ if (nextIndex >= steps.length) {
209
+ if (driverObj) driverObj.destroy();
210
+ finishTour();
211
+ return;
212
+ }
213
+ // Step 11 (index 10) has no action for the user to take, so
214
+ // advance straight to step 12 without minimizing to the
215
+ // floating "Continue Tutorial" button.
216
+ if (currentStep === 10) {
217
+ currentStep = nextIndex;
218
+ safeSet(STEP_KEY, String(currentStep));
219
+ driverObj.moveNext();
220
+ return;
221
+ }
222
+ if (driverObj) driverObj.destroy();
223
+ currentStep = nextIndex;
224
+ safeSet(STEP_KEY, String(currentStep));
225
+ showFloatBtn();
226
+ },
227
+ onPrevClick: () => {
228
+ currentStep = Math.max(0, currentStep - 1);
229
+ safeSet(STEP_KEY, String(currentStep));
230
+ if (driverObj) driverObj.destroy();
231
+ showFloatBtn();
232
+ },
233
+ onCloseClick: () => {
234
+ finishTour();
235
+ }
236
+ });
237
+ }
238
+
239
+ function startFresh() {
240
+ safeRemove(DONE_KEY);
241
+ currentStep = 0;
242
+ safeSet(STEP_KEY, '0');
243
+ hideFloatBtn();
244
+ driverObj = createDriver();
245
+ if (driverObj) driverObj.drive(0);
246
+ }
247
+
248
+ function resumeTour() {
249
+ hideFloatBtn();
250
+ driverObj = createDriver();
251
+ if (driverObj) driverObj.drive(currentStep);
252
+ }
253
+
254
+ function finishTour() {
255
+ safeSet(DONE_KEY, 'true');
256
+ safeRemove(STEP_KEY);
257
+ if (driverObj) {
258
+ try { driverObj.destroy(); } catch (e) { /* ignore */ }
259
+ driverObj = null;
260
+ }
261
+ hideFloatBtn();
262
+ }
263
+
264
+ function restoreFromStorage() {
265
+ if (safeGet(DONE_KEY) === 'true') return;
266
+ const saved = safeGet(STEP_KEY);
267
+ if (saved === null) return;
268
+ const n = parseInt(saved, 10);
269
+ if (isNaN(n) || n < 0) return;
270
+ currentStep = n;
271
+ showFloatBtn();
272
+ }
273
+
274
+ // Event delegation — Dash renders the DOM after load, so the header button
275
+ // may not exist when this script first runs.
276
+ document.addEventListener('click', (e) => {
277
+ if (e.target.closest && e.target.closest('#start-tutorial-btn')) {
278
+ startFresh();
279
+ }
280
+ });
281
+
282
+ if (document.readyState === 'loading') {
283
+ document.addEventListener('DOMContentLoaded', restoreFromStorage);
284
+ } else {
285
+ restoreFromStorage();
286
+ }
287
+ })();
components/model_selector.py CHANGED
@@ -87,13 +87,13 @@ def create_model_selector():
87
  placeholder="Enter text prompt for analysis...",
88
  value="",
89
  style={
90
- "width": "100%",
91
  "height": "100px",
92
  "resize": "vertical"
93
  },
94
  className="prompt-input"
95
  )
96
- ], className="input-container"),
97
 
98
  # Status indicator
99
  html.Div([
 
87
  placeholder="Enter text prompt for analysis...",
88
  value="",
89
  style={
90
+ "width": "100%",
91
  "height": "100px",
92
  "resize": "vertical"
93
  },
94
  className="prompt-input"
95
  )
96
+ ], id="prompt-section", className="input-container"),
97
 
98
  # Status indicator
99
  html.Div([