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
+
+
);
}