diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl index e69de29bb..c9db504a8 100644 --- a/.beads/issues.jsonl +++ b/.beads/issues.jsonl @@ -0,0 +1,4 @@ +{"id":"ct-labs-f8g","content_hash":"1b69b91461a3f1d847e8faea516eecc43f978445dd2146c1e0498cffc2466b91","title":"Integrate Suggestion pattern","description":"Add AI suggestion powered by Suggestion pattern","status":"open","priority":2,"issue_type":"task","created_at":"2025-11-21T11:25:33.60042-08:00","updated_at":"2025-11-21T11:25:33.60042-08:00","source_repo":".","dependencies":[{"issue_id":"ct-labs-f8g","depends_on_id":"ct-labs-lk6","type":"blocks","created_at":"2025-11-21T11:25:33.600965-08:00","created_by":"daemon"}]} +{"id":"ct-labs-i1z","content_hash":"e6a28322e1a86178e563e140ec0ffb70e178ce7ec7be55e94ff471cae06646cd","title":"Implement todo list base functionality","description":"Add/remove/toggle todo items with bidirectional binding","status":"open","priority":2,"issue_type":"task","created_at":"2025-11-21T11:25:33.517179-08:00","updated_at":"2025-11-21T11:25:33.517179-08:00","source_repo":".","dependencies":[{"issue_id":"ct-labs-i1z","depends_on_id":"ct-labs-lk6","type":"blocks","created_at":"2025-11-21T11:25:33.517738-08:00","created_by":"daemon"}]} +{"id":"ct-labs-l3a","content_hash":"3c78bd4e9fbc0f4a6211a62eee827e01b9b12d406c5a804aa5ec8d880cb8119e","title":"Test pattern with ct dev","description":"Verify pattern works with deno task ct dev","status":"open","priority":2,"issue_type":"task","created_at":"2025-11-21T11:25:33.682807-08:00","updated_at":"2025-11-21T11:25:33.682807-08:00","source_repo":".","dependencies":[{"issue_id":"ct-labs-l3a","depends_on_id":"ct-labs-lk6","type":"blocks","created_at":"2025-11-21T11:25:33.68338-08:00","created_by":"daemon"}]} +{"id":"ct-labs-lk6","content_hash":"99a834f63aa5afe4143a8539ad34234e981eb88826bbe12f016faa8dcd44cce4","title":"Create todo-with-suggestion pattern","description":"Create packages/patterns/todo-with-suggestion.tsx - a todo list pattern that uses the Suggestion pattern for AI-powered suggestions","status":"open","priority":1,"issue_type":"feature","created_at":"2025-11-21T11:25:24.899876-08:00","updated_at":"2025-11-21T11:25:24.899876-08:00","source_repo":"."} diff --git a/packages/patterns/suggestion.tsx b/packages/patterns/suggestion.tsx index 792384d8d..9ca406c54 100644 --- a/packages/patterns/suggestion.tsx +++ b/packages/patterns/suggestion.tsx @@ -29,7 +29,7 @@ export const Suggestion = pattern( fetchAndRunPattern: patternTool(fetchAndRunPattern), listPatternIndex: patternTool(listPatternIndex), }, - model: "anthropic:claude-sonnet-4-5", + model: "anthropic:claude-haiku-4-5", schema: toSchema<{ cell: Cell }>(), }); diff --git a/packages/patterns/todo-with-suggestion.tsx b/packages/patterns/todo-with-suggestion.tsx new file mode 100644 index 000000000..8e8f518ee --- /dev/null +++ b/packages/patterns/todo-with-suggestion.tsx @@ -0,0 +1,107 @@ +/// +import { Cell, Default, derive, NAME, pattern, UI } from "commontools"; +import { Suggestion } from "./suggestion.tsx"; + +interface TodoItem { + title: string; + done: Default; +} + +interface Input { + items: Cell; +} + +interface Output { + items: Cell; +} + +export default pattern(({ items }) => { + // AI suggestion based on current todos + const suggestion = Suggestion({ + situation: "Based on my todo list, use a pattern to help me.", + context: { items }, + }); + + return { + [NAME]: "Todo with Suggestions", + [UI]: ( +
+

Todo List

+ + {/* Add new item */} + { + const title = e.detail?.message?.trim(); + if (title) { + items.push({ title, done: false }); + } + }} + /> + + {/* Todo items with per-item suggestions */} +
+ {items.map((item) => { + return ( +
+
+ + + {item.title} + + + { + const current = items.get(); + const index = current.findIndex((el) => + Cell.equals(item, el) + ); + if (index >= 0) { + items.set(current.toSpliced(index, 1)); + } + }} + > + × + +
+
+ ); + })} +
+ + {/* AI Suggestion */} +
+

AI Suggestion

+ {derive(suggestion, (s) => + s?.cell ?? ( + Getting suggestion... + ))} +
+
+ ), + items, + suggestion, + }; +}); diff --git a/packages/patterns/write-and-run.tsx b/packages/patterns/write-and-run.tsx new file mode 100644 index 000000000..d366625ce --- /dev/null +++ b/packages/patterns/write-and-run.tsx @@ -0,0 +1,202 @@ +/// +import { + Cell, + compileAndRun, + computed, + Default, + derive, + generateText, + handler, + ifElse, + NAME, + navigateTo, + pattern, + UI, +} from "commontools"; + +// Template for the AI to reference +const TEMPLATE = `/// +import { Cell, derive, handler, Default, NAME, pattern, UI } from "commontools"; + +interface Input { + value: Default; +} + +const increment = handler }>( + (_, { value }) => value.set(value.get() + 1) +); + +const decrement = handler }>( + (_, { value }) => value.set(value.get() - 1) +); + +export default pattern(({ value }) => { + return { + [NAME]: derive(value, (v) => \`Counter: \${v}\`), + [UI]: ( +
+ - + {value} + + +
+ ), + value, + }; +});`; + +const SYSTEM_PROMPT = + `You are a CommonTools pattern generator. Given a user request, generate a complete TypeScript pattern file. + +IMPORTANT RULES: +1. Start with: /// +2. Import from "commontools": Cell, Default, derive, handler, NAME, pattern, UI, computed, ifElse +3. Use the pattern() or pattern() API +4. For arrays that need mutation, use Cell in the interface +5. Use $checked, $value for bidirectional binding on ct-checkbox, ct-input +6. Use inline handlers for simple operations, handler() for complex ones +7. Always return [NAME] and [UI] from the pattern + +TEMPLATE FOR REFERENCE: +${TEMPLATE} + +Generate ONLY the TypeScript code, no explanations or markdown.`; + +interface Input { + prompt: Default; +} + +interface Output { + prompt: Cell; +} + +const updatePrompt = handler< + { detail: { message: string } }, + { prompt: Cell } +>((event, { prompt }) => { + const newPrompt = event.detail?.message?.trim(); + if (newPrompt) { + prompt.set(newPrompt); + } +}); + +const visit = handler }>((_, { result }) => { + return navigateTo(result); +}); + +export default pattern(({ prompt }) => { + // Step 1: Generate pattern source code from prompt + const generated = generateText({ + system: SYSTEM_PROMPT, + prompt, + model: "anthropic:claude-sonnet-4-5", + }); + + const processedResult = computed(() => { + const result = generated?.result ?? ""; + // Remove wrapping ```typescript``` if it exists + return result.replace(/^```typescript\n?/, "").replace(/\n?```$/, ""); + }); + + // Step 2: Compile the generated code when ready + const compileParams = derive(processedResult, (p) => ({ + files: p ? [{ name: "/main.tsx", contents: p }] : [], + main: p ? "/main.tsx" : "", + })); + + const compiled = compileAndRun(compileParams); + + // Compute states + const isGenerating = generated.pending; + const hasCode = computed(() => !!generated.result); + const hasError = computed(() => !!compiled.error); + const isReady = computed(() => + !compiled.pending && !!compiled.result && !compiled.error + ); + + return { + [NAME]: "Write and Run", + [UI]: ( +
+

Write and Run

+

+ Describe a pattern and I'll generate, compile, and run it. +

+ + + +
+ {ifElse( + isGenerating, + Generating code..., + ifElse( + hasError, +
+ Compile error: {compiled.error} +
, + ifElse( + isReady, + + Open Generated Pattern + , + + Enter a prompt to generate a pattern + , + ), + ), + )} +
+ + {ifElse( + isReady, +
+

Generated Pattern

+
+ {compiled.result} +
+
, + , + )} + + {ifElse( + hasCode, +
+

Generated Code

+ +
, + , + )} +
+ ), + prompt, + generatedCode: generated.result, + compiledCharm: compiled.result, + error: compiled.error, + }; +}); diff --git a/packages/toolshed/routes/ai/llm/models.ts b/packages/toolshed/routes/ai/llm/models.ts index 1c9073ad4..64b0bd215 100644 --- a/packages/toolshed/routes/ai/llm/models.ts +++ b/packages/toolshed/routes/ai/llm/models.ts @@ -210,6 +210,22 @@ if (env.CTTS_AI_LLM_ANTHROPIC_API_KEY) { }, }, }); + + addModel({ + provider: anthropicProvider, + name: "anthropic:claude-haiku-4-5", + aliases: ["haiku-4-5", "haiku-4.5"], + capabilities: { + contextWindow: 200_000, + maxOutputTokens: 8192, + images: true, + prefill: true, + systemPrompt: true, + stopSequences: true, + streaming: true, + reasoning: false, + }, + }); } if (env.CTTS_AI_LLM_GROQ_API_KEY) {