From 15f9187627f6b090a64c1542344f97e77e36ec8a Mon Sep 17 00:00:00 2001 From: Pierre Wizla Date: Fri, 23 May 2025 15:02:08 +0200 Subject: [PATCH 1/4] First draft --- .../ContributeLink/ContributeLink.jsx | 2 + docusaurus/src/components/SendToAiButton.js | 306 ++++++++++++++++++ docusaurus/src/scss/__index.scss | 1 + docusaurus/src/scss/send-to-ai.scss | 268 +++++++++++++++ 4 files changed, 577 insertions(+) create mode 100644 docusaurus/src/components/SendToAiButton.js create mode 100644 docusaurus/src/scss/send-to-ai.scss diff --git a/docusaurus/src/components/ContributeLink/ContributeLink.jsx b/docusaurus/src/components/ContributeLink/ContributeLink.jsx index f801c6bd38..3a9cf9056e 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..b4d04fa9b0 --- /dev/null +++ b/docusaurus/src/components/SendToAiButton.js @@ -0,0 +1,306 @@ +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 success message + alert(`✅ Prompt copied to clipboard! Paste it in ${currentAI} to start chatting.`); + + } 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 + alert('❌ Auto-copy failed. Please manually copy the selected text (Ctrl+C / Cmd+C)'); + } + } + }, [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! +

+ +