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/Button.astro b/src/components/Button.astro index a377afbe57..499a651c4d 100644 --- a/src/components/Button.astro +++ b/src/components/Button.astro @@ -10,10 +10,12 @@ type SharedProps = { disabled?: boolean; }; -// Extend the native button attributes, or the anchor ones when an href is given +// Extend the native button attributes, the anchor ones when an href is given, +// or the summary ones when the button is a `
` disclosure trigger type Props = - | (HTMLAttributes<'button'> & SharedProps & { href?: never }) - | (HTMLAttributes<'a'> & SharedProps & { href: string | URL }); + | (HTMLAttributes<'button'> & SharedProps & { href?: never; as?: 'button' }) + | (HTMLAttributes<'a'> & SharedProps & { href: string | URL; as?: never }) + | (HTMLAttributes<'summary'> & SharedProps & { href?: never; as: 'summary' }); const { label, @@ -23,6 +25,7 @@ const { importance = 'default', disabled = false, href, + as = 'button', type = 'button', class: className, ...rest @@ -41,7 +44,9 @@ const tagSpecificProps = href 'aria-disabled': disabled ? 'true' : undefined, tabindex: disabled ? -1 : undefined, } - : { type, disabled }; + : as === 'summary' + ? { 'aria-disabled': disabled ? 'true' : undefined } + : { type, disabled }; const iconDetails = (name: string | undefined) => { if (!name) return null; @@ -55,7 +60,7 @@ const endIcon = iconDetails(iconEnd); const StartIconTag = startIcon?.tag; const EndIconTag = endIcon?.tag; -const Tag = href ? 'a' : 'button'; +const Tag = href ? 'a' : as; --- @@ -106,6 +111,15 @@ const Tag = href ? 'a' : 'button'; --btn-padding-inline: var(--space4); } + summary.btn { + list-style: none; + user-select: none; + } + + summary.btn::-webkit-details-marker { + display: none; + } + .btn__label { padding-inline: var(--space4); } 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..8d5dc00bee --- /dev/null +++ b/src/components/OpenInLlm.astro @@ -0,0 +1,224 @@ +--- +import { SITE } from '@config'; +import { Lang, Translations } from '@util/Languages'; +import { getEligibleSlugs } from '@util/mdxContent'; +import Button from './Button.astro'; + +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 && ( +
+
+ + ) +} + + diff --git a/src/data/language.json b/src/data/language.json index 3d59270d31..c45f710e68 100644 --- a/src/data/language.json +++ b/src/data/language.json @@ -22,24 +22,21 @@ "en": "Edit on GitHub" } }, - "octopus_copy_md": { - "label": { - "en": "Use Octopus docs with AI" + "octopus_open_in_llm": { + "open_in_claude": { + "en": "Open in Claude" }, - "copy": { - "en": "Copy this page as markdown" + "open_in_chatgpt": { + "en": "Open in ChatGPT" }, - "copied": { - "en": "Copied!" + "open_in_gemini": { + "en": "Open in Gemini" }, - "view_raw": { - "en": "Open this page as markdown" + "more_assistants": { + "en": "Open in another AI assistant" }, - "download_all": { - "en": "Open all docs as markdown" - }, - "error": { - "en": "Copy failed" + "prompt": { + "en": "Read {url} so I can ask questions about it." } }, "post": { diff --git a/src/layouts/Default.astro b/src/layouts/Default.astro index e574e748c4..9b44b7c71e 100644 --- a/src/layouts/Default.astro +++ b/src/layouts/Default.astro @@ -22,7 +22,7 @@ import ArticleJourney from '../components/ArticleJourney.astro'; import Feedback from '../components/Feedback.astro'; import EditOnGithub from '../components/EditOnGithub.astro'; import LastUpdated from '../components/LastUpdated.astro'; -import CopyMarkdown from '../components/CopyMarkdown.astro'; +import OpenInLlm from '../components/OpenInLlm.astro'; import Plausible from 'src/components/Plausible.astro'; import SharedFooter from 'src/components/SharedFooter.astro'; import SharedScripts from 'src/components/SharedScripts.astro'; @@ -87,6 +87,7 @@ const showSearch = !isSearchPage; headings={headings} lang={lang} /> +
@@ -102,7 +103,6 @@ const showSearch = !isSearchPage; headings={headings} lang={lang} /> - `, or an `` when given an `href 5. **disabled**: Optional `true`/`false` 6. **href**: Optional. When set, the button renders as a link rather than a `