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..e39ad4d5 --- /dev/null +++ b/snippets/CopyMarkdownButton.jsx @@ -0,0 +1,131 @@ +export const CopyMarkdownButton = (props = {}) => { + const { + className = "", + label = "Copy article as 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..a2934409 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.75rem 0 1.5rem; + padding: 8px 12px; + border: 1px solid rgba(82, 82, 91, 0.22); + border-radius: 999px; + background: rgba(82, 82, 91, 0.06); + color: #52525b; + 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(82, 82, 91, 0.36); + background: rgba(82, 82, 91, 0.1); +} + +.venice-copy-markdown-button:disabled { + cursor: wait; + opacity: 0.72; +} + +.venice-copy-markdown-button:focus-visible { + outline: 2px solid rgba(82, 82, 91, 0.35); + 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(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(161, 161, 170, 0.4); + background: rgba(161, 161, 170, 0.14); +}