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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .beads/issues.jsonl
Original file line number Diff line number Diff line change
@@ -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":"."}
2 changes: 1 addition & 1 deletion packages/patterns/suggestion.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<any> }>(),
});

Expand Down
107 changes: 107 additions & 0 deletions packages/patterns/todo-with-suggestion.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/// <cts-enable />
import { Cell, Default, derive, NAME, pattern, UI } from "commontools";
import { Suggestion } from "./suggestion.tsx";

interface TodoItem {
title: string;
done: Default<boolean, false>;
}

interface Input {
items: Cell<TodoItem[]>;
}

interface Output {
items: Cell<TodoItem[]>;
}

export default pattern<Input, Output>(({ 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]: (
<div style={{ display: "flex", flexDirection: "column", gap: "16px" }}>
<h2>Todo List</h2>

{/* Add new item */}
<ct-message-input
placeholder="Add a todo item..."
onct-send={(e: { detail?: { message?: string } }) => {
const title = e.detail?.message?.trim();
if (title) {
items.push({ title, done: false });
}
}}
/>

{/* Todo items with per-item suggestions */}
<div style={{ display: "flex", flexDirection: "column", gap: "12px" }}>
{items.map((item) => {
return (
<div
style={{
display: "flex",
flexDirection: "column",
gap: "4px",
padding: "8px",
border: "1px solid #e0e0e0",
borderRadius: "8px",
}}
>
<div
style={{ display: "flex", alignItems: "center", gap: "8px" }}
>
<ct-checkbox $checked={item.done}>
<span
style={item.done
? { textDecoration: "line-through", opacity: 0.6 }
: {}}
>
{item.title}
</span>
</ct-checkbox>
<ct-button
onClick={() => {
const current = items.get();
const index = current.findIndex((el) =>
Cell.equals(item, el)
);
if (index >= 0) {
items.set(current.toSpliced(index, 1));
}
}}
>
×
</ct-button>
</div>
</div>
);
})}
</div>

{/* AI Suggestion */}
<div
style={{
marginTop: "16px",
padding: "12px",
backgroundColor: "#f5f5f5",
borderRadius: "8px",
}}
>
<h3>AI Suggestion</h3>
{derive(suggestion, (s) =>
s?.cell ?? (
<span style={{ opacity: 0.6 }}>Getting suggestion...</span>
))}
</div>
</div>
),
items,
suggestion,
};
});
202 changes: 202 additions & 0 deletions packages/patterns/write-and-run.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
/// <cts-enable />
import {
Cell,
compileAndRun,
computed,
Default,
derive,
generateText,
handler,
ifElse,
NAME,
navigateTo,
pattern,
UI,
} from "commontools";

// Template for the AI to reference
const TEMPLATE = `/// <cts-enable />
import { Cell, derive, handler, Default, NAME, pattern, UI } from "commontools";

interface Input {
value: Default<number, 0>;
}

const increment = handler<unknown, { value: Cell<number> }>(
(_, { value }) => value.set(value.get() + 1)
);

const decrement = handler<unknown, { value: Cell<number> }>(
(_, { value }) => value.set(value.get() - 1)
);

export default pattern<Input>(({ value }) => {
return {
[NAME]: derive(value, (v) => \`Counter: \${v}\`),
[UI]: (
<div style={{ display: "flex", gap: "8px", alignItems: "center" }}>
<ct-button onClick={decrement({ value })}>-</ct-button>
<span>{value}</span>
<ct-button onClick={increment({ value })}>+</ct-button>
</div>
),
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: /// <cts-enable />
2. Import from "commontools": Cell, Default, derive, handler, NAME, pattern, UI, computed, ifElse
3. Use the pattern<Input>() or pattern<Input, Output>() API
4. For arrays that need mutation, use Cell<T[]> 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<string, "Create a simple counter">;
}

interface Output {
prompt: Cell<string>;
}

const updatePrompt = handler<
{ detail: { message: string } },
{ prompt: Cell<string> }
>((event, { prompt }) => {
const newPrompt = event.detail?.message?.trim();
if (newPrompt) {
prompt.set(newPrompt);
}
});

const visit = handler<unknown, { result: Cell<any> }>((_, { result }) => {
return navigateTo(result);
});

export default pattern<Input, Output>(({ 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]: (
<div
style={{
display: "flex",
flexDirection: "column",
gap: "16px",
padding: "16px",
}}
>
<h2>Write and Run</h2>
<p style={{ color: "#666" }}>
Describe a pattern and I'll generate, compile, and run it.
</p>

<ct-message-input
placeholder="Describe the pattern you want..."
onct-send={updatePrompt({ prompt })}
/>

<div
style={{
padding: "12px",
backgroundColor: "#f5f5f5",
borderRadius: "8px",
}}
>
{ifElse(
isGenerating,
<span>Generating code...</span>,
ifElse(
hasError,
<div style={{ color: "red" }}>
<b>Compile error:</b> {compiled.error}
</div>,
ifElse(
isReady,
<ct-button onClick={visit({ result: compiled.result })}>
Open Generated Pattern
</ct-button>,
<span style={{ opacity: 0.6 }}>
Enter a prompt to generate a pattern
</span>,
),
),
)}
</div>

{ifElse(
isReady,
<div>
<h3>Generated Pattern</h3>
<div
style={{
border: "1px solid #ccc",
borderRadius: "8px",
padding: "16px",
backgroundColor: "#fff",
}}
>
{compiled.result}
</div>
</div>,
<span />,
)}

{ifElse(
hasCode,
<div>
<h3>Generated Code</h3>
<ct-code-editor
value={generated.result}
language="text/x.typescript"
readonly
/>
</div>,
<span />,
)}
</div>
),
prompt,
generatedCode: generated.result,
compiledCharm: compiled.result,
error: compiled.error,
};
});
16 changes: 16 additions & 0 deletions packages/toolshed/routes/ai/llm/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down