diff --git a/docusaurus/src/components/ContributeLink/ContributeLink.jsx b/docusaurus/src/components/ContributeLink/ContributeLink.jsx index f801c6bd38..d60da1b745 100644 --- a/docusaurus/src/components/ContributeLink/ContributeLink.jsx +++ b/docusaurus/src/components/ContributeLink/ContributeLink.jsx @@ -6,6 +6,7 @@ import {translate} from '@docusaurus/Translate'; import styles from './contribute-link.module.scss'; import Icon from '../Icon'; import CopyMarkdownButton from '../CopyMarkdownButton'; +import SendToAIButton from '../SendToAiButton'; export default function ContributeLink() { const {siteConfig} = useDocusaurusContext(); @@ -37,6 +38,7 @@ export default function ContributeLink() { {editThisPageMessage} + ); } \ No newline at end of file diff --git a/docusaurus/src/components/SendToAiButton.js b/docusaurus/src/components/SendToAiButton.js new file mode 100644 index 0000000000..4fb726f3fd --- /dev/null +++ b/docusaurus/src/components/SendToAiButton.js @@ -0,0 +1,389 @@ +import React, { useState, useCallback, useRef } from 'react'; + +const SendToAIButton = ({ Icon }) => { + const [isOpen, setIsOpen] = useState(false); + const [isLoading, setIsLoading] = useState(false); + const [showPromptModal, setShowPromptModal] = useState(false); + const [currentPrompt, setCurrentPrompt] = useState(''); + const [currentAI, setCurrentAI] = useState(''); + const [dropdownPosition, setDropdownPosition] = useState({ top: 0, left: 0 }); + const buttonRef = useRef(null); + const textareaRef = useRef(null); + + // Calculate dropdown position + const calculatePosition = useCallback(() => { + if (buttonRef.current) { + const rect = buttonRef.current.getBoundingClientRect(); + setDropdownPosition({ + top: rect.bottom + window.scrollY + 4, + left: rect.left + window.scrollX + }); + } + }, []); + + // Get current document info + const getCurrentDocId = () => { + if (typeof window === 'undefined') return null; + const path = window.location.pathname; + const segments = path.replace(/^\/|\/$/g, '').split('/'); + return segments.length >= 2 ? segments.join('/') : null; + }; + + const getCurrentDocPath = () => { + if (typeof window === 'undefined') return null; + const path = window.location.pathname; + const cleanPath = path.replace(/^\/|\/$/g, ''); + return cleanPath ? `docs/${cleanPath}.md` : null; + }; + + // Fetch markdown content + const fetchMarkdownContent = async () => { + const currentDocId = getCurrentDocId(); + const currentDocPath = getCurrentDocPath(); + + if (!currentDocId && !currentDocPath) { + throw new Error('Could not determine document path'); + } + + const baseUrl = 'https://raw.githubusercontent.com/strapi/documentation/main/docusaurus'; + const markdownUrl = currentDocPath + ? `${baseUrl}/${currentDocPath}` + : `${baseUrl}/docs/${currentDocId}.md`; + + const response = await fetch(markdownUrl); + if (!response.ok) throw new Error(`Failed to fetch: ${response.status}`); + return await response.text(); + }; + + // Build prompt + const buildPrompt = (markdownContent) => { + return `You're a Strapi documentation expert. Please use this content as context and get ready to answer my questions: + +--- + +${markdownContent} + +--- + +I'm ready for your questions about this Strapi documentation!`; + }; + + // Handle AI selection + const handleAIClick = useCallback(async (aiType) => { + setIsLoading(true); + setIsOpen(false); + + try { + const markdownContent = await fetchMarkdownContent(); + const prompt = buildPrompt(markdownContent); + + setCurrentPrompt(prompt); + setCurrentAI(aiType); + setShowPromptModal(true); + + } catch (error) { + console.error('Error fetching content:', error); + alert('Error: Could not fetch the markdown content'); + } finally { + setIsLoading(false); + } + }, []); + + // Handle copy from modal + const handleCopyFromModal = useCallback(async () => { + try { + await navigator.clipboard.writeText(currentPrompt); + + // Open the AI tool + const url = currentAI === 'ChatGPT' + ? 'https://chat.openai.com/' + : 'https://claude.ai/chat'; + window.open(url, '_blank'); + + // Close modal + setShowPromptModal(false); + + // Show elegant notification instead of alert + const notification = document.createElement('div'); + notification.style.cssText = ` + position: fixed; + top: 20px; + right: 20px; + background: #28a745; + color: white; + padding: 12px 16px; + border-radius: 6px; + z-index: 1000001; + font-family: inherit; + font-size: 14px; + font-weight: 500; + box-shadow: 0 4px 12px rgba(0,0,0,0.15); + max-width: 300px; + display: flex; + align-items: center; + gap: 8px; + `; + notification.innerHTML = ` + + + + Prompt copied! Paste it in ${currentAI} + `; + document.body.appendChild(notification); + + setTimeout(() => { + if (document.body.contains(notification)) { + document.body.removeChild(notification); + } + }, 4000); + + } catch (error) { + console.error('Clipboard failed:', error); + // If clipboard fails, select all text for manual copy + if (textareaRef.current) { + textareaRef.current.select(); + textareaRef.current.setSelectionRange(0, 99999); // For mobile + + // Show warning notification instead of alert + const notification = document.createElement('div'); + notification.style.cssText = ` + position: fixed; + top: 20px; + right: 20px; + background: #ffc107; + color: #212529; + padding: 12px 16px; + border-radius: 6px; + z-index: 1000001; + font-family: inherit; + font-size: 14px; + font-weight: 500; + box-shadow: 0 4px 12px rgba(0,0,0,0.15); + max-width: 300px; + display: flex; + align-items: center; + gap: 8px; + `; + notification.innerHTML = ` + + + + Auto-copy failed. Please copy manually (Ctrl+C) + `; + document.body.appendChild(notification); + + setTimeout(() => { + if (document.body.contains(notification)) { + document.body.removeChild(notification); + } + }, 5000); + } + } + }, [currentPrompt, currentAI]); + + const handleToggle = useCallback((e) => { + e.preventDefault(); + e.stopPropagation(); + + if (!isOpen) { + calculatePosition(); + } + setIsOpen(prev => !prev); + }, [isOpen, calculatePosition]); + + if (!getCurrentDocId()) return null; + + return ( + <> + + + {/* Dropdown */} + {isOpen && ( +
+ + +
+ )} + + {/* Prompt Modal */} + {showPromptModal && ( +
+
+

+ + Copy this prompt to {currentAI} +

+

+ The prompt below contains the documentation content. Copy it and paste it in {currentAI} to start chatting! +

+ +