From 6ae46aa6f7eb1078253142795c59ecace0fd9f1c Mon Sep 17 00:00:00 2001 From: Ben Follington <5009316+bfollington@users.noreply.github.com> Date: Wed, 30 Apr 2025 05:52:06 +1000 Subject: [PATCH 1/7] Handle direct string response from LLM as well as `.content` unwrap --- jumble/public/module/charm/sandbox/bootstrap.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jumble/public/module/charm/sandbox/bootstrap.js b/jumble/public/module/charm/sandbox/bootstrap.js index 6f6cfa132..1e3c8ecf6 100644 --- a/jumble/public/module/charm/sandbox/bootstrap.js +++ b/jumble/public/module/charm/sandbox/bootstrap.js @@ -106,7 +106,7 @@ window.generateText = function ({ system, messages }) { return window.llm({ system, messages, - }).then(result => result.content) + }).then(result => typeof result === 'string' ? result : result?.content) } window.generateObject = function ({ system, messages }) { From 502a356579f2c5141a1f5dc6a5454d15b5e0c22d Mon Sep 17 00:00:00 2001 From: Ben Follington <5009316+bfollington@users.noreply.github.com> Date: Wed, 30 Apr 2025 07:39:06 +1000 Subject: [PATCH 2/7] Support keyPath for useReactiveCell --- charm/src/iframe/static.ts | 34 +++--- .../public/module/charm/sandbox/bootstrap.js | 110 +++++++++++------- 2 files changed, 80 insertions(+), 64 deletions(-) diff --git a/charm/src/iframe/static.ts b/charm/src/iframe/static.ts index fe7e2173b..a32f14a29 100644 --- a/charm/src/iframe/static.ts +++ b/charm/src/iframe/static.ts @@ -114,21 +114,20 @@ Create an interactive React component that fulfills the user's request. Focus on -- **useReactiveCell(key, defaultValue)** - Persistent data storage with reactive updates +- **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 + + ## 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 function components or custom hooks - Do not call useReactiveCell inside loops, conditions, or nested functions - - useReactiveCell cannot be used outside of \`onReady\` components - it must be called during rendering - + @@ -151,7 +150,7 @@ ${security()} ## 1. \`useReactiveCell\` Hook -The \`useReactiveCell\` hook binds to a reactive cell given key and returns a tuple \`[doc, setDoc]\`: +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**. @@ -175,12 +174,8 @@ For this schema: \`\`\`jsx function CounterComponent() { // Correct: useReactiveCell called at top level of component - const [counter, setCounter] = useReactiveCell("counter", -1); // default - - // Incorrect: would cause errors - // if(something) { - // const [data, setData] = useReactiveCell("data", {}); // Never do this! - // } + const [counter, setCounter] = useReactiveCell("counter"); + const [maxValue, setMaxValue] = useReactiveCell(['settings', 'maxValue']); const onIncrement = useCallback(() => { // writing to the cell automatically triggers a re-render @@ -428,11 +423,9 @@ function onReady(mount, sourceData, libs) { const { useSpring, animated } = libs['@react-spring/web']; // Access imported module function MyApp() { - const [count, setCount] = useReactiveCell('count', 0); - const [todos, setTodos] = useReactiveCell('todos', [ - { id: 1, text: 'Learn React', completed: false }, - { id: 2, text: 'Build a Todo App', completed: false } - ]); + const [count, setCount] = useReactiveCell('count'); + const [todos, setTodos] = useReactiveCell('todos'); + const props = useSpring({ from: { opacity: 0 }, to: { opacity: 1 } @@ -486,19 +479,18 @@ Create an interactive React component that fulfills the user's request. Focus on 4. For form handling, use \`onClick\` handlers instead of \`onSubmit\` ## Available APIs -- **useReactiveCell(key, defaultValue)** - Persistent data storage with reactive updates +- **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 +## 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 function components or custom hooks - Do not call useReactiveCell inside loops, conditions, or nested functions -- useReactiveCell cannot be used outside of \`onReady\` components - it must be called during rendering ## Library Usage - Request additional libraries in \`onLoad\` by returning an array of module names @@ -513,7 +505,7 @@ ${security()} ## 1. \`useReactiveCell\` Hook -The \`useReactiveCell\` hook binds to a reactive cell given key and returns a tuple \`[doc, setDoc]\`: +The \`useReactiveCell\` hook binds to a reactive cell given a key path and returns a tuple \`[doc, setDoc]\`: Any keys from the schema are valid for useReactiveCell, any other keys will fail. Provide a default as the second argument, **do not set an initial value explicitly**. diff --git a/jumble/public/module/charm/sandbox/bootstrap.js b/jumble/public/module/charm/sandbox/bootstrap.js index 1e3c8ecf6..e0670a62d 100644 --- a/jumble/public/module/charm/sandbox/bootstrap.js +++ b/jumble/public/module/charm/sandbox/bootstrap.js @@ -8,60 +8,84 @@ window.React = React window.ReactDOM = ReactDOM window.Babel = Babel -window.useReactiveCell = function (key) { - // Track if we've received a response from the parent - const [received, setReceived] = React.useState(false) - // Initialize state with defaultValue - const [doc, setDocState] = React.useState(undefined) - +window.useReactiveCell = function useReactiveCell(pathOrKey) { + const pathArr = Array.isArray(pathOrKey) ? pathOrKey : [pathOrKey]; + const rootKey = pathArr[0]; // the key used for IPC + const nestedPath = pathArr.slice(1); // [] when we stay at the root + + /* ------------------------------------------------------------------ utils */ + const getNested = (obj, p = []) => + p.reduce((acc, k) => (acc != null ? acc[k] : undefined), obj); + + const setNested = (obj, p, val) => { + if (p.length === 0) return val; + const [k, ...rest] = p; + const clone = + typeof k === "number" + ? Array.isArray(obj) ? obj.slice() : [] // keep arrays as arrays + : obj && typeof obj === "object" + ? { ...obj } + : {}; + clone[k] = setNested(clone[k], rest, val); + return clone; + }; + /* ------------------------------------------------------------------------ */ + + // Last full root value we have seen. + const rootRef = React.useRef( + window.sourceData ? window.sourceData[rootKey] : undefined, + ); + + // Local state holds ONLY the nested value the caller cares about. + const [received, setReceived] = React.useState(false); + const [doc, setDocState] = React.useState(() => + getNested(rootRef.current, nestedPath) + ); + + /* -------------------------------- listen for updates from the host ------ */ React.useEffect(() => { - // Handler for document updates - function handleMessage(event) { + function handleMessage(e) { if ( - event.data && - event.data.type === "update" && - event.data.data[0] === key + e.data && + e.data.type === "update" && + e.data.data?.[0] === rootKey ) { - // Mark that we've received a response - setReceived(true) - - // Update the state with the received value or null if undefined - const value = event.data.data[1] - console.log("useReactiveCell", key, "updated", value) - setDocState(value) + const newRoot = e.data.data[1]; + rootRef.current = newRoot; + setDocState(getNested(newRoot, nestedPath)); + setReceived(true); } } - window.addEventListener("message", handleMessage) - - // Subscribe to the specific key - window.parent.postMessage({ type: "subscribe", data: [key] }, "*") - window.parent.postMessage({ type: "read", data: key }, "*") + window.addEventListener("message", handleMessage); + window.parent.postMessage({ type: "subscribe", data: [rootKey] }, "*"); + window.parent.postMessage({ type: "read", data: rootKey }, "*"); return () => { - window.removeEventListener("message", handleMessage) - window.parent.postMessage({ type: "unsubscribe", data: [key] }, "*") - } - }, [key]) + window.removeEventListener("message", handleMessage); + window.parent.postMessage({ type: "unsubscribe", data: [rootKey] }, "*"); + }; + }, [rootKey, nestedPath.join(".")]); // Dependency on stringified path + /* ------------------------------------------------------------------------ */ - // Update function + /* ----------------------------- setter ----------------------------------- */ const updateDoc = newValue => { - if (typeof newValue === "function") { - newValue = newValue(doc) - } - console.log("useReactiveCell", key, "written", newValue) - setDocState(newValue) - window.parent.postMessage({ type: "write", data: [key, newValue] }, "*") - } + if (typeof newValue === "function") newValue = newValue(doc); - // If we have not yet received response from the host we use field from the - // sourceData which was preloaded via `subscribeToSource` during initialization - // in the `initializeApp`. - // ⚠️ Please note that value we prefetched still could be out of date because - // `*` subscription is removed in the iframe-ctx.ts file which could lead - // charm to make wrong conclusion and overwrite key. - return [received ? doc : window.sourceData[key], updateDoc] -} + // Build the new *root* object immutably + const newRoot = setNested(rootRef.current ?? {}, nestedPath, newValue); + rootRef.current = newRoot; + setDocState(newValue); + + window.parent.postMessage({ type: "write", data: [rootKey, newRoot] }, "*"); + }; + /* ------------------------------------------------------------------------ */ + + // If we never received an explicit update yet, fall back to pre-loaded data + const fallback = getNested(window.sourceData?.[rootKey], nestedPath); + + return [received ? doc : fallback, updateDoc]; +}; window.useDoc = window.useReactiveCell; From 61cff6038fc43624e91ee4f5109033bfc47213dd Mon Sep 17 00:00:00 2001 From: Ben Follington <5009316+bfollington@users.noreply.github.com> Date: Wed, 30 Apr 2025 07:39:15 +1000 Subject: [PATCH 3/7] Fix empty string boolean test --- charm/src/iterate.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/charm/src/iterate.ts b/charm/src/iterate.ts index f1a9ad091..613e72a32 100644 --- a/charm/src/iterate.ts +++ b/charm/src/iterate.ts @@ -105,7 +105,7 @@ export async function iterate( const { iframe } = getIframeRecipe(charm); const prevSpec = iframe?.spec; - if (!plan?.description) { + if (plan?.description === undefined) { throw new Error("No specification provided"); } const newSpec = plan.description; From 76b3bcf5b26d0d76e6a24151fc59b7d511d0a483 Mon Sep 17 00:00:00 2001 From: Ben Follington <5009316+bfollington@users.noreply.github.com> Date: Wed, 30 Apr 2025 10:55:37 +1000 Subject: [PATCH 4/7] generateImage -> generateImageUrl --- charm/src/iframe/static.ts | 10 +++++++--- jumble/public/module/charm/sandbox/bootstrap.js | 3 ++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/charm/src/iframe/static.ts b/charm/src/iframe/static.ts index a32f14a29..236fd542f 100644 --- a/charm/src/iframe/static.ts +++ b/charm/src/iframe/static.ts @@ -299,14 +299,18 @@ async function fetchFromUrl() { } \`\`\` -## 5. generateImage Function + + +Synchronous, generates a URL that will load the image. \`\`\`jsx function ImageComponent() { - return Generated landscape; + return Generated landscape; } - \`\`\` + + + ## 6. Using the Interface Functions \`\`\`javascript diff --git a/jumble/public/module/charm/sandbox/bootstrap.js b/jumble/public/module/charm/sandbox/bootstrap.js index e0670a62d..f7247bfca 100644 --- a/jumble/public/module/charm/sandbox/bootstrap.js +++ b/jumble/public/module/charm/sandbox/bootstrap.js @@ -239,11 +239,12 @@ window.readWebpage = (function () { return readWebpage })() -// Define generateImage utility with React available window.generateImage = function (prompt) { return "/api/ai/img?prompt=" + encodeURIComponent(prompt) } +window.generateImageUrl = window.generateImage; + // Error handling window.onerror = function (message, source, lineno, colno, error) { window.parent.postMessage( From 5999695625c2b5747ade0846cd6780c9302ffce7 Mon Sep 17 00:00:00 2001 From: Ben Follington <5009316+bfollington@users.noreply.github.com> Date: Wed, 30 Apr 2025 11:49:20 +1000 Subject: [PATCH 5/7] Try 4.1 nano for JSON --- jumble/public/module/charm/sandbox/bootstrap.js | 7 +++++-- llm/src/types.ts | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/jumble/public/module/charm/sandbox/bootstrap.js b/jumble/public/module/charm/sandbox/bootstrap.js index f7247bfca..2cadbe42e 100644 --- a/jumble/public/module/charm/sandbox/bootstrap.js +++ b/jumble/public/module/charm/sandbox/bootstrap.js @@ -7,6 +7,9 @@ import * as Babel from "https://esm.sh/@babel/standalone" window.React = React window.ReactDOM = ReactDOM window.Babel = Babel +window.useState = window.React.useState +window.useEffect = window.React.useEffect +window.useCallback = window.React.useCallback window.useReactiveCell = function useReactiveCell(pathOrKey) { const pathArr = Array.isArray(pathOrKey) ? pathOrKey : [pathOrKey]; @@ -87,6 +90,7 @@ window.useReactiveCell = function useReactiveCell(pathOrKey) { return [received ? doc : fallback, updateDoc]; }; + window.useDoc = window.useReactiveCell; // Define llm utility with React available @@ -130,7 +134,7 @@ window.generateText = function ({ system, messages }) { return window.llm({ system, messages, - }).then(result => typeof result === 'string' ? result : result?.content) + }) } window.generateObject = function ({ system, messages }) { @@ -139,7 +143,6 @@ window.generateObject = function ({ system, messages }) { messages, mode: 'json' }) - .then(result => result.content) .then(result => { try { // Handle possible control characters and escape sequences diff --git a/llm/src/types.ts b/llm/src/types.ts index 13bbe7d77..5ee47d02c 100644 --- a/llm/src/types.ts +++ b/llm/src/types.ts @@ -5,7 +5,7 @@ export const DEFAULT_MODEL_NAME: ModelName = // NOTE(ja): This should be an array of models, the first model will be tried, if it // fails, the second model will be tried, etc. -export const DEFAULT_IFRAME_MODELS: ModelName = "google:gemini-2.0-flash"; +export const DEFAULT_IFRAME_MODELS: ModelName = "openai:gpt-4.1-nano"; export type LLMResponse = { content: string; From de43fa126e620eeaac13dfb711564ac7d7a51c55 Mon Sep 17 00:00:00 2001 From: Ben Follington <5009316+bfollington@users.noreply.github.com> Date: Wed, 30 Apr 2025 12:29:21 +1000 Subject: [PATCH 6/7] Clarify prompt structure --- charm/src/iframe/static.ts | 54 +++++++++++++++++++++++++------------- 1 file changed, 36 insertions(+), 18 deletions(-) diff --git a/charm/src/iframe/static.ts b/charm/src/iframe/static.ts index 236fd542f..9b5817dcf 100644 --- a/charm/src/iframe/static.ts +++ b/charm/src/iframe/static.ts @@ -148,6 +148,7 @@ ${security()} # SDK Usage Guide + ## 1. \`useReactiveCell\` Hook The \`useReactiveCell\` hook binds to a reactive cell given a key path and returns a tuple \`[doc, setDoc]\`: @@ -189,8 +190,13 @@ function CounterComponent() { ); } \`\`\` + -## 2. \`generateText\` Function + +Several APIs exist for generating text, JSON or image urls. + + +\`generateText({ system, messages}): Promise\` \`\`\`jsx async function fetchLLMResponse() { @@ -201,17 +207,21 @@ async function fetchLLMResponse() { console.log('LLM responded:', result); } \`\`\` + -## 3. \`generateObject\` (JSON) Function + -Important: ensure you explain the intended schema of the response in the prompt. +\`window.generateObject({ system, messages }): Promise\` + +You must give the exact schema of the response in the prompt. For example: "Generate a traditional Vietnamese recipe in JSON format, with the following properties: name (string), ingredients (array of strings), instructions (array of strings)" -\`generateObject\` returns a parsed object already, or \`undefined\`. +\`generateObject\` returns a parsed object already, or \`undefined\`. Be defensive working with the response, the LLM may make mistakes. + \`\`\`jsx const promptPayload = ; const result = await generateObject({ @@ -235,14 +245,14 @@ console.log('JSON response from llm:', result); // } // ] \`\`\` + -ANOTHER NOTE: Language model requests are globally cached based on your prompt. + +NOTE: Language model requests are globally cached based on your prompt. This means that identical requests will return the same result. If your llm use requires unique results on every request, make sure to introduce a cache-breaking string such as a timestamp or incrementing number/id. -Another example: - \`\`\`jsx // To avoid the cache we'll use a cache-busting string. const cacheBreaker = Date.now(); @@ -288,18 +298,12 @@ console.log('JSON response from llm:', result); // "cookTime": 30 // } \`\`\` + -## 4. \`readWebpage\` Function + -\`\`\`jsx -async function fetchFromUrl() { - const url = 'https://twopm.studio'; - const result = await readWebpage(url); - console.log('Markdown:', result.content); -} -\`\`\` - + Synchronous, generates a URL that will load the image. @@ -309,9 +313,23 @@ function ImageComponent() { } \`\`\` - + + + + +\`readWebpage(url: string): Promise\` Returns markdown format. -## 6. Using the Interface Functions +\`\`\`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: From 501a3f455a7f1d80f9a2919d5e312a243ba896e9 Mon Sep 17 00:00:00 2001 From: Ben Follington <5009316+bfollington@users.noreply.github.com> Date: Wed, 30 Apr 2025 12:48:16 +1000 Subject: [PATCH 7/7] Update LLM cache --- ...3c86778ba0ab463300f5c4ccea4fd2e25799320d.json | 16 ---------------- ...d385c7fbbcdf51eb92b77d1542532771f52b110c.json | 16 ---------------- ...1cc83cf9ec59127763bea39a7428ec03e7ca2313.json | 2 +- ...81f7c92d486bb6069e2ae506b70d9679833c3ef4.json | 16 ---------------- ...a7fd531b842043b4a4e21b3296f0a93f1b69e153.json | 16 ++++++++++++++++ 5 files changed, 17 insertions(+), 49 deletions(-) delete mode 100644 jumble/integration/cache/llm-api-cache/4d4e13fee4f15d7637d8fbfc3c86778ba0ab463300f5c4ccea4fd2e25799320d.json delete mode 100644 jumble/integration/cache/llm-api-cache/626bfe00188f06706ffa67bdd385c7fbbcdf51eb92b77d1542532771f52b110c.json delete mode 100644 jumble/integration/cache/llm-api-cache/e115e9055568f076618b75ba81f7c92d486bb6069e2ae506b70d9679833c3ef4.json create mode 100644 jumble/integration/cache/llm-api-cache/fa94cc4941bcd6408c15eee3a7fd531b842043b4a4e21b3296f0a93f1b69e153.json diff --git a/jumble/integration/cache/llm-api-cache/4d4e13fee4f15d7637d8fbfc3c86778ba0ab463300f5c4ccea4fd2e25799320d.json b/jumble/integration/cache/llm-api-cache/4d4e13fee4f15d7637d8fbfc3c86778ba0ab463300f5c4ccea4fd2e25799320d.json deleted file mode 100644 index 4cee7faae..000000000 --- a/jumble/integration/cache/llm-api-cache/4d4e13fee4f15d7637d8fbfc3c86778ba0ab463300f5c4ccea4fd2e25799320d.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "model": "anthropic:claude-3-7-sonnet-latest", - "system": "# React Component Builder\n\nCreate an interactive React component that fulfills the user's request. Focus on delivering a clean, useful implementation with appropriate features.\n\n## You Are Part of a Two-Phase Process\n\n1. First phase (already completed):\n - Analyzed the user's request\n - Created a detailed specification\n - Generated a structured data schema\n\n2. Your job (second phase):\n - Create a reactive UI component based on the provided specification and schema\n - Implement the UI exactly according to the specification\n - Strictly adhere to the data schema provided\n\n## Required Elements\n- Define a title with `const title = 'Your App Name';`\n- Implement both `onLoad` and `onReady` functions\n- Use Tailwind CSS for styling with tasteful defaults\n- Do not write inline, use emoji for icons\n- Carefully avoid infinite loops and recursion that may cause performance issues\n\n## Code Structure\n1. React and ReactDOM are pre-imported - don't import them again\n2. All React hooks must be namespaced (e.g., `React.useState`, `React.useEffect`)\n3. Follow React hooks rules - never nest or conditionally call hooks\n4. For form handling, use `onClick` handlers instead of `onSubmit`\n\n## Available APIs\n- **useDoc(key, defaultValue)** - Persistent data storage with reactive updates\n- **llm(promptPayload)** - Send requests to the language model\n- **readWebpage(url)** - Fetch and parse external web content\n- **generateImage(prompt)** - Create AI-generated images\n\n## Important Note About useDoc\n- **useDoc is a React Hook** and must follow all React hook rules\n- It should only be used for persistent state and must draw from the provided schema\n - For any ephemeral state, use `React.useState`\n- Only call useDoc at the top level of your function components or custom hooks\n- Do not call useDoc inside loops, conditions, or nested functions\n- useDoc cannot be used outside of `onReady` components - it must be called during rendering\n\n## Library Usage\n- Request additional libraries in `onLoad` by returning an array of module names\n- Available libraries:\n - imports : [object Object]\n- Only use the explicitly provided libraries\n\n## Security Restrictions\n- Do not use browser dialog functions (`prompt()`, `alert()`, `confirm()`)\n- Avoid any methods that could compromise security or user experience\n\n\n\n{\n \"type\": \"object\",\n \"properties\": {\n \"simplevalue2\": {\n \"type\": \"object\",\n \"properties\": {\n \"values\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n }\n }\n },\n \"values\": {\n \"type\": \"array\",\n \"title\": \"Values\",\n \"description\": \"List of string values from simpleValue2\",\n \"default\": [],\n \"items\": {\n \"type\": \"object\",\n \"properties\": {\n \"index\": {\n \"type\": \"integer\",\n \"title\": \"Index\",\n \"description\": \"Position in the values array\",\n \"default\": 0\n },\n \"value\": {\n \"type\": \"string\",\n \"title\": \"Value\",\n \"description\": \"The string value at this position\"\n },\n \"displayText\": {\n \"type\": \"string\",\n \"title\": \"Display Text\",\n \"description\": \"Formatted text for display\",\n \"default\": \"\"\n }\n },\n \"required\": [\n \"index\",\n \"value\"\n ]\n }\n },\n \"isEmpty\": {\n \"type\": \"boolean\",\n \"title\": \"Is Empty\",\n \"description\": \"Whether the values array is empty\",\n \"default\": true\n }\n },\n \"title\": \"SimpleValue2 Viewer\",\n \"description\": \"A viewer to display the current values and metadata from the simpleValue2 charm.\"\n}\n\n\n\n# SDK Usage Guide\n\n## 1. `useDoc` Hook\n\nThe `useDoc` hook binds to a reactive cell given key and returns a tuple `[doc, setDoc]`:\n\nAny keys from the view-model-schema are valid for useDoc, any other keys will fail. Provide a default as the second argument, **do not set an initial value explicitly**.\n\nFor this schema:\n\n```json\n{\n \"type\": \"object\",\n \"properties\": {\n \"counter\": {\n \"type\": \"number\",\n },\n \"title\": {\n \"type\": \"string\",\n \"default\": \"My Counter App\"\n }\n }\n}\n```\n\n```jsx\nfunction CounterComponent() {\n // Correct: useDoc called at top level of component\n const [counter, setCounter] = useDoc(\"counter\", -1); // default\n\n // Incorrect: would cause errors\n // if(something) {\n // const [data, setData] = useDoc(\"data\", {}); // Never do this!\n // }\n\n const onIncrement = useCallback(() => {\n // writing to the cell automatically triggers a re-render\n setCounter(counter + 1);\n }, [counter]);\n\n return (\n \n );\n}\n```\n\n## 2. llm Function\n\n```jsx\nasync function fetchLLMResponse() {\n // place user-level requirements in system prompt\n const promptPayload = {\n system: 'Translate all the messages to emojis, reply in JSON.',\n messages: ['Hi', 'How can I help you today?', 'tell me a joke']\n };\n const result = await llm(promptPayload)\n console.log('LLM responded:', result);\n}\n```\n\n\n## 3. llm Function with JSON\n\nIf you need JSON to be returned from the LLM, you can enable the `mode: 'json'`\nin the `promptPayload`. However, if you do this, you'll need to make sure you\ndefine the schema you expect from the JSON response. Do this as plain english.\n\nFor example: \"Generate a traditional Vietnamese recipe in JSON format, with the\nfollowing properties: name (string), ingredients (array of strings),\ninstructions (array of strings)\"\n\nNOTE: You'll need to parse the result string into an object using `JSON.parse()`.\n\nANOTHER NOTE: Language model requests are globally cached based on your prompt.\nThis means that identical requests will return the same result. If your llm use\nrequires unique results on every request, make sure to introduce a cache-breaking\nstring such as a timestamp or incrementing number/id.\n\n```jsx\nconst promptPayload = {\n system: 'Translate all the messages to emojis, reply in JSON with the following properties: an array of objects, each with original_text (string), emoji_translation (string)',\n messages: ['Hi', 'How can I help you today?', 'tell me a joke'],\n mode: 'json'\n};\nconst result = await llm(promptPayload);\nconsole.log('JSON response from llm:', JSON.parse(result));\n```\n\nThis `result` variable will be a stringified JSON object. Once you JSON.parse() it, you'll get the following object:\n\n[\n {\n \"original_text\": \"Hi\",\n \"emoji_translation\": \"👋\"\n },\n {\n \"original_text\": \"How can I help you today?\",\n \"emoji_translation\": \"🤔❓🙋‍♂️📅\"\n },\n {\n \"original_text\": \"tell me a joke\",\n \"emoji_translation\": \"🗣️👉😂\"\n }\n]\n\nAnother example:\n\n```jsx\n// Every time we run this prompt, we want a unique result, so we'll use a cache-breaking string.\nconst cacheBreaker = Date.now();\n\nconst promptPayload = {\n system: \"You are a professional chef specializing in Mexican cuisine. Generate a detailed, authentic Mexican recipe in JSON format with the following properties: title (string), ingredients (array of strings), instructions (array of strings), prepTime (integer in minutes), cookTime (integer in minutes)\",\n messages :[\"give me something spicy!\" + \" \" + cacheBreaker],\n mode: \"json\",\n};\nconst result = await llm(promptPayload);\nconsole.log('JSON response from llm:', JSON.parse(result));\n```\n\nThe `result` variable will be a stringified JSON object. Once you JSON.parse() it, you'll get the following object:\n\n{\n \"title\": \"Camarones a la Diabla (Devil Shrimp)\",\n \"ingredients\": [\n \"1.5 lbs Large Shrimp, peeled and deveined\",\n \"4 tbsp Olive Oil\",\n \"1 medium White Onion, finely chopped\",\n \"4 cloves Garlic, minced\",\n \"2-3 Habanero Peppers, finely chopped (adjust to your spice preference, remove seeds for less heat)\",\n \"1 (28 oz) can Crushed Tomatoes\",\n \"1/2 cup Chicken Broth\",\n \"2 tbsp Tomato Paste\",\n \"1 tbsp Apple Cider Vinegar\",\n \"1 tbsp Dried Oregano\",\n \"1 tsp Cumin\",\n \"1/2 tsp Smoked Paprika\",\n \"1/4 tsp Ground Cloves\",\n \"Salt and Black Pepper to taste\",\n \"Fresh Cilantro, chopped, for garnish\",\n \"Lime wedges, for serving\"\n ],\n \"instructions\": [\n \"In a large bowl, toss the shrimp with salt and pepper.\",\n \"Heat the olive oil in a large skillet or Dutch oven over medium-high heat.\",\n \"Add the onion and cook until softened, about 5 minutes.\",\n \"Add the garlic and habanero peppers and cook for 1 minute more, until fragrant.\",\n \"Stir in the crushed tomatoes, chicken broth, tomato paste, apple cider vinegar, oregano, cumin, smoked paprika, and cloves.\",\n \"Bring the sauce to a simmer and cook for 15 minutes, stirring occasionally, until slightly thickened.\",\n \"Add the shrimp to the sauce and cook for 3-5 minutes, or until the shrimp are pink and cooked through.\",\n \"Taste and adjust seasoning with salt and pepper as needed.\",\n \"Garnish with fresh cilantro and serve immediately with lime wedges. Serve with rice or tortillas.\"\n ],\n \"prepTime\": 20,\n \"cookTime\": 30\n}\n\n## 4. readWebpage Function\n\n```jsx\nasync function fetchFromUrl() {\n const url = 'https://twopm.studio';\n const result = await readWebpage(url);\n console.log('Markdown:', result.content);\n}\n```\n\n## 5. generateImage Function\n\n```jsx\nfunction ImageComponent() {\n return \"Generated;\n}\n\n```\n## 6. Using the Interface Functions\n\n```javascript\n// Import from modern ESM libraries:\n// - react\n// - react-dom\n// - react-dom/client\n// - d3\n// - moment\n// - marked\n// - @react-spring/web\n// - @use-gesture/react\n// - uuid\n// - tone\nfunction onLoad() {\n return ['@react-spring/web']; // Request the modules you need\n}\n\nconst title = 'My ESM App';\nfunction ImageComponent({ url }) {\n return \"Generated;\n}\n\nfunction MyComponent({ label, description }) {\n return (\n
\n

{label}

\n

{description}

\n \n
\n );\n}\n\nfunction TodoItem({ todo, onToggle, onDelete }) {\n return (\n
\n \n \n {todo.text}\n \n \n Delete\n \n
\n );\n}\n\nfunction TodoList({ todo, setTodos}) {\n const [newTodo, setNewTodo] = React.useState('');\n\n const addTodo = () => {\n if (newTodo.trim() === '') return;\n\n const newTodoItem = {\n id: Date.now(),\n text: newTodo,\n completed: false\n };\n\n setTodos([...todos, newTodoItem]);\n setNewTodo('');\n };\n\n const toggleTodo = (id) => {\n setTodos(todos.map(todo =>\n todo.id === id ? { ...todo, completed: !todo.completed } : todo\n ));\n };\n\n const deleteTodo = (id) => {\n setTodos(todos.filter(todo => todo.id !== id));\n };\n\n return (\n
\n

Todo List

\n\n
\n setNewTodo(e.target.value)}\n placeholder=\"Add a new todo\"\n className=\"flex-grow p-2 border rounded-l\"\n />\n \n Add\n \n
\n\n
\n {todos.length > 0 ? (\n todos.map(todo => (\n toggleTodo(todo.id)}\n onDelete={() => deleteTodo(todo.id)}\n />\n ))\n ) : (\n

No todos yet!

\n )}\n
\n
\n );\n}\n\n// Main application code with modules passed as third parameter\nfunction onReady(mount, sourceData, libs) {\n const { useState, useEffect } = React; // React is available globally\n const { useSpring, animated } = libs['@react-spring/web']; // Access imported module\n\n function MyApp() {\n const [count, setCount] = useDoc('count', 0);\n const [todos, setTodos] = useDoc('todos', [\n { id: 1, text: 'Learn React', completed: false },\n { id: 2, text: 'Build a Todo App', completed: false }\n ]);\n const props = useSpring({\n from: { opacity: 0 },\n to: { opacity: 1 }\n });\n\n return (\n
\n \n setCount(count + 1)}\n >\n Clicks: {count}\n \n \n \n
\n );\n }\n\n // Use the client API for React 18\n const root = ReactDOM.createRoot(mount);\n root.render();\n}\n```\n\n", - "messages": [ - { - "role": "user", - "content": "The user asked you to create the source code with the following specification:\n```\n\nshow the data from `simpleValue2`\n\nShow the data from the `simpleValue2` charm, displaying its current value and any associated metadata.\n\n1. Fetch the simpleValue2 data from the existing schema\n2. Parse and format the values array for display\n3. Render the values in a simple list interface with index numbers\nThe SimpleValue2 Viewer will display all string values stored in the simpleValue2.values array. It will show each value as a list item with its index position. The viewer will handle empty arrays gracefully by displaying a \"No values found\" message.\n```" - }, - { - "role": "assistant", - "content": "\nfunction onLoad() {\n return []; // No additional libraries needed\n}\n\nconst title = 'SimpleValue2 Viewer';\n\nfunction onReady(mountElement, sourceData) {\n function SimpleValue2Viewer() {\n // Get the simpleValue2 data\n const [simpleValue2, setSimpleValue2] = useDoc('simpleValue2', { values: [] });\n const [values, setValues] = useDoc('values', []);\n const [isEmpty, setIsEmpty] = useDoc('isEmpty', true);\n\n // Process the data when simpleValue2 changes\n React.useEffect(() => {\n if (simpleValue2 && Array.isArray(simpleValue2.values)) {\n // Format the values for display\n const formattedValues = simpleValue2.values.map((value, index) => ({\n index,\n value,\n displayText: `${index}: ${value}`\n }));\n \n setValues(formattedValues);\n setIsEmpty(formattedValues.length === 0);\n } else {\n setValues([]);\n setIsEmpty(true);\n }\n }, [simpleValue2, setValues, setIsEmpty]);\n\n return (\n
\n

{title}

\n \n
\n

Current Values

\n \n {isEmpty ? (\n
\n

No values found

\n

The simpleValue2 array is empty

\n
\n ) : (\n
    \n {values.map((item) => (\n
  • \n \n {item.index}\n \n {item.value}\n
  • \n ))}\n
\n )}\n \n
\n

Total values: {values.length}

\n
\n
\n
\n );\n }\n\n // Render the component\n const root = ReactDOM.createRoot(mountElement);\n root.render();\n}\n```" - } - ], - "stop": "\n```", - "maxTokens": 64000 -} \ No newline at end of file diff --git a/jumble/integration/cache/llm-api-cache/626bfe00188f06706ffa67bdd385c7fbbcdf51eb92b77d1542532771f52b110c.json b/jumble/integration/cache/llm-api-cache/626bfe00188f06706ffa67bdd385c7fbbcdf51eb92b77d1542532771f52b110c.json deleted file mode 100644 index 855887c9f..000000000 --- a/jumble/integration/cache/llm-api-cache/626bfe00188f06706ffa67bdd385c7fbbcdf51eb92b77d1542532771f52b110c.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "model": "anthropic:claude-3-7-sonnet-latest", - "system": "\nYou are creating a simple MVP based on the user's goal, using an existing data schema. Focus on the simplest implementation that works with the provided schema.\n\nGiven a user's feature request and an existing data schema, you will:\n1. Create a short title (2-5 words) that names the artifact\n2. Create a one-sentence description in the format \"A to \"\n3. Create a concise specification (3-5 sentences max) that works with the existing schema\n4. Generate a brief implementation plan (3 steps max)\n5. Design a minimal JSON schema that represents the core data model\n\nYour response must be structured as follows:\n\n\n[Short title for the artifact, 2-5 words]\n\n\n\n[One-sentence description in the format \"A to \"]\n\n\n\n[Concise specification that captures only the essential requirements]\n\n\n\n[Brief 3-step implementation plan using the existing schema]\n\n\n\n[Minimal JSON Schema in valid JSON format that represents data created by the artifact]\n\n\nSCHEMA GUIDELINES:\n1. Keep it minimal:\n - Include only essential fields (5-7 properties max)\n - Focus on the core functionality\n - If user requested complex features, simplify for this first version\n\n2. Each property should have:\n - A descriptive \"title\" field\n - A brief \"description\" field\n - A sensible default value where appropriate\n\n3. Example of a simple schema:\n```json\n{\n \"type\": \"object\",\n \"title\": \"Note List\",\n \"description\": \"A simple note list for the user\",\n \"properties\": {\n \"notes\": {\n \"type\": \"array\",\n \"title\": \"Notes\",\n \"description\": \"List of user notes\",\n \"default\": [],\n \"items\": {\n \"type\": \"object\",\n \"properties\": {\n \"title\": {\n \"type\": \"string\",\n \"title\": \"Title\",\n \"description\": \"Title of the note\",\n \"default\": \"New Note\"\n },\n \"content\": {\n \"type\": \"string\",\n \"title\": \"Content\",\n \"description\": \"Content of the note\"\n },\n \"created\": {\n \"type\": \"string\",\n \"format\": \"date-time\",\n \"title\": \"Created Date\",\n \"description\": \"When the note was created\"\n }\n },\n \"required\": [\"title\", \"content\"]\n }\n }\n },\n \"required\": [\"title\", \"content\"]\n}\n```\n\nGUIDELINES:\n- Aim for the simplest possible solution that works with the existing schema\n- The specification should take into account the existing schema structure\n- Focus on what can be achieved quickly with the existing data model\n- Avoid suggesting modifications to the schema if possible\n\nIMPORTANT:\n- Focus on the simplest working version\n- Aim for fewer fields rather than more\n- But still capture all the important state the user is creating\n- The user can always iterate and improve the solution later\n", - "stream": false, - "messages": [ - { - "role": "user", - "content": "\n\nshow the data from `simpleValue2`\n\nShow the data from the `simpleValue2` charm, displaying its current value and any associated metadata.\n\n\nExisting Schema:\n```json\n{\n \"type\": \"object\",\n \"properties\": {\n \"simplevalue2\": {\n \"type\": \"object\",\n \"properties\": {\n \"values\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n }\n }\n }\n }\n}\n```\n\nBased on this goal and the existing schema, please provide a title, description, any additional schema,detailed specification, and implementation plan.\n" - }, - { - "role": "assistant", - "content": "\nSimpleValue2 Viewer\n\n\n\nA viewer to display the current values and metadata from the simpleValue2 charm.\n\n\n\nThe SimpleValue2 Viewer will display all string values stored in the simpleValue2.values array. It will show each value as a list item with its index position. The viewer will handle empty arrays gracefully by displaying a \"No values found\" message.\n\n\n\n1. Fetch the simpleValue2 data from the existing schema\n2. Parse and format the values array for display\n3. Render the values in a simple list interface with index numbers\n\n\n\n{\n \"type\": \"object\",\n \"title\": \"SimpleValue2 Viewer\",\n \"description\": \"Displays the values from the simpleValue2 charm\",\n \"properties\": {\n \"values\": {\n \"type\": \"array\",\n \"title\": \"Values\",\n \"description\": \"List of string values from simpleValue2\",\n \"default\": [],\n \"items\": {\n \"type\": \"object\",\n \"properties\": {\n \"index\": {\n \"type\": \"integer\",\n \"title\": \"Index\",\n \"description\": \"Position in the values array\",\n \"default\": 0\n },\n \"value\": {\n \"type\": \"string\",\n \"title\": \"Value\",\n \"description\": \"The string value at this position\"\n },\n \"displayText\": {\n \"type\": \"string\",\n \"title\": \"Display Text\",\n \"description\": \"Formatted text for display\",\n \"default\": \"\"\n }\n },\n \"required\": [\"index\", \"value\"]\n }\n },\n \"isEmpty\": {\n \"type\": \"boolean\",\n \"title\": \"Is Empty\",\n \"description\": \"Whether the values array is empty\",\n \"default\": true\n }\n },\n \"required\": [\"values\", \"isEmpty\"]\n}\n" - } - ], - "maxTokens": 64000 -} \ No newline at end of file diff --git a/jumble/integration/cache/llm-api-cache/b0a1a973797ea0e477e6bf4d1cc83cf9ec59127763bea39a7428ec03e7ca2313.json b/jumble/integration/cache/llm-api-cache/b0a1a973797ea0e477e6bf4d1cc83cf9ec59127763bea39a7428ec03e7ca2313.json index 682f46614..4128bb616 100644 --- a/jumble/integration/cache/llm-api-cache/b0a1a973797ea0e477e6bf4d1cc83cf9ec59127763bea39a7428ec03e7ca2313.json +++ b/jumble/integration/cache/llm-api-cache/b0a1a973797ea0e477e6bf4d1cc83cf9ec59127763bea39a7428ec03e7ca2313.json @@ -9,7 +9,7 @@ }, { "role": "assistant", - "content": "\nLet's analyze what we have:\n\n1. The user wants to display data from a `simpleValue2` charm.\n2. The existing schema shows that `simpleValue2` contains an object with a property called `values`, which is an array of strings.\n3. The user wants to see the current value and any associated metadata.\n\nFrom the schema, it seems like the main data is in the `values` array. There doesn't appear to be explicit metadata in the schema, just the array of string values.\n\nFor this MVP, I'll create a simple viewer that displays the values from the array. Since there's no explicit metadata in the schema, I'll focus on displaying the values themselves in a clean format.\n\nThe artifact will be a viewer component that renders the values from the `simpleValue2` charm.\n\n\n\nSimpleValue2 Viewer\n\n\n\nA viewer component to display data from the simpleValue2 charm.\n\n\n\nThe SimpleValue2 Viewer will display all string values stored in the simpleValue2.values array. It will render each value as a list item, showing the index and the string content. If the array is empty, it will display a message indicating no values are present.\n\n\n\n1. Access the simpleValue2 object from the existing schema\n2. Extract the values array from the simpleValue2 object\n3. Render each string in the values array as a list item with its index\n\n\n\n{\n \"type\": \"object\",\n \"title\": \"SimpleValue2 Viewer\",\n \"description\": \"Displays the values from the simpleValue2 charm\",\n \"properties\": {\n \"displayData\": {\n \"type\": \"object\",\n \"title\": \"Display Data\",\n \"description\": \"Data prepared for display\",\n \"properties\": {\n \"values\": {\n \"type\": \"array\",\n \"title\": \"Values\",\n \"description\": \"List of string values from simpleValue2\",\n \"default\": [],\n \"items\": {\n \"type\": \"object\",\n \"properties\": {\n \"index\": {\n \"type\": \"integer\",\n \"title\": \"Index\",\n \"description\": \"Position in the values array\"\n },\n \"value\": {\n \"type\": \"string\",\n \"title\": \"Value\",\n \"description\": \"String value from the array\"\n }\n },\n \"required\": [\"index\", \"value\"]\n }\n },\n \"isEmpty\": {\n \"type\": \"boolean\",\n \"title\": \"Is Empty\",\n \"description\": \"Indicates if there are no values to display\",\n \"default\": true\n }\n },\n \"required\": [\"values\", \"isEmpty\"]\n }\n },\n \"required\": [\"displayData\"]\n}\n```" + "content": "\nLet's analyze what we have:\n\n1. The user wants to display data from a `simpleValue2` charm.\n2. The existing schema shows that `simpleValue2` contains an object with a property called `values`, which is an array of strings.\n\nThe user wants a simple way to view this data. I'll create a simple viewer component that displays the current values in the array.\n\nFor the schema, I'll keep it minimal since we're just displaying existing data. The result schema will represent what the viewer component will display - the values from the array and potentially some metadata about the display.\n\n\n\nSimpleValue2 Viewer\n\n\n\nA component to display the current values stored in the simpleValue2 charm.\n\n\n\nThe viewer will display all string values currently stored in the simpleValue2.values array. Each value will be presented in a readable format with appropriate styling. The component will update automatically when the underlying data changes.\n\n\n\n1. Create a viewer component that reads from the existing simpleValue2.values array\n2. Implement the display logic to render each string value in the array\n3. Add basic styling and empty state handling when the array is empty\n\n\n\n{\n \"type\": \"object\",\n \"title\": \"SimpleValue2 Viewer\",\n \"description\": \"Display configuration for the SimpleValue2 values\",\n \"properties\": {\n \"displayMode\": {\n \"type\": \"string\",\n \"title\": \"Display Mode\",\n \"description\": \"How to display the values\",\n \"default\": \"list\",\n \"enum\": [\"list\", \"cards\", \"inline\"]\n },\n \"showCount\": {\n \"type\": \"boolean\",\n \"title\": \"Show Count\",\n \"description\": \"Whether to show the total count of values\",\n \"default\": true\n },\n \"emptyMessage\": {\n \"type\": \"string\",\n \"title\": \"Empty Message\",\n \"description\": \"Message to display when there are no values\",\n \"default\": \"No values to display\"\n },\n \"title\": {\n \"type\": \"string\",\n \"title\": \"Viewer Title\",\n \"description\": \"Title to display above the values\",\n \"default\": \"SimpleValue2 Data\"\n }\n },\n \"required\": [\"displayMode\", \"showCount\"]\n}\n```\n" } ], "maxTokens": 64000 diff --git a/jumble/integration/cache/llm-api-cache/e115e9055568f076618b75ba81f7c92d486bb6069e2ae506b70d9679833c3ef4.json b/jumble/integration/cache/llm-api-cache/e115e9055568f076618b75ba81f7c92d486bb6069e2ae506b70d9679833c3ef4.json deleted file mode 100644 index 2404d10f1..000000000 --- a/jumble/integration/cache/llm-api-cache/e115e9055568f076618b75ba81f7c92d486bb6069e2ae506b70d9679833c3ef4.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "model": "anthropic:claude-3-7-sonnet-latest", - "system": "# React Component Builder\n\nCreate an interactive React component that fulfills the user's request. Focus on delivering a clean, useful implementation with appropriate features.\n\n\n**This task is part of a 2-Phase Process.**\n\n1. First phase (already completed):\n - Analyzed the user's request\n - Created a detailed specification\n - Generated a structured data schema\n\n2. Your job (second phase):\n - Create a reactive UI component based on the provided specification and schema\n - Implement the UI exactly according to the specification\n - Strictly adhere to the data schema provided\n\n\n\n- Define a title with `const title = 'Your App Name';`\n- Implement both `onLoad` and `onReady` functions\n- Use Tailwind CSS for styling with tasteful defaults\n- Do not write inline, use emoji for icons\n- Carefully avoid infinite loops and recursion that may cause performance issues\n\n\n\n- React and ReactDOM are pre-imported - don't import them again\n- All React hooks must be namespaced (e.g., `React.useState`, `React.useEffect`)\n- Follow React hooks rules - never nest or conditionally call hooks\n- For form handling, use `onClick` handlers instead of `onSubmit`\n\n\n\n- **useReactiveCell(key, defaultValue)** - Persistent data storage with reactive updates\n- **generateText({ system, messages })** - Generate text via Large Language Model\n- **generateObject({ system, messages })** - Generate JSON object via Large Language Model\n- **readWebpage(url)** - Fetch and parse external web content\n- **generateImage(prompt)** - Create AI-generated images\n\n \n ## Important Note About useReactiveCell\n - **useReactiveCell is a React Hook** and must follow all React hook rules\n - It should only be used for persistent state and must draw from the provided schema\n - For any ephemeral state, use `React.useState`\n - Only call useReactiveCell at the top level of your function components or custom hooks\n - Do not call useReactiveCell inside loops, conditions, or nested functions\n - useReactiveCell cannot be used outside of `onReady` components - it must be called during rendering\n \n\n\n\n- Request additional libraries in `onLoad` by returning an array of module names\n- Available libraries:\n - imports : [object Object]\n- Only use the explicitly provided libraries\n\n\n\n\n- Do not use browser dialog functions (`prompt()`, `alert()`, `confirm()`)\n- Avoid any methods that could compromise security or user experience\n\n\n\n\n{\n \"type\": \"object\",\n \"properties\": {\n \"simplevalue2\": {\n \"type\": \"object\",\n \"properties\": {\n \"values\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n }\n }\n }\n },\n \"title\": \"SimpleValue2 Viewer\",\n \"description\": \"A viewer component to display data from the simpleValue2 charm.\"\n}\n\n\n\n# SDK Usage Guide\n\n## 1. `useReactiveCell` Hook\n\nThe `useReactiveCell` hook binds to a reactive cell given key and returns a tuple `[doc, setDoc]`:\n\nAny 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**.\n\nFor this schema:\n\n```json\n{\n \"type\": \"object\",\n \"properties\": {\n \"counter\": {\n \"type\": \"number\",\n },\n \"title\": {\n \"type\": \"string\",\n \"default\": \"My Counter App\"\n }\n }\n}\n```\n\n```jsx\nfunction CounterComponent() {\n // Correct: useReactiveCell called at top level of component\n const [counter, setCounter] = useReactiveCell(\"counter\", -1); // default\n\n // Incorrect: would cause errors\n // if(something) {\n // const [data, setData] = useReactiveCell(\"data\", {}); // Never do this!\n // }\n\n const onIncrement = useCallback(() => {\n // writing to the cell automatically triggers a re-render\n setCounter(counter + 1);\n }, [counter]);\n\n return (\n \n );\n}\n```\n\n## 2. `generateText` Function\n\n```jsx\nasync function fetchLLMResponse() {\n const result = await generateText({\n system: 'Translate all the messages to emojis, reply in JSON.',\n messages: ['Hi', 'How can I help you today?', 'tell me a joke']\n })\n console.log('LLM responded:', result);\n}\n```\n\n## 3. `generateObject` (JSON) Function\n\nImportant: ensure you explain the intended schema of the response in the prompt.\n\nFor example: \"Generate a traditional Vietnamese recipe in JSON format, with the\nfollowing properties: name (string), ingredients (array of strings),\ninstructions (array of strings)\"\n\n`generateObject` returns a parsed object already, or `undefined`.\n\n```jsx\nconst promptPayload = ;\nconst result = await generateObject({\n system: 'Translate all the messages to emojis, reply in JSON with the following properties: an array of objects, each with original_text (string), emoji_translation (string)',\n messages: ['Hi', 'How can I help you today?', 'tell me a joke'],\n});\nconsole.log('JSON response from llm:', result);\n\n// [\n// {\n// \"original_text\": \"Hi\",\n// \"emoji_translation\": \"👋\"\n// },\n// {\n// \"original_text\": \"How can I help you today?\",\n// \"emoji_translation\": \"🤔❓🙋‍♂️📅\"\n// },\n// {\n// \"original_text\": \"tell me a joke\",\n// \"emoji_translation\": \"🗣️👉😂\"\n// }\n// ]\n```\n\nANOTHER NOTE: Language model requests are globally cached based on your prompt.\nThis means that identical requests will return the same result. If your llm use\nrequires unique results on every request, make sure to introduce a cache-breaking\nstring such as a timestamp or incrementing number/id.\n\nAnother example:\n\n```jsx\n// To avoid the cache we'll use a cache-busting string.\nconst cacheBreaker = Date.now();\n\nconst result = await generateObject({\n system: \"You are a professional chef specializing in Mexican cuisine. Generate a detailed, authentic Mexican recipe in JSON format with the following properties: title (string), ingredients (array of strings), instructions (array of strings), prepTime (integer in minutes), cookTime (integer in minutes)\",\n messages: [\"give me something spicy!\" + \" \" + cacheBreaker],\n});\nconsole.log('JSON response from llm:', result);\n\n// {\n// \"title\": \"Camarones a la Diabla (Devil Shrimp)\",\n// \"ingredients\": [\n// \"1.5 lbs Large Shrimp, peeled and deveined\",\n// \"4 tbsp Olive Oil\",\n// \"1 medium White Onion, finely chopped\",\n// \"4 cloves Garlic, minced\",\n// \"2-3 Habanero Peppers, finely chopped (adjust to your spice preference, remove seeds for less heat)\",\n// \"1 (28 oz) can Crushed Tomatoes\",\n// \"1/2 cup Chicken Broth\",\n// \"2 tbsp Tomato Paste\",\n// \"1 tbsp Apple Cider Vinegar\",\n// \"1 tbsp Dried Oregano\",\n// \"1 tsp Cumin\",\n// \"1/2 tsp Smoked Paprika\",\n// \"1/4 tsp Ground Cloves\",\n// \"Salt and Black Pepper to taste\",\n// \"Fresh Cilantro, chopped, for garnish\",\n// \"Lime wedges, for serving\"\n// ],\n// \"instructions\": [\n// \"In a large bowl, toss the shrimp with salt and pepper.\",\n// \"Heat the olive oil in a large skillet or Dutch oven over medium-high heat.\",\n// \"Add the onion and cook until softened, about 5 minutes.\",\n// \"Add the garlic and habanero peppers and cook for 1 minute more, until fragrant.\",\n// \"Stir in the crushed tomatoes, chicken broth, tomato paste, apple cider vinegar, oregano, cumin, smoked paprika, and cloves.\",\n// \"Bring the sauce to a simmer and cook for 15 minutes, stirring occasionally, until slightly thickened.\",\n// \"Add the shrimp to the sauce and cook for 3-5 minutes, or until the shrimp are pink and cooked through.\",\n// \"Taste and adjust seasoning with salt and pepper as needed.\",\n// \"Garnish with fresh cilantro and serve immediately with lime wedges. Serve with rice or tortillas.\"\n// ],\n// \"prepTime\": 20,\n// \"cookTime\": 30\n// }\n```\n\n## 4. `readWebpage` Function\n\n```jsx\nasync function fetchFromUrl() {\n const url = 'https://twopm.studio';\n const result = await readWebpage(url);\n console.log('Markdown:', result.content);\n}\n```\n\n## 5. generateImage Function\n\n```jsx\nfunction ImageComponent() {\n return \"Generated;\n}\n\n```\n## 6. Using the Interface Functions\n\n```javascript\n// Import from modern ESM libraries:\n// - react\n// - react-dom\n// - react-dom/client\n// - d3\n// - moment\n// - marked\n// - @react-spring/web\n// - @use-gesture/react\n// - uuid\n// - tone\nfunction onLoad() {\n return ['@react-spring/web']; // Request the modules you need\n}\n\nconst title = 'My ESM App';\nfunction ImageComponent({ url }) {\n return \"Generated;\n}\n\nfunction MyComponent({ label, description }) {\n return (\n
\n

{label}

\n

{description}

\n \n
\n );\n}\n\nfunction TodoItem({ todo, onToggle, onDelete }) {\n return (\n
\n \n \n {todo.text}\n \n \n Delete\n \n
\n );\n}\n\nfunction TodoList({ todo, setTodos}) {\n const [newTodo, setNewTodo] = React.useState('');\n\n const addTodo = () => {\n if (newTodo.trim() === '') return;\n\n const newTodoItem = {\n id: Date.now(),\n text: newTodo,\n completed: false\n };\n\n setTodos([...todos, newTodoItem]);\n setNewTodo('');\n };\n\n const toggleTodo = (id) => {\n setTodos(todos.map(todo =>\n todo.id === id ? { ...todo, completed: !todo.completed } : todo\n ));\n };\n\n const deleteTodo = (id) => {\n setTodos(todos.filter(todo => todo.id !== id));\n };\n\n return (\n
\n

Todo List

\n\n
\n setNewTodo(e.target.value)}\n placeholder=\"Add a new todo\"\n className=\"flex-grow p-2 border rounded-l\"\n />\n \n Add\n \n
\n\n
\n {todos.length > 0 ? (\n todos.map(todo => (\n toggleTodo(todo.id)}\n onDelete={() => deleteTodo(todo.id)}\n />\n ))\n ) : (\n

No todos yet!

\n )}\n
\n
\n );\n}\n\n// Main application code with modules passed as third parameter\nfunction onReady(mount, sourceData, libs) {\n const { useState, useEffect } = React; // React is available globally\n const { useSpring, animated } = libs['@react-spring/web']; // Access imported module\n\n function MyApp() {\n const [count, setCount] = useReactiveCell('count', 0);\n const [todos, setTodos] = useReactiveCell('todos', [\n { id: 1, text: 'Learn React', completed: false },\n { id: 2, text: 'Build a Todo App', completed: false }\n ]);\n const props = useSpring({\n from: { opacity: 0 },\n to: { opacity: 1 }\n });\n\n return (\n
\n \n setCount(count + 1)}\n >\n Clicks: {count}\n \n \n \n
\n );\n }\n\n // Use the client API for React 18\n const root = ReactDOM.createRoot(mount);\n root.render();\n}\n```\n
\n", - "messages": [ - { - "role": "user", - "content": "The user asked you to create the source code with the following specification:\n```\n\nshow the data from `simpleValue2`\n\nShow the data from the `simpleValue2` charm, displaying its current value and any associated metadata.\n\n1. Access the simpleValue2 object from the existing schema\n2. Extract the values array from the simpleValue2 object\n3. Render each string in the values array as a list item with its index\nThe SimpleValue2 Viewer will display all string values stored in the simpleValue2.values array. It will render each value as a list item, showing the index and the string content. If the array is empty, it will display a message indicating no values are present.\n```" - }, - { - "role": "assistant", - "content": "\nfunction onLoad() {\n return []; // No additional libraries needed\n}\n\nconst title = 'SimpleValue2 Viewer';\n\nfunction onReady(mount, sourceData) {\n function SimpleValue2Viewer() {\n // Access the simpleValue2 data using useReactiveCell\n const [simpleValue2, setSimpleValue2] = useReactiveCell('simplevalue2', { values: [] });\n \n // Extract the values array from simpleValue2\n const values = simpleValue2.values || [];\n \n return (\n
\n

{title}

\n \n
\n

Values

\n \n {values.length > 0 ? (\n
    \n {values.map((value, index) => (\n
  • \n \n {index}\n \n {value}\n
  • \n ))}\n
\n ) : (\n
\n

📭 No values present in the array

\n
\n )}\n \n
\n

Total values: {values.length}

\n
\n
\n
\n );\n }\n\n // Render the component\n const root = ReactDOM.createRoot(mount);\n root.render();\n}\n```" - } - ], - "stop": "\n```", - "maxTokens": 64000 -} \ No newline at end of file diff --git a/jumble/integration/cache/llm-api-cache/fa94cc4941bcd6408c15eee3a7fd531b842043b4a4e21b3296f0a93f1b69e153.json b/jumble/integration/cache/llm-api-cache/fa94cc4941bcd6408c15eee3a7fd531b842043b4a4e21b3296f0a93f1b69e153.json new file mode 100644 index 000000000..9b9f096ee --- /dev/null +++ b/jumble/integration/cache/llm-api-cache/fa94cc4941bcd6408c15eee3a7fd531b842043b4a4e21b3296f0a93f1b69e153.json @@ -0,0 +1,16 @@ +{ + "model": "anthropic:claude-3-7-sonnet-latest", + "system": "# React Component Builder\n\nCreate an interactive React component that fulfills the user's request. Focus on delivering a clean, useful implementation with appropriate features.\n\n\n**This task is part of a 2-Phase Process.**\n\n1. First phase (already completed):\n - Analyzed the user's request\n - Created a detailed specification\n - Generated a structured data schema\n\n2. Your job (second phase):\n - Create a reactive UI component based on the provided specification and schema\n - Implement the UI exactly according to the specification\n - Strictly adhere to the data schema provided\n\n\n\n- Define a title with `const title = 'Your App Name';`\n- Implement both `onLoad` and `onReady` functions\n- Use Tailwind CSS for styling with tasteful defaults\n- Do not write inline, use emoji for icons\n- Carefully avoid infinite loops and recursion that may cause performance issues\n\n\n\n- React and ReactDOM are pre-imported - don't import them again\n- All React hooks must be namespaced (e.g., `React.useState`, `React.useEffect`)\n- Follow React hooks rules - never nest or conditionally call hooks\n- For form handling, use `onClick` handlers instead of `onSubmit`\n\n\n\n- **useReactiveCell(keyPath: string[])** - Persistent data storage with reactive updates\n- **generateText({ system, messages })** - Generate text via Large Language Model\n- **generateObject({ system, messages })** - Generate JSON object via Large Language Model\n- **readWebpage(url)** - Fetch and parse external web content\n- **generateImage(prompt)** - Create AI-generated images\n\n \n ## Important Note About useReactiveCell(keyPath: string[])\n - **useReactiveCell is a React Hook** and must follow all React hook rules\n - It should only be used for persistent state and must draw from the provided schema\n - For any ephemeral state, use `React.useState`\n - Only call useReactiveCell at the top level of your function components or custom hooks\n - Do not call useReactiveCell inside loops, conditions, or nested functions\n \n\n\n\n- Request additional libraries in `onLoad` by returning an array of module names\n- Available libraries:\n - imports : [object Object]\n- Only use the explicitly provided libraries\n\n\n\n\n- Do not use browser dialog functions (`prompt()`, `alert()`, `confirm()`)\n- Avoid any methods that could compromise security or user experience\n\n\n\n\n{\n \"type\": \"object\",\n \"properties\": {\n \"simplevalue2\": {\n \"type\": \"object\",\n \"properties\": {\n \"values\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n }\n }\n }\n },\n \"title\": \"SimpleValue2 Viewer\",\n \"description\": \"A component to display the current values stored in the simpleValue2 charm.\"\n}\n\n\n\n# SDK Usage Guide\n\n\n## 1. `useReactiveCell` Hook\n\nThe `useReactiveCell` hook binds to a reactive cell given a key path and returns a tuple `[doc, setDoc]`:\n\nAny 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**.\n\nFor this schema:\n\n```json\n{\n \"type\": \"object\",\n \"properties\": {\n \"counter\": {\n \"type\": \"number\",\n },\n \"title\": {\n \"type\": \"string\",\n \"default\": \"My Counter App\"\n }\n }\n}\n```\n\n```jsx\nfunction CounterComponent() {\n // Correct: useReactiveCell called at top level of component\n const [counter, setCounter] = useReactiveCell(\"counter\");\n const [maxValue, setMaxValue] = useReactiveCell(['settings', 'maxValue']);\n\n const onIncrement = useCallback(() => {\n // writing to the cell automatically triggers a re-render\n setCounter(counter + 1);\n }, [counter]);\n\n return (\n \n );\n}\n```\n\n\n\nSeveral APIs exist for generating text, JSON or image urls.\n\n\n`generateText({ system, messages}): Promise`\n\n```jsx\nasync function fetchLLMResponse() {\n const result = await generateText({\n system: 'Translate all the messages to emojis, reply in JSON.',\n messages: ['Hi', 'How can I help you today?', 'tell me a joke']\n })\n console.log('LLM responded:', result);\n}\n```\n\n\n\n\n`window.generateObject({ system, messages }): Promise`\n\nYou must give the exact schema of the response in the prompt.\n\nFor example: \"Generate a traditional Vietnamese recipe in JSON format, with the\nfollowing properties: name (string), ingredients (array of strings),\ninstructions (array of strings)\"\n\n`generateObject` returns a parsed object already, or `undefined`. Be defensive working with the response, the LLM may make mistakes.\n\n\n```jsx\nconst promptPayload = ;\nconst result = await generateObject({\n system: 'Translate all the messages to emojis, reply in JSON with the following properties: an array of objects, each with original_text (string), emoji_translation (string)',\n messages: ['Hi', 'How can I help you today?', 'tell me a joke'],\n});\nconsole.log('JSON response from llm:', result);\n\n// [\n// {\n// \"original_text\": \"Hi\",\n// \"emoji_translation\": \"👋\"\n// },\n// {\n// \"original_text\": \"How can I help you today?\",\n// \"emoji_translation\": \"🤔❓🙋‍♂️📅\"\n// },\n// {\n// \"original_text\": \"tell me a joke\",\n// \"emoji_translation\": \"🗣️👉😂\"\n// }\n// ]\n```\n\n\n\nNOTE: Language model requests are globally cached based on your prompt.\nThis means that identical requests will return the same result. If your llm use\nrequires unique results on every request, make sure to introduce a cache-breaking\nstring such as a timestamp or incrementing number/id.\n\n```jsx\n// To avoid the cache we'll use a cache-busting string.\nconst cacheBreaker = Date.now();\n\nconst result = await generateObject({\n system: \"You are a professional chef specializing in Mexican cuisine. Generate a detailed, authentic Mexican recipe in JSON format with the following properties: title (string), ingredients (array of strings), instructions (array of strings), prepTime (integer in minutes), cookTime (integer in minutes)\",\n messages: [\"give me something spicy!\" + \" \" + cacheBreaker],\n});\nconsole.log('JSON response from llm:', result);\n\n// {\n// \"title\": \"Camarones a la Diabla (Devil Shrimp)\",\n// \"ingredients\": [\n// \"1.5 lbs Large Shrimp, peeled and deveined\",\n// \"4 tbsp Olive Oil\",\n// \"1 medium White Onion, finely chopped\",\n// \"4 cloves Garlic, minced\",\n// \"2-3 Habanero Peppers, finely chopped (adjust to your spice preference, remove seeds for less heat)\",\n// \"1 (28 oz) can Crushed Tomatoes\",\n// \"1/2 cup Chicken Broth\",\n// \"2 tbsp Tomato Paste\",\n// \"1 tbsp Apple Cider Vinegar\",\n// \"1 tbsp Dried Oregano\",\n// \"1 tsp Cumin\",\n// \"1/2 tsp Smoked Paprika\",\n// \"1/4 tsp Ground Cloves\",\n// \"Salt and Black Pepper to taste\",\n// \"Fresh Cilantro, chopped, for garnish\",\n// \"Lime wedges, for serving\"\n// ],\n// \"instructions\": [\n// \"In a large bowl, toss the shrimp with salt and pepper.\",\n// \"Heat the olive oil in a large skillet or Dutch oven over medium-high heat.\",\n// \"Add the onion and cook until softened, about 5 minutes.\",\n// \"Add the garlic and habanero peppers and cook for 1 minute more, until fragrant.\",\n// \"Stir in the crushed tomatoes, chicken broth, tomato paste, apple cider vinegar, oregano, cumin, smoked paprika, and cloves.\",\n// \"Bring the sauce to a simmer and cook for 15 minutes, stirring occasionally, until slightly thickened.\",\n// \"Add the shrimp to the sauce and cook for 3-5 minutes, or until the shrimp are pink and cooked through.\",\n// \"Taste and adjust seasoning with salt and pepper as needed.\",\n// \"Garnish with fresh cilantro and serve immediately with lime wedges. Serve with rice or tortillas.\"\n// ],\n// \"prepTime\": 20,\n// \"cookTime\": 30\n// }\n```\n\n\n\n\n\n\n\nSynchronous, generates a URL that will load the image.\n\n```jsx\nfunction ImageComponent() {\n return \"Generated;\n}\n```\n\n\n\n\n\n`readWebpage(url: string): Promise` Returns markdown format.\n\n```jsx\nasync function fetchFromUrl() {\n const url = 'https://twopm.studio';\n const result = await readWebpage(url);\n console.log('Markdown:', result.content);\n}\n```\n\n\n\nAll generated code must follow this pattern:\n\n```javascript\n// Import from modern ESM libraries:\n// - react\n// - react-dom\n// - react-dom/client\n// - d3\n// - moment\n// - marked\n// - @react-spring/web\n// - @use-gesture/react\n// - uuid\n// - tone\nfunction onLoad() {\n return ['@react-spring/web']; // Request the modules you need\n}\n\nconst title = 'My ESM App';\nfunction ImageComponent({ url }) {\n return \"Generated;\n}\n\nfunction MyComponent({ label, description }) {\n return (\n
\n

{label}

\n

{description}

\n \n
\n );\n}\n\nfunction TodoItem({ todo, onToggle, onDelete }) {\n return (\n
\n \n \n {todo.text}\n \n \n Delete\n \n
\n );\n}\n\nfunction TodoList({ todo, setTodos}) {\n const [newTodo, setNewTodo] = React.useState('');\n\n const addTodo = () => {\n if (newTodo.trim() === '') return;\n\n const newTodoItem = {\n id: Date.now(),\n text: newTodo,\n completed: false\n };\n\n setTodos([...todos, newTodoItem]);\n setNewTodo('');\n };\n\n const toggleTodo = (id) => {\n setTodos(todos.map(todo =>\n todo.id === id ? { ...todo, completed: !todo.completed } : todo\n ));\n };\n\n const deleteTodo = (id) => {\n setTodos(todos.filter(todo => todo.id !== id));\n };\n\n return (\n
\n

Todo List

\n\n
\n setNewTodo(e.target.value)}\n placeholder=\"Add a new todo\"\n className=\"flex-grow p-2 border rounded-l\"\n />\n \n Add\n \n
\n\n
\n {todos.length > 0 ? (\n todos.map(todo => (\n toggleTodo(todo.id)}\n onDelete={() => deleteTodo(todo.id)}\n />\n ))\n ) : (\n

No todos yet!

\n )}\n
\n
\n );\n}\n\n// Main application code with modules passed as third parameter\nfunction onReady(mount, sourceData, libs) {\n const { useState, useEffect } = React; // React is available globally\n const { useSpring, animated } = libs['@react-spring/web']; // Access imported module\n\n function MyApp() {\n const [count, setCount] = useReactiveCell('count');\n const [todos, setTodos] = useReactiveCell('todos');\n\n const props = useSpring({\n from: { opacity: 0 },\n to: { opacity: 1 }\n });\n\n return (\n
\n \n setCount(count + 1)}\n >\n Clicks: {count}\n \n \n \n
\n );\n }\n\n // Use the client API for React 18\n const root = ReactDOM.createRoot(mount);\n root.render();\n}\n```\n\n", + "messages": [ + { + "role": "user", + "content": "The user asked you to create the source code with the following specification:\n```\n\nshow the data from `simpleValue2`\n\nShow the data from the `simpleValue2` charm, displaying its current value and any associated metadata.\n\n1. Create a viewer component that reads from the existing simpleValue2.values array\n2. Implement the display logic to render each string value in the array\n3. Add basic styling and empty state handling when the array is empty\nThe viewer will display all string values currently stored in the simpleValue2.values array. Each value will be presented in a readable format with appropriate styling. The component will update automatically when the underlying data changes.\n```" + }, + { + "role": "assistant", + "content": "\nfunction onLoad() {\n return []; // No additional libraries needed\n}\n\nconst title = 'SimpleValue2 Viewer';\n\nfunction ValueItem({ value, index }) {\n return (\n
\n
\n #{index + 1}\n {value}\n
\n
\n );\n}\n\nfunction EmptyState() {\n return (\n
\n 📋\n

No values found

\n

The simpleValue2.values array is currently empty.

\n
\n );\n}\n\nfunction SimpleValue2Viewer() {\n // Access the values array from the simpleValue2 object\n const [values, setValues] = useReactiveCell(['simplevalue2', 'values'], []);\n\n return (\n
\n
\n

{title}

\n

\n Displaying current values from simpleValue2\n

\n
\n \n
\n
\n

Values

\n \n {values.length} items\n \n
\n \n {values.length > 0 ? (\n
\n {values.map((value, index) => (\n \n ))}\n
\n ) : (\n \n )}\n
\n
\n );\n}\n\nfunction onReady(mount, sourceData) {\n const root = ReactDOM.createRoot(mount);\n root.render();\n}\n```" + } + ], + "stop": "\n```", + "maxTokens": 64000 +} \ No newline at end of file