Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion scw_js/llm_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,16 @@ export async function callLLMAPI(
choices: [
{
index: 0,
message: { role: "assistant", content: "I am a placeholder for the LLM response" },
message: {
role: "assistant",
content:
"**Placeholder response**\n\n" +
"This is a *mock* answer used for local/testnet testing. It includes:\n\n" +
"- a list item\n" +
"- some `inline code`\n" +
"- a [link](https://example.com)\n\n" +
"```js\nconsole.log('mock code block');\n```",
},
finish_reason: "stop",
},
],
Expand Down
10 changes: 9 additions & 1 deletion website/components/AssistantChat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
*/

import React, { useState, useMemo, useEffect } from "react";
import ReactMarkdown from "react-markdown";
import remarkGfm from "remark-gfm";
import { AgentInfoPanel } from "./AgentInfoPanel";
import { AgentSelector } from "./AgentSelector";
import * as styles from "../layouts/styles";
Expand Down Expand Up @@ -291,7 +293,13 @@ export function AssistantChat() {
}`}
>
<div className={styles.messageRole}>{message.role === "user" ? youLabel : assistantLabel}</div>
<div className={styles.messageContent}>{message.content}</div>
<div className={styles.messageContent}>
{message.role === "assistant" ? (
<ReactMarkdown remarkPlugins={[remarkGfm]}>{message.content}</ReactMarkdown>
) : (
<div className={styles.messageContentPlain}>{message.content}</div>
)}
</div>
</div>
</div>
))
Expand Down
10 changes: 8 additions & 2 deletions website/components/CodeBlock.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* CodeBlock — the shared docs code block: syntax-highlighted, copyable.
*
* Highlighting runs through highlight.js's *core* build with only the three languages we
* Highlighting runs through highlight.js's *core* build with only the languages we
* actually use registered. The full `highlight.js` bundle registers ~190 grammars and would
* push a client chunk past the 700 kB ceiling enforced by utils/checkChunkSizes.ts — never
* import the default entry point here.
Expand All @@ -18,15 +18,21 @@ import hljs from "highlight.js/lib/core";
import typescript from "highlight.js/lib/languages/typescript";
import json from "highlight.js/lib/languages/json";
import bash from "highlight.js/lib/languages/bash";
import javascript from "highlight.js/lib/languages/javascript";
import python from "highlight.js/lib/languages/python";
import yaml from "highlight.js/lib/languages/yaml";
import "highlight.js/styles/vs2015.css";
import { css } from "../styled-system/css";

hljs.registerLanguage("typescript", typescript);
hljs.registerLanguage("json", json);
hljs.registerLanguage("bash", bash);
hljs.registerLanguage("javascript", javascript);
hljs.registerLanguage("python", python);
hljs.registerLanguage("yaml", yaml);

/** `plaintext` opts out of highlighting — use it for terminal transcripts and annotated output. */
export type CodeLang = "typescript" | "json" | "bash" | "plaintext";
export type CodeLang = "typescript" | "json" | "bash" | "javascript" | "python" | "yaml" | "plaintext";

const wrapper = css({ position: "relative", mt: "1", mb: "2" });

Expand Down
58 changes: 58 additions & 0 deletions website/components/MdxCodeBlock.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import React from "react";
import { CodeBlock, CodeLang } from "./CodeBlock";

/** Fence languages that don't map 1:1 onto a registered hljs grammar. */
const LANG_ALIASES: Record<string, CodeLang> = {
shell: "bash",
sh: "bash",
};

/**
* `remark-math` (configured in vite.config.ts) also renders through `<pre><code class="language-math ...">`
* — the same shape as a fenced code block, but it's not one. `useKaTeXRenderer.ts` finds these via
* `querySelectorAll("code.language-math")` and replaces them with real KaTeX markup client-side.
* MdxPre must leave this class untouched, or that selector stops matching and math silently stops
* rendering (this exact regression happened once already — hence the dedicated check and test).
*/
function isMathFence(className?: string): boolean {
return /(^|\s)language-math(\s|$)/.test(className ?? "");
}

function resolveLang(className?: string): CodeLang {
const match = /language-(\w+)/.exec(className ?? "");
const raw = match?.[1];
if (!raw) return "plaintext";
const aliased = LANG_ALIASES[raw] ?? raw;
switch (aliased) {
case "typescript":
case "json":
case "bash":
case "javascript":
case "python":
case "yaml":
return aliased;
default:
return "plaintext";
}
}

/**
* MDX renders fenced code blocks as `<pre><code className="language-xxx">...</code></pre>`.
* This adapter, passed to the compiled MDX component as its `components.pre` prop (see Post.tsx —
* MDX here is compiled without `providerImportSource`, so it reads overrides from `props.components`,
* not from `MDXProvider` context), redirects that markup through CodeBlock for highlighting + copy
* support. Falls back to a plain `<pre>` for a `language-math` fence (see isMathFence above) or for
* any `pre` that doesn't have the expected single `<code>` child (defensive — MDX always produces
* this shape for fenced blocks, but `pre` can in principle appear standalone in hand-written MDX/HTML).
*/
export function MdxPre({ children, ...rest }: React.HTMLAttributes<HTMLPreElement>) {
const child = React.isValidElement(children) ? children : null;
const childProps = child?.props as { className?: string; children?: React.ReactNode } | undefined;
const code = childProps?.children;

if (!child || typeof code !== "string" || isMathFence(childProps?.className)) {
return <pre {...rest}>{children}</pre>;
}

return <CodeBlock lang={resolveLang(childProps?.className)}>{code.replace(/\n$/, "")}</CodeBlock>;
}
9 changes: 7 additions & 2 deletions website/components/Post.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { PostProps } from "../types/components";
import MetadataLine from "./MetadataLine";
import { Link } from "./Link";
import { NFTFloatImage } from "./NFTFloatImage";
import { MdxPre } from "./MdxCodeBlock";
import { post, titleBar } from "../layouts/styles";
import { loadLazyModuleFromDirectory } from "../utils/lazyGlobRegistry";
import { isSupportedDirectory, getSupportedDirectories } from "../utils/supportedDirectories";
Expand All @@ -15,14 +16,18 @@ import { TableOfContents } from "./TableOfContents";
import { Webmentions } from "./Webmentions";
import { CommentsSection } from "./CommentsSection";

// MDX components accept an extra `components` override prop (used to route `pre` through
// MdxPre); plain TSX posts simply ignore it.
type PostComponent = React.ComponentType<{ components?: Record<string, React.ComponentType> }>;

// Dynamic React component renderer
const ReactPostRenderer: React.FC<{
componentPath: string;
tokenID?: number;
contentRef: React.RefObject<HTMLDivElement>;
onReady?: () => void;
}> = ({ componentPath, tokenID, contentRef, onReady }) => {
const [Component, setComponent] = React.useState<React.ComponentType | null>(null);
const [Component, setComponent] = React.useState<PostComponent | null>(null);
const [error, setError] = React.useState<string | null>(null);
const [loading, setLoading] = React.useState<boolean>(true);

Expand Down Expand Up @@ -139,7 +144,7 @@ const ReactPostRenderer: React.FC<{
return (
<div className={post.contentContainer} ref={contentRef}>
{tokenID && <NFTFloatImage tokenId={tokenID} />}
<Component />
<Component components={{ pre: MdxPre }} />
</div>
);
};
Expand Down
8 changes: 7 additions & 1 deletion website/layouts/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2593,10 +2593,16 @@ export const messageRole = css({
});

export const messageContent = css({
whiteSpace: "pre-wrap",
lineHeight: "1.5",
});

// Plain-text messages (user input) preserve literal newlines/spacing.
// Markdown-rendered messages (assistant output) skip this — Markdown's own
// block spacing would otherwise double up with pre-wrap.
export const messageContentPlain = css({
whiteSpace: "pre-wrap",
});

// Loading message
export const loadingMessage = css({
margin: "md 0",
Expand Down
37 changes: 37 additions & 0 deletions website/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions website/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"react-chartjs-2": "^5.3.1",
"react-dom": "^19.2.8",
"react-markdown": "^10.1.0",
"rehype-mdx-import-media": "^1.4.0",
"remark-frontmatter": "^5.0.0",
"remark-gfm": "^4.0.1",
"remark-math": "^6.0.0",
Expand Down
41 changes: 14 additions & 27 deletions website/pages/x402/+Page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, { useEffect, useState } from "react";
import { css } from "../../styled-system/css";
import MermaidDiagram from "../../components/MermaidDiagram";
import { FacilitatorApproval } from "../../components/FacilitatorApproval";
import { CodeBlock } from "../../components/CodeBlock";
import { titleBar } from "../../layouts/styles";
import * as styles from "../../layouts/styles";
import { Link } from "../../components/Link";
Expand Down Expand Up @@ -327,8 +328,7 @@ export default function Page() {
Replace <code>0xYourMerchantAddress</code> with your wallet address and set <code>amount</code> to your
price in USDC (6 decimals — <code>100000</code> = $0.10).
</p>
<pre>
<code>{`// HTTP 402 response body:
<CodeBlock lang="json">{`// HTTP 402 response body:
{
"x402Version": 2,
"accepts": [{
Expand All @@ -341,8 +341,7 @@ export default function Page() {
"extra": { "name": "USD Coin", "version": "2" }
}],
"facilitatorUrl": "https://facilitator.fretchen.eu"
}`}</code>
</pre>
}`}</CodeBlock>
</div>

<div className={stepContainer}>
Expand All @@ -364,8 +363,7 @@ export default function Page() {
When a client sends a request with a <code>PAYMENT-SIGNATURE</code> header, verify the payment before
delivering the resource, then settle it on-chain:
</p>
<pre>
<code>{`// 1. Verify payment (before delivering resource)
<CodeBlock lang="javascript">{`// 1. Verify payment (before delivering resource)
const verifyRes = await fetch("https://facilitator.fretchen.eu/verify", {
method: "POST",
headers: { "Content-Type": "application/json" },
Expand All @@ -386,8 +384,7 @@ await fetch("https://facilitator.fretchen.eu/settle", {
network: "eip155:10", payload, details })
});

return new Response(JSON.stringify(result), { status: 200 });`}</code>
</pre>
return new Response(JSON.stringify(result), { status: 200 });`}</CodeBlock>
<p>
That&apos;s it — your service now accepts crypto payments. See the{" "}
<Link href="/agent-onboarding">agent onboarding guide</Link> for a complete walkthrough.
Expand Down Expand Up @@ -500,8 +497,7 @@ return new Response(JSON.stringify(result), { status: 200 });`}</code>
Validates a signed payment off-chain. Checks signature validity, sufficient balance, correct recipient, and
expiration. Call this <strong>before</strong> delivering your resource.
</p>
<pre>
<code>{`curl -X POST https://facilitator.fretchen.eu/verify \\
<CodeBlock lang="bash">{`curl -X POST https://facilitator.fretchen.eu/verify \\
-H "Content-Type: application/json" \\
-d '{
"x402Version": 2,
Expand All @@ -515,8 +511,7 @@ return new Response(JSON.stringify(result), { status: 200 });`}</code>
"asset": "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85",
"payTo": "0xYourMerchantAddress"
}
}'`}</code>
</pre>
}'`}</CodeBlock>
<p>
Response: <code>{`{ "valid": true }`}</code> or <code>{`{ "valid": false, "invalidReason": "..." }`}</code>
</p>
Expand All @@ -528,8 +523,7 @@ return new Response(JSON.stringify(result), { status: 200 });`}</code>
Executes the payment on-chain via EIP-3009 <code>transferWithAuthorization</code>. Call this{" "}
<strong>after</strong> successful verification and resource delivery.
</p>
<pre>
<code>{`curl -X POST https://facilitator.fretchen.eu/settle \\
<CodeBlock lang="bash">{`curl -X POST https://facilitator.fretchen.eu/settle \\
-H "Content-Type: application/json" \\
-d '{
"x402Version": 2,
Expand All @@ -543,8 +537,7 @@ return new Response(JSON.stringify(result), { status: 200 });`}</code>
"asset": "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85",
"payTo": "0xYourMerchantAddress"
}
}'`}</code>
</pre>
}'`}</CodeBlock>
<p>
Response: <code>{`{ "success": true, "txHash": "0x..." }`}</code>
</p>
Expand All @@ -553,9 +546,7 @@ return new Response(JSON.stringify(result), { status: 200 });`}</code>
<h3>GET /supported</h3>
<div className={endpointBox}>
<p>Returns supported networks, payment schemes, and fee configuration.</p>
<pre>
<code>{`curl https://facilitator.fretchen.eu/supported`}</code>
</pre>
<CodeBlock lang="bash">{`curl https://facilitator.fretchen.eu/supported`}</CodeBlock>
<p>
Returns a JSON object with <code>kinds</code> (supported network/scheme pairs), <code>extensions</code>{" "}
(advertised extension keys), <code>signers</code> (facilitator addresses per network), and{" "}
Expand All @@ -579,8 +570,7 @@ return new Response(JSON.stringify(result), { status: 200 });`}</code>
<p>
Using the official <code>@x402/fetch</code> SDK, a client can pay for any x402 resource automatically:
</p>
<pre>
<code>{`import { x402Client, wrapFetchWithPayment } from "@x402/fetch";
<CodeBlock lang="typescript">{`import { x402Client, wrapFetchWithPayment } from "@x402/fetch";
import { registerExactEvmScheme } from "@x402/evm/exact/client";
import { privateKeyToAccount } from "viem/accounts";

Expand All @@ -602,13 +592,11 @@ const response = await fetchWithPayment(

const result = await response.json();
console.log("Image:", result.imageUrl);
console.log("NFT:", result.tokenId);`}</code>
</pre>
console.log("NFT:", result.tokenId);`}</CodeBlock>

<h3>Your server (resource server)</h3>
<p>Full example of a Node.js endpoint protected by x402. Adapt the resource generation to your use case:</p>
<pre>
<code>{`// Express / Node.js example
<CodeBlock lang="javascript">{`// Express / Node.js example
app.post("/api/resource", async (req, res) => {
const paymentHeader = req.headers["payment-signature"];

Expand Down Expand Up @@ -658,8 +646,7 @@ app.post("/api/resource", async (req, res) => {
});

return res.json(result);
});`}</code>
</pre>
});`}</CodeBlock>

{/* ── 7. Supported networks ────────────────────────────────────── */}

Expand Down
Loading
Loading