Spaces:
Sleeping
Sleeping
Commit ·
e1f91c2
1
Parent(s): 4416500
Tutorial, feedback, and github buttons added
Browse files- .claude/settings.local.json +6 -1
- app.py +26 -5
- assets/driver.css +1 -0
- assets/driver.js.iife.js +2 -0
- assets/style.css +155 -2
- assets/tutorial.js +287 -0
- components/model_selector.py +2 -2
.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.
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 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 →",prevBtnText:v=a("prevBtnText")||"← 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="×";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="← Previous";const v=document.createElement("button");return v.type="button",v.classList.add("driver-popover-next-btn"),v.innerHTML="Next →",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-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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-
|
| 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([
|