@stackql/docusaurus-plugin-aeo 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +42 -0
- package/README.md +34 -8
- package/package.json +13 -2
- package/src/index.js +6 -3
- package/src/theme/AskAiButton/icons.js +50 -33
- package/src/theme/AskAiButton/index.jsx +80 -71
- package/src/theme/AskAiButton/styles.module.css +8 -71
- package/src/theme/BlogPostItem/Header/Title/index.jsx +19 -0
- package/src/theme/BlogPostItem/Header/Title/styles.module.css +8 -0
- package/src/theme/DocBreadcrumbs/index.jsx +17 -0
- package/src/theme/DocBreadcrumbs/styles.module.css +8 -0
- package/src/theme/BlogPostItem/Footer/index.jsx +0 -15
- package/src/theme/DocItem/Footer/index.jsx +0 -17
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,47 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.0
|
|
4
|
+
|
|
5
|
+
Visual refresh of the Ask AI button (feature 3) to match the look-and-feel of MUI-based dropdown components used elsewhere on consumer sites. No changes to features 1, 2, or 4.
|
|
6
|
+
|
|
7
|
+
### Changed (breaking)
|
|
8
|
+
|
|
9
|
+
- `@mui/material`, `@mui/icons-material`, `@emotion/react`, `@emotion/styled` are now required peer dependencies when `askAi.enabled` is `true`. Consumers that already use MUI (common in Docusaurus sites) get a single deduped copy at install time; consumers without MUI will get a clean npm peer-dependency install error pointing at exactly what to install. Consumers who disable the button (`askAi.enabled: false`) can skip these - the theme components are not registered and MUI is never imported.
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- Ask AI button reimplemented with MUI primitives: outlined `Button` (small, with `KeyboardArrowDownIcon` caret) for the trigger; `Menu` with `MenuItem`s containing `ListItemIcon` (the simple-icons brand SVGs from v0.3.0) and `ListItemText`. Menu is anchored bottom-right of the trigger and opens with transform-origin top-right.
|
|
14
|
+
- Button is hidden on viewports under 997px (matches the Docusaurus mobile breakpoint).
|
|
15
|
+
|
|
16
|
+
### Removed
|
|
17
|
+
|
|
18
|
+
- Custom click-outside and Esc-to-close handlers. MUI `Menu` provides both natively. Net reduction in `src/theme/AskAiButton/index.jsx`: ~25 lines.
|
|
19
|
+
- All custom trigger / menu CSS classes. The CSS module is now a 2-rule wrapper that controls only the responsive show/hide; all other styling lives on the MUI `sx` prop.
|
|
20
|
+
|
|
21
|
+
## 0.3.0
|
|
22
|
+
|
|
23
|
+
Focused UX upgrade to the Ask AI button (feature 3). No changes to features 1, 2, or 4.
|
|
24
|
+
|
|
25
|
+
### Changed (breaking)
|
|
26
|
+
|
|
27
|
+
- Default Ask AI button placement moved from doc / blog footer to the breadcrumb row at the top of each page. On doc pages the button sits right-aligned in the breadcrumb row; on blog posts (which have no breadcrumbs) it sits right-aligned above the post title.
|
|
28
|
+
- `askAi.placement` no longer accepts `'doc-footer'`. Valid values are `'breadcrumb-row'` (the new default) and `'none'`. The v0.1.x-v0.2.x `DocItem/Footer` and `BlogPostItem/Footer` swizzles have been deleted; there is no config flag that restores them. Consumers who want footer placement (or any other custom location) should set `askAi.placement: 'none'` and swizzle `@theme/AskAiButton` manually.
|
|
29
|
+
- Ask AI button restyled as a solid pill dropdown (themed background, rounded-full corners, animated caret). The trigger reads "Ask AI about this page". The menu is a right-aligned card with rounded corners and a subtle shadow.
|
|
30
|
+
|
|
31
|
+
### Changed
|
|
32
|
+
|
|
33
|
+
- Provider icons replaced with real brand SVGs sourced from [simple-icons](https://simpleicons.org) where available - Claude, Perplexity, Gemini. OpenAI / ChatGPT was removed from simple-icons in 2024 following a takedown; the ChatGPT menu row renders a neutral chat-bubble glyph instead. No build failure; the missing icon is logged as a warning when verbose.
|
|
34
|
+
|
|
35
|
+
### Added
|
|
36
|
+
|
|
37
|
+
- `simple-icons` (`^16.22.0`, CC0-1.0) as a runtime dependency. Bundle-size impact is bounded by webpack tree-shaking: the icons module uses named ESM imports so only the three referenced icons end up in the production bundle.
|
|
38
|
+
|
|
39
|
+
Even though placement changes are breaking, this is 0.3.0 (not 1.0.0) - the project is still pre-1.0 and the README never promised a stable placement option set.
|
|
40
|
+
|
|
41
|
+
### Migration
|
|
42
|
+
|
|
43
|
+
Bump the plugin version. If `askAi.placement: 'doc-footer'` is set in plugin options, remove it (or change it to `'breadcrumb-row'` for explicitness). No other config changes needed. After rebuild, the button appears at the top of each doc and blog page.
|
|
44
|
+
|
|
3
45
|
## 0.2.0
|
|
4
46
|
|
|
5
47
|
- Added: `llmsTxt.instanceSections` option for per-content-plugin-instance llms.txt section grouping. Use case: consumers with multiple content-docs instances (e.g. human docs + AI reference) can split them into named sections. Map keys are `"${pluginName}@${pluginId}"`; values are `{ title, order? }`. Unmapped instances are collected into a single appended "Other" section. `llms-full.txt` mirrors the same section structure as `llms.txt`.
|
package/README.md
CHANGED
|
@@ -170,7 +170,12 @@ Behavior:
|
|
|
170
170
|
|
|
171
171
|
## Feature 3: "Ask AI" button
|
|
172
172
|
|
|
173
|
-
|
|
173
|
+
An outlined pill button with a caret reading "Ask AI about this page" is injected at the top of every doc and blog-post content area, right-aligned in the breadcrumb row. Each item in the dropdown opens the corresponding AI surface in a new tab with a prefilled prompt that references the current page's `.md` companion. The button is hidden on viewports under 997px to keep the breadcrumb row uncluttered on mobile.
|
|
174
|
+
|
|
175
|
+
Placement specifics:
|
|
176
|
+
|
|
177
|
+
- **Doc pages** - the button sits on the same row as the breadcrumb trail, flex-aligned to the right edge of the content area.
|
|
178
|
+
- **Blog posts** - blog posts have no breadcrumbs, so the button sits at the very top of the post, right-aligned above the post title (the closest visual equivalent to the docs breadcrumb-row position).
|
|
174
179
|
|
|
175
180
|
Providers and URL patterns:
|
|
176
181
|
|
|
@@ -189,6 +194,10 @@ Read https://your-site.example/path/to/page.md and help me with the following qu
|
|
|
189
194
|
|
|
190
195
|
The trailing space is intentional - the cursor lands ready for the user to type.
|
|
191
196
|
|
|
197
|
+
Provider icons are real brand SVGs sourced from [simple-icons](https://simpleicons.org). Three of four (Claude, Perplexity, Gemini) come from the upstream catalog directly. OpenAI / ChatGPT was removed from simple-icons in 2024 following a takedown, so the ChatGPT row shows a neutral chat-bubble glyph as a placeholder.
|
|
198
|
+
|
|
199
|
+
**Trademark note.** Brand logos are trademarks of their respective owners; their inclusion via simple-icons does not imply endorsement. Consumers using the Ask AI button in commercial contexts should review each provider's brand-usage policy.
|
|
200
|
+
|
|
192
201
|
Options:
|
|
193
202
|
|
|
194
203
|
| Option | Default | Description |
|
|
@@ -196,17 +205,34 @@ Options:
|
|
|
196
205
|
| `askAi.enabled` | `true` | When `false`, the theme components are not registered. |
|
|
197
206
|
| `askAi.providerOrder` | `['claude', 'chatgpt', 'perplexity', 'gemini']` | Order of items in the dropdown. Drop entries to hide them. |
|
|
198
207
|
| `askAi.promptTemplate` | `'Read {pageUrl}.md and help me with the following question about it: '` | Prompt sent to each provider. `{pageUrl}` is the page's canonical URL (no trailing slash). If feature 1 is disabled, the consumer should remove the `.md` from the template. |
|
|
199
|
-
| `askAi.placement` | `'
|
|
208
|
+
| `askAi.placement` | `'breadcrumb-row'` | `'breadcrumb-row'` puts the button at the top of every doc/blog page (docs breadcrumb row, or above the blog title). `'none'` does not register any theme components - swizzle the button into your preferred location manually. |
|
|
200
209
|
|
|
201
|
-
|
|
210
|
+
The button is built from [MUI](https://mui.com) primitives - outlined `Button` with a `KeyboardArrowDownIcon` caret as the trigger, and a `Menu` of `MenuItem` rows for the providers. Theming reads `--ifm-color-primary` and `--ifm-font-family-base` via MUI's `sx` prop, so dark/light mode work automatically. The MUI `Menu` handles click-outside-to-close and Esc-to-close natively.
|
|
202
211
|
|
|
203
|
-
|
|
212
|
+
### Peer dependencies
|
|
204
213
|
|
|
205
|
-
|
|
206
|
-
|
|
214
|
+
When `askAi.enabled` is `true` (the default), the following peer dependencies must be installed by the consumer site:
|
|
215
|
+
|
|
216
|
+
| Package | Range |
|
|
217
|
+
| --- | --- |
|
|
218
|
+
| `@mui/material` | `^5.0.0 \|\| ^6.0.0 \|\| ^7.0.0` |
|
|
219
|
+
| `@mui/icons-material` | `^5.0.0 \|\| ^6.0.0 \|\| ^7.0.0` |
|
|
220
|
+
| `@emotion/react` | `^11.0.0` |
|
|
221
|
+
| `@emotion/styled` | `^11.0.0` |
|
|
222
|
+
|
|
223
|
+
These are declared as peer dependencies (not direct dependencies) so consumers that already use MUI - common in Docusaurus sites - get a single deduped copy at install time. Consumers without MUI will get a clean npm peer-dependency error pointing at exactly what to install.
|
|
224
|
+
|
|
225
|
+
Consumers who disable the Ask AI button (`askAi.enabled: false`) can ignore these peers; the theme components are not registered in that case and MUI is never imported.
|
|
226
|
+
|
|
227
|
+
### Customizing placement
|
|
228
|
+
|
|
229
|
+
To put the button somewhere other than the breadcrumb row - sidebar, header, a specific page region - set `askAi.placement: 'none'` to disable the bundled swizzles, then import and place the component manually wherever you want:
|
|
230
|
+
|
|
231
|
+
```jsx
|
|
232
|
+
import AskAiButton from '@theme/AskAiButton';
|
|
207
233
|
```
|
|
208
234
|
|
|
209
|
-
|
|
235
|
+
Wrap an existing theme component (e.g. `@theme/Layout`) the same way Docusaurus documents for any swizzle.
|
|
210
236
|
|
|
211
237
|
## Feature 4: `/ai/*` routing convention
|
|
212
238
|
|
|
@@ -342,7 +368,7 @@ The `sitemapExclude` helper, by contrast, does work with globs because `@docusau
|
|
|
342
368
|
enabled: true,
|
|
343
369
|
providerOrder: ['claude', 'chatgpt', 'perplexity', 'gemini'],
|
|
344
370
|
promptTemplate: 'Read {pageUrl}.md and help me with the following question about it: ',
|
|
345
|
-
placement: '
|
|
371
|
+
placement: 'breadcrumb-row', // 'breadcrumb-row' | 'none'
|
|
346
372
|
},
|
|
347
373
|
|
|
348
374
|
// Feature 4
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stackql/docusaurus-plugin-aeo",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "AEO (Answer Engine Optimization) helpers for Docusaurus: .md companion files, llms.txt / llms-full.txt, an Ask AI dropdown, and /ai/* route conventions.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"exports": {
|
|
@@ -21,11 +21,22 @@
|
|
|
21
21
|
},
|
|
22
22
|
"peerDependencies": {
|
|
23
23
|
"@docusaurus/core": "^3.0.0",
|
|
24
|
+
"@mui/material": "^5.0.0 || ^6.0.0 || ^7.0.0",
|
|
25
|
+
"@mui/icons-material": "^5.0.0 || ^6.0.0 || ^7.0.0",
|
|
26
|
+
"@emotion/react": "^11.0.0",
|
|
27
|
+
"@emotion/styled": "^11.0.0",
|
|
24
28
|
"react": "^18.0.0 || ^19.0.0",
|
|
25
29
|
"react-dom": "^18.0.0 || ^19.0.0"
|
|
26
30
|
},
|
|
31
|
+
"peerDependenciesMeta": {
|
|
32
|
+
"@mui/material": { "optional": false },
|
|
33
|
+
"@mui/icons-material": { "optional": false },
|
|
34
|
+
"@emotion/react": { "optional": false },
|
|
35
|
+
"@emotion/styled": { "optional": false }
|
|
36
|
+
},
|
|
27
37
|
"dependencies": {
|
|
28
|
-
"gray-matter": "^4.0.3"
|
|
38
|
+
"gray-matter": "^4.0.3",
|
|
39
|
+
"simple-icons": "^16.22.0"
|
|
29
40
|
},
|
|
30
41
|
"overrides": {
|
|
31
42
|
"serialize-javascript": "^7.0.5",
|
package/src/index.js
CHANGED
|
@@ -51,7 +51,7 @@ function normalizeOptions(raw) {
|
|
|
51
51
|
enabled: askAi.enabled !== false,
|
|
52
52
|
providerOrder: askAi.providerOrder || DEFAULT_PROVIDER_ORDER,
|
|
53
53
|
promptTemplate: askAi.promptTemplate || DEFAULT_PROMPT_TEMPLATE,
|
|
54
|
-
placement: askAi.placement || '
|
|
54
|
+
placement: askAi.placement || 'breadcrumb-row',
|
|
55
55
|
},
|
|
56
56
|
aiRoutes: {
|
|
57
57
|
validate: aiRoutes.validate === true,
|
|
@@ -126,9 +126,12 @@ function validateOptions(opts) {
|
|
|
126
126
|
if (typeof opts.askAi.promptTemplate !== 'string') {
|
|
127
127
|
errs.push('askAi.promptTemplate must be a string');
|
|
128
128
|
}
|
|
129
|
-
if (!['
|
|
129
|
+
if (!['breadcrumb-row', 'none'].includes(opts.askAi.placement)) {
|
|
130
|
+
// 'doc-footer' was the v0.1.x-v0.2.x default and is no longer
|
|
131
|
+
// accepted in v0.3.0. The wrappers that implemented it have been
|
|
132
|
+
// removed. See CHANGELOG for migration notes.
|
|
130
133
|
errs.push(
|
|
131
|
-
`askAi.placement must be "
|
|
134
|
+
`askAi.placement must be "breadcrumb-row" or "none", got "${opts.askAi.placement}"`,
|
|
132
135
|
);
|
|
133
136
|
}
|
|
134
137
|
|
|
@@ -1,55 +1,72 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
1
|
+
// Brand icons for the four supported AI providers.
|
|
2
|
+
//
|
|
3
|
+
// Three of four come from simple-icons (siClaude, siPerplexity,
|
|
4
|
+
// siGooglegemini). OpenAI / ChatGPT was removed from simple-icons in
|
|
5
|
+
// 2024 following a takedown, so for that one we ship a neutral inline
|
|
6
|
+
// chat-bubble glyph. The same fallback path is reused for any future
|
|
7
|
+
// icon that disappears from upstream.
|
|
8
|
+
//
|
|
9
|
+
// All icons render as a 24x24 viewBox SVG with `fill="currentColor"`,
|
|
10
|
+
// so the menu CSS can size them via font-size or width/height and tint
|
|
11
|
+
// them via the surrounding text color. ESM `import` is used so webpack
|
|
12
|
+
// can tree-shake unused simple-icons exports out of the bundle.
|
|
4
13
|
|
|
5
|
-
|
|
14
|
+
import React from 'react';
|
|
15
|
+
import { siClaude, siPerplexity, siGooglegemini } from 'simple-icons';
|
|
6
16
|
|
|
7
|
-
function svg(children
|
|
17
|
+
function svg(children) {
|
|
8
18
|
return React.createElement(
|
|
9
19
|
'svg',
|
|
10
20
|
{
|
|
11
21
|
xmlns: 'http://www.w3.org/2000/svg',
|
|
12
22
|
width: '1em',
|
|
13
23
|
height: '1em',
|
|
14
|
-
viewBox,
|
|
24
|
+
viewBox: '0 0 24 24',
|
|
15
25
|
fill: 'currentColor',
|
|
26
|
+
role: 'img',
|
|
16
27
|
'aria-hidden': 'true',
|
|
17
28
|
},
|
|
18
29
|
children,
|
|
19
30
|
);
|
|
20
31
|
}
|
|
21
32
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
33
|
+
function fromSimpleIcons(si) {
|
|
34
|
+
if (!si || typeof si.path !== 'string') {
|
|
35
|
+
return GenericAiIcon;
|
|
36
|
+
}
|
|
37
|
+
return function BrandIcon() {
|
|
38
|
+
return svg(React.createElement('path', { d: si.path }));
|
|
39
|
+
};
|
|
40
|
+
}
|
|
28
41
|
|
|
29
|
-
|
|
30
|
-
|
|
42
|
+
// Neutral chat-bubble glyph - used when a brand icon isn't available
|
|
43
|
+
// upstream. Not a brand mark, no trademark concerns.
|
|
44
|
+
function GenericAiIcon() {
|
|
45
|
+
return svg(
|
|
31
46
|
React.createElement('path', {
|
|
32
|
-
d: '
|
|
47
|
+
d: 'M4 4h16a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2h-8l-5 4v-4H4a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2Zm3 6h2v2H7v-2Zm4 0h2v2h-2v-2Zm4 0h2v2h-2v-2Z',
|
|
33
48
|
}),
|
|
34
49
|
);
|
|
50
|
+
}
|
|
35
51
|
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
52
|
+
const icons = {
|
|
53
|
+
claude: fromSimpleIcons(siClaude),
|
|
54
|
+
// simple-icons dropped OpenAI/ChatGPT in 2024; fall back to a neutral
|
|
55
|
+
// chat-bubble glyph rather than failing the build or shipping a guess
|
|
56
|
+
// at the wordmark.
|
|
57
|
+
chatgpt: GenericAiIcon,
|
|
58
|
+
perplexity: fromSimpleIcons(siPerplexity),
|
|
59
|
+
gemini: fromSimpleIcons(siGooglegemini),
|
|
60
|
+
};
|
|
42
61
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
62
|
+
if (typeof console !== 'undefined') {
|
|
63
|
+
for (const [name, icon] of Object.entries(icons)) {
|
|
64
|
+
if (icon === GenericAiIcon && name !== 'chatgpt') {
|
|
65
|
+
console.warn(
|
|
66
|
+
`[plugin-aeo] AskAiButton: brand icon for "${name}" missing from simple-icons; using generic AI glyph`,
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
49
71
|
|
|
50
|
-
|
|
51
|
-
claude: ClaudeIcon,
|
|
52
|
-
chatgpt: ChatGptIcon,
|
|
53
|
-
perplexity: PerplexityIcon,
|
|
54
|
-
gemini: GeminiIcon,
|
|
55
|
-
};
|
|
72
|
+
export default icons;
|
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import React, {
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import Button from '@mui/material/Button';
|
|
3
|
+
import Menu from '@mui/material/Menu';
|
|
4
|
+
import MenuItem from '@mui/material/MenuItem';
|
|
5
|
+
import ListItemIcon from '@mui/material/ListItemIcon';
|
|
6
|
+
import ListItemText from '@mui/material/ListItemText';
|
|
7
|
+
import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown';
|
|
2
8
|
import { useLocation } from '@docusaurus/router';
|
|
3
9
|
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
|
4
10
|
import { usePluginData } from '@docusaurus/useGlobalData';
|
|
@@ -19,51 +25,24 @@ const PROVIDER_URLS = {
|
|
|
19
25
|
gemini: 'https://gemini.google.com/app?q=',
|
|
20
26
|
};
|
|
21
27
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
28
|
+
const DEFAULT_PROMPT_TEMPLATE =
|
|
29
|
+
'Read {pageUrl}.md and help me with the following question about it: ';
|
|
25
30
|
|
|
26
|
-
export default function AskAiButton(
|
|
31
|
+
export default function AskAiButton() {
|
|
27
32
|
const { siteConfig } = useDocusaurusContext();
|
|
28
33
|
const data = usePluginData('@stackql/docusaurus-plugin-aeo') || {};
|
|
29
34
|
const cfg = data.askAi || {};
|
|
30
35
|
const location = useLocation();
|
|
31
36
|
|
|
32
|
-
const [
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
useEffect(() => {
|
|
36
|
-
function onDocClick(e) {
|
|
37
|
-
if (wrapperRef.current && !wrapperRef.current.contains(e.target)) {
|
|
38
|
-
setOpen(false);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
function onEsc(e) {
|
|
42
|
-
if (e.key === 'Escape') setOpen(false);
|
|
43
|
-
}
|
|
44
|
-
if (open) {
|
|
45
|
-
document.addEventListener('mousedown', onDocClick);
|
|
46
|
-
document.addEventListener('keydown', onEsc);
|
|
47
|
-
}
|
|
48
|
-
return () => {
|
|
49
|
-
document.removeEventListener('mousedown', onDocClick);
|
|
50
|
-
document.removeEventListener('keydown', onEsc);
|
|
51
|
-
};
|
|
52
|
-
}, [open]);
|
|
53
|
-
|
|
54
|
-
const toggle = useCallback(() => setOpen((v) => !v), []);
|
|
37
|
+
const [anchorEl, setAnchorEl] = useState(null);
|
|
38
|
+
const open = Boolean(anchorEl);
|
|
55
39
|
|
|
56
40
|
if (cfg.enabled === false) return null;
|
|
57
41
|
|
|
58
42
|
const baseUrl = (siteConfig.url || '').replace(/\/$/, '');
|
|
59
43
|
const pageUrl = `${baseUrl}${location.pathname.replace(/\/$/, '') || ''}`;
|
|
60
|
-
const promptTemplate =
|
|
61
|
-
|
|
62
|
-
'Read {pageUrl}.md and help me with the following question about it: ';
|
|
63
|
-
const targetUrl = cfg.companionsEnabled === false
|
|
64
|
-
? pageUrl
|
|
65
|
-
: pageUrl; // template controls .md suffix; pageUrl is the HTML route
|
|
66
|
-
const prompt = buildPrompt(promptTemplate, targetUrl);
|
|
44
|
+
const promptTemplate = cfg.promptTemplate || DEFAULT_PROMPT_TEMPLATE;
|
|
45
|
+
const prompt = promptTemplate.replace('{pageUrl}', pageUrl);
|
|
67
46
|
const encoded = encodeURIComponent(prompt);
|
|
68
47
|
|
|
69
48
|
const order =
|
|
@@ -71,46 +50,76 @@ export default function AskAiButton(props) {
|
|
|
71
50
|
? cfg.providerOrder
|
|
72
51
|
: ['claude', 'chatgpt', 'perplexity', 'gemini'];
|
|
73
52
|
|
|
53
|
+
const handleOpen = (e) => setAnchorEl(e.currentTarget);
|
|
54
|
+
const handleClose = () => setAnchorEl(null);
|
|
55
|
+
|
|
74
56
|
return (
|
|
75
|
-
<div className={styles.
|
|
76
|
-
<
|
|
77
|
-
|
|
78
|
-
|
|
57
|
+
<div className={styles.dropdownWrapper}>
|
|
58
|
+
<Button
|
|
59
|
+
variant="outlined"
|
|
60
|
+
size="small"
|
|
61
|
+
endIcon={<KeyboardArrowDownIcon />}
|
|
62
|
+
onClick={handleOpen}
|
|
79
63
|
aria-haspopup="menu"
|
|
80
64
|
aria-expanded={open}
|
|
81
|
-
|
|
65
|
+
sx={{
|
|
66
|
+
textTransform: 'none',
|
|
67
|
+
fontFamily: 'var(--ifm-font-family-base)',
|
|
68
|
+
fontWeight: 600,
|
|
69
|
+
fontSize: '0.75rem',
|
|
70
|
+
borderColor: 'var(--ifm-color-primary)',
|
|
71
|
+
color: 'var(--ifm-color-primary)',
|
|
72
|
+
'&:hover': {
|
|
73
|
+
borderColor: 'var(--ifm-color-primary)',
|
|
74
|
+
backgroundColor: 'rgba(0, 65, 101, 0.04)',
|
|
75
|
+
},
|
|
76
|
+
}}
|
|
77
|
+
>
|
|
78
|
+
Ask AI about this page
|
|
79
|
+
</Button>
|
|
80
|
+
<Menu
|
|
81
|
+
anchorEl={anchorEl}
|
|
82
|
+
open={open}
|
|
83
|
+
onClose={handleClose}
|
|
84
|
+
anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
|
|
85
|
+
transformOrigin={{ vertical: 'top', horizontal: 'right' }}
|
|
86
|
+
sx={{
|
|
87
|
+
'& .MuiPaper-root': {
|
|
88
|
+
fontFamily: 'var(--ifm-font-family-base)',
|
|
89
|
+
minWidth: 200,
|
|
90
|
+
},
|
|
91
|
+
}}
|
|
82
92
|
>
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
)}
|
|
93
|
+
{order.map((key) => {
|
|
94
|
+
const Icon = icons[key];
|
|
95
|
+
const label = PROVIDER_LABELS[key];
|
|
96
|
+
const base = PROVIDER_URLS[key];
|
|
97
|
+
if (!base || !Icon) return null;
|
|
98
|
+
const href = `${base}${encoded}`;
|
|
99
|
+
return (
|
|
100
|
+
<MenuItem
|
|
101
|
+
key={key}
|
|
102
|
+
component="a"
|
|
103
|
+
href={href}
|
|
104
|
+
target="_blank"
|
|
105
|
+
rel="noopener noreferrer"
|
|
106
|
+
onClick={handleClose}
|
|
107
|
+
>
|
|
108
|
+
<ListItemIcon>
|
|
109
|
+
<Icon />
|
|
110
|
+
</ListItemIcon>
|
|
111
|
+
<ListItemText
|
|
112
|
+
primaryTypographyProps={{
|
|
113
|
+
fontSize: '0.85rem',
|
|
114
|
+
fontFamily: 'var(--ifm-font-family-base)',
|
|
115
|
+
}}
|
|
116
|
+
>
|
|
117
|
+
{label}
|
|
118
|
+
</ListItemText>
|
|
119
|
+
</MenuItem>
|
|
120
|
+
);
|
|
121
|
+
})}
|
|
122
|
+
</Menu>
|
|
114
123
|
</div>
|
|
115
124
|
);
|
|
116
125
|
}
|
|
@@ -1,74 +1,11 @@
|
|
|
1
|
-
.
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
margin: 1.5rem 0;
|
|
1
|
+
.dropdownWrapper {
|
|
2
|
+
display: none;
|
|
3
|
+
flex-shrink: 0;
|
|
5
4
|
}
|
|
6
5
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
background: var(--ifm-color-primary);
|
|
13
|
-
color: var(--ifm-color-primary-contrast-foreground, #fff);
|
|
14
|
-
border: 1px solid var(--ifm-color-primary-dark);
|
|
15
|
-
border-radius: var(--ifm-button-border-radius, 0.4rem);
|
|
16
|
-
font: inherit;
|
|
17
|
-
font-weight: 600;
|
|
18
|
-
cursor: pointer;
|
|
19
|
-
transition: background-color 0.15s ease, transform 0.05s ease;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
.trigger:hover {
|
|
23
|
-
background: var(--ifm-color-primary-dark);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
.trigger:active {
|
|
27
|
-
transform: translateY(1px);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
.caret {
|
|
31
|
-
font-size: 0.7em;
|
|
32
|
-
opacity: 0.85;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
.menu {
|
|
36
|
-
position: absolute;
|
|
37
|
-
top: calc(100% + 0.25rem);
|
|
38
|
-
left: 0;
|
|
39
|
-
z-index: 50;
|
|
40
|
-
min-width: 12rem;
|
|
41
|
-
margin: 0;
|
|
42
|
-
padding: 0.25rem 0;
|
|
43
|
-
list-style: none;
|
|
44
|
-
background: var(--ifm-background-surface-color, var(--ifm-background-color));
|
|
45
|
-
border: 1px solid var(--ifm-color-emphasis-300);
|
|
46
|
-
border-radius: var(--ifm-button-border-radius, 0.4rem);
|
|
47
|
-
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
.item {
|
|
51
|
-
margin: 0;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
.itemLink {
|
|
55
|
-
display: flex;
|
|
56
|
-
align-items: center;
|
|
57
|
-
gap: 0.6rem;
|
|
58
|
-
padding: 0.5rem 0.85rem;
|
|
59
|
-
color: var(--ifm-font-color-base);
|
|
60
|
-
text-decoration: none;
|
|
61
|
-
font-size: 0.95em;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
.itemLink:hover {
|
|
65
|
-
background: var(--ifm-color-emphasis-100);
|
|
66
|
-
text-decoration: none;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
.icon {
|
|
70
|
-
display: inline-flex;
|
|
71
|
-
align-items: center;
|
|
72
|
-
font-size: 1.1em;
|
|
73
|
-
color: var(--ifm-color-primary);
|
|
6
|
+
@media screen and (min-width: 997px) {
|
|
7
|
+
.dropdownWrapper {
|
|
8
|
+
display: flex;
|
|
9
|
+
align-items: center;
|
|
10
|
+
}
|
|
74
11
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import Title from '@theme-init/BlogPostItem/Header/Title';
|
|
3
|
+
import AskAiButton from '@theme/AskAiButton';
|
|
4
|
+
import styles from './styles.module.css';
|
|
5
|
+
|
|
6
|
+
// Mirror the docs breadcrumb-row placement: render a flex row above the
|
|
7
|
+
// post title with the Ask AI button right-aligned. Blog posts have no
|
|
8
|
+
// breadcrumbs, so the title block is the closest visual analog to the
|
|
9
|
+
// docs "above the H1, top of content" position.
|
|
10
|
+
export default function TitleWrapper(props) {
|
|
11
|
+
return (
|
|
12
|
+
<>
|
|
13
|
+
<div className={styles.row}>
|
|
14
|
+
<AskAiButton />
|
|
15
|
+
</div>
|
|
16
|
+
<Title {...props} />
|
|
17
|
+
</>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
// See note in the AskAiButton swizzle: @theme-init resolves to the
|
|
3
|
+
// initial component from the theme chain (theme-classic), avoiding the
|
|
4
|
+
// recursion that @theme-original produces when a plugin (not a theme)
|
|
5
|
+
// is the only contributor in the wrapper layer.
|
|
6
|
+
import DocBreadcrumbs from '@theme-init/DocBreadcrumbs';
|
|
7
|
+
import AskAiButton from '@theme/AskAiButton';
|
|
8
|
+
import styles from './styles.module.css';
|
|
9
|
+
|
|
10
|
+
export default function DocBreadcrumbsWrapper(props) {
|
|
11
|
+
return (
|
|
12
|
+
<div className={styles.row}>
|
|
13
|
+
<DocBreadcrumbs {...props} />
|
|
14
|
+
<AskAiButton />
|
|
15
|
+
</div>
|
|
16
|
+
);
|
|
17
|
+
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
// See note in src/theme/DocItem/Footer/index.jsx - @theme-init avoids the
|
|
3
|
-
// SSR recursion that @theme-original triggers when a plugin (not a theme)
|
|
4
|
-
// contributes the wrapper.
|
|
5
|
-
import Footer from '@theme-init/BlogPostItem/Footer';
|
|
6
|
-
import AskAiButton from '@theme/AskAiButton';
|
|
7
|
-
|
|
8
|
-
export default function FooterWrapper(props) {
|
|
9
|
-
return (
|
|
10
|
-
<>
|
|
11
|
-
<AskAiButton />
|
|
12
|
-
<Footer {...props} />
|
|
13
|
-
</>
|
|
14
|
-
);
|
|
15
|
-
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
// Use @theme-init (the initial component from the theme chain, before any
|
|
3
|
-
// wrappers) instead of @theme-original. When a plugin contributes both the
|
|
4
|
-
// wrapper and is the only contributor in the wrapper layer,
|
|
5
|
-
// @theme-original/X resolves back to this wrapper file and renders infinitely
|
|
6
|
-
// on every SSR pass. @theme-init/X always resolves to the un-wrapped origin.
|
|
7
|
-
import Footer from '@theme-init/DocItem/Footer';
|
|
8
|
-
import AskAiButton from '@theme/AskAiButton';
|
|
9
|
-
|
|
10
|
-
export default function FooterWrapper(props) {
|
|
11
|
-
return (
|
|
12
|
-
<>
|
|
13
|
-
<AskAiButton />
|
|
14
|
-
<Footer {...props} />
|
|
15
|
-
</>
|
|
16
|
-
);
|
|
17
|
-
}
|