diff --git a/src/css/custom.css b/src/css/custom.css index 4930c472bb32..191064215ac4 100644 --- a/src/css/custom.css +++ b/src/css/custom.css @@ -71,9 +71,76 @@ html[data-theme='dark'] .header-github-link:before { margin-bottom: 20px; padding-top: 12px; - text-align: center; + text-align: center; border: solid 1px #c0c0c0; border-radius: 8px; min-width: 300px; max-width: 400px; } + +/* Initially hide the emoji */ +.ai-helper-icon { + display: inline-block; + border: solid 1px transparent; + background: rgba(0, 155, 229, 0.4); + border-radius: 50%; + width: 32px; + height: 32px; + + opacity: 0; + transition: opacity 0.4s ease; + text-align: center; + + position: relative; + float: left; + margin-left: -48px; +} + +/* Show the emoji when the paragraph is hovered */ +p:hover .ai-helper-icon { + opacity: 1; +} + +article p { + border-left: solid 4px transparent; + transition: border 0.4s ease; + padding-left: 4px; + padding-right: 8px; +} + +article p:hover { + border-color: rgba(0, 155, 229, 0.2) +} + +article p.explained { + border-color: rgb(0, 155, 229) +} + +.sparkle-button { + border: none; + background: transparent; + + display: inline-block; /* Inline-block to flow with text */ + vertical-align: middle; /* Align with text on the same line */ + /* border: 1px solid rgba(0, 0, 0, 0.1); */ + color: #ffd700; /* Gold color for the sparkle emoji */ + font-size: 0.8rem; /* Smaller size */ + padding: 0.2em 0.4em; /* Compact padding */ + border-radius: 8px; + /* box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05); */ + cursor: pointer; + /* transition: transform 0.2s ease, box-shadow 0.2s ease, + background-color 0.3s ease; */ + margin-right: 6px; +} + +/* .sparkle-button:hover { + background: #f0f0f0; + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); + transform: translateY(-2px); +} */ + +/* .sparkle-button:active { + transform: translateY(1px); + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); +} */ diff --git a/src/theme/Root.js b/src/theme/Root.js new file mode 100644 index 000000000000..68eeb4ee2ce4 --- /dev/null +++ b/src/theme/Root.js @@ -0,0 +1,137 @@ +import React, { useEffect, useState, useCallback } from 'react'; +import { useLocation } from '@docusaurus/router'; + + +const APOLLO_URL = 'http://localhost:3001/services/docsite_explainer' + +// Explain some text +async function explain(text) { + try { + console.log(' Explaining paragraph') + console.log(text) + // lets call apollo's echo endpoint for now + + const payload = { + text, + page: window.location.href + } + + const response = await fetch(APOLLO_URL, { + method:'POST', + body: JSON.stringify(payload), + headers: { + 'content-type': 'application/json', + }, + }) + + const json = await response.json() + console.log(json) + return json.answer + } catch (e) { + console.error(e) + } +} + +const HelpDialog = ({ children, close }) => { + const outerStyle = { + position: 'sticky', + width: '100%', + minHeight: '200px', + right: 10, + bottom: 10, + padding: '2px', + }; + const innerStyle = { + backgroundColor: 'white', + width: '30vw', + maxWidth: '1000px', + border: 'double 4px rgb(158 195 238)', + padding: '10px 20px', + borderRadius: '4px', + float: 'right', + marginRight: '10px', + boxShadow: 'rgb(158, 158, 158) -3px 3px 6px -2px' + + } + return (<div id="ai-help" style={outerStyle}> + <div style={innerStyle}> + <div style={{ textAlign: 'center', borderBottom: 'solid 1px #c0c0c0', marginBottom: '6px'}}> + <b>Super friendly not evil AI Helper 😃</b> + <div style={{ float: 'right', cursor: 'pointer' }} onClick={() => close()}>X</div> + </div> + {children} + </div> + </div>) + +} + +const Help = ({ text, close }) => { + if (text) { + if (text === true) { + return <HelpDialog close={close}> + Googling Frantically... + <div style={{ textAlign: 'center'}}> + <img width="250" src="img/riker2-1.png"></img> + </div> + </HelpDialog> + } + return <HelpDialog close={close}> + {text} + </HelpDialog> + } + + return <></> +} + +export default function Root({ children }) { + const location = useLocation(); + + const [help, setHelp] = useState() + + const handleClose = useCallback(() => { + setHelp(undefined) + }, []); + + useEffect(() => { + const observer = new MutationObserver(() => { + const paragraphs = document.querySelectorAll('p'); + + paragraphs.forEach(paragraph => { + if (!paragraph.querySelector('.ai-helper-icon')) { + const icon = document.createElement('span'); + + icon.title = "Explain this with AI" + icon.className = 'ai-helper-icon'; + icon.innerHTML = '<button class="sparkle-button">🤖</button>'; + + icon.onclick = () => { + for (const n of document.querySelectorAll('p.explained').values()) { + n.classList.remove('explained') + } + + paragraph.classList.toggle('explained', true) + setHelp(true); + explain(paragraph.textContent).then(x => { + setHelp(x); + }) + } + + paragraph.insertBefore( icon, paragraph.firstChild); + } + }); + }); + + // Observe changes in the main content area + const targetNode = document.querySelector('#__docusaurus'); + if (targetNode) { + observer.observe(targetNode, { childList: true, subtree: true }); + } + + return () => observer.disconnect(); // Cleanup the observer on unmount + }, [location]); + + return <> + {children} + <Help text={help} close={handleClose} /> + </>; +} diff --git a/static/img/riker2-1.png b/static/img/riker2-1.png new file mode 100644 index 000000000000..e50336f77fa4 Binary files /dev/null and b/static/img/riker2-1.png differ