From e6361549aa81f60ca121d351212df9664610f689 Mon Sep 17 00:00:00 2001 From: Giselle van Dongen Date: Wed, 28 May 2025 13:52:22 +0200 Subject: [PATCH 1/5] Add page AI chat links --- package.json | 1 + src/components/CopyPageDropdown/index.tsx | 150 +++++++++ .../CopyPageDropdown/styles.module.css | 36 +++ src/theme/DocBreadcrumbs/index.tsx | 16 + src/theme/SearchBar/index.tsx | 21 ++ yarn.lock | 306 +++++++++++++++++- 6 files changed, 529 insertions(+), 1 deletion(-) create mode 100644 src/components/CopyPageDropdown/index.tsx create mode 100644 src/components/CopyPageDropdown/styles.module.css create mode 100644 src/theme/DocBreadcrumbs/index.tsx create mode 100644 src/theme/SearchBar/index.tsx diff --git a/package.json b/package.json index 634a15b6c..423fe2fbb 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "@docusaurus/preset-classic": "^3.7.0", "@mdx-js/react": "^3.0.0", "@radix-ui/react-collapsible": "^1.1.3", + "@radix-ui/react-dropdown-menu": "^2.1.15", "class-variance-authority": "^0.7.0", "clsx": "^2.0.0", "codehike": "1.0.4", diff --git a/src/components/CopyPageDropdown/index.tsx b/src/components/CopyPageDropdown/index.tsx new file mode 100644 index 000000000..eb8488295 --- /dev/null +++ b/src/components/CopyPageDropdown/index.tsx @@ -0,0 +1,150 @@ +// src/components/CopyPageDropdown.js +import React from 'react'; +import {useLocation} from '@docusaurus/router'; +import {DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem} from '@radix-ui/react-dropdown-menu'; +import styles from "./styles.module.css"; +import clsx from "clsx"; + + +const IconSize = "20" + +const ExternalLinkArrow = + + + + +const AiIcon = + + + + +const CopyIcon = + + + + +const MarkdownIcon = + + + +const ChatGptIcon = + ChatGPT + + + +const ClaudeIcon = Claude + + + + +const AskAiButton = () => { + const location = useLocation(); + const currentUrl = location.pathname; + const markdownUrl = `${window.location.origin}${currentUrl}.md`; + const query = `Read from ${markdownUrl} so I can ask questions about it.`; + const chatGPTUrl = `https://chatgpt.com/?hints=search&q=${encodeURIComponent(query)}`; + const claudeUrl = `https://claude.ai/chat?message=${encodeURIComponent(query)}`; + + const handleCopyMarkdown = async () => { + try { + const response = await fetch(markdownUrl); + const text = await response.text(); + await navigator.clipboard.writeText(text); + alert('Markdown copied to clipboard!'); + } catch (err) { + alert('Failed to copy markdown.'); + console.error(err); + } + }; + + return ( +
+ + + {AiIcon} + Ask AI + + + + window.open( + chatGPTUrl, + '_blank' + ) + } + > +
+
{ChatGptIcon}
+
+ Open in ChatGPT + {ExternalLinkArrow} +
+ Ask questions about this page +
+
+
+ + window.open( + claudeUrl, + '_blank' + ) + } + > +
+
{ClaudeIcon}
+
+ Open in Claude + {ExternalLinkArrow} +
+ Ask questions about this page +
+
+
+ +
+
{CopyIcon}
+
+ Copy page as Markdown +
+ Copy as input for LLMs +
+
+
+ window.open(markdownUrl, '_blank')} + > +
+
{MarkdownIcon}
+
+ View as Markdown + {ExternalLinkArrow} +
+ View page as plain text +
+
+
+
+
+
+ ); +}; + +export default AskAiButton; diff --git a/src/components/CopyPageDropdown/styles.module.css b/src/components/CopyPageDropdown/styles.module.css new file mode 100644 index 000000000..c278524cf --- /dev/null +++ b/src/components/CopyPageDropdown/styles.module.css @@ -0,0 +1,36 @@ +.dropdownTrigger { + background-color: #f6f8fa; + border: 1px solid #d1d5da; + padding: 6px 12px; + border-radius: 6px; + cursor: pointer; + font-size: 14px; +} + +.dropdownContent { + background: white; + border: 1px solid #ccc; + padding: 6px 0; + border-radius: 6px; + z-index: 9999; + box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.1); +} + +.dropdownContent > * { + padding: 8px 16px; + cursor: pointer; + font-size: 14px; +} + +.dropdownContent > *:hover { + background-color: #f0f0f0; +} + +.child { + display: inline-block; + vertical-align: middle; +} + +.listIcon { + padding-right: 8px; +} diff --git a/src/theme/DocBreadcrumbs/index.tsx b/src/theme/DocBreadcrumbs/index.tsx new file mode 100644 index 000000000..23ff0c459 --- /dev/null +++ b/src/theme/DocBreadcrumbs/index.tsx @@ -0,0 +1,16 @@ +import React, {type ReactNode} from 'react'; +import DocBreadcrumbs from '@theme-original/DocBreadcrumbs'; +import type DocBreadcrumbsType from '@theme/DocBreadcrumbs'; +import type {WrapperProps} from '@docusaurus/types'; +import AskAiButton from "../../components/CopyPageDropdown"; + +type Props = WrapperProps; + +export default function DocBreadcrumbsWrapper(props: Props): ReactNode { + return ( + <> + + + + ); +} diff --git a/src/theme/SearchBar/index.tsx b/src/theme/SearchBar/index.tsx new file mode 100644 index 000000000..5d591bbff --- /dev/null +++ b/src/theme/SearchBar/index.tsx @@ -0,0 +1,21 @@ +import React, {type ReactNode} from 'react'; +import SearchBar from '@theme-original/SearchBar'; +import type SearchBarType from '@theme/SearchBar'; +import type {WrapperProps} from '@docusaurus/types'; +import AskAiButton from "../../components/CopyPageDropdown"; + +type Props = WrapperProps; + +export default function SearchBarWrapper(props: Props): ReactNode { + return ( + <> +
+ +
+
+ +
+ + + ); +} diff --git a/yarn.lock b/yarn.lock index 1c5a1f9d0..480254d0b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1921,6 +1921,33 @@ resolved "https://registry.npmjs.org/@faker-js/faker/-/faker-5.5.3.tgz" integrity sha512-R11tGE6yIFwqpaIqcfkcg7AICXzFg14+5h5v0TfF/9+RMDL6jhzCy/pxHVOfbALGdtVYdt6JdR21tuxEgl34dw== +"@floating-ui/core@^1.7.0": + version "1.7.0" + resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.7.0.tgz#1aff27a993ea1b254a586318c29c3b16ea0f4d0a" + integrity sha512-FRdBLykrPPA6P76GGGqlex/e7fbe0F1ykgxHYNXQsH/iTEtjMj/f9bpY5oQqbjt5VgZvgz/uKXbGuROijh3VLA== + dependencies: + "@floating-ui/utils" "^0.2.9" + +"@floating-ui/dom@^1.0.0": + version "1.7.0" + resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.7.0.tgz#f9f83ee4fee78ac23ad9e65b128fc11a27857532" + integrity sha512-lGTor4VlXcesUMh1cupTUTDoCxMb0V6bm3CnxHzQcw8Eaf1jQbgQX4i02fYgT0vJ82tb5MZ4CZk1LRGkktJCzg== + dependencies: + "@floating-ui/core" "^1.7.0" + "@floating-ui/utils" "^0.2.9" + +"@floating-ui/react-dom@^2.0.0": + version "2.1.2" + resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-2.1.2.tgz#a1349bbf6a0e5cb5ded55d023766f20a4d439a31" + integrity sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A== + dependencies: + "@floating-ui/dom" "^1.0.0" + +"@floating-ui/utils@^0.2.9": + version "0.2.9" + resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.9.tgz#50dea3616bc8191fb8e112283b49eaff03e78429" + integrity sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg== + "@hapi/hoek@^9.0.0", "@hapi/hoek@^9.3.0": version "9.3.0" resolved "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz" @@ -2202,6 +2229,18 @@ resolved "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.1.tgz" integrity sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA== +"@radix-ui/primitive@1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@radix-ui/primitive/-/primitive-1.1.2.tgz#83f415c4425f21e3d27914c12b3272a32e3dae65" + integrity sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA== + +"@radix-ui/react-arrow@1.1.7": + version "1.1.7" + resolved "https://registry.yarnpkg.com/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz#e14a2657c81d961598c5e72b73dd6098acc04f09" + integrity sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w== + dependencies: + "@radix-ui/react-primitive" "2.1.3" + "@radix-ui/react-collapsible@^1.1.3": version "1.1.3" resolved "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.3.tgz" @@ -2216,16 +2255,79 @@ "@radix-ui/react-use-controllable-state" "1.1.0" "@radix-ui/react-use-layout-effect" "1.1.0" +"@radix-ui/react-collection@1.1.7": + version "1.1.7" + resolved "https://registry.yarnpkg.com/@radix-ui/react-collection/-/react-collection-1.1.7.tgz#d05c25ca9ac4695cc19ba91f42f686e3ea2d9aec" + integrity sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw== + dependencies: + "@radix-ui/react-compose-refs" "1.1.2" + "@radix-ui/react-context" "1.1.2" + "@radix-ui/react-primitive" "2.1.3" + "@radix-ui/react-slot" "1.2.3" + "@radix-ui/react-compose-refs@1.1.1": version "1.1.1" resolved "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz" integrity sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw== +"@radix-ui/react-compose-refs@1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz#a2c4c47af6337048ee78ff6dc0d090b390d2bb30" + integrity sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg== + "@radix-ui/react-context@1.1.1": version "1.1.1" resolved "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.1.tgz" integrity sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q== +"@radix-ui/react-context@1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@radix-ui/react-context/-/react-context-1.1.2.tgz#61628ef269a433382c364f6f1e3788a6dc213a36" + integrity sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA== + +"@radix-ui/react-direction@1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-direction/-/react-direction-1.1.1.tgz#39e5a5769e676c753204b792fbe6cf508e550a14" + integrity sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw== + +"@radix-ui/react-dismissable-layer@1.1.10": + version "1.1.10" + resolved "https://registry.yarnpkg.com/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.10.tgz#429b9bada3672c6895a5d6a642aca6ecaf4f18c3" + integrity sha512-IM1zzRV4W3HtVgftdQiiOmA0AdJlCtMLe00FXaHwgt3rAnNsIyDqshvkIW3hj/iu5hu8ERP7KIYki6NkqDxAwQ== + dependencies: + "@radix-ui/primitive" "1.1.2" + "@radix-ui/react-compose-refs" "1.1.2" + "@radix-ui/react-primitive" "2.1.3" + "@radix-ui/react-use-callback-ref" "1.1.1" + "@radix-ui/react-use-escape-keydown" "1.1.1" + +"@radix-ui/react-dropdown-menu@^2.1.15": + version "2.1.15" + resolved "https://registry.yarnpkg.com/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.15.tgz#f507320de8e11bc1e671a6ec0c27a7a89e725131" + integrity sha512-mIBnOjgwo9AH3FyKaSWoSu/dYj6VdhJ7frEPiGTeXCdUFHjl9h3mFh2wwhEtINOmYXWhdpf1rY2minFsmaNgVQ== + dependencies: + "@radix-ui/primitive" "1.1.2" + "@radix-ui/react-compose-refs" "1.1.2" + "@radix-ui/react-context" "1.1.2" + "@radix-ui/react-id" "1.1.1" + "@radix-ui/react-menu" "2.1.15" + "@radix-ui/react-primitive" "2.1.3" + "@radix-ui/react-use-controllable-state" "1.2.2" + +"@radix-ui/react-focus-guards@1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.2.tgz#4ec9a7e50925f7fb661394460045b46212a33bed" + integrity sha512-fyjAACV62oPV925xFCrH8DR5xWhg9KYtJT4s3u54jxp+L/hbpTY2kIeEFFbFe+a/HCE94zGQMZLIpVTPVZDhaA== + +"@radix-ui/react-focus-scope@1.1.7": + version "1.1.7" + resolved "https://registry.yarnpkg.com/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz#dfe76fc103537d80bf42723a183773fd07bfb58d" + integrity sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw== + dependencies: + "@radix-ui/react-compose-refs" "1.1.2" + "@radix-ui/react-primitive" "2.1.3" + "@radix-ui/react-use-callback-ref" "1.1.1" + "@radix-ui/react-id@1.1.0": version "1.1.0" resolved "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.0.tgz" @@ -2233,6 +2335,61 @@ dependencies: "@radix-ui/react-use-layout-effect" "1.1.0" +"@radix-ui/react-id@1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-id/-/react-id-1.1.1.tgz#1404002e79a03fe062b7e3864aa01e24bd1471f7" + integrity sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg== + dependencies: + "@radix-ui/react-use-layout-effect" "1.1.1" + +"@radix-ui/react-menu@2.1.15": + version "2.1.15" + resolved "https://registry.yarnpkg.com/@radix-ui/react-menu/-/react-menu-2.1.15.tgz#a1a8f06cab3c309f9998cdbd2b3ad279e42ed483" + integrity sha512-tVlmA3Vb9n8SZSd+YSbuFR66l87Wiy4du+YE+0hzKQEANA+7cWKH1WgqcEX4pXqxUFQKrWQGHdvEfw00TjFiew== + dependencies: + "@radix-ui/primitive" "1.1.2" + "@radix-ui/react-collection" "1.1.7" + "@radix-ui/react-compose-refs" "1.1.2" + "@radix-ui/react-context" "1.1.2" + "@radix-ui/react-direction" "1.1.1" + "@radix-ui/react-dismissable-layer" "1.1.10" + "@radix-ui/react-focus-guards" "1.1.2" + "@radix-ui/react-focus-scope" "1.1.7" + "@radix-ui/react-id" "1.1.1" + "@radix-ui/react-popper" "1.2.7" + "@radix-ui/react-portal" "1.1.9" + "@radix-ui/react-presence" "1.1.4" + "@radix-ui/react-primitive" "2.1.3" + "@radix-ui/react-roving-focus" "1.1.10" + "@radix-ui/react-slot" "1.2.3" + "@radix-ui/react-use-callback-ref" "1.1.1" + aria-hidden "^1.2.4" + react-remove-scroll "^2.6.3" + +"@radix-ui/react-popper@1.2.7": + version "1.2.7" + resolved "https://registry.yarnpkg.com/@radix-ui/react-popper/-/react-popper-1.2.7.tgz#531cf2eebb3d3270d58f7d8136e4517646429978" + integrity sha512-IUFAccz1JyKcf/RjB552PlWwxjeCJB8/4KxT7EhBHOJM+mN7LdW+B3kacJXILm32xawcMMjb2i0cIZpo+f9kiQ== + dependencies: + "@floating-ui/react-dom" "^2.0.0" + "@radix-ui/react-arrow" "1.1.7" + "@radix-ui/react-compose-refs" "1.1.2" + "@radix-ui/react-context" "1.1.2" + "@radix-ui/react-primitive" "2.1.3" + "@radix-ui/react-use-callback-ref" "1.1.1" + "@radix-ui/react-use-layout-effect" "1.1.1" + "@radix-ui/react-use-rect" "1.1.1" + "@radix-ui/react-use-size" "1.1.1" + "@radix-ui/rect" "1.1.1" + +"@radix-ui/react-portal@1.1.9": + version "1.1.9" + resolved "https://registry.yarnpkg.com/@radix-ui/react-portal/-/react-portal-1.1.9.tgz#14c3649fe48ec474ac51ed9f2b9f5da4d91c4472" + integrity sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ== + dependencies: + "@radix-ui/react-primitive" "2.1.3" + "@radix-ui/react-use-layout-effect" "1.1.1" + "@radix-ui/react-presence@1.1.2": version "1.1.2" resolved "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.2.tgz" @@ -2241,6 +2398,14 @@ "@radix-ui/react-compose-refs" "1.1.1" "@radix-ui/react-use-layout-effect" "1.1.0" +"@radix-ui/react-presence@1.1.4": + version "1.1.4" + resolved "https://registry.yarnpkg.com/@radix-ui/react-presence/-/react-presence-1.1.4.tgz#253ac0ad4946c5b4a9c66878335f5cf07c967ced" + integrity sha512-ueDqRbdc4/bkaQT3GIpLQssRlFgWaL/U2z/S31qRwwLWoxHLgry3SIfCwhxeQNbirEUXFa+lq3RL3oBYXtcmIA== + dependencies: + "@radix-ui/react-compose-refs" "1.1.2" + "@radix-ui/react-use-layout-effect" "1.1.1" + "@radix-ui/react-primitive@2.0.2": version "2.0.2" resolved "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.2.tgz" @@ -2248,6 +2413,28 @@ dependencies: "@radix-ui/react-slot" "1.1.2" +"@radix-ui/react-primitive@2.1.3": + version "2.1.3" + resolved "https://registry.yarnpkg.com/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz#db9b8bcff49e01be510ad79893fb0e4cda50f1bc" + integrity sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ== + dependencies: + "@radix-ui/react-slot" "1.2.3" + +"@radix-ui/react-roving-focus@1.1.10": + version "1.1.10" + resolved "https://registry.yarnpkg.com/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.10.tgz#46030496d2a490c4979d29a7e1252465e51e4b0b" + integrity sha512-dT9aOXUen9JSsxnMPv/0VqySQf5eDQ6LCk5Sw28kamz8wSOW2bJdlX2Bg5VUIIcV+6XlHpWTIuTPCf/UNIyq8Q== + dependencies: + "@radix-ui/primitive" "1.1.2" + "@radix-ui/react-collection" "1.1.7" + "@radix-ui/react-compose-refs" "1.1.2" + "@radix-ui/react-context" "1.1.2" + "@radix-ui/react-direction" "1.1.1" + "@radix-ui/react-id" "1.1.1" + "@radix-ui/react-primitive" "2.1.3" + "@radix-ui/react-use-callback-ref" "1.1.1" + "@radix-ui/react-use-controllable-state" "1.2.2" + "@radix-ui/react-slot@1.1.2": version "1.1.2" resolved "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.2.tgz" @@ -2255,11 +2442,23 @@ dependencies: "@radix-ui/react-compose-refs" "1.1.1" +"@radix-ui/react-slot@1.2.3": + version "1.2.3" + resolved "https://registry.yarnpkg.com/@radix-ui/react-slot/-/react-slot-1.2.3.tgz#502d6e354fc847d4169c3bc5f189de777f68cfe1" + integrity sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A== + dependencies: + "@radix-ui/react-compose-refs" "1.1.2" + "@radix-ui/react-use-callback-ref@1.1.0": version "1.1.0" resolved "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz" integrity sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw== +"@radix-ui/react-use-callback-ref@1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz#62a4dba8b3255fdc5cc7787faeac1c6e4cc58d40" + integrity sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg== + "@radix-ui/react-use-controllable-state@1.1.0": version "1.1.0" resolved "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.1.0.tgz" @@ -2267,11 +2466,57 @@ dependencies: "@radix-ui/react-use-callback-ref" "1.1.0" +"@radix-ui/react-use-controllable-state@1.2.2": + version "1.2.2" + resolved "https://registry.yarnpkg.com/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz#905793405de57d61a439f4afebbb17d0645f3190" + integrity sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg== + dependencies: + "@radix-ui/react-use-effect-event" "0.0.2" + "@radix-ui/react-use-layout-effect" "1.1.1" + +"@radix-ui/react-use-effect-event@0.0.2": + version "0.0.2" + resolved "https://registry.yarnpkg.com/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz#090cf30d00a4c7632a15548512e9152217593907" + integrity sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA== + dependencies: + "@radix-ui/react-use-layout-effect" "1.1.1" + +"@radix-ui/react-use-escape-keydown@1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz#b3fed9bbea366a118f40427ac40500aa1423cc29" + integrity sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g== + dependencies: + "@radix-ui/react-use-callback-ref" "1.1.1" + "@radix-ui/react-use-layout-effect@1.1.0": version "1.1.0" resolved "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.0.tgz" integrity sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w== +"@radix-ui/react-use-layout-effect@1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz#0c4230a9eed49d4589c967e2d9c0d9d60a23971e" + integrity sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ== + +"@radix-ui/react-use-rect@1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-use-rect/-/react-use-rect-1.1.1.tgz#01443ca8ed071d33023c1113e5173b5ed8769152" + integrity sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w== + dependencies: + "@radix-ui/rect" "1.1.1" + +"@radix-ui/react-use-size@1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz#6de276ffbc389a537ffe4316f5b0f24129405b37" + integrity sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ== + dependencies: + "@radix-ui/react-use-layout-effect" "1.1.1" + +"@radix-ui/rect@1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@radix-ui/rect/-/rect-1.1.1.tgz#78244efe12930c56fd255d7923865857c41ac8cb" + integrity sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw== + "@redocly/ajv@^8.11.2": version "8.11.2" resolved "https://registry.npmjs.org/@redocly/ajv/-/ajv-8.11.2.tgz" @@ -3259,6 +3504,13 @@ argparse@^2.0.1: resolved "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz" integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== +aria-hidden@^1.2.4: + version "1.2.6" + resolved "https://registry.yarnpkg.com/aria-hidden/-/aria-hidden-1.2.6.tgz#73051c9b088114c795b1ea414e9c0fff874ffc1a" + integrity sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA== + dependencies: + tslib "^2.0.0" + array-buffer-byte-length@^1.0.1, array-buffer-byte-length@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz" @@ -4562,6 +4814,11 @@ detect-libc@^2.0.0, detect-libc@^2.0.2: resolved "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz" integrity sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw== +detect-node-es@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/detect-node-es/-/detect-node-es-1.1.0.tgz#163acdf643330caa0b4cd7c21e7ee7755d6fa493" + integrity sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ== + detect-node@^2.0.4: version "2.1.0" resolved "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz" @@ -5555,6 +5812,11 @@ get-intrinsic@^1.2.4, get-intrinsic@^1.2.5, get-intrinsic@^1.2.6, get-intrinsic@ hasown "^2.0.2" math-intrinsics "^1.1.0" +get-nonce@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/get-nonce/-/get-nonce-1.0.1.tgz#fdf3f0278073820d2ce9426c18f07481b1e0cdf3" + integrity sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q== + get-own-enumerable-property-symbols@^3.0.0: version "3.0.2" resolved "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz" @@ -9914,6 +10176,25 @@ react-redux@^7.2.0: prop-types "^15.7.2" react-is "^17.0.2" +react-remove-scroll-bar@^2.3.7: + version "2.3.8" + resolved "https://registry.yarnpkg.com/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz#99c20f908ee467b385b68a3469b4a3e750012223" + integrity sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q== + dependencies: + react-style-singleton "^2.2.2" + tslib "^2.0.0" + +react-remove-scroll@^2.6.3: + version "2.7.0" + resolved "https://registry.yarnpkg.com/react-remove-scroll/-/react-remove-scroll-2.7.0.tgz#0a483417fe9919acd3e459ce92008640cf898669" + integrity sha512-sGsQtcjMqdQyijAHytfGEELB8FufGbfXIsvUTe+NLx1GDRJCXtCFLBLUI1eyZCKXXvbEU2C6gai0PZKoIE9Vbg== + dependencies: + react-remove-scroll-bar "^2.3.7" + react-style-singleton "^2.2.3" + tslib "^2.1.0" + use-callback-ref "^1.3.3" + use-sidecar "^1.1.3" + react-router-config@^5.1.1: version "5.1.1" resolved "https://registry.npmjs.org/react-router-config/-/react-router-config-5.1.1.tgz" @@ -9949,6 +10230,14 @@ react-router@5.3.4, react-router@^5.3.4: tiny-invariant "^1.0.2" tiny-warning "^1.0.0" +react-style-singleton@^2.2.2, react-style-singleton@^2.2.3: + version "2.2.3" + resolved "https://registry.yarnpkg.com/react-style-singleton/-/react-style-singleton-2.2.3.tgz#4265608be69a4d70cfe3047f2c6c88b2c3ace388" + integrity sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ== + dependencies: + get-nonce "^1.0.0" + tslib "^2.0.0" + react-waypoint@^10.3.0: version "10.3.0" resolved "https://registry.npmjs.org/react-waypoint/-/react-waypoint-10.3.0.tgz" @@ -11402,7 +11691,7 @@ ts-interface-checker@^0.1.9: resolved "https://registry.yarnpkg.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz#784fd3d679722bc103b1b4b8030bcddb5db2a699" integrity sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA== -tslib@^2.0.3, tslib@^2.6.0: +tslib@^2.0.0, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.6.0: version "2.8.1" resolved "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz" integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== @@ -11735,11 +12024,26 @@ url@^0.11.1: punycode "^1.4.1" qs "^6.12.3" +use-callback-ref@^1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/use-callback-ref/-/use-callback-ref-1.3.3.tgz#98d9fab067075841c5b2c6852090d5d0feabe2bf" + integrity sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg== + dependencies: + tslib "^2.0.0" + use-editable@^2.3.3: version "2.3.3" resolved "https://registry.yarnpkg.com/use-editable/-/use-editable-2.3.3.tgz#a292fe9ba4c291cd28d1cc2728c75a5fc8d9a33f" integrity sha512-7wVD2JbfAFJ3DK0vITvXBdpd9JAz5BcKAAolsnLBuBn6UDDwBGuCIAGvR3yA2BNKm578vAMVHFCWaOcA+BhhiA== +use-sidecar@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/use-sidecar/-/use-sidecar-1.1.3.tgz#10e7fd897d130b896e2c546c63a5e8233d00efdb" + integrity sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ== + dependencies: + detect-node-es "^1.1.0" + tslib "^2.0.0" + util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" From 9ce0c56c76277fdc710fa7366a7a484847e4ccfa Mon Sep 17 00:00:00 2001 From: Giselle van Dongen Date: Wed, 28 May 2025 17:09:55 +0200 Subject: [PATCH 2/5] Add llms-full.txt and add buttons to pages and header --- docs/overview.mdx | 2 +- docusaurus.config.js | 1 + .../src/cli/command.ts | 4 +- .../src/constants.ts | 1 + .../src/fs/io/write.ts | 21 ++- .../docusaurus-plugin-llms-txt/src/index.ts | 1 + .../src/pipeline/core/route-processor.ts | 8 +- .../src/pipeline/html/html-processor.ts | 8 +- .../{CopyPageDropdown => AiSearch}/index.tsx | 103 +++++-------- src/components/AiSearch/styles.module.css | 51 +++++++ src/components/ChatPageButton/index.tsx | 139 ++++++++++++++++++ .../styles.module.css | 11 ++ src/theme/DocBreadcrumbs/index.tsx | 4 +- src/theme/SearchBar/index.tsx | 6 +- 14 files changed, 284 insertions(+), 76 deletions(-) rename src/components/{CopyPageDropdown => AiSearch}/index.tsx (73%) create mode 100644 src/components/AiSearch/styles.module.css create mode 100644 src/components/ChatPageButton/index.tsx rename src/components/{CopyPageDropdown => ChatPageButton}/styles.module.css (79%) diff --git a/docs/overview.mdx b/docs/overview.mdx index bff7b55a4..a967ec345 100644 --- a/docs/overview.mdx +++ b/docs/overview.mdx @@ -177,4 +177,4 @@ Restate provides a distributed durable version of your everyday building blocks, ]} /> -Are you an LLM? Check out [llms.txt](https://docs.restate.dev/llms.txt). \ No newline at end of file +Are you an LLM? Check out [llms.txt](https://docs.restate.dev/llms.txt) or [llms-full.txt](https://docs.restate.dev/llms-full.txt). \ No newline at end of file diff --git a/docusaurus.config.js b/docusaurus.config.js index e7a551224..dcf90c8d0 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -274,6 +274,7 @@ const config = { "/plugins/**", "/assets/**", "/404.html", + "/adminapi/**" ], }, diff --git a/plugins/docusaurus-plugin-llms-txt/src/cli/command.ts b/plugins/docusaurus-plugin-llms-txt/src/cli/command.ts index f32588a1d..7f2726310 100644 --- a/plugins/docusaurus-plugin-llms-txt/src/cli/command.ts +++ b/plugins/docusaurus-plugin-llms-txt/src/cli/command.ts @@ -96,6 +96,7 @@ export async function generateLlmsTxt( */ async function runCliConversion( siteDir: string, + outDir: string, options: Partial, context: LoadContext, ): Promise { @@ -131,6 +132,7 @@ async function runCliConversion( directories.docsDir, directories.mdOutDir, siteDir, + outDir, config, log, siteUrl @@ -172,7 +174,7 @@ export function registerLlmsTxt( .description('Generate llms.txt and/or Markdown files using cached routes from build') .action(async (siteDirArg: string | undefined) => { const siteDir = siteDirArg ? path.resolve(siteDirArg) : process.cwd(); - await runCliConversion(siteDir, baseOptions, context); + await runCliConversion(siteDir, ".", baseOptions, context); }); } diff --git a/plugins/docusaurus-plugin-llms-txt/src/constants.ts b/plugins/docusaurus-plugin-llms-txt/src/constants.ts index 241a935e3..adcb39501 100644 --- a/plugins/docusaurus-plugin-llms-txt/src/constants.ts +++ b/plugins/docusaurus-plugin-llms-txt/src/constants.ts @@ -52,6 +52,7 @@ export const DEFAULT_CONTENT_SELECTORS = [ // Output files export const LLMS_TXT_FILENAME = 'llms.txt' as const; +export const LLMS_FULL_TXT_FILENAME = 'llms-full.txt' as const; // Plugin configuration defaults import type { Options as GfmOptions } from 'remark-gfm'; diff --git a/plugins/docusaurus-plugin-llms-txt/src/fs/io/write.ts b/plugins/docusaurus-plugin-llms-txt/src/fs/io/write.ts index 1a6c4c924..f519419c3 100644 --- a/plugins/docusaurus-plugin-llms-txt/src/fs/io/write.ts +++ b/plugins/docusaurus-plugin-llms-txt/src/fs/io/write.ts @@ -1,6 +1,8 @@ import path from 'path'; import fs from 'fs-extra'; import { createFileError, getErrorCause } from '../../utils'; +import {Logger} from "../../types/logging"; +import { noopLogger } from '../../logging'; /** * Saves markdown content to a file, creating directories as needed. @@ -20,4 +22,21 @@ export async function saveMarkdownFile( errorCause, ); } -} \ No newline at end of file +} +export async function appendToMarkdownFile( + outputPath: string, + content: string, + logger: Logger = noopLogger, +): Promise { + try { + await fs.ensureDir(path.dirname(outputPath)); + await fs.promises.appendFile(outputPath, content); + } catch (error) { + const errorCause = getErrorCause(error); + throw createFileError( + `Failed to save markdown file to ${outputPath}`, + outputPath, + errorCause, + ); + } +} \ No newline at end of file diff --git a/plugins/docusaurus-plugin-llms-txt/src/index.ts b/plugins/docusaurus-plugin-llms-txt/src/index.ts index 29f8e60bf..b96384765 100644 --- a/plugins/docusaurus-plugin-llms-txt/src/index.ts +++ b/plugins/docusaurus-plugin-llms-txt/src/index.ts @@ -61,6 +61,7 @@ export default function llmsTxtPlugin( directories.docsDir, directories.mdOutDir, siteDir, + outDir, config, log, siteUrl diff --git a/plugins/docusaurus-plugin-llms-txt/src/pipeline/core/route-processor.ts b/plugins/docusaurus-plugin-llms-txt/src/pipeline/core/route-processor.ts index fe5dba92e..c904d2afa 100644 --- a/plugins/docusaurus-plugin-llms-txt/src/pipeline/core/route-processor.ts +++ b/plugins/docusaurus-plugin-llms-txt/src/pipeline/core/route-processor.ts @@ -127,6 +127,7 @@ async function processRouteAndUpdateCache( cachedRoutes: CachedRouteInfo[], docsDir: string, mdOutDir: string, + outDir: string, options: PluginOptions, logger: Logger, baseUrl: string @@ -155,7 +156,8 @@ async function processRouteAndUpdateCache( route, cachedRoute, docsDir, - mdOutDir, + mdOutDir, + outDir, options, logger, baseUrl @@ -185,6 +187,7 @@ export async function processRoutesStream( docsDir: string, mdOutDir: string, siteDir: string, + outDir: string, options: PluginOptions, logger: Logger, baseUrl: string = '', @@ -213,6 +216,7 @@ export async function processRoutesStream( cachedRoutes, docsDir, mdOutDir, + outDir, options, logger, baseUrl @@ -243,6 +247,7 @@ async function processRouteWithCache( cachedRoute: CachedRouteInfo, docsDir: string, mdOutDir: string, + outDir: string, options: PluginOptions, logger: Logger, baseUrl: string, @@ -281,6 +286,7 @@ async function processRouteWithCache( route.path, docsDir, mdOutDir, + outDir, options, logger, baseUrl diff --git a/plugins/docusaurus-plugin-llms-txt/src/pipeline/html/html-processor.ts b/plugins/docusaurus-plugin-llms-txt/src/pipeline/html/html-processor.ts index 637f46f1b..804da03b8 100644 --- a/plugins/docusaurus-plugin-llms-txt/src/pipeline/html/html-processor.ts +++ b/plugins/docusaurus-plugin-llms-txt/src/pipeline/html/html-processor.ts @@ -5,8 +5,8 @@ import { processHtmlToMarkdown, extractHtmlMetadata } from './html-converter'; import { htmlPathToMdPath } from '../../fs/path'; import { noopLogger } from '../../logging'; import { createDocumentError, getErrorMessage, getErrorCause } from '../../utils'; -import { saveMarkdownFile } from '../../fs/io/write'; -import { TITLE_TRUNCATE_LENGTH } from '../../constants'; +import {appendToMarkdownFile, saveMarkdownFile} from '../../fs/io/write'; +import {LLMS_FULL_TXT_FILENAME, LLMS_TXT_FILENAME, TITLE_TRUNCATE_LENGTH} from '../../constants'; /** * Check if a markdown file exists for the given HTML path @@ -32,6 +32,7 @@ export async function processHtmlFileWithRoute( routePath: string, docsDir: string, mdOutDir: string, + outDir: string, config: PluginOptions, logger: Logger = noopLogger, baseUrl: string = '', @@ -75,6 +76,9 @@ export async function processHtmlFileWithRoute( logger.debug(`Saving markdown file for ${relHtmlPath}`); const mdPath = htmlPathToMdPath(relHtmlPath, mdOutDir); await saveMarkdownFile(mdPath, markdown); + + // Append content to llms-full.txt + await appendToMarkdownFile(path.join(outDir, LLMS_FULL_TXT_FILENAME), `\n\n---\n\n# ${title}\n\n${markdown}`, logger); } else { // Lightweight processing for llms.txt only - just extract metadata const result = await extractHtmlMetadata(html, contentSelectors); diff --git a/src/components/CopyPageDropdown/index.tsx b/src/components/AiSearch/index.tsx similarity index 73% rename from src/components/CopyPageDropdown/index.tsx rename to src/components/AiSearch/index.tsx index eb8488295..b4420ecd1 100644 --- a/src/components/CopyPageDropdown/index.tsx +++ b/src/components/AiSearch/index.tsx @@ -1,10 +1,10 @@ -// src/components/CopyPageDropdown.js import React from 'react'; -import {useLocation} from '@docusaurus/router'; import {DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem} from '@radix-ui/react-dropdown-menu'; import styles from "./styles.module.css"; import clsx from "clsx"; +const llms_url = "https://docs.restate.dev/llms.txt" +const llms_full_url = "https://docs.restate.dev/llms-full.txt" const IconSize = "20" @@ -15,21 +15,21 @@ const ExternalLinkArrow = -const AiIcon = +const AiIcon = + stroke="#00116b" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> + stroke="#00116b" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> -const CopyIcon = - - +const RobotIcon = + + + + + const MarkdownIcon = { - const location = useLocation(); - const currentUrl = location.pathname; - const markdownUrl = `${window.location.origin}${currentUrl}.md`; - const query = `Read from ${markdownUrl} so I can ask questions about it.`; + const query = `Read the Restate Documentation ${llms_full_url} and Restate Examples https://github.com/restatedev/examples so I can ask questions about Restate.`; const chatGPTUrl = `https://chatgpt.com/?hints=search&q=${encodeURIComponent(query)}`; - const claudeUrl = `https://claude.ai/chat?message=${encodeURIComponent(query)}`; - - const handleCopyMarkdown = async () => { - try { - const response = await fetch(markdownUrl); - const text = await response.text(); - await navigator.clipboard.writeText(text); - alert('Markdown copied to clipboard!'); - } catch (err) { - alert('Failed to copy markdown.'); - console.error(err); - } - }; + const claudeUrl = `https://claude.ai/new?q=${encodeURIComponent(query)}`; return (
- {AiIcon} - Ask AI +
+
{AiIcon}
+
+ Ask AI +
+
- - window.open( - chatGPTUrl, - '_blank' - ) - } - > - + - - window.open( - claudeUrl, - '_blank' - ) - } - > - + - - + - window.open(markdownUrl, '_blank')} - > - +
diff --git a/src/components/AiSearch/styles.module.css b/src/components/AiSearch/styles.module.css new file mode 100644 index 000000000..ae8efc36c --- /dev/null +++ b/src/components/AiSearch/styles.module.css @@ -0,0 +1,51 @@ +.dropdownTrigger { + background-color: #f6f8fa; + border: 1px solid #d1d5da; + padding: 6px 12px; + border-radius: 40px; + cursor: pointer; + font-size: 14px; +} + +.dropdownContent { + background: white; + border: 1px solid #ccc; + padding: 6px 0; + border-radius: 6px; + z-index: 9999; + box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.1); +} + +.dropdownContent > * { + padding: 8px 16px; + cursor: pointer; + font-size: 14px; +} + +.dropdownContent > *:hover { + background-color: #f0f0f0; +} + +.child { + display: inline-block; + vertical-align: middle; +} + +.listIcon { + padding-right: 8px; +} + +.dropdownLink { + text-decoration: none; + color: inherit; +} + +.dropdownLink:hover { + text-decoration: none; + color: inherit; + border: none; +} + +.aiSearchTrigger{ + color: #00116b; +} \ No newline at end of file diff --git a/src/components/ChatPageButton/index.tsx b/src/components/ChatPageButton/index.tsx new file mode 100644 index 000000000..57cf7d9a2 --- /dev/null +++ b/src/components/ChatPageButton/index.tsx @@ -0,0 +1,139 @@ +import React from 'react'; +import {useLocation} from '@docusaurus/router'; +import {DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem} from '@radix-ui/react-dropdown-menu'; +import styles from "./styles.module.css"; +import clsx from "clsx"; + +const IconSize = "20" + +const ExternalLinkArrow = + + + + +const AiIcon = + + + +const CopyIcon = + + + + +const MarkdownIcon = + + + +const ChatGptIcon = + ChatGPT + + + +const ClaudeIcon = Claude + + + + +const ChatPageButton = () => { + const location = useLocation(); + const currentUrl = location.pathname; + const markdownUrl = `https://docs.restate.dev${currentUrl}.md`; + const query = `Read this page of the Restate documentation ${markdownUrl} so I can ask questions about it.`; + const chatGPTUrl = `https://chatgpt.com/?hints=search&q=${encodeURIComponent(query)}`; + const claudeUrl = `https://claude.ai/new?q==${encodeURIComponent(query)}`; + const ignoredPages = ['/', '/guides', '/develop/', '/operate/'] + + const ignoreCurrentPage = ignoredPages.includes(currentUrl) || + currentUrl.includes('/category') || + currentUrl.includes('/adminapi') + + if (ignoreCurrentPage){ + return <> + } else { + const handleCopyMarkdown = async () => { + try { + const response = await fetch(markdownUrl); + const text = await response.text(); + navigator.clipboard.writeText(text); + alert('Markdown copied to clipboard!'); + } catch (err) { + alert('Failed to copy markdown.'); + console.error(err); + } + }; + return ( + + ); + } +}; + +export default ChatPageButton; diff --git a/src/components/CopyPageDropdown/styles.module.css b/src/components/ChatPageButton/styles.module.css similarity index 79% rename from src/components/CopyPageDropdown/styles.module.css rename to src/components/ChatPageButton/styles.module.css index c278524cf..d1860bcd0 100644 --- a/src/components/CopyPageDropdown/styles.module.css +++ b/src/components/ChatPageButton/styles.module.css @@ -34,3 +34,14 @@ .listIcon { padding-right: 8px; } + +.dropdownLink { + text-decoration: none; + color: inherit; +} + +.dropdownLink:hover { + text-decoration: none; + color: inherit; + border: none; +} diff --git a/src/theme/DocBreadcrumbs/index.tsx b/src/theme/DocBreadcrumbs/index.tsx index 23ff0c459..01a78b0f1 100644 --- a/src/theme/DocBreadcrumbs/index.tsx +++ b/src/theme/DocBreadcrumbs/index.tsx @@ -2,7 +2,7 @@ import React, {type ReactNode} from 'react'; import DocBreadcrumbs from '@theme-original/DocBreadcrumbs'; import type DocBreadcrumbsType from '@theme/DocBreadcrumbs'; import type {WrapperProps} from '@docusaurus/types'; -import AskAiButton from "../../components/CopyPageDropdown"; +import ChatPageButton from "../../components/ChatPageButton"; type Props = WrapperProps; @@ -10,7 +10,7 @@ export default function DocBreadcrumbsWrapper(props: Props): ReactNode { return ( <> - + ); } diff --git a/src/theme/SearchBar/index.tsx b/src/theme/SearchBar/index.tsx index 5d591bbff..ce769cbf7 100644 --- a/src/theme/SearchBar/index.tsx +++ b/src/theme/SearchBar/index.tsx @@ -2,18 +2,18 @@ import React, {type ReactNode} from 'react'; import SearchBar from '@theme-original/SearchBar'; import type SearchBarType from '@theme/SearchBar'; import type {WrapperProps} from '@docusaurus/types'; -import AskAiButton from "../../components/CopyPageDropdown"; +import AiSearch from "../../components/AiSearch"; type Props = WrapperProps; export default function SearchBarWrapper(props: Props): ReactNode { return ( <> -
+
- +
From 0244b1acb12bb51baea4065c364bf87d836952b3 Mon Sep 17 00:00:00 2001 From: Giselle van Dongen Date: Wed, 28 May 2025 17:55:03 +0200 Subject: [PATCH 3/5] Make it work for dev deployments --- src/components/AiSearch/index.tsx | 145 ++++++++++++---------- src/components/ChatPageButton/index.tsx | 156 +++++++++++++----------- 2 files changed, 167 insertions(+), 134 deletions(-) diff --git a/src/components/AiSearch/index.tsx b/src/components/AiSearch/index.tsx index b4420ecd1..d0cf9e8f4 100644 --- a/src/components/AiSearch/index.tsx +++ b/src/components/AiSearch/index.tsx @@ -2,9 +2,7 @@ import React from 'react'; import {DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem} from '@radix-ui/react-dropdown-menu'; import styles from "./styles.module.css"; import clsx from "clsx"; - -const llms_url = "https://docs.restate.dev/llms.txt" -const llms_full_url = "https://docs.restate.dev/llms-full.txt" +import BrowserOnly from "@docusaurus/BrowserOnly"; const IconSize = "20" @@ -54,69 +52,90 @@ const ClaudeIcon = -const AskAiButton = () => { - const query = `Read the Restate Documentation ${llms_full_url} and Restate Examples https://github.com/restatedev/examples so I can ask questions about Restate.`; - const chatGPTUrl = `https://chatgpt.com/?hints=search&q=${encodeURIComponent(query)}`; - const claudeUrl = `https://claude.ai/new?q=${encodeURIComponent(query)}`; +function getLlmsUrl(host){ + return `${host}/llms.txt` +} + +function getLlmsFullUrl(host){ + return `${host}/llms-full.txt` +} + +function getQuery(host){ + const query = `Read the Restate Documentation ${getLlmsFullUrl(host)} and Restate Examples https://github.com/restatedev/examples so I can ask questions about Restate.`; + return encodeURIComponent(query) +} + +function getChatGptUrl(host){ + return `https://chatgpt.com/?hints=search&q=${getQuery(host)}` +} +function getClaudeUrl(host){ + return `https://claude.ai/new?q==${getQuery(host)}`; +} + +const AskAiButton = () => { return ( -
- - -
-
{AiIcon}
-
- Ask AI -
-
-
- - - -
{ChatGptIcon}
-
- Open in ChatGPT - {ExternalLinkArrow} -
- Ask questions about Restate -
-
-
- - -
{ClaudeIcon}
-
- Open in Claude - {ExternalLinkArrow} -
- Ask questions about Restate -
-
-
- - -
{RobotIcon}
-
- View llms.txt -
- View docs index as LLM input -
-
-
- - -
{MarkdownIcon}
-
+ } + ); }; diff --git a/src/components/ChatPageButton/index.tsx b/src/components/ChatPageButton/index.tsx index 57cf7d9a2..8f0ea424c 100644 --- a/src/components/ChatPageButton/index.tsx +++ b/src/components/ChatPageButton/index.tsx @@ -3,6 +3,7 @@ import {useLocation} from '@docusaurus/router'; import {DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem} from '@radix-ui/react-dropdown-menu'; import styles from "./styles.module.css"; import clsx from "clsx"; +import BrowserOnly from "@docusaurus/BrowserOnly"; const IconSize = "20" @@ -46,14 +47,34 @@ const ClaudeIcon = +function getQuery(currentUrl){ + const query = `Read this page of the Restate documentation ${currentUrl}.md so I can ask questions about it.`; + return encodeURIComponent(query) +} + +function getChatGptUrl(currentUrl){ + return `https://chatgpt.com/?hints=search&q=${getQuery(currentUrl)}` +} + +function getClaudeUrl(currentUrl){ + return `https://claude.ai/new?q==${getQuery(currentUrl)}`; +} + +async function handleCopyMarkdown(url) { + try { + const response = await fetch(url + '.md'); + const text = await response.text(); + await navigator.clipboard.writeText(text); + alert('Markdown copied to clipboard!'); + } catch (err) { + alert('Failed to copy markdown.'); + console.error(err); + } +} const ChatPageButton = () => { const location = useLocation(); const currentUrl = location.pathname; - const markdownUrl = `https://docs.restate.dev${currentUrl}.md`; - const query = `Read this page of the Restate documentation ${markdownUrl} so I can ask questions about it.`; - const chatGPTUrl = `https://chatgpt.com/?hints=search&q=${encodeURIComponent(query)}`; - const claudeUrl = `https://claude.ai/new?q==${encodeURIComponent(query)}`; const ignoredPages = ['/', '/guides', '/develop/', '/operate/'] const ignoreCurrentPage = ignoredPages.includes(currentUrl) || @@ -63,75 +84,68 @@ const ChatPageButton = () => { if (ignoreCurrentPage){ return <> } else { - const handleCopyMarkdown = async () => { - try { - const response = await fetch(markdownUrl); - const text = await response.text(); - navigator.clipboard.writeText(text); - alert('Markdown copied to clipboard!'); - } catch (err) { - alert('Failed to copy markdown.'); - console.error(err); - } - }; return ( -
- - -
-
{AiIcon}
-
- Chat with page -
-
-
- - - -
{ChatGptIcon}
-
- Open in ChatGPT - {ExternalLinkArrow} -
- Ask questions about this page -
-
-
- - -
{ClaudeIcon}
-
- Open in Claude - {ExternalLinkArrow} -
- Ask questions about this page -
-
-
- - -
{CopyIcon}
-
- Copy page as Markdown -
- Copy page as input for LLMs -
-
-
- - -
{MarkdownIcon}
-
+ } + ); } }; From 176ed17a0e66646b0e0980eb9d690a2ed522e954 Mon Sep 17 00:00:00 2001 From: Giselle van Dongen Date: Thu, 29 May 2025 11:44:02 +0200 Subject: [PATCH 4/5] Address feedback and use llms.txt for chats --- .../docusaurus-plugin-llms-txt/src/fs/io/write.ts | 2 +- .../src/pipeline/html/html-processor.ts | 2 +- src/components/AiSearch/index.tsx | 12 ++++++------ src/components/ChatPageButton/index.tsx | 8 ++++---- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/plugins/docusaurus-plugin-llms-txt/src/fs/io/write.ts b/plugins/docusaurus-plugin-llms-txt/src/fs/io/write.ts index f519419c3..59ad9c8e0 100644 --- a/plugins/docusaurus-plugin-llms-txt/src/fs/io/write.ts +++ b/plugins/docusaurus-plugin-llms-txt/src/fs/io/write.ts @@ -34,7 +34,7 @@ export async function appendToMarkdownFile( } catch (error) { const errorCause = getErrorCause(error); throw createFileError( - `Failed to save markdown file to ${outputPath}`, + `Failed to append to markdown file at ${outputPath}`, outputPath, errorCause, ); diff --git a/plugins/docusaurus-plugin-llms-txt/src/pipeline/html/html-processor.ts b/plugins/docusaurus-plugin-llms-txt/src/pipeline/html/html-processor.ts index 804da03b8..2129d1380 100644 --- a/plugins/docusaurus-plugin-llms-txt/src/pipeline/html/html-processor.ts +++ b/plugins/docusaurus-plugin-llms-txt/src/pipeline/html/html-processor.ts @@ -6,7 +6,7 @@ import { htmlPathToMdPath } from '../../fs/path'; import { noopLogger } from '../../logging'; import { createDocumentError, getErrorMessage, getErrorCause } from '../../utils'; import {appendToMarkdownFile, saveMarkdownFile} from '../../fs/io/write'; -import {LLMS_FULL_TXT_FILENAME, LLMS_TXT_FILENAME, TITLE_TRUNCATE_LENGTH} from '../../constants'; +import {LLMS_FULL_TXT_FILENAME, TITLE_TRUNCATE_LENGTH} from '../../constants'; /** * Check if a markdown file exists for the given HTML path diff --git a/src/components/AiSearch/index.tsx b/src/components/AiSearch/index.tsx index d0cf9e8f4..3d822e7e1 100644 --- a/src/components/AiSearch/index.tsx +++ b/src/components/AiSearch/index.tsx @@ -61,7 +61,7 @@ function getLlmsFullUrl(host){ } function getQuery(host){ - const query = `Read the Restate Documentation ${getLlmsFullUrl(host)} and Restate Examples https://github.com/restatedev/examples so I can ask questions about Restate.`; + const query = `Read the Restate Documentation ${getLlmsUrl(host)} and Restate Examples https://github.com/restatedev/examples so I can ask questions about Restate.`; return encodeURIComponent(query) } @@ -70,7 +70,7 @@ function getChatGptUrl(host){ } function getClaudeUrl(host){ - return `https://claude.ai/new?q==${getQuery(host)}`; + return `https://claude.ai/new?q=${getQuery(host)}`; } const AskAiButton = () => { @@ -89,7 +89,7 @@ const AskAiButton = () => { - +
{ChatGptIcon}
Open in ChatGPT @@ -100,7 +100,7 @@ const AskAiButton = () => { - +
{ClaudeIcon}
Open in Claude @@ -111,7 +111,7 @@ const AskAiButton = () => { - +
{RobotIcon}
View llms.txt @@ -121,7 +121,7 @@ const AskAiButton = () => { - +
{MarkdownIcon}
View llms-full.txt diff --git a/src/components/ChatPageButton/index.tsx b/src/components/ChatPageButton/index.tsx index 8f0ea424c..d4a44385e 100644 --- a/src/components/ChatPageButton/index.tsx +++ b/src/components/ChatPageButton/index.tsx @@ -57,7 +57,7 @@ function getChatGptUrl(currentUrl){ } function getClaudeUrl(currentUrl){ - return `https://claude.ai/new?q==${getQuery(currentUrl)}`; + return `https://claude.ai/new?q=${getQuery(currentUrl)}`; } async function handleCopyMarkdown(url) { @@ -99,7 +99,7 @@ const ChatPageButton = () => { - +
{ChatGptIcon}
Open in ChatGPT @@ -110,7 +110,7 @@ const ChatPageButton = () => { - +
{ClaudeIcon}
Open in Claude @@ -131,7 +131,7 @@ const ChatPageButton = () => { - +
{MarkdownIcon}
View as Markdown From 25706066d48255642881b525a4da8b52ef6fd2a8 Mon Sep 17 00:00:00 2001 From: Giselle van Dongen Date: Mon, 9 Jun 2025 14:50:08 +0200 Subject: [PATCH 5/5] Improve markdown generation by specifying language next to code snippet --- src/components/AiSearch/index.tsx | 11 ++++++++++- src/components/ChatPageButton/index.tsx | 14 +++++++++++++- src/components/code/code.tsx | 11 ++++++++++- 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/src/components/AiSearch/index.tsx b/src/components/AiSearch/index.tsx index 3d822e7e1..0de7414fa 100644 --- a/src/components/AiSearch/index.tsx +++ b/src/components/AiSearch/index.tsx @@ -61,7 +61,16 @@ function getLlmsFullUrl(host){ } function getQuery(host){ - const query = `Read the Restate Documentation ${getLlmsUrl(host)} and Restate Examples https://github.com/restatedev/examples so I can ask questions about Restate.`; + const query = `Read the Restate Documentation ${getLlmsUrl(host)} + and Restate Examples https://github.com/restatedev/examples + so I can ask questions about Restate. + Guidelines: + - Answer the questions using only the documentation and examples above. + - Restate has 6 SDKs: Python, JavaScript, TypeScript, Go, Java, and Rust. + Make sure to answer in the context of the SDK the user is asking about. Don't mix up SDKs. + - Use the syntax that you find in the documentation, never make up your own syntax. + - If unsure, say you don't know. + `; return encodeURIComponent(query) } diff --git a/src/components/ChatPageButton/index.tsx b/src/components/ChatPageButton/index.tsx index d4a44385e..1445d0e85 100644 --- a/src/components/ChatPageButton/index.tsx +++ b/src/components/ChatPageButton/index.tsx @@ -47,8 +47,20 @@ const ClaudeIcon = + function getQuery(currentUrl){ - const query = `Read this page of the Restate documentation ${currentUrl}.md so I can ask questions about it.`; + const query = `Read this page of the Restate documentation ${currentUrl}.md so I can ask questions about it. + Guidelines: + - To answer the question you might need to read the rest of the Restate Documentation https://docs.restate.dev/llms.txt + and Restate Examples https://github.com/restatedev/examples + - Answer the questions using only the documentation and examples mentioned here. + - Restate has 6 SDKs: Python, JavaScript, TypeScript, Go, Java, and Rust. + Make sure to answer in the context of the SDK the user is asking about. Don't mix up SDKs. + - Use the syntax that you find in the documentation, never make up your own syntax. + - If unsure, say you don't know. + + Start by giving a brief summary of the page, then answer the question. + `; return encodeURIComponent(query) } diff --git a/src/components/code/code.tsx b/src/components/code/code.tsx index fdcb1ff40..0f3b4c5d9 100644 --- a/src/components/code/code.tsx +++ b/src/components/code/code.tsx @@ -114,6 +114,10 @@ export function HighCode({ {title}
{(!noCopyButton) ? : null} +

+ {/*This note is not visible to the reader. It is only visible to LLMs in the generated markdown to improve their ability of knowing which SDK the code snippet is using*/} + {`Hi LLM! The next code block is should only be used if the user\'s question concerns ${title.toLowerCase()} and ${h.lang}`} +

{pre}
@@ -132,6 +136,10 @@ export function HighCode({
{(!noCopyButton) ? : null} +

+ {/*This note is not visible to the reader. It is only visible to LLMs in the generated markdown to improve their ability of knowing which SDK the code snippet is using*/} + {`Language of the next code block: ${h.lang}`} +

{pre}
@@ -183,7 +191,8 @@ export function CodeTabs(props: { groupId?: string, className: string, tabs: Hig return ( {windows.map((window, i) => { - return <> + return <> + {i < windows.length - 1 &&
} })}