import { hydratePrompt, LlmPrompt, llmPrompt } from "@commontools/llm";
import { type StaticCache } from "@commontools/static";
const libraries = {
"imports": {
"react": "https://esm.sh/react@18.3.0",
"react-dom": "https://esm.sh/react-dom@18.3.0",
"react-dom/client": "https://esm.sh/react-dom@18.3.0/client",
"d3": "https://esm.sh/d3@7.8.5",
"moment": "https://esm.sh/moment@2.29.4",
"marked": "https://esm.sh/marked@15.0.7",
"@react-spring/web":
"https://esm.sh/@react-spring/web@9.7.3?external=react",
"@use-gesture/react":
"https://esm.sh/@use-gesture/react@10.3.0?external=react",
"uuid": "https://esm.sh/uuid@11.0.1",
"tone": "https://esm.sh/tone@15.0.4",
"@babel/standalone": "https://esm.sh/@babel/standalone@7.24.7",
},
};
// The HTML template that wraps the developer's code
export const prefillHtml = `
`;
// Function to inject the user's code into the template
export function injectUserCode(userCode: string) {
// Add comment fences around the user code for later extraction
const fencedUserCode = `// BEGIN_USER_CODE\n${userCode}\n// END_USER_CODE`;
return prefillHtml.replace("// USER_CODE_PLACEHOLDER", fencedUserCode);
}
// Function to extract the user code from HTML with fences
export function extractUserCode(html: string): string | null {
const startMarker = "// BEGIN_USER_CODE\n";
const endMarker = "\n// END_USER_CODE";
const startIndex = html.indexOf(startMarker);
if (startIndex === -1) return null;
const endIndex = html.indexOf(endMarker, startIndex);
if (endIndex === -1) return null;
return html.substring(startIndex + startMarker.length, endIndex);
}
export function extractVersionTag(template?: string) {
// Extract the template version from the HTML comment
const versionMatch = template?.match(
//,
);
return versionMatch ? versionMatch[1] : null;
}
const security = () => `
- Do not use browser dialog functions (\`prompt()\`, \`alert()\`, \`confirm()\`)
- Avoid any methods that could compromise security or user experience
`;
// Update the system message to reflect the new interface
export const systemMd = llmPrompt(
"iframe-react-system",
`# React Component Builder
Create an interactive React component that fulfills the user's request. Focus on delivering a clean, useful implementation with appropriate features.
**This task is part of a 2-Phase Process.**
1. First phase (already completed):
- Analyzed the user's request
- Created a detailed specification
- Generated a structured data schema
2. Your job (second phase):
- Create a reactive UI component based on the provided specification and schema
- Implement the UI exactly according to the specification
- Strictly adhere to the data schema provided
- Define a title with \`const title = 'Your App Name';\`
- Implement both \`onLoad\` and \`onReady\` functions
- Use Tailwind CSS for styling with tasteful defaults
- Do not write
- React and ReactDOM are pre-imported - don't import them again
- All React hooks must be namespaced (e.g., \`React.useState\`, \`React.useEffect\`)
- Follow React hooks rules - never nest or conditionally call hooks
- For form handling, use \`onClick\` handlers instead of \`onSubmit\`
- **useReactiveCell(keyPath: string[])** - Persistent data storage with reactive updates
- **generateText({ system, messages })** - Generate text via Large Language Model
- **generateObject({ system, messages })** - Generate JSON object via Large Language Model
- **readWebpage(url)** - Fetch and parse external web content
- **generateImage(prompt)** - Create AI-generated images
## Important Note About useReactiveCell(keyPath: string[])
- **useReactiveCell is a React Hook** and must follow all React hook rules
- It should only be used for persistent state and must draw from the provided schema
- For any ephemeral state, use \`React.useState\`
- Only call useReactiveCell at the top level of your React function components or custom hooks
- Do not call useReactiveCell inside loops, conditions, or nested functions
- Do not call useReactiveCell directly inside the onReady function - it can only be used inside React components
- The onReady function itself is not a React component and cannot use React hooks directly
- Example of correct usage:
\`\`\`jsx
function onReady(mount, sourceData, modules) {
// This is NOT a React component, so do NOT use hooks here!
// Create a React component to use hooks properly:
function MyApp() {
// React hooks can be used here
const [data, setData] = useReactiveCell('myData', defaultValue);
return
{/* your JSX */}
;
}
// Render the React component to the mount point
const root = ReactDOM.createRoot(mount);
root.render();
}
\`\`\`
- Request additional libraries in \`onLoad\` by returning an array of module names
- Available libraries:
${Object.entries(libraries).map(([k, v]) => `- ${k} : ${v}`).join("\n")}
- Only use the explicitly provided libraries
${security()}
{{SCHEMA}}
# SDK Usage Guide
## 1. \`useReactiveCell\` Hook
The \`useReactiveCell\` hook binds to a reactive cell given a key path and returns a tuple \`[doc, setDoc]\`:
Any keys from the view-model-schema are valid for useReactiveCell, any other keys will fail. Provide a default as the second argument, **do not set an initial value explicitly**.
For this schema:
\`\`\`json
{
"type": "object",
"properties": {
"counter": {
"type": "number",
},
"title": {
"type": "string",
"default": "My Counter App"
}
}
}
\`\`\`
\`\`\`jsx
function CounterComponent() {
// Correct: useReactiveCell called at top level of component
const [counter, setCounter] = useReactiveCell("counter");
const [maxValue, setMaxValue] = useReactiveCell(['settings', 'maxValue']);
const onIncrement = useCallback(() => {
// writing to the cell automatically triggers a re-render
setCounter(counter + 1);
}, [counter]);
return (
);
}
\`\`\`
Several APIs exist for generating text, JSON or image urls.
\`generateText({ system, messages}): Promise\`
\`\`\`jsx
async function fetchLLMResponse() {
const result = await generateText({
system: 'Translate all the messages to emojis, reply in JSON.',
messages: ['Hi', 'How can I help you today?', 'tell me a joke']
})
console.log('LLM responded:', result);
}
\`\`\`
\`window.generateObject({ system, messages }): Promise
Synchronous, generates a URL that will load the image.
\`\`\`jsx
function ImageComponent() {
return ;
}
\`\`\`
\`readWebpage(url: string): Promise\` Returns markdown format.
\`\`\`jsx
async function fetchFromUrl() {
const url = 'https://twopm.studio';
const result = await readWebpage(url);
console.log('Markdown:', result.content);
}
\`\`\`
All generated code must follow this pattern:
\`\`\`javascript
// Import from modern ESM libraries:
${Object.keys(libraries.imports).map((lib) => `// - ${lib}`).join("\n")}
function onLoad() {
return ['@react-spring/web']; // Request the modules you need
}
const title = 'My ESM App';
function ImageComponent({ url }) {
return ;
}
function MyComponent({ label, description }) {
return (
{label}
{description}
);
}
function TodoItem({ todo, onToggle, onDelete }) {
return (
);
}
// Main application code with modules passed as third parameter
function onReady(mount, sourceData, libs) {
const { useState, useEffect } = React; // React is available globally
const { useSpring, animated } = libs['@react-spring/web']; // Access imported module
function MyApp() {
const [count, setCount] = useReactiveCell('count');
const [todos, setTodos] = useReactiveCell('todos');
const props = useSpring({
from: { opacity: 0 },
to: { opacity: 1 }
});
return (
);
}
// Use the client API for React 18
const root = ReactDOM.createRoot(mount);
root.render();
}
\`\`\`
`,
);
// Update the system message to reflect the new interface
export const systemMdConcise = llmPrompt(
"iframe-react-system-concise",
`# Source Code Generation
Create an interactive React component that fulfills the user's request. Focus on delivering a clean, useful implementation with appropriate features:
- Implement the UI exactly according to the specification
- Strictly adhere to the data schema provided
- Follow the instructions below
## Required Elements
- Define a title with \`const title = 'Your App Name';\`
- Implement both \`onLoad\` and \`onReady\` functions
- Use Tailwind CSS for styling with tasteful defaults
- Do not write