From 4598e8783bc6edab999f5bbd3c170e7202ba3329 Mon Sep 17 00:00:00 2001 From: Pierre Wizla Date: Tue, 4 Nov 2025 11:02:49 +0000 Subject: [PATCH 01/43] Add new docContet.js util --- .../AiToolbar/actions/actionRegistry.js | 22 +++--- .../AiToolbar/actions/copyMarkdown.js | 71 ++++++------------ .../AiToolbar/config/aiToolsConfig.js | 38 ++++++++-- .../components/AiToolbar/utils/docContext.js | 75 +++++++++++++++++++ 4 files changed, 141 insertions(+), 65 deletions(-) create mode 100644 docusaurus/src/components/AiToolbar/utils/docContext.js diff --git a/docusaurus/src/components/AiToolbar/actions/actionRegistry.js b/docusaurus/src/components/AiToolbar/actions/actionRegistry.js index 49a327e25e..e5a962e219 100644 --- a/docusaurus/src/components/AiToolbar/actions/actionRegistry.js +++ b/docusaurus/src/components/AiToolbar/actions/actionRegistry.js @@ -1,26 +1,28 @@ import { copyMarkdownAction } from './copyMarkdown'; import { navigateAction } from './navigate'; +import { openLLMAction } from './openLLM'; // Central registry of all available actions export const actionHandlers = { 'copy-markdown': copyMarkdownAction, - 'navigate': navigateAction, + navigate: navigateAction, + 'open-llm': openLLMAction, }; // Main function to execute an action export const executeAction = async (actionConfig, additionalContext = {}) => { const handler = actionHandlers[actionConfig.actionType]; - + if (!handler) { console.warn(`Unknown action type: ${actionConfig.actionType}`); return; } - + const context = { ...actionConfig, // url, etc. ...additionalContext, // docId, docPath, updateActionState, closeDropdown, etc. }; - + try { await handler(context); } catch (error) { @@ -38,28 +40,28 @@ export const getActionDisplay = (actionId, currentState = 'idle') => { icon: 'circle-notch', iconClasses: 'ph-bold spinning', label: 'Copying...', - className: 'ai-toolbar-button--loading' + className: 'ai-toolbar-button--loading', }; case 'success': return { icon: 'check-circle', iconClasses: 'ph-fill', label: 'Copied!', - className: 'ai-toolbar-button--success' + className: 'ai-toolbar-button--success', }; case 'error': return { icon: 'warning-circle', iconClasses: 'ph-fill', label: 'Copy failed', - className: 'ai-toolbar-button--error' + className: 'ai-toolbar-button--error', }; default: // 'idle' return { icon: 'copy', iconClasses: 'ph-bold', label: 'Copy Markdown', - className: 'ai-toolbar-button--idle' + className: 'ai-toolbar-button--idle', }; } default: @@ -68,7 +70,7 @@ export const getActionDisplay = (actionId, currentState = 'idle') => { icon: 'question', iconClasses: 'ph-bold', label: 'Unknown', - className: 'ai-toolbar-button--idle' + className: 'ai-toolbar-button--idle', }; } -}; \ No newline at end of file +}; diff --git a/docusaurus/src/components/AiToolbar/actions/copyMarkdown.js b/docusaurus/src/components/AiToolbar/actions/copyMarkdown.js index f3580738d7..037a224b08 100644 --- a/docusaurus/src/components/AiToolbar/actions/copyMarkdown.js +++ b/docusaurus/src/components/AiToolbar/actions/copyMarkdown.js @@ -1,62 +1,39 @@ +import { resolveDocContext, getRawMarkdownUrl } from '../utils/docContext'; + export const copyMarkdownAction = async (context) => { const { docId, docPath, updateActionState } = context; - + try { - updateActionState('copy-markdown', 'loading'); - - // Helper functions to get current document info from URL (reused from CopyMarkdownButton) - const getCurrentDocId = () => { - if (typeof window === 'undefined') return null; - const path = window.location.pathname; - // Remove leading/trailing slashes and split - const segments = path.replace(/^\/|\/$/g, '').split('/'); - // For paths like /cms/api/rest or /cloud/getting-started/intro - if (segments.length >= 2) { - return segments.join('/'); - } - return null; - }; - - const getCurrentDocPath = () => { - if (typeof window === 'undefined') return null; - const path = window.location.pathname; - // Convert URL path to docs file path - const cleanPath = path.replace(/^\/|\/$/g, ''); - return cleanPath ? `docs/${cleanPath}.md` : null; - }; - - // Use props or try to get from current URL - const currentDocId = docId || getCurrentDocId(); - const currentDocPath = docPath || getCurrentDocPath(); - - if (!currentDocId && !currentDocPath) { + if (updateActionState) { + updateActionState('copy-markdown', 'loading'); + } + + const { docId: resolvedDocId, docPath: resolvedDocPath } = resolveDocContext(docId, docPath); + const markdownUrl = getRawMarkdownUrl({ docId: resolvedDocId, docPath: resolvedDocPath }); + + if (!markdownUrl) { throw new Error('Unable to determine document path'); } - // Build the raw markdown URL from GitHub - const baseUrl = 'https://raw.githubusercontent.com/strapi/documentation/main/docusaurus'; - const markdownUrl = currentDocPath - ? `${baseUrl}/${currentDocPath}` - : `${baseUrl}/docs/${currentDocId}.md`; - - // Fetch the raw markdown content const response = await fetch(markdownUrl); - + if (!response.ok) { throw new Error(`Failed to fetch markdown: ${response.status}`); } - + const markdownContent = await response.text(); - - // Copy to clipboard await navigator.clipboard.writeText(markdownContent); - - updateActionState('copy-markdown', 'success'); - setTimeout(() => updateActionState('copy-markdown', 'idle'), 3000); - + + if (updateActionState) { + updateActionState('copy-markdown', 'success'); + setTimeout(() => updateActionState('copy-markdown', 'idle'), 3000); + } } catch (error) { console.error('Error copying markdown:', error); - updateActionState('copy-markdown', 'error'); - setTimeout(() => updateActionState('copy-markdown', 'idle'), 3000); + + if (updateActionState) { + updateActionState('copy-markdown', 'error'); + setTimeout(() => updateActionState('copy-markdown', 'idle'), 3000); + } } -}; \ No newline at end of file +}; diff --git a/docusaurus/src/components/AiToolbar/config/aiToolsConfig.js b/docusaurus/src/components/AiToolbar/config/aiToolsConfig.js index 0b84e435fb..9f0bbc584c 100644 --- a/docusaurus/src/components/AiToolbar/config/aiToolsConfig.js +++ b/docusaurus/src/components/AiToolbar/config/aiToolsConfig.js @@ -1,13 +1,13 @@ export const aiToolsConfig = { primaryActionId: 'copy-markdown', - + actions: [ { id: 'copy-markdown', label: 'Copy Markdown', description: 'Copy the raw markdown content of this page', icon: 'copy', - actionType: 'copy-markdown' + actionType: 'copy-markdown', }, { id: 'view-llms', @@ -15,15 +15,37 @@ export const aiToolsConfig = { description: 'Lightweight version for AI models', icon: 'file-text', actionType: 'navigate', - url: '/llms.txt' + url: '/llms.txt', }, { id: 'view-llms-full', - label: 'View LLMs-full.txt', + label: 'View LLMs-full.txt', description: 'Complete documentation for AI models', icon: 'file-text', actionType: 'navigate', - url: '/llms-full.txt' - } - ] -}; \ No newline at end of file + url: '/llms-full.txt', + }, + { + id: 'open-chatgpt', + label: 'Open with ChatGPT', + description: 'Open ChatGPT with a prompt about this page', + icon: 'chat-circle-dots', + actionType: 'open-llm', + targetUrl: 'https://chat.openai.com/', + promptTemplate: 'Read from {{url}} so I can ask questions about it.', + promptParam: 'prompt', + openIn: '_blank', + }, + { + id: 'open-claude', + label: 'Open with Claude', + description: 'Open Claude with a prompt about this page', + icon: 'sparkle', + actionType: 'open-llm', + targetUrl: 'https://claude.ai/new', + promptTemplate: 'Read from {{url}} so I can ask questions about it.', + promptParam: 'query', + openIn: '_blank', + }, + ], +}; diff --git a/docusaurus/src/components/AiToolbar/utils/docContext.js b/docusaurus/src/components/AiToolbar/utils/docContext.js new file mode 100644 index 0000000000..fefeb6e954 --- /dev/null +++ b/docusaurus/src/components/AiToolbar/utils/docContext.js @@ -0,0 +1,75 @@ +const RAW_BASE_URL = 'https://raw.githubusercontent.com/strapi/documentation/main/docusaurus'; + +const isBrowser = () => typeof window !== 'undefined'; + +export const getCurrentDocId = () => { + if (!isBrowser()) return null; + const path = window.location.pathname; + const segments = path.replace(/^\/|\/$/g, '').split('/'); + return segments.length >= 2 ? segments.join('/') : null; +}; + +export const getCurrentDocPath = () => { + if (!isBrowser()) return null; + const path = window.location.pathname; + const cleanPath = path.replace(/^\/|\/$/g, ''); + return cleanPath ? `docs/${cleanPath}.md` : null; +}; + +export const getCurrentDocUrl = () => { + if (!isBrowser()) return null; + return window.location.href; +}; + +export const resolveDocContext = (docId, docPath) => { + return { + docId: docId || getCurrentDocId(), + docPath: docPath || getCurrentDocPath(), + }; +}; + +export const getRawMarkdownUrl = ({ docId, docPath }) => { + if (docPath) { + return `${RAW_BASE_URL}/${docPath}`; + } + if (docId) { + return `${RAW_BASE_URL}/docs/${docId}.md`; + } + return null; +}; + +const TEMPLATE_PATTERN = /\{\{\s*(\w+)\s*\}\}/g; + +export const applyTemplate = (template, values) => { + if (typeof template !== 'string') return ''; + return template.replace(TEMPLATE_PATTERN, (match, key) => { + const value = values[key]; + return value == null ? '' : String(value); + }); +}; + +export const buildPromptFromTemplate = (template, extraValues = {}) => { + const values = { + url: getCurrentDocUrl(), + docId: getCurrentDocId(), + docPath: getCurrentDocPath(), + ...extraValues, + }; + return applyTemplate(template, values); +}; + +export const buildUrlWithPrompt = ({ targetUrl, prompt, promptParam = 'prompt' }) => { + if (!targetUrl) return null; + if (!promptParam || !prompt) return targetUrl; + + try { + const url = new URL(targetUrl, isBrowser() ? window.location.origin : undefined); + url.searchParams.set(promptParam, prompt); + return url.toString(); + } catch (error) { + const encodedPrompt = encodeURIComponent(prompt); + const encodedParam = encodeURIComponent(promptParam); + const separator = targetUrl.includes('?') ? '&' : '?'; + return `${targetUrl}${separator}${encodedParam}=${encodedPrompt}`; + } +}; From 6f0bde0e9e681aea2602a2541ae54a6d159a4bd3 Mon Sep 17 00:00:00 2001 From: Pierre Wizla Date: Tue, 4 Nov 2025 11:03:35 +0000 Subject: [PATCH 02/43] Add new openLLM action --- .../components/AiToolbar/actions/openLLM.js | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 docusaurus/src/components/AiToolbar/actions/openLLM.js diff --git a/docusaurus/src/components/AiToolbar/actions/openLLM.js b/docusaurus/src/components/AiToolbar/actions/openLLM.js new file mode 100644 index 0000000000..d8b6e037af --- /dev/null +++ b/docusaurus/src/components/AiToolbar/actions/openLLM.js @@ -0,0 +1,31 @@ +import { buildPromptFromTemplate, buildUrlWithPrompt } from '../utils/docContext'; + +const DEFAULT_PROMPT_TEMPLATE = 'Read from {{url}} so I can ask questions about it.'; + +export const openLLMAction = (context) => { + const { + targetUrl, + promptTemplate = DEFAULT_PROMPT_TEMPLATE, + promptParam = 'prompt', + openIn = '_blank', + closeDropdown, + } = context; + + if (!targetUrl) { + console.error('open-llm action requires a targetUrl'); + return; + } + + const prompt = buildPromptFromTemplate(promptTemplate); + const fullUrl = buildUrlWithPrompt({ + targetUrl, + prompt, + promptParam, + }); + + window.open(fullUrl || targetUrl, openIn); + + if (closeDropdown) { + closeDropdown(); + } +}; From 925e20d09387cdeea578117cf39545d0c6628559 Mon Sep 17 00:00:00 2001 From: Pierre Wizla Date: Tue, 4 Nov 2025 13:15:31 +0000 Subject: [PATCH 03/43] Swap generic icon for ChatGPT with the official OpenAI logo from PhosphorIcons library --- docusaurus/src/components/AiToolbar/config/aiToolsConfig.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docusaurus/src/components/AiToolbar/config/aiToolsConfig.js b/docusaurus/src/components/AiToolbar/config/aiToolsConfig.js index 9f0bbc584c..626c9316b0 100644 --- a/docusaurus/src/components/AiToolbar/config/aiToolsConfig.js +++ b/docusaurus/src/components/AiToolbar/config/aiToolsConfig.js @@ -29,7 +29,7 @@ export const aiToolsConfig = { id: 'open-chatgpt', label: 'Open with ChatGPT', description: 'Open ChatGPT with a prompt about this page', - icon: 'chat-circle-dots', + icon: 'open-ai-logo', actionType: 'open-llm', targetUrl: 'https://chat.openai.com/', promptTemplate: 'Read from {{url}} so I can ask questions about it.', From c50254c59e325f44fa2d609bfbb86c481113185e Mon Sep 17 00:00:00 2001 From: Pierre Wizla Date: Tue, 4 Nov 2025 13:46:37 +0000 Subject: [PATCH 04/43] Swap sparkle icon for Claude with a chat bubble icon, since sparkle is used for Kapa --- docusaurus/src/components/AiToolbar/config/aiToolsConfig.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docusaurus/src/components/AiToolbar/config/aiToolsConfig.js b/docusaurus/src/components/AiToolbar/config/aiToolsConfig.js index 626c9316b0..551e47769f 100644 --- a/docusaurus/src/components/AiToolbar/config/aiToolsConfig.js +++ b/docusaurus/src/components/AiToolbar/config/aiToolsConfig.js @@ -40,7 +40,7 @@ export const aiToolsConfig = { id: 'open-claude', label: 'Open with Claude', description: 'Open Claude with a prompt about this page', - icon: 'sparkle', + icon: 'circles-three-plus', actionType: 'open-llm', targetUrl: 'https://claude.ai/new', promptTemplate: 'Read from {{url}} so I can ask questions about it.', From 04c94d798f53157adba6c44649554cd0ea04d3af Mon Sep 17 00:00:00 2001 From: Pierre Wizla Date: Tue, 4 Nov 2025 14:23:08 +0000 Subject: [PATCH 05/43] Add prompt localization for the top 10 most used languages --- .../components/AiToolbar/actions/openLLM.js | 10 +++- .../AiToolbar/config/aiToolsConfig.js | 24 +++++++++ .../components/AiToolbar/utils/docContext.js | 49 +++++++++++++++++++ 3 files changed, 81 insertions(+), 2 deletions(-) diff --git a/docusaurus/src/components/AiToolbar/actions/openLLM.js b/docusaurus/src/components/AiToolbar/actions/openLLM.js index d8b6e037af..be34c7efee 100644 --- a/docusaurus/src/components/AiToolbar/actions/openLLM.js +++ b/docusaurus/src/components/AiToolbar/actions/openLLM.js @@ -1,4 +1,8 @@ -import { buildPromptFromTemplate, buildUrlWithPrompt } from '../utils/docContext'; +import { + buildPromptFromTemplate, + buildUrlWithPrompt, + selectLocalizedTemplate, +} from '../utils/docContext'; const DEFAULT_PROMPT_TEMPLATE = 'Read from {{url}} so I can ask questions about it.'; @@ -6,6 +10,7 @@ export const openLLMAction = (context) => { const { targetUrl, promptTemplate = DEFAULT_PROMPT_TEMPLATE, + localizedPromptTemplates = {}, promptParam = 'prompt', openIn = '_blank', closeDropdown, @@ -16,7 +21,8 @@ export const openLLMAction = (context) => { return; } - const prompt = buildPromptFromTemplate(promptTemplate); + const template = selectLocalizedTemplate(promptTemplate, localizedPromptTemplates); + const prompt = buildPromptFromTemplate(template); const fullUrl = buildUrlWithPrompt({ targetUrl, prompt, diff --git a/docusaurus/src/components/AiToolbar/config/aiToolsConfig.js b/docusaurus/src/components/AiToolbar/config/aiToolsConfig.js index 551e47769f..f9b8ff6dd5 100644 --- a/docusaurus/src/components/AiToolbar/config/aiToolsConfig.js +++ b/docusaurus/src/components/AiToolbar/config/aiToolsConfig.js @@ -33,6 +33,18 @@ export const aiToolsConfig = { actionType: 'open-llm', targetUrl: 'https://chat.openai.com/', promptTemplate: 'Read from {{url}} so I can ask questions about it.', + localizedPromptTemplates: { + en: 'Read from {{url}} so I can ask questions about it.', + zh: '請閱讀 {{url}} 的內容,以便我能就其提出問題。', + es: 'Lee {{url}} para que pueda hacer preguntas al respecto.', + hi: '{{url}} पढ़ें ताकि मैं इसके बारे में प्रश्न पूछ सकूँ।', + ar: 'اقرأ من {{url}} حتى أتمكن من طرح أسئلة حوله.', + fr: 'Lis {{url}} pour que je puisse poser des questions à son sujet.', + pt: 'Leia {{url}} para que eu possa fazer perguntas sobre isso.', + bn: '{{url}} থেকে পড়ুন যাতে আমি এর সম্পর্কে প্রশ্ন করতে পারি।', + ru: 'Прочитай {{url}}, чтобы я мог задавать вопросы по нему.', + ja: '{{url}} を読んで、質問できるようにしてください。', + }, promptParam: 'prompt', openIn: '_blank', }, @@ -44,6 +56,18 @@ export const aiToolsConfig = { actionType: 'open-llm', targetUrl: 'https://claude.ai/new', promptTemplate: 'Read from {{url}} so I can ask questions about it.', + localizedPromptTemplates: { + en: 'Read from {{url}} so I can ask questions about it.', + zh: '請閱讀 {{url}} 的內容,以便我能就其提出問題。', + es: 'Lee {{url}} para que pueda hacer preguntas al respecto.', + hi: '{{url}} पढ़ें ताकि मैं इसके बारे में प्रश्न पूछ सकूँ।', + ar: 'اقرأ من {{url}} حتى أتمكن من طرح أسئلة حوله.', + fr: 'Lis {{url}} pour que je puisse poser des questions à son sujet.', + pt: 'Leia {{url}} para que eu possa fazer perguntas sobre isso.', + bn: '{{url}} থেকে পড়ুন যাতে আমি এর সম্পর্কে প্রশ্ন করতে পারি।', + ru: 'Прочитай {{url}}, чтобы я мог задавать вопросы по нему.', + ja: '{{url}} を読んで、質問できるようにしてください。', + }, promptParam: 'query', openIn: '_blank', }, diff --git a/docusaurus/src/components/AiToolbar/utils/docContext.js b/docusaurus/src/components/AiToolbar/utils/docContext.js index fefeb6e954..9dfe028c86 100644 --- a/docusaurus/src/components/AiToolbar/utils/docContext.js +++ b/docusaurus/src/components/AiToolbar/utils/docContext.js @@ -73,3 +73,52 @@ export const buildUrlWithPrompt = ({ targetUrl, prompt, promptParam = 'prompt' } return `${targetUrl}${separator}${encodedParam}=${encodedPrompt}`; } }; + +export const getUserLanguagePreferences = () => { + if (!isBrowser()) { + return []; + } + + const languages = Array.isArray(navigator.languages) ? navigator.languages : []; + const fallback = navigator.language ? [navigator.language] : []; + const merged = [...languages, ...fallback]; + + return merged + .map((lang) => (typeof lang === 'string' ? lang.toLowerCase() : null)) + .filter(Boolean); +}; + +export const selectLocalizedTemplate = (defaultTemplate, localizedTemplates = {}) => { + const userLanguages = getUserLanguagePreferences(); + const availableLanguages = Object.keys(localizedTemplates); + + const tryMatch = (languageTag) => { + if (!languageTag) { + return null; + } + + if (localizedTemplates[languageTag]) { + return localizedTemplates[languageTag]; + } + + const base = languageTag.split('-')[0]; + if (localizedTemplates[base]) { + return localizedTemplates[base]; + } + + return null; + }; + + for (const lang of userLanguages) { + const match = tryMatch(lang); + if (match) { + return match; + } + } + + if (localizedTemplates.en) { + return localizedTemplates.en; + } + + return defaultTemplate; +}; From 68742d8f19bec9f3095f034a431e44e04bdaacfb Mon Sep 17 00:00:00 2001 From: Pierre Wizla Date: Tue, 4 Nov 2025 15:21:46 +0000 Subject: [PATCH 06/43] Fix Claude incorrectly building translated prompt --- .../AiToolbar/config/aiToolsConfig.js | 8 ++--- .../components/AiToolbar/utils/docContext.js | 31 +++++++++++++++---- 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/docusaurus/src/components/AiToolbar/config/aiToolsConfig.js b/docusaurus/src/components/AiToolbar/config/aiToolsConfig.js index f9b8ff6dd5..2c95be80e0 100644 --- a/docusaurus/src/components/AiToolbar/config/aiToolsConfig.js +++ b/docusaurus/src/components/AiToolbar/config/aiToolsConfig.js @@ -35,7 +35,7 @@ export const aiToolsConfig = { promptTemplate: 'Read from {{url}} so I can ask questions about it.', localizedPromptTemplates: { en: 'Read from {{url}} so I can ask questions about it.', - zh: '請閱讀 {{url}} 的內容,以便我能就其提出問題。', + zh: '请阅读 {{url}} 的内容,以便我能就其提出问题。', es: 'Lee {{url}} para que pueda hacer preguntas al respecto.', hi: '{{url}} पढ़ें ताकि मैं इसके बारे में प्रश्न पूछ सकूँ।', ar: 'اقرأ من {{url}} حتى أتمكن من طرح أسئلة حوله.', @@ -58,17 +58,17 @@ export const aiToolsConfig = { promptTemplate: 'Read from {{url}} so I can ask questions about it.', localizedPromptTemplates: { en: 'Read from {{url}} so I can ask questions about it.', - zh: '請閱讀 {{url}} 的內容,以便我能就其提出問題。', + zh: '请阅读 {{url}} 的内容,以便我能就其提出问题。', es: 'Lee {{url}} para que pueda hacer preguntas al respecto.', hi: '{{url}} पढ़ें ताकि मैं इसके बारे में प्रश्न पूछ सकूँ।', ar: 'اقرأ من {{url}} حتى أتمكن من طرح أسئلة حوله.', fr: 'Lis {{url}} pour que je puisse poser des questions à son sujet.', pt: 'Leia {{url}} para que eu possa fazer perguntas sobre isso.', - bn: '{{url}} থেকে পড়ুন যাতে আমি এর সম্পর্কে প্রশ্ন করতে পারি।', + bn: '{{url}} থেকে পড়ুন যাতে আমি এর সম্পর্কে प्रश्न করতে পারি।', ru: 'Прочитай {{url}}, чтобы я мог задавать вопросы по нему.', ja: '{{url}} を読んで、質問できるようにしてください。', }, - promptParam: 'query', + promptParam: ['query', 'initial_message'], openIn: '_blank', }, ], diff --git a/docusaurus/src/components/AiToolbar/utils/docContext.js b/docusaurus/src/components/AiToolbar/utils/docContext.js index 9dfe028c86..9bc7744677 100644 --- a/docusaurus/src/components/AiToolbar/utils/docContext.js +++ b/docusaurus/src/components/AiToolbar/utils/docContext.js @@ -58,19 +58,40 @@ export const buildPromptFromTemplate = (template, extraValues = {}) => { return applyTemplate(template, values); }; +const normalizeParams = (promptParam) => { + if (!promptParam) { + return []; + } + + if (Array.isArray(promptParam)) { + return promptParam.filter(Boolean); + } + + return [promptParam]; +}; + export const buildUrlWithPrompt = ({ targetUrl, prompt, promptParam = 'prompt' }) => { if (!targetUrl) return null; - if (!promptParam || !prompt) return targetUrl; + const params = normalizeParams(promptParam); + + if (!prompt || params.length === 0) { + return targetUrl; + } try { const url = new URL(targetUrl, isBrowser() ? window.location.origin : undefined); - url.searchParams.set(promptParam, prompt); + params.forEach((param) => { + url.searchParams.set(param, prompt); + }); return url.toString(); } catch (error) { const encodedPrompt = encodeURIComponent(prompt); - const encodedParam = encodeURIComponent(promptParam); + const encodedParams = params.map((param) => encodeURIComponent(param)); const separator = targetUrl.includes('?') ? '&' : '?'; - return `${targetUrl}${separator}${encodedParam}=${encodedPrompt}`; + const query = encodedParams + .map((param) => `${param}=${encodedPrompt}`) + .join('&'); + return `${targetUrl}${separator}${query}`; } }; @@ -90,8 +111,6 @@ export const getUserLanguagePreferences = () => { export const selectLocalizedTemplate = (defaultTemplate, localizedTemplates = {}) => { const userLanguages = getUserLanguagePreferences(); - const availableLanguages = Object.keys(localizedTemplates); - const tryMatch = (languageTag) => { if (!languageTag) { return null; From 0fc185c3904711920729f5ee5b3b2e0add059c33 Mon Sep 17 00:00:00 2001 From: Pierre Wizla Date: Tue, 4 Nov 2025 16:06:10 +0000 Subject: [PATCH 07/43] Better fix Claude handling of locales by adding dual English + localized prompt --- .../src/components/AiToolbar/actions/openLLM.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/docusaurus/src/components/AiToolbar/actions/openLLM.js b/docusaurus/src/components/AiToolbar/actions/openLLM.js index be34c7efee..6a1d84556f 100644 --- a/docusaurus/src/components/AiToolbar/actions/openLLM.js +++ b/docusaurus/src/components/AiToolbar/actions/openLLM.js @@ -6,6 +6,14 @@ import { const DEFAULT_PROMPT_TEMPLATE = 'Read from {{url}} so I can ask questions about it.'; +const buildLocalizedPromptTemplate = (defaultTemplate, selectedTemplate) => { + if (!selectedTemplate || selectedTemplate === defaultTemplate) { + return defaultTemplate; + } + + return `${selectedTemplate}\n\n---\n${defaultTemplate}`; +}; + export const openLLMAction = (context) => { const { targetUrl, @@ -21,7 +29,8 @@ export const openLLMAction = (context) => { return; } - const template = selectLocalizedTemplate(promptTemplate, localizedPromptTemplates); + const selectedTemplate = selectLocalizedTemplate(promptTemplate, localizedPromptTemplates); + const template = buildLocalizedPromptTemplate(DEFAULT_PROMPT_TEMPLATE, selectedTemplate); const prompt = buildPromptFromTemplate(template); const fullUrl = buildUrlWithPrompt({ targetUrl, From 98c95ccfe12331560d4d04b1b3d04c16d47d929d Mon Sep 17 00:00:00 2001 From: Pierre Wizla Date: Tue, 4 Nov 2025 16:34:29 +0000 Subject: [PATCH 08/43] Ensure only Claude shows dual prompts --- .../components/AiToolbar/actions/openLLM.js | 18 +++++++++++++----- .../AiToolbar/config/aiToolsConfig.js | 3 ++- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/docusaurus/src/components/AiToolbar/actions/openLLM.js b/docusaurus/src/components/AiToolbar/actions/openLLM.js index 6a1d84556f..d287d51a88 100644 --- a/docusaurus/src/components/AiToolbar/actions/openLLM.js +++ b/docusaurus/src/components/AiToolbar/actions/openLLM.js @@ -6,12 +6,19 @@ import { const DEFAULT_PROMPT_TEMPLATE = 'Read from {{url}} so I can ask questions about it.'; -const buildLocalizedPromptTemplate = (defaultTemplate, selectedTemplate) => { - if (!selectedTemplate || selectedTemplate === defaultTemplate) { - return defaultTemplate; +const resolveTemplate = (defaultTemplate, selectedTemplate, includeDefaultPrompt) => { + const safeDefault = defaultTemplate || DEFAULT_PROMPT_TEMPLATE; + const safeSelected = selectedTemplate || safeDefault; + + if (!includeDefaultPrompt) { + return safeSelected; + } + + if (safeSelected === safeDefault) { + return safeDefault; } - return `${selectedTemplate}\n\n---\n${defaultTemplate}`; + return `${safeSelected}\n\n---\n${safeDefault}`; }; export const openLLMAction = (context) => { @@ -19,6 +26,7 @@ export const openLLMAction = (context) => { targetUrl, promptTemplate = DEFAULT_PROMPT_TEMPLATE, localizedPromptTemplates = {}, + includeDefaultPrompt = false, promptParam = 'prompt', openIn = '_blank', closeDropdown, @@ -30,7 +38,7 @@ export const openLLMAction = (context) => { } const selectedTemplate = selectLocalizedTemplate(promptTemplate, localizedPromptTemplates); - const template = buildLocalizedPromptTemplate(DEFAULT_PROMPT_TEMPLATE, selectedTemplate); + const template = resolveTemplate(promptTemplate, selectedTemplate, includeDefaultPrompt); const prompt = buildPromptFromTemplate(template); const fullUrl = buildUrlWithPrompt({ targetUrl, diff --git a/docusaurus/src/components/AiToolbar/config/aiToolsConfig.js b/docusaurus/src/components/AiToolbar/config/aiToolsConfig.js index 2c95be80e0..0a4c91ce79 100644 --- a/docusaurus/src/components/AiToolbar/config/aiToolsConfig.js +++ b/docusaurus/src/components/AiToolbar/config/aiToolsConfig.js @@ -64,10 +64,11 @@ export const aiToolsConfig = { ar: 'اقرأ من {{url}} حتى أتمكن من طرح أسئلة حوله.', fr: 'Lis {{url}} pour que je puisse poser des questions à son sujet.', pt: 'Leia {{url}} para que eu possa fazer perguntas sobre isso.', - bn: '{{url}} থেকে পড়ুন যাতে আমি এর সম্পর্কে प्रश्न করতে পারি।', + bn: '{{url}} থেকে পড়ুন যাতে నేను এর সম্পর্কে প্রশ্ন করতে পারি।', ru: 'Прочитай {{url}}, чтобы я мог задавать вопросы по нему.', ja: '{{url}} を読んで、質問できるようにしてください。', }, + includeDefaultPrompt: true, promptParam: ['query', 'initial_message'], openIn: '_blank', }, From a68f7b08c9c8e85d39f84ed8529df51e64437f60 Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Tue, 4 Nov 2025 18:03:15 +0100 Subject: [PATCH 09/43] =?UTF-8?q?Hide=20descriptions=20for=20Open=20with?= =?UTF-8?q?=E2=80=A6=20buttons?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docusaurus/src/components/AiToolbar/config/aiToolsConfig.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docusaurus/src/components/AiToolbar/config/aiToolsConfig.js b/docusaurus/src/components/AiToolbar/config/aiToolsConfig.js index 0a4c91ce79..c3f220097f 100644 --- a/docusaurus/src/components/AiToolbar/config/aiToolsConfig.js +++ b/docusaurus/src/components/AiToolbar/config/aiToolsConfig.js @@ -28,7 +28,7 @@ export const aiToolsConfig = { { id: 'open-chatgpt', label: 'Open with ChatGPT', - description: 'Open ChatGPT with a prompt about this page', + // description: 'Open ChatGPT with a prompt about this page', icon: 'open-ai-logo', actionType: 'open-llm', targetUrl: 'https://chat.openai.com/', @@ -51,7 +51,7 @@ export const aiToolsConfig = { { id: 'open-claude', label: 'Open with Claude', - description: 'Open Claude with a prompt about this page', + // description: 'Open Claude with a prompt about this page', icon: 'circles-three-plus', actionType: 'open-llm', targetUrl: 'https://claude.ai/new', From 83d7e8ac17ff8fc5301841f9f976ecef6779b904 Mon Sep 17 00:00:00 2001 From: Pierre Wizla <4233866+pwizla@users.noreply.github.com> Date: Tue, 4 Nov 2025 18:03:24 +0100 Subject: [PATCH 10/43] Update icon for Claude --- docusaurus/src/components/AiToolbar/config/aiToolsConfig.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docusaurus/src/components/AiToolbar/config/aiToolsConfig.js b/docusaurus/src/components/AiToolbar/config/aiToolsConfig.js index c3f220097f..be605dec24 100644 --- a/docusaurus/src/components/AiToolbar/config/aiToolsConfig.js +++ b/docusaurus/src/components/AiToolbar/config/aiToolsConfig.js @@ -52,7 +52,7 @@ export const aiToolsConfig = { id: 'open-claude', label: 'Open with Claude', // description: 'Open Claude with a prompt about this page', - icon: 'circles-three-plus', + icon: 'chat-teardrop-text', actionType: 'open-llm', targetUrl: 'https://claude.ai/new', promptTemplate: 'Read from {{url}} so I can ask questions about it.', From 6766923d481c2282918388a19c8170bb382d6b6d Mon Sep 17 00:00:00 2001 From: Pierre Wizla Date: Tue, 4 Nov 2025 17:18:16 +0000 Subject: [PATCH 11/43] Fix icon position when no description provided --- docusaurus/src/components/AiToolbar/AiToolbar.jsx | 4 ++-- docusaurus/src/scss/ai-toolbar.scss | 14 +++++++++++++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/docusaurus/src/components/AiToolbar/AiToolbar.jsx b/docusaurus/src/components/AiToolbar/AiToolbar.jsx index c94964d621..401e15c5cd 100644 --- a/docusaurus/src/components/AiToolbar/AiToolbar.jsx +++ b/docusaurus/src/components/AiToolbar/AiToolbar.jsx @@ -124,7 +124,7 @@ const AiToolbar = () => {