From fbe5065ba0cf364fafcdf2ab764fd75403202280 Mon Sep 17 00:00:00 2001 From: David Karlsson <35727626+dvdksn@users.noreply.github.com> Date: Mon, 10 Nov 2025 08:56:12 +0100 Subject: [PATCH 1/8] site: use gordon for ask ai Signed-off-by: David Karlsson <35727626+dvdksn@users.noreply.github.com> --- assets/css/style.css | 1 + .../images/ask-ai-logo.svg | 0 assets/js/src/alpine.js | 80 ++- hugo.yaml | 3 + hugo_stats.json | 91 ++- layouts/_default/baseof.html | 1 + layouts/index.html | 1 + layouts/partials/gordon-chat.html | 558 ++++++++++++++++++ layouts/partials/head.html | 53 -- layouts/partials/header.html | 3 +- layouts/partials/search-bar.html | 7 +- package-lock.json | 23 + package.json | 2 + 13 files changed, 764 insertions(+), 59 deletions(-) rename static/assets/images/logo-icon-white.svg => assets/images/ask-ai-logo.svg (100%) create mode 100644 layouts/partials/gordon-chat.html diff --git a/assets/css/style.css b/assets/css/style.css index d8469b419a5f..8315621f425d 100644 --- a/assets/css/style.css +++ b/assets/css/style.css @@ -41,5 +41,6 @@ @import "syntax-dark.css"; @import "syntax-light.css"; @import "components.css"; +@import "highlight-github-dark.css"; @variant dark (&:where(.dark, .dark *)); diff --git a/static/assets/images/logo-icon-white.svg b/assets/images/ask-ai-logo.svg similarity index 100% rename from static/assets/images/logo-icon-white.svg rename to assets/images/ask-ai-logo.svg diff --git a/assets/js/src/alpine.js b/assets/js/src/alpine.js index cdcdfeb93055..d023fb11d5b2 100644 --- a/assets/js/src/alpine.js +++ b/assets/js/src/alpine.js @@ -2,12 +2,90 @@ import Alpine from 'alpinejs' import collapse from '@alpinejs/collapse' import persist from '@alpinejs/persist' import focus from '@alpinejs/focus' - +import { marked } from 'marked' +import hljs from 'highlight.js/lib/core' +// Import languages relevant to Docker docs +import bash from 'highlight.js/lib/languages/bash' +import dockerfile from 'highlight.js/lib/languages/dockerfile' +import yaml from 'highlight.js/lib/languages/yaml' +import json from 'highlight.js/lib/languages/json' +import javascript from 'highlight.js/lib/languages/javascript' +import python from 'highlight.js/lib/languages/python' +import go from 'highlight.js/lib/languages/go' + window.Alpine = Alpine Alpine.plugin(collapse) Alpine.plugin(persist) Alpine.plugin(focus) + +// Register highlight.js languages +hljs.registerLanguage('bash', bash) +hljs.registerLanguage('sh', bash) +hljs.registerLanguage('shell', bash) +hljs.registerLanguage('console', bash) +hljs.registerLanguage('dockerfile', dockerfile) +hljs.registerLanguage('yaml', yaml) +hljs.registerLanguage('yml', yaml) +hljs.registerLanguage('json', json) +hljs.registerLanguage('javascript', javascript) +hljs.registerLanguage('js', javascript) +hljs.registerLanguage('python', python) +hljs.registerLanguage('py', python) +hljs.registerLanguage('go', go) +hljs.registerLanguage('golang', go) + +// Add $markdown magic for rendering markdown with syntax highlighting +Alpine.magic('markdown', () => { + return (content) => { + if (!content) return '' + const html = marked(content) + + // Parse and highlight code blocks + const div = document.createElement('div') + div.innerHTML = html + + // Handle code blocks (pre > code) + div.querySelectorAll('pre').forEach((pre) => { + // Add not-prose to prevent Tailwind Typography styling + pre.classList.add('not-prose') + const code = pre.querySelector('code') + if (code) { + // Preserve the original text with newlines + const codeText = code.textContent + + // Clear and set as plain text first to preserve structure + code.textContent = codeText + + // Now apply highlight.js which will work with the text nodes + hljs.highlightElement(code) + } + }) + + // Handle inline code elements (not in pre blocks) + div.querySelectorAll('code:not(pre code)').forEach((code) => { + code.classList.add('not-prose') + }) + + return div.innerHTML + } +}) + +// Stores Alpine.store("showSidebar", false) +Alpine.store('gordon', { + isOpen: Alpine.$persist(false).using(sessionStorage).as('gordon-isOpen'), + query: '', + toggle() { + this.isOpen = !this.isOpen + }, + open(query) { + this.isOpen = true + if (query) this.query = query + }, + close() { + this.isOpen = false + } +}) Alpine.start() diff --git a/hugo.yaml b/hugo.yaml index d0d6c5d26204..ab988a04240b 100644 --- a/hugo.yaml +++ b/hugo.yaml @@ -271,6 +271,9 @@ module: # Mount the icon files to assets so we can access them with resources.Get - source: node_modules/@material-symbols/svg-400/rounded target: assets/icons + # Mount highlight.js theme for Gordon chat syntax highlighting + - source: node_modules/highlight.js/styles/github-dark.css + target: assets/css/highlight-github-dark.css imports: diff --git a/hugo_stats.json b/hugo_stats.json index 88e02b5a3862..e2709a5a2bf5 100644 --- a/hugo_stats.json +++ b/hugo_stats.json @@ -4,9 +4,11 @@ "classes": [ "--mount", "--tmpfs", + "-bottom-3", "-mt-8", "-top-10", "-top-16", + "-translate-y-1/2", "-v", "-z-10", ".NET", @@ -92,7 +94,6 @@ "JavaScript", "Jenkins", "Latest", - "Legacy-Docker-plans", "Legacy-Docker-subscription", "Linux", "Local-or-Hub-storage", @@ -179,27 +180,38 @@ "admonition-tip", "admonition-title", "admonition-warning", + "animate-bounce", + "animate-spin", "aspect-video", + "assistant", "bake-action", "bg-amber-500", "bg-background-toc", "bg-black/100", "bg-black/50", "bg-blue", + "bg-blue-100", "bg-blue-400", + "bg-blue-50", "bg-blue-500", "bg-blue-600", "bg-blue-700", + "bg-current", "bg-gray-100", + "bg-gray-200", "bg-gray-400", "bg-gray-50", "bg-gray-500", "bg-gray-700", + "bg-gray-900", + "bg-green-200", "bg-green-400", "bg-green-500", "bg-navbar-bg", "bg-pattern-blue", "bg-pattern-purple", + "bg-red-200", + "bg-red-50", "bg-red-500", "bg-violet-500", "bg-white", @@ -219,10 +231,13 @@ "border-green-400", "border-l-2", "border-l-magenta-light", + "border-red-200", "border-t", "border-transparent", "bottom-0", + "bottom-full", "breadcrumbs", + "break-words", "build-push-action", "button", "card", @@ -239,6 +254,7 @@ "cls-2", "col-start-2", "containerd-image-store", + "cursor-not-allowed", "cursor-pointer", "dark:bg-amber-400", "dark:bg-background-dark", @@ -246,16 +262,22 @@ "dark:bg-blue-400", "dark:bg-blue-500", "dark:bg-blue-800", + "dark:bg-blue-900", + "dark:bg-blue-900/50", "dark:bg-gray-300", "dark:bg-gray-400", "dark:bg-gray-500", + "dark:bg-gray-700", "dark:bg-gray-800", "dark:bg-gray-900", "dark:bg-gray-950", "dark:bg-green-700", + "dark:bg-green-900/50", "dark:bg-green-dark-400", "dark:bg-navbar-bg-dark", "dark:bg-red-400", + "dark:bg-red-900/20", + "dark:bg-red-900/50", "dark:bg-violet-400", "dark:block", "dark:border-b-blue-600", @@ -265,11 +287,14 @@ "dark:border-gray-700", "dark:border-green-400", "dark:border-l-magenta-dark", + "dark:border-red-800", + "dark:focus:border-blue-400", "dark:focus:ring-3-blue-dark", "dark:hidden", "dark:hover:bg-blue-400", "dark:hover:bg-blue-700", "dark:hover:bg-gray-600", + "dark:hover:bg-gray-700", "dark:hover:bg-gray-900", "dark:hover:text-blue", "dark:outline-gray-800", @@ -278,23 +303,34 @@ "dark:ring-3-gray-dark-400", "dark:syntax-dark", "dark:text-blue", + "dark:text-blue-400", "dark:text-blue-700", "dark:text-divider-dark", + "dark:text-gray-100", "dark:text-gray-200", "dark:text-gray-300", "dark:text-gray-400", "dark:text-gray-500", "dark:text-gray-600", "dark:text-gray-800", + "dark:text-green-400", "dark:text-magenta-dark", + "dark:text-red-400", "dark:text-white", + "disabled:cursor-not-allowed", + "disabled:opacity-50", "docker/bake-action", "docker/build-push-action", "download-links", "download-links-subcontainer", "drop-shadow", "dropdown-base", + "duration-100", + "duration-200", "duration-300", + "duration-75", + "ease-in", + "ease-out", "fixed", "flex", "flex-1", @@ -306,10 +342,13 @@ "flex-shrink", "flex-shrink-0", "flex-wrap", + "focus:border-blue-500", "focus:outline-none", "focus:ring", + "focus:ring-2", "focus:ring-3-blue-light", "focus:ring-blue-400", + "focus:ring-blue-500/20", "font-bold", "font-medium", "font-normal", @@ -351,6 +390,7 @@ "hover:bg-blue-400", "hover:bg-blue-500", "hover:bg-blue-600", + "hover:bg-blue-700", "hover:bg-blue-800", "hover:bg-gray-100", "hover:bg-gray-200", @@ -360,12 +400,16 @@ "hover:dark:bg-gray-800", "hover:dark:text-blue-400", "hover:dark:text-blue-700", + "hover:opacity-70", + "hover:opacity-80", "hover:opacity-90", "hover:text-blue", + "hover:text-white", "hover:underline", "icon-lg", "icon-sm", "icon-svg", + "icon-xs", "inline", "inline-block", "inline-flex", @@ -378,7 +422,9 @@ "justify-between", "justify-center", "justify-end", + "justify-start", "leading-none", + "leading-normal", "leading-snug", "leading-tight", "left-0", @@ -410,6 +456,7 @@ "max-w-[1920px]", "max-w-full", "max-w-none", + "max-w-sm", "mb-1", "mb-1.5", "mb-2", @@ -424,16 +471,21 @@ "md:flex-row", "md:grid-cols-2", "md:h-[334px]", + "md:h-[calc(100vh-1rem)]", "md:h-[calc(100vh-64px)]", "md:hidden", "md:max-w-[66%]", + "md:right-2", "md:sticky", "md:text-base", "md:text-sm", "md:top-16", + "md:top-2", "md:w-[300px]", "md:w-[320px]", + "md:w-[min(70ch,90vw)]", "md:z-auto", + "min-h-[3rem]", "min-h-screen", "min-w-0", "min-w-48", @@ -460,10 +512,14 @@ "navbar-font", "navbar-group", "navbar-group-font-title", + "negative", "no-underline", "no-wrap", "not-prose", "object-cover", + "opacity-0", + "opacity-100", + "opacity-50", "open-kapa-widget", "openSUSE-and-SLES", "origin-bottom-right", @@ -481,7 +537,9 @@ "overflow-x-hidden", "overflow-y-auto", "p-1", + "p-1.5", "p-2", + "p-2.5", "p-3", "p-4", "p-6", @@ -497,8 +555,11 @@ "pl-4", "pl-5", "placeholder-blue-300", + "positive", "pr-2", + "pr-24", "prose", + "prose-sm", "pt-2", "pt-20", "pt-4", @@ -511,12 +572,16 @@ "py-0.5", "py-1", "py-2", + "py-2.5", + "py-3", "py-4", "py-8", "py-[0.5625rem]", "relative", + "resize-none", "right-0", "right-2", + "right-4", "right-8", "ring-3-2", "ring-3-[1.5px]", @@ -524,11 +589,15 @@ "ring-3-gray-light-200", "rotate-45", "rounded", + "rounded-b-lg", "rounded-full", "rounded-lg", "rounded-md", "rounded-sm", + "rounded-t-lg", + "scale-100", "scale-50", + "scale-95", "scroll-mt-2", "scroll-mt-20", "scroll-mt-36", @@ -539,12 +608,16 @@ "select-none", "self-center", "self-start", + "self-stretch", "shadow", + "shadow-2xl", + "shadow-lg", "shadow-md", "shimmer", "sm:block", "sm:flex-row", "sm:hidden", + "sm:inline", "sm:items-center", "social", "space-y-2", @@ -563,7 +636,9 @@ "text-base", "text-black", "text-blue", + "text-blue-600", "text-blue-light", + "text-center", "text-divider-light", "text-gray-200", "text-gray-300", @@ -572,26 +647,38 @@ "text-gray-600", "text-gray-700", "text-gray-800", + "text-gray-900", + "text-green-600", + "text-green-700", "text-left", "text-lg", "text-magenta-light", - "text-md", + "text-red-700", + "text-red-800", "text-sm", "text-white", + "text-white/80", "text-xl", "text-xs", "toc", "top-0", "top-1", + "top-1/2", "top-16", "top-6", "topbar-button", "transition", + "transition-all", "transition-colors", + "transition-opacity", "transition-transform", + "translate-x-0", + "translate-x-full", "truncate", + "user", "w-2", "w-5", + "w-56", "w-64", "w-65", "w-8", diff --git a/layouts/_default/baseof.html b/layouts/_default/baseof.html index 7b06c790c819..946147a21e9c 100644 --- a/layouts/_default/baseof.html +++ b/layouts/_default/baseof.html @@ -8,6 +8,7 @@ class="dark:bg-navbar-bg-dark bg-navbar-bg flex flex-col items-center text-base text-black dark:text-white" > {{ partial "header.html" . }} + {{ partial "gordon-chat.html" . }}
{{ partial "header.html" . }} + {{ partial "gordon-chat.html" . }}
diff --git a/layouts/partials/gordon-chat.html b/layouts/partials/gordon-chat.html new file mode 100644 index 000000000000..e62b895f0588 --- /dev/null +++ b/layouts/partials/gordon-chat.html @@ -0,0 +1,558 @@ + +
+ +
+ + +
+ +
+
+ {{ partial "utils/svg.html" "images/ask-ai-logo.svg" }} +
+
+ + +
+
+ + +
+ + + + + + + + +
+ + +
+
+
+ + +
+ + + +
+
+ +
+
+ + +
+ This is a custom LLM for answering questions about Docker. Answers are + based on the documentation. +
+
+
+ + + diff --git a/layouts/partials/head.html b/layouts/partials/head.html index 4c1455512c37..af9892537126 100644 --- a/layouts/partials/head.html +++ b/layouts/partials/head.html @@ -51,59 +51,6 @@ })(window,document,'https://static.hotjar.com/c/hotjar-','.js?sv='); {{ end }} -{{/* kapa.ai widget */}} - - {{/* preload Roboto Flex as it's a critical font: https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/rel/preload */}}
. + .
` }} {{- $emptyState | safe.HTML }} diff --git a/package-lock.json b/package-lock.json index 4aefc775b6d7..3b7316c8b938 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,8 @@ "@tailwindcss/cli": "^4.1.6", "@tailwindcss/typography": "^0.5.15", "alpinejs": "^3.14.3", + "highlight.js": "^11.11.1", + "marked": "^17.0.0", "tailwindcss": "^4.1.6" }, "devDependencies": { @@ -976,6 +978,15 @@ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "license": "ISC" }, + "node_modules/highlight.js": { + "version": "11.11.1", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.11.1.tgz", + "integrity": "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/is-alphabetical": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", @@ -1367,6 +1378,18 @@ "url": "https://github.com/sponsors/DavidAnson" } }, + "node_modules/marked": { + "version": "17.0.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-17.0.0.tgz", + "integrity": "sha512-KkDYEWEEiYJw/KC+DVm1zzlpMQSMIu6YRltkcCvwheCp8HWPXCk9JwOmHJKBlGfzcpzcIt6x3sMnTsRm/51oDg==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 20" + } + }, "node_modules/micromark": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", diff --git a/package.json b/package.json index de8098b00b20..795ed12561b4 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,8 @@ "@tailwindcss/cli": "^4.1.6", "@tailwindcss/typography": "^0.5.15", "alpinejs": "^3.14.3", + "highlight.js": "^11.11.1", + "marked": "^17.0.0", "tailwindcss": "^4.1.6" }, "devDependencies": { From 32062fd611e754b4315f860779f93b15449dad9d Mon Sep 17 00:00:00 2001 From: David Karlsson <35727626+dvdksn@users.noreply.github.com> Date: Thu, 18 Dec 2025 11:36:43 +0100 Subject: [PATCH 2/8] site(gordon): show error message when rate limited Signed-off-by: David Karlsson <35727626+dvdksn@users.noreply.github.com> --- layouts/partials/gordon-chat.html | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/layouts/partials/gordon-chat.html b/layouts/partials/gordon-chat.html index e62b895f0588..69882299e4a4 100644 --- a/layouts/partials/gordon-chat.html +++ b/layouts/partials/gordon-chat.html @@ -79,7 +79,11 @@ } catch (err) { // Only set error if messages weren't cleared if (this.messages.length > 0) { - this.error = 'Failed to get response. Please try again.' + if (err.message === 'RATE_LIMIT_EXCEEDED') { + this.error = 'You\'ve exceeded your question quota for the day. Please come back tomorrow.' + } else { + this.error = 'Failed to get response. Please try again.' + } } console.error('Gordon API error:', err) // Only try to remove message if it still exists @@ -160,6 +164,9 @@ }) if (!response.ok) { + if (response.status === 429) { + throw new Error('RATE_LIMIT_EXCEEDED') + } throw new Error(`HTTP ${response.status}: ${response.statusText}`) } From 35832a0d6e32d1dbc3b8dd0eb768da7e5bf7eeda Mon Sep 17 00:00:00 2001 From: David Karlsson <35727626+dvdksn@users.noreply.github.com> Date: Thu, 18 Dec 2025 11:47:26 +0100 Subject: [PATCH 3/8] site(gordon): configure hostname with HUGO_USE_LOCAL_GORDON Signed-off-by: David Karlsson <35727626+dvdksn@users.noreply.github.com> --- layouts/partials/gordon-chat.html | 29 +++++++++++------------------ 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/layouts/partials/gordon-chat.html b/layouts/partials/gordon-chat.html index 69882299e4a4..5fc5c6378440 100644 --- a/layouts/partials/gordon-chat.html +++ b/layouts/partials/gordon-chat.html @@ -1,4 +1,13 @@ +
+ class="fixed top-0 right-0 z-50 flex h-screen w-full flex-col overflow-hidden rounded-lg bg-white shadow-2xl transition-all duration-200 md:w-[min(80ch,90vw)] md:h-[calc(100vh-1rem)] md:top-2 md:right-2 dark:bg-gray-900">
@@ -355,8 +355,8 @@

- + Context @@ -474,30 +491,16 @@

x-transition:leave="transition ease-in duration-75" x-transition:leave-start="opacity-100 scale-100" x-transition:leave-end="opacity-0 scale-95" - class="absolute bottom-full right-0 mb-2 w-56 rounded-lg bg-gray-900 p-2.5 text-xs text-white shadow-lg dark:bg-gray-700" + class="absolute bottom-full left-0 mb-2 w-56 rounded-lg bg-gray-900 p-2.5 text-xs text-white shadow-lg dark:bg-gray-700" style="display: none;">

When enabled, Gordon considers the current page you're viewing to provide more relevant answers.

-
+

-
From 3fb0787b9fcaa1a5a90f7d25b8d5250b64942544 Mon Sep 17 00:00:00 2001 From: David Karlsson <35727626+dvdksn@users.noreply.github.com> Date: Thu, 18 Dec 2025 12:54:26 +0100 Subject: [PATCH 7/8] site(gordon): add y padding for response in loading state Signed-off-by: David Karlsson <35727626+dvdksn@users.noreply.github.com> --- layouts/partials/gordon-chat.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/layouts/partials/gordon-chat.html b/layouts/partials/gordon-chat.html index daefbd78afc7..d641c9b41c2d 100644 --- a/layouts/partials/gordon-chat.html +++ b/layouts/partials/gordon-chat.html @@ -358,7 +358,7 @@

-