diff --git a/src/components/LLMActions/LLMActions.module.css b/src/components/LLMActions/LLMActions.module.css index 9769ccff7f..812a57243b 100644 --- a/src/components/LLMActions/LLMActions.module.css +++ b/src/components/LLMActions/LLMActions.module.css @@ -15,9 +15,13 @@ border: 1px solid var(--ifm-color-emphasis-300); background: transparent; color: var(--ifm-color-emphasis-700); + font-family: inherit; font-size: 0.8rem; font-weight: 500; + line-height: 1.5; + box-sizing: border-box; cursor: pointer; + text-decoration: none; transition: background-color 150ms ease, border-color 150ms ease, color 150ms ease; } @@ -25,6 +29,7 @@ background: var(--ifm-color-emphasis-100); border-color: var(--ifm-color-emphasis-400); color: var(--ifm-color-emphasis-800); + text-decoration: none; } .actionButton:focus-visible { @@ -43,6 +48,13 @@ flex-shrink: 0; } +.externalIcon { + width: 0.7em; + height: 0.7em; + flex-shrink: 0; + opacity: 0.6; +} + /* Dark mode adjustments */ :global([data-theme='dark']) .actionButton { border-color: var(--ifm-color-emphasis-400); diff --git a/src/components/LLMActions/LLMActions.tsx b/src/components/LLMActions/LLMActions.tsx index fe8bd44d7b..38117c6f2d 100644 --- a/src/components/LLMActions/LLMActions.tsx +++ b/src/components/LLMActions/LLMActions.tsx @@ -1,6 +1,8 @@ import React, { useState, useCallback } from 'react'; import { useDoc } from '@docusaurus/plugin-content-docs/client'; -import { FaRegCopy, FaCheck, FaMarkdown } from 'react-icons/fa'; +import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; +import { FaRegCopy, FaCheck, FaMarkdown, FaExternalLinkAlt } from 'react-icons/fa'; +import { SiOpenai, SiClaude } from 'react-icons/si'; import styles from './LLMActions.module.css'; /** @@ -38,7 +40,17 @@ export default function LLMActions() { const [loading, setLoading] = useState(false); const { metadata, frontMatter } = useDoc(); - const { editUrl, slug } = metadata; + const { editUrl, slug, permalink } = metadata; + const { siteConfig } = useDocusaurusContext(); + + // Canonical, absolute page URL derived from Docusaurus config + permalink. + // Available during SSR and the first render, so the prompts are always valid. + const pageUrl = `${siteConfig.url}${permalink}`; + + // Prefilled prompts that point the assistant at this page. + const prompt = `Read ${pageUrl} and answer questions about the content.`; + const chatGptUrl = `https://chatgpt.com/?prompt=${encodeURIComponent(prompt)}`; + const claudeUrl = `https://claude.ai/new?q=${encodeURIComponent(prompt)}`; // Try to get raw URL from editUrl first, then fall back to slug-based construction let rawUrl = editUrl ? getGitHubRawUrl(editUrl) : null; @@ -59,7 +71,6 @@ export default function LLMActions() { throw new Error(`Failed to fetch: ${response.status}`); } const markdown = await response.text(); - const pageUrl = window.location.href; const content = `Source: ${pageUrl}\n\n${markdown}`; await navigator.clipboard.writeText(content); @@ -70,7 +81,7 @@ export default function LLMActions() { } finally { setLoading(false); } - }, [rawUrl]); + }, [rawUrl, pageUrl]); const handleViewMarkdown = useCallback(() => { if (rawUrl) { @@ -109,6 +120,32 @@ export default function LLMActions() { View as Markdown + + + Open in ChatGPT + + + + + Open in Claude + + ); }