Skip to content

Commit 3ee7c5f

Browse files
authored
Charmathon 2025-05-30 fixes (#1126)
* Handle direct string response from LLM as well as `.content` unwrap * Support keyPath for useReactiveCell * Fix empty string boolean test * generateImage -> generateImageUrl * Try 4.1 nano for JSON * Clarify prompt structure * Update LLM cache
1 parent 639b234 commit 3ee7c5f

9 files changed

+146
-136
lines changed

charm/src/iframe/static.ts

+52-38
Original file line numberDiff line numberDiff line change
@@ -114,21 +114,20 @@ Create an interactive React component that fulfills the user's request. Focus on
114114
</code_structure>
115115
116116
<charm_api>
117-
- **useReactiveCell(key, defaultValue)** - Persistent data storage with reactive updates
117+
- **useReactiveCell(keyPath: string[])** - Persistent data storage with reactive updates
118118
- **generateText({ system, messages })** - Generate text via Large Language Model
119119
- **generateObject({ system, messages })** - Generate JSON object via Large Language Model
120120
- **readWebpage(url)** - Fetch and parse external web content
121121
- **generateImage(prompt)** - Create AI-generated images
122122
123-
<use_doc>
124-
## Important Note About useReactiveCell
123+
<use_reactive_cell>
124+
## Important Note About useReactiveCell(keyPath: string[])
125125
- **useReactiveCell is a React Hook** and must follow all React hook rules
126126
- It should only be used for persistent state and must draw from the provided schema
127127
- For any ephemeral state, use \`React.useState\`
128128
- Only call useReactiveCell at the top level of your function components or custom hooks
129129
- Do not call useReactiveCell inside loops, conditions, or nested functions
130-
- useReactiveCell cannot be used outside of \`onReady\` components - it must be called during rendering
131-
</use_doc>
130+
</use_reactive_cell>
132131
</charm_api>
133132
134133
<importing_libraries>
@@ -149,9 +148,10 @@ ${security()}
149148
<guide>
150149
# SDK Usage Guide
151150
151+
<persistent-reactive-state>
152152
## 1. \`useReactiveCell\` Hook
153153
154-
The \`useReactiveCell\` hook binds to a reactive cell given key and returns a tuple \`[doc, setDoc]\`:
154+
The \`useReactiveCell\` hook binds to a reactive cell given a key path and returns a tuple \`[doc, setDoc]\`:
155155
156156
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**.
157157
@@ -175,12 +175,8 @@ For this schema:
175175
\`\`\`jsx
176176
function CounterComponent() {
177177
// Correct: useReactiveCell called at top level of component
178-
const [counter, setCounter] = useReactiveCell("counter", -1); // default
179-
180-
// Incorrect: would cause errors
181-
// if(something) {
182-
// const [data, setData] = useReactiveCell("data", {}); // Never do this!
183-
// }
178+
const [counter, setCounter] = useReactiveCell("counter");
179+
const [maxValue, setMaxValue] = useReactiveCell(['settings', 'maxValue']);
184180
185181
const onIncrement = useCallback(() => {
186182
// writing to the cell automatically triggers a re-render
@@ -194,8 +190,13 @@ function CounterComponent() {
194190
);
195191
}
196192
\`\`\`
193+
</persistent-reactive-state>
197194
198-
## 2. \`generateText\` Function
195+
<generating_content>
196+
Several APIs exist for generating text, JSON or image urls.
197+
198+
<text>
199+
\`generateText({ system, messages}): Promise<string>\`
199200
200201
\`\`\`jsx
201202
async function fetchLLMResponse() {
@@ -206,17 +207,21 @@ async function fetchLLMResponse() {
206207
console.log('LLM responded:', result);
207208
}
208209
\`\`\`
210+
</text>
209211
210-
## 3. \`generateObject\` (JSON) Function
212+
<json>
211213
212-
Important: ensure you explain the intended schema of the response in the prompt.
214+
\`window.generateObject({ system, messages }): Promise<object>\`
215+
216+
You must give the exact schema of the response in the prompt.
213217
214218
For example: "Generate a traditional Vietnamese recipe in JSON format, with the
215219
following properties: name (string), ingredients (array of strings),
216220
instructions (array of strings)"
217221
218-
\`generateObject\` returns a parsed object already, or \`undefined\`.
222+
\`generateObject\` returns a parsed object already, or \`undefined\`. Be defensive working with the response, the LLM may make mistakes.
219223
224+
<example>
220225
\`\`\`jsx
221226
const promptPayload = ;
222227
const result = await generateObject({
@@ -240,14 +245,14 @@ console.log('JSON response from llm:', result);
240245
// }
241246
// ]
242247
\`\`\`
248+
</example>
243249
244-
ANOTHER NOTE: Language model requests are globally cached based on your prompt.
250+
<example>
251+
NOTE: Language model requests are globally cached based on your prompt.
245252
This means that identical requests will return the same result. If your llm use
246253
requires unique results on every request, make sure to introduce a cache-breaking
247254
string such as a timestamp or incrementing number/id.
248255
249-
Another example:
250-
251256
\`\`\`jsx
252257
// To avoid the cache we'll use a cache-busting string.
253258
const cacheBreaker = Date.now();
@@ -293,26 +298,38 @@ console.log('JSON response from llm:', result);
293298
// "cookTime": 30
294299
// }
295300
\`\`\`
301+
</example>
296302
297-
## 4. \`readWebpage\` Function
303+
</json>
304+
305+
306+
<images>
307+
308+
Synchronous, generates a URL that will load the image.
298309
299310
\`\`\`jsx
300-
async function fetchFromUrl() {
301-
const url = 'https://twopm.studio';
302-
const result = await readWebpage(url);
303-
console.log('Markdown:', result.content);
311+
function ImageComponent() {
312+
return <img src={generateImageUrl("A beautiful sunset over mountains")} alt="Generated landscape" />;
304313
}
305314
\`\`\`
306315
307-
## 5. generateImage Function
316+
</images>
317+
</generating_content>
318+
319+
<fetching_content>
320+
\`readWebpage(url: string): Promise<string>\` Returns markdown format.
308321
309322
\`\`\`jsx
310-
function ImageComponent() {
311-
return <img src={generateImage("A beautiful sunset over mountains")} alt="Generated landscape" />;
323+
async function fetchFromUrl() {
324+
const url = 'https://twopm.studio';
325+
const result = await readWebpage(url);
326+
console.log('Markdown:', result.content);
312327
}
313-
314328
\`\`\`
315-
## 6. Using the Interface Functions
329+
</fetching_content>
330+
331+
<code_structure>
332+
All generated code must follow this pattern:
316333
317334
\`\`\`javascript
318335
// Import from modern ESM libraries:
@@ -428,11 +445,9 @@ function onReady(mount, sourceData, libs) {
428445
const { useSpring, animated } = libs['@react-spring/web']; // Access imported module
429446
430447
function MyApp() {
431-
const [count, setCount] = useReactiveCell('count', 0);
432-
const [todos, setTodos] = useReactiveCell('todos', [
433-
{ id: 1, text: 'Learn React', completed: false },
434-
{ id: 2, text: 'Build a Todo App', completed: false }
435-
]);
448+
const [count, setCount] = useReactiveCell('count');
449+
const [todos, setTodos] = useReactiveCell('todos');
450+
436451
const props = useSpring({
437452
from: { opacity: 0 },
438453
to: { opacity: 1 }
@@ -486,19 +501,18 @@ Create an interactive React component that fulfills the user's request. Focus on
486501
4. For form handling, use \`onClick\` handlers instead of \`onSubmit\`
487502
488503
## Available APIs
489-
- **useReactiveCell(key, defaultValue)** - Persistent data storage with reactive updates
504+
- **useReactiveCell(keyPath: string[])** - Persistent data storage with reactive updates
490505
- **generateText({ system, messages })** - Generate text via Large Language Model
491506
- **generateObject({ system, messages })** - Generate JSON object via Large Language Model
492507
- **readWebpage(url)** - Fetch and parse external web content
493508
- **generateImage(prompt)** - Create AI-generated images
494509
495-
## Important Note About useReactiveCell
510+
## Important Note About useReactiveCell(keyPath: string[])
496511
- **useReactiveCell is a React Hook** and must follow all React hook rules
497512
- It should only be used for persistent state and must draw from the provided schema
498513
- For any ephemeral state, use \`React.useState\`
499514
- Only call useReactiveCell at the top level of your function components or custom hooks
500515
- Do not call useReactiveCell inside loops, conditions, or nested functions
501-
- useReactiveCell cannot be used outside of \`onReady\` components - it must be called during rendering
502516
503517
## Library Usage
504518
- Request additional libraries in \`onLoad\` by returning an array of module names
@@ -513,7 +527,7 @@ ${security()}
513527
514528
## 1. \`useReactiveCell\` Hook
515529
516-
The \`useReactiveCell\` hook binds to a reactive cell given key and returns a tuple \`[doc, setDoc]\`:
530+
The \`useReactiveCell\` hook binds to a reactive cell given a key path and returns a tuple \`[doc, setDoc]\`:
517531
518532
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**.
519533

charm/src/iterate.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ export async function iterate(
105105
const { iframe } = getIframeRecipe(charm);
106106

107107
const prevSpec = iframe?.spec;
108-
if (!plan?.description) {
108+
if (plan?.description === undefined) {
109109
throw new Error("No specification provided");
110110
}
111111
const newSpec = plan.description;

0 commit comments

Comments
 (0)