Browse Source

Add davo-bot 2000 widget loader

Cross-origin loader for davidwindham.com (a different origin from the
davidawindham.com /ask proxy): probes the widget bundle and injects it
only when the response is really JavaScript, so it's a silent no-op when
the backend is down.
windhamdavid 4 days ago
parent
commit
654c640f0f
2 changed files with 59 additions and 6 deletions
  1. 9 6
      index.html
  2. 50 0
      js/ask-widget.js

+ 9 - 6
index.html

@@ -12,6 +12,7 @@
 <meta name="description" content="David Windham">
 <meta name="description" content="David Windham">
 <meta name="author" content="David Windham">
 <meta name="author" content="David Windham">
 <title>David Windham</title>
 <title>David Windham</title>
+<link rel="canonical" href="https://davidwindham.com/">
 <meta property="og:title" content="David Windham">
 <meta property="og:title" content="David Windham">
 <meta property="og:description" content="Something Else">
 <meta property="og:description" content="Something Else">
 <meta property="og:type" content="website"><meta property="og:url" content="https://davidwindham.com"><meta property="og:site_name" content="David Windham"><meta property="og:image" content="img/og_image.jpg"><meta property="fb:app_id" content="203136806559589"><meta name="twitter:site" content="@windhamdavid"><meta name="twitter:creator" content="@windhamdavid"><meta name="twitter:title" content="David Windham"><meta name="twitter:description" content="Something Else"><meta name="twitter:image" content="img/ogt_image.jpg"><meta name="twitter:card" content="summary_large_image"><link rel="apple-touch-icon" sizes="180x180" href="img/icons/apple-touch-icon.png"><link rel="icon" type="image/png" sizes="32x32" href="img/icons/favicon-32x32.png"><link rel="icon" type="image/png" sizes="16x16" href="img/icons/favicon-16x16.png"><link rel="manifest" href="img/icons/site.webmanifest"><!--<link rel="mask-icon" href="img/icons/safari-pinned-tab.svg" color="#3f3f3f">--><meta name="msapplication-TileColor" content="#000000"><meta name="theme-color" content="#ffffff">
 <meta property="og:type" content="website"><meta property="og:url" content="https://davidwindham.com"><meta property="og:site_name" content="David Windham"><meta property="og:image" content="img/og_image.jpg"><meta property="fb:app_id" content="203136806559589"><meta name="twitter:site" content="@windhamdavid"><meta name="twitter:creator" content="@windhamdavid"><meta name="twitter:title" content="David Windham"><meta name="twitter:description" content="Something Else"><meta name="twitter:image" content="img/ogt_image.jpg"><meta name="twitter:card" content="summary_large_image"><link rel="apple-touch-icon" sizes="180x180" href="img/icons/apple-touch-icon.png"><link rel="icon" type="image/png" sizes="32x32" href="img/icons/favicon-32x32.png"><link rel="icon" type="image/png" sizes="16x16" href="img/icons/favicon-16x16.png"><link rel="manifest" href="img/icons/site.webmanifest"><!--<link rel="mask-icon" href="img/icons/safari-pinned-tab.svg" color="#3f3f3f">--><meta name="msapplication-TileColor" content="#000000"><meta name="theme-color" content="#ffffff">
@@ -25,7 +26,7 @@
     "@context": "https://schema.org",
     "@context": "https://schema.org",
     "@type": "LocalBusiness",
     "@type": "LocalBusiness",
     "name": "David Windham",
     "name": "David Windham",
-    "image": "https://davidwindham.com/ogt_image.jpg",
+    "image": "https://davidwindham.com/img/ogt_image.jpg",
     "@id": "https://davidwindham.com/#who",
     "@id": "https://davidwindham.com/#who",
     "url": "https://davidwindham.com",
     "url": "https://davidwindham.com",
     "telephone": "803-712-3283",
     "telephone": "803-712-3283",
@@ -428,7 +429,7 @@
                 <li>Jerry Moran</li>
                 <li>Jerry Moran</li>
                 <li>Holy City Farms</li>
                 <li>Holy City Farms</li>
                 <li>Harvard Kennedy School</li>
                 <li>Harvard Kennedy School</li>
-                <li>Clemson University;</li>
+                <li>Clemson University</li>
                 <li>The Chelcum Family Foundation</li>
                 <li>The Chelcum Family Foundation</li>
                 <li>Morris Publishing</li>
                 <li>Morris Publishing</li>
                 <li>The State Newspaper </li>
                 <li>The State Newspaper </li>
@@ -537,7 +538,7 @@
               <p class="mb-n2">I keep accounts with these seven vendors and between them, I can purchase and accomplish almost anything. In most cases, I prefer to cut myself out of the middle and register it to your company.</p>
               <p class="mb-n2">I keep accounts with these seven vendors and between them, I can purchase and accomplish almost anything. In most cases, I prefer to cut myself out of the middle and register it to your company.</p>
               <div class="btn-group btn-group-toggle" data-toggle="buttons">
               <div class="btn-group btn-group-toggle" data-toggle="buttons">
                 <a href="https://www.apple.com/business/" target="_blank" rel="noopener">
                 <a href="https://www.apple.com/business/" target="_blank" rel="noopener">
-                  <img class="vendor-image" alt="Google Cloud" src="img/vendor-apple.svg">
+                  <img class="vendor-image" alt="Apple Business" src="img/vendor-apple.svg">
                 </a>
                 </a>
                 <a href="https://azure.microsoft.com/" target="_blank" rel="noopener">
                 <a href="https://azure.microsoft.com/" target="_blank" rel="noopener">
                   <img class="vendor-image" alt="Microsoft Azure" src="img/vendor-microsoft.svg">
                   <img class="vendor-image" alt="Microsoft Azure" src="img/vendor-microsoft.svg">
@@ -551,13 +552,13 @@
                   <img class="vendor-image" alt="Amazon Web Services" src="img/vendor-amazon.svg">
                   <img class="vendor-image" alt="Amazon Web Services" src="img/vendor-amazon.svg">
                 </a>
                 </a>
                 <a href="https://vercel.com" target="_blank" rel="noopener">
                 <a href="https://vercel.com" target="_blank" rel="noopener">
-                  <img class="vendor-image" alt="Linode" src="img/vendor-vercel.svg">
+                  <img class="vendor-image" alt="Vercel" src="img/vendor-vercel.svg">
                 </a>
                 </a>
                 <a href="https://linode.com" target="_blank" rel="noopener">
                 <a href="https://linode.com" target="_blank" rel="noopener">
                   <img class="vendor-image" alt="Linode" src="img/vendor-linode.svg">
                   <img class="vendor-image" alt="Linode" src="img/vendor-linode.svg">
                 </a>
                 </a>
                 <a href="https://www.akamai.com/" target="_blank" rel="noopener">
                 <a href="https://www.akamai.com/" target="_blank" rel="noopener">
-                  <img class="vendor-image" alt="Google Cloud" src="img/vendor-akamai.svg">
+                  <img class="vendor-image" alt="Akamai" src="img/vendor-akamai.svg">
                 </a>
                 </a>
               </div>
               </div>
             </div>
             </div>
@@ -658,7 +659,7 @@
       <div class="row">
       <div class="row">
         <div class="col-md-6 text-md-left">
         <div class="col-md-6 text-md-left">
           &copy; 2004-2026<br>
           &copy; 2004-2026<br>
-          <small>Updated: 01/19/26</small>
+          <small>Updated: 06/11/26</small>
         </div>
         </div>
         <div class="col-md-6 text-end">
         <div class="col-md-6 text-end">
           <p class="gaegu mb-n1">I'm not really active on some of these:</p>
           <p class="gaegu mb-n1">I'm not really active on some of these:</p>
@@ -688,6 +689,8 @@
 <script src="js/waypoints-inview.js"></script>
 <script src="js/waypoints-inview.js"></script>
 <script src="js/_init.js"></script>
 <script src="js/_init.js"></script>
 <!-- endbuild -->
 <!-- endbuild -->
+<!-- davo-bot 2000 — floating AI assistant, served cross-origin from davidawindham.com/ask -->
+<script src="js/ask-widget.js" defer></script>
 <!--
 <!--
 <script>
 <script>
   var _paq = window._paq = window._paq || [];
   var _paq = window._paq = window._paq || [];

+ 50 - 0
js/ask-widget.js

@@ -0,0 +1,50 @@
+/**
+ * Loads "davo-bot 2000" (the floating launcher) on davidwindham.com.
+ *
+ * Unlike the daw (WordPress) and daw_til (Docusaurus) consumers — which live on
+ * davidawindham.com and load the widget SAME-ORIGIN through that site's Apache
+ * /ask proxy — this single-page portfolio is on a DIFFERENT origin
+ * (davidwindham.com). So it loads the bundle and calls the API CROSS-ORIGIN
+ * against the absolute davidawindham.com/ask/ URLs. The widget bundle has open
+ * CORS, so the button always loads; for it to *answer*, the ralph server's
+ * ALLOWED_ORIGINS must include https://davidwindham.com (see ralph README).
+ * Local dev (browserSync/localhost) points at the ralph dev server on :3001.
+ *
+ * We probe the widget URL first and only inject it when the response is really
+ * JavaScript: when ralph is down, davidawindham.com falls through to WordPress
+ * and returns HTML, and injecting that would throw "Unexpected token '<'". The
+ * content-type probe makes this a silent no-op until the backend is actually
+ * serving the widget. The widget sets window.__dawaskLoaded to guard against a
+ * double instance.
+ */
+( function () {
+	if ( typeof document === 'undefined' || typeof fetch === 'undefined' ) {
+		return;
+	}
+
+	var local = /^(localhost|127\.0\.0\.1|0\.0\.0\.0)$/.test( window.location.hostname );
+	var ORIGIN = local ? 'http://localhost:3001' : 'https://davidawindham.com';
+	var WIDGET_URL = ORIGIN + '/ask/widget.js';
+	var API_URL = local ? ORIGIN + '/api/ask' : ORIGIN + '/ask/api/ask';
+
+	function inject() {
+		if ( document.getElementById( 'dawask-script' ) ) {
+			return; // already injected
+		}
+		var s = document.createElement( 'script' );
+		s.id = 'dawask-script';
+		s.src = WIDGET_URL;
+		s.setAttribute( 'data-api-url', API_URL );
+		s.setAttribute( 'data-mode', 'launcher' );
+		document.body.appendChild( s );
+	}
+
+	fetch( WIDGET_URL, { method: 'GET', cache: 'no-store' } )
+		.then( function ( r ) {
+			var ct = ( r.headers.get( 'content-type' ) || '' ).toLowerCase();
+			if ( r.ok && ct.indexOf( 'javascript' ) !== -1 ) {
+				inject();
+			}
+		} )
+		.catch( function () {} ); // backend unreachable → no widget, no error
+} )();