From fa024fea3e6c117173dd497bd2876df4303fbdd0 Mon Sep 17 00:00:00 2001 From: William Laugesen Date: Mon, 10 Aug 2026 18:15:32 +1200 Subject: [PATCH 1/2] Turn the AI dropdown into an Open in Claude split button The "Use Octopus docs with AI" pill offered three markdown actions. The design replaces it with a split button: a primary "Open in Claude", and a caret that opens a list of assistants. Each entry hands the assistant this page's .md URL to read. The markdown actions come back as a separate copy button once the shared copy module lands. Both halves reuse the .btn component, so the pair matches "Edit on GitHub" beside it in the page actions row, and the whole control moves from the foot of the article up under the header. Co-Authored-By: Claude Opus 5 (1M context) --- src/assets/icons/chatgpt.svg | 3 + src/assets/icons/claude.svg | 3 + src/assets/icons/external-link.svg | 4 + src/assets/icons/gemini.svg | 3 + src/components/CopyMarkdown.astro | 87 --------- src/components/OpenInLlm.astro | 101 ++++++++++ src/data/language.json | 25 ++- src/layouts/Default.astro | 4 +- src/scripts/main.js | 2 +- src/scripts/modules/copy-markdown.js | 128 ------------- src/scripts/modules/open-in-llm.js | 41 ++++ src/styles/main.css | 208 +++++++++------------ src/themes/octopus/utilities/mdxContent.ts | 2 +- tests/llm-endpoints.spec.ts | 48 +++-- 14 files changed, 292 insertions(+), 367 deletions(-) create mode 100644 src/assets/icons/chatgpt.svg create mode 100644 src/assets/icons/claude.svg create mode 100644 src/assets/icons/external-link.svg create mode 100644 src/assets/icons/gemini.svg delete mode 100644 src/components/CopyMarkdown.astro create mode 100644 src/components/OpenInLlm.astro delete mode 100644 src/scripts/modules/copy-markdown.js create mode 100644 src/scripts/modules/open-in-llm.js diff --git a/src/assets/icons/chatgpt.svg b/src/assets/icons/chatgpt.svg new file mode 100644 index 0000000000..cdd5b878a4 --- /dev/null +++ b/src/assets/icons/chatgpt.svg @@ -0,0 +1,3 @@ + diff --git a/src/assets/icons/claude.svg b/src/assets/icons/claude.svg new file mode 100644 index 0000000000..316f06e41a --- /dev/null +++ b/src/assets/icons/claude.svg @@ -0,0 +1,3 @@ + diff --git a/src/assets/icons/external-link.svg b/src/assets/icons/external-link.svg new file mode 100644 index 0000000000..259c6c254a --- /dev/null +++ b/src/assets/icons/external-link.svg @@ -0,0 +1,4 @@ + + diff --git a/src/assets/icons/gemini.svg b/src/assets/icons/gemini.svg new file mode 100644 index 0000000000..55e9fd488a --- /dev/null +++ b/src/assets/icons/gemini.svg @@ -0,0 +1,3 @@ + diff --git a/src/components/CopyMarkdown.astro b/src/components/CopyMarkdown.astro deleted file mode 100644 index 30ba16072d..0000000000 --- a/src/components/CopyMarkdown.astro +++ /dev/null @@ -1,87 +0,0 @@ ---- -import { SITE } from '@config'; -import { Lang, Translations } from '@util/Languages'; -import { getEligibleSlugs } from '@util/mdxContent'; - -type Props = { - lang: string; -}; -const { lang } = Astro.props satisfies Props; - -const _ = Lang(lang); - -const docsPath = Astro.url.pathname.replace(/\/$/, ''); -const subfolderPrefix = SITE.subfolder.replace(/\/$/, '') + '/'; -const docsSlug = docsPath.startsWith(subfolderPrefix) - ? docsPath.slice(subfolderPrefix.length) - : null; -const eligibleSlugs = getEligibleSlugs(); -const pageMdUrl = - docsSlug && eligibleSlugs.has(docsSlug) ? docsPath + '.md' : null; -const allDocsUrl = SITE.subfolder.replace(/\/$/, '') + '/llms-full.txt'; ---- - -{ - pageMdUrl && ( -
-
- - - -
-
-
- ) -} diff --git a/src/components/OpenInLlm.astro b/src/components/OpenInLlm.astro new file mode 100644 index 0000000000..8093ac46bd --- /dev/null +++ b/src/components/OpenInLlm.astro @@ -0,0 +1,101 @@ +--- +import { SITE } from '@config'; +import { Lang, Translations } from '@util/Languages'; +import { getEligibleSlugs } from '@util/mdxContent'; + +type Props = { + lang: string; +}; +const { lang } = Astro.props satisfies Props; + +const _ = Lang(lang); + +const docsPath = Astro.url.pathname.replace(/\/$/, ''); +const subfolderPrefix = SITE.subfolder.replace(/\/$/, '') + '/'; +const docsSlug = docsPath.startsWith(subfolderPrefix) + ? docsPath.slice(subfolderPrefix.length) + : null; +const eligibleSlugs = getEligibleSlugs(); +const pageMdUrl = + docsSlug && eligibleSlugs.has(docsSlug) ? docsPath + '.md' : null; + +// The assistant fetches the page itself, so the prompt carries an absolute URL +// rather than the site-relative one the rest of the site links with. +const prompt = pageMdUrl + ? _(Translations.octopus_open_in_llm.prompt).replace( + '{url}', + new URL(pageMdUrl, SITE.url).href + ) + : ''; + +// gemini.google.com ignores prompt parameters, so "Open in Gemini" targets AI +// Studio, the Gemini surface that accepts one. +const assistants = [ + { + id: 'claude', + label: _(Translations.octopus_open_in_llm.open_in_claude), + href: 'https://claude.ai/new?q=' + encodeURIComponent(prompt), + }, + { + id: 'chatgpt', + label: _(Translations.octopus_open_in_llm.open_in_chatgpt), + href: 'https://chatgpt.com/?hints=search&q=' + encodeURIComponent(prompt), + }, + { + id: 'gemini', + label: _(Translations.octopus_open_in_llm.open_in_gemini), + href: + 'https://aistudio.google.com/prompts/new_chat?prompt=' + + encodeURIComponent(prompt), + }, +]; + +const [primary] = assistants; +--- + +{ + pageMdUrl && ( +
+ + +
+ +