From 1912efe7651d07287599a5654038f485622dee5c Mon Sep 17 00:00:00 2001 From: Joshua Mo Date: Sun, 7 Jun 2026 19:02:24 +0100 Subject: [PATCH 1/4] feat: Copy to Markdown button --- README.md | 18 +++ guides/projects/private-rag-bot.mdx | 3 + guides/projects/private-research-agent.mdx | 3 + guides/projects/security-code-reviewer.mdx | 3 + snippets/CopyMarkdownButton.jsx | 131 +++++++++++++++++++++ style.css | 59 ++++++++++ 6 files changed, 217 insertions(+) create mode 100644 snippets/CopyMarkdownButton.jsx diff --git a/README.md b/README.md index 619323e6..61d4a225 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,24 @@ The documentation will be available at `http://localhost:3000`. - Place images and assets in the corresponding directories - Reference the OpenAPI specification in `swagger.yaml` for API details +### Copy Markdown Button + +Use the shared snippet when you want a page-level control that copies the current page's Mintlify Markdown export: + +```mdx +import { CopyMarkdownButton } from "/snippets/CopyMarkdownButton.jsx"; + + +``` + +By default, the button fetches the current page URL with a `.md` extension. If a page needs to copy a different Markdown export, pass `sourcePath`: + +```mdx + +``` + +Mintlify's local preview does not serve `.md` exports, and browsers cannot copy from the deployed docs during local testing unless the deployed site allows cross-origin reads. In local preview, the button displays `Unavailable locally`; test the full copy flow on deployed docs. + ## 📖 Documentation Features - 🎨 Clean, modern UI with customizable theming diff --git a/guides/projects/private-rag-bot.mdx b/guides/projects/private-rag-bot.mdx index 0c1c20d5..b2fe0f22 100644 --- a/guides/projects/private-rag-bot.mdx +++ b/guides/projects/private-rag-bot.mdx @@ -6,9 +6,12 @@ slug: private-rag-bot-venice-qdrant-reranking --- import { AuthorByline } from "/snippets/authorByline.jsx"; +import { CopyMarkdownButton } from "/snippets/CopyMarkdownButton.jsx"; + + Retrieval-augmented generation, or RAG, is one of the most useful patterns for building AI applications that need to answer from your own documents. Instead of asking a model to rely on memory alone, you retrieve relevant source material first, send that context to the model, and ask it to answer with citations. In this tutorial, we'll build a private RAG bot using Python, Venice for embeddings and chat completions, Qdrant for vector search, and FastEmbed for local re-ranking. By the end, you'll have the core pieces for a local document assistant that can ingest your files, retrieve relevant chunks, re-rank them, and answer with citations. diff --git a/guides/projects/private-research-agent.mdx b/guides/projects/private-research-agent.mdx index b67062bb..7c676f41 100644 --- a/guides/projects/private-research-agent.mdx +++ b/guides/projects/private-research-agent.mdx @@ -6,9 +6,12 @@ slug: private-research-agent-venice --- import { AuthorByline } from "/snippets/authorByline.jsx"; +import { CopyMarkdownButton } from "/snippets/CopyMarkdownButton.jsx"; + + Research agents are useful when you want more than a single search result or a quick model answer. A good research agent can turn a broad topic into search queries, collect sources, extract the important evidence, follow up on gaps, and write a cited briefing that you can inspect afterward. In this tutorial, we'll build a private research agent using Python and the Venice API. By the end, you'll have a CLI that can research a topic, scrape public pages into Markdown, summarize source chunks, run gap-aware follow-up research passes, and generate a cited report with optional local JSONL artifacts. diff --git a/guides/projects/security-code-reviewer.mdx b/guides/projects/security-code-reviewer.mdx index fb0b03bc..1b3fdf39 100644 --- a/guides/projects/security-code-reviewer.mdx +++ b/guides/projects/security-code-reviewer.mdx @@ -6,9 +6,12 @@ slug: security-code-reviewer-venice --- import { AuthorByline } from "/snippets/authorByline.jsx"; +import { CopyMarkdownButton } from "/snippets/CopyMarkdownButton.jsx"; + + Most static security tools find bugs in isolation. They scan one file, list the issues, and move on. The problem is that the most damaging vulnerabilities in modern codebases are rarely a single bug. They're a chain: a hardcoded signing key plus a missing authorization check plus a SQL injection that, on their own, all look manageable. Together they're an account-takeover path. This is exactly the kind of cross-cutting reasoning LLMs are good at, if you give them the right structure. In this article, we'll build a two-agent security code reviewer using Python and the Venice AI API. By the end, you'll have a CLI you can point at any Python codebase to produce a Markdown report with atomic findings and exploit chains. diff --git a/snippets/CopyMarkdownButton.jsx b/snippets/CopyMarkdownButton.jsx new file mode 100644 index 00000000..7214ba53 --- /dev/null +++ b/snippets/CopyMarkdownButton.jsx @@ -0,0 +1,131 @@ +export const CopyMarkdownButton = (props = {}) => { + const { + className = "", + label = "Copy Markdown", + sourcePath, + } = props; + const statusText = { + loading: "Copying...", + copied: "Copied!", + error: "Could not copy", + localUnavailable: "Unavailable locally", + }; + + const getMarkdownUrl = () => { + const baseUrl = new URL(window.location.href); + + if (sourcePath) { + return new URL(sourcePath, baseUrl).toString(); + } + + const pathname = baseUrl.pathname.replace(/\/$/, "") || "/"; + const markdownPath = + pathname === "/" + ? "/index.md" + : `${pathname.replace(/\.(html|mdx?|md)$/i, "")}.md`; + + return new URL(markdownPath, baseUrl.origin).toString(); + }; + + const isLocalPreview = () => { + const { hostname } = window.location; + + return ( + hostname === "localhost" || + hostname === "0.0.0.0" || + hostname === "[::1]" || + hostname.startsWith("127.") + ); + }; + + const fetchMarkdown = async (url, credentials = "same-origin") => { + const response = await fetch(url, { + headers: { + Accept: "text/markdown, text/plain;q=0.9, */*;q=0.1", + }, + credentials, + }); + + if (!response.ok) { + throw new Error(`Markdown request failed: ${response.status}`); + } + + return response.text(); + }; + + const copyToClipboard = async (text) => { + if (navigator.clipboard?.writeText && window.isSecureContext) { + await navigator.clipboard.writeText(text); + return; + } + + const textarea = document.createElement("textarea"); + textarea.value = text; + textarea.setAttribute("readonly", ""); + textarea.style.position = "fixed"; + textarea.style.top = "-9999px"; + textarea.style.left = "-9999px"; + document.body.appendChild(textarea); + textarea.select(); + + try { + document.execCommand("copy"); + } finally { + document.body.removeChild(textarea); + } + }; + + const updateButton = (button, text, disabled = false) => { + const labelNode = button.querySelector( + ".venice-copy-markdown-button-label", + ); + + if (labelNode) { + labelNode.textContent = text; + } + + button.disabled = disabled; + }; + + const handleCopy = async (event) => { + const button = event.currentTarget; + updateButton(button, statusText.loading, true); + + try { + if (isLocalPreview()) { + throw new Error("Mintlify local preview does not serve Markdown exports"); + } + + const markdown = await fetchMarkdown(getMarkdownUrl()); + await copyToClipboard(markdown); + updateButton(button, statusText.copied); + window.setTimeout(() => updateButton(button, label), 2000); + } catch (error) { + console.error("Failed to copy page Markdown:", error); + updateButton( + button, + isLocalPreview() ? statusText.localUnavailable : statusText.error, + ); + window.setTimeout(() => updateButton(button, label), 2500); + } + }; + + return ( + + ); +}; diff --git a/style.css b/style.css index 6f988b9a..392fe567 100644 --- a/style.css +++ b/style.css @@ -2430,3 +2430,62 @@ } } + +.venice-copy-markdown-button { + display: inline-flex; + align-items: center; + gap: 8px; + margin: 0 0 18px; + padding: 8px 12px; + border: 1px solid rgba(18, 93, 163, 0.22); + border-radius: 999px; + background: rgba(18, 93, 163, 0.07); + color: #125DA3; + font-size: 13px; + font-weight: 650; + line-height: 1; + cursor: pointer; + transition: + background 150ms ease, + border-color 150ms ease, + color 150ms ease, + opacity 150ms ease; +} + +.venice-copy-markdown-button:hover:not(:disabled) { + border-color: rgba(18, 93, 163, 0.42); + background: rgba(18, 93, 163, 0.12); +} + +.venice-copy-markdown-button:disabled { + cursor: wait; + opacity: 0.72; +} + +.venice-copy-markdown-button:focus-visible { + outline: 2px solid rgba(18, 93, 163, 0.45); + outline-offset: 2px; +} + +.venice-copy-markdown-button-icon { + width: 15px; + height: 15px; + fill: none; + stroke: currentColor; + stroke-width: 2; + stroke-linecap: round; + stroke-linejoin: round; +} + +.dark .venice-copy-markdown-button, +[data-theme="dark"] .venice-copy-markdown-button { + border-color: rgba(112, 177, 232, 0.28); + background: rgba(112, 177, 232, 0.1); + color: #70b1e8; +} + +.dark .venice-copy-markdown-button:hover:not(:disabled), +[data-theme="dark"] .venice-copy-markdown-button:hover:not(:disabled) { + border-color: rgba(112, 177, 232, 0.48); + background: rgba(112, 177, 232, 0.16); +} From 23fe9f5433d24acf104537f87ab8fc039c123738 Mon Sep 17 00:00:00 2001 From: Joshua Mo Date: Sun, 7 Jun 2026 22:47:53 +0100 Subject: [PATCH 2/4] chore: amendments --- snippets/CopyMarkdownButton.jsx | 2 +- style.css | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/snippets/CopyMarkdownButton.jsx b/snippets/CopyMarkdownButton.jsx index 7214ba53..7ea364b1 100644 --- a/snippets/CopyMarkdownButton.jsx +++ b/snippets/CopyMarkdownButton.jsx @@ -1,7 +1,7 @@ export const CopyMarkdownButton = (props = {}) => { const { className = "", - label = "Copy Markdown", + label = "Copy to Markdown", sourcePath, } = props; const statusText = { diff --git a/style.css b/style.css index 392fe567..18a89161 100644 --- a/style.css +++ b/style.css @@ -2435,7 +2435,7 @@ display: inline-flex; align-items: center; gap: 8px; - margin: 0 0 18px; + margin: -0.75rem 0 1rem; padding: 8px 12px; border: 1px solid rgba(18, 93, 163, 0.22); border-radius: 999px; From 77e2e61139fa46e262f499fc3c96b5c88412a3f3 Mon Sep 17 00:00:00 2001 From: Joshua Mo Date: Sun, 7 Jun 2026 23:04:57 +0100 Subject: [PATCH 3/4] refactor: wording --- snippets/CopyMarkdownButton.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/snippets/CopyMarkdownButton.jsx b/snippets/CopyMarkdownButton.jsx index 7ea364b1..68639c99 100644 --- a/snippets/CopyMarkdownButton.jsx +++ b/snippets/CopyMarkdownButton.jsx @@ -1,7 +1,7 @@ export const CopyMarkdownButton = (props = {}) => { const { className = "", - label = "Copy to Markdown", + label = "Copy article to Markdown", sourcePath, } = props; const statusText = { From 0da2a10d1cd2e1cf9b9195a822c7fd88fff64d4d Mon Sep 17 00:00:00 2001 From: Joshua Mo Date: Mon, 8 Jun 2026 17:15:38 +0100 Subject: [PATCH 4/4] chore: amendments --- snippets/CopyMarkdownButton.jsx | 2 +- style.css | 24 ++++++++++++------------ 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/snippets/CopyMarkdownButton.jsx b/snippets/CopyMarkdownButton.jsx index 68639c99..e39ad4d5 100644 --- a/snippets/CopyMarkdownButton.jsx +++ b/snippets/CopyMarkdownButton.jsx @@ -1,7 +1,7 @@ export const CopyMarkdownButton = (props = {}) => { const { className = "", - label = "Copy article to Markdown", + label = "Copy article as Markdown", sourcePath, } = props; const statusText = { diff --git a/style.css b/style.css index 18a89161..a2934409 100644 --- a/style.css +++ b/style.css @@ -2435,12 +2435,12 @@ display: inline-flex; align-items: center; gap: 8px; - margin: -0.75rem 0 1rem; + margin: -0.75rem 0 1.5rem; padding: 8px 12px; - border: 1px solid rgba(18, 93, 163, 0.22); + border: 1px solid rgba(82, 82, 91, 0.22); border-radius: 999px; - background: rgba(18, 93, 163, 0.07); - color: #125DA3; + background: rgba(82, 82, 91, 0.06); + color: #52525b; font-size: 13px; font-weight: 650; line-height: 1; @@ -2453,8 +2453,8 @@ } .venice-copy-markdown-button:hover:not(:disabled) { - border-color: rgba(18, 93, 163, 0.42); - background: rgba(18, 93, 163, 0.12); + border-color: rgba(82, 82, 91, 0.36); + background: rgba(82, 82, 91, 0.1); } .venice-copy-markdown-button:disabled { @@ -2463,7 +2463,7 @@ } .venice-copy-markdown-button:focus-visible { - outline: 2px solid rgba(18, 93, 163, 0.45); + outline: 2px solid rgba(82, 82, 91, 0.35); outline-offset: 2px; } @@ -2479,13 +2479,13 @@ .dark .venice-copy-markdown-button, [data-theme="dark"] .venice-copy-markdown-button { - border-color: rgba(112, 177, 232, 0.28); - background: rgba(112, 177, 232, 0.1); - color: #70b1e8; + border-color: rgba(161, 161, 170, 0.24); + background: rgba(161, 161, 170, 0.08); + color: #a1a1aa; } .dark .venice-copy-markdown-button:hover:not(:disabled), [data-theme="dark"] .venice-copy-markdown-button:hover:not(:disabled) { - border-color: rgba(112, 177, 232, 0.48); - background: rgba(112, 177, 232, 0.16); + border-color: rgba(161, 161, 170, 0.4); + background: rgba(161, 161, 170, 0.14); }