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
87 changes: 77 additions & 10 deletions packages/patterns/chatbot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ type ChatInput = {
messages: Default<Array<BuiltInLLMMessage>, []>;
tools: any;
theme?: any;
system?: string;
};

type PromptAttachment = {
Expand Down Expand Up @@ -268,15 +269,71 @@ const listMentionable = handler<
},
);

const listRecent = handler<
{
/** A cell to store the result text */
result: Cell<string>;
},
{ recentCharms: MentionableCharm[] }
>(
(args, state) => {
try {
const namesList = state.recentCharms.map((charm) => charm[NAME]);
args.result.set(JSON.stringify(namesList));
} catch (error) {
args.result.set(`Error: ${(error as any)?.message || "<error>"}`);
}
},
);

export default recipe<ChatInput, ChatOutput>(
"Chat",
({ messages, tools, theme }) => {
({ messages, tools, theme, system }) => {
const model = cell<string>("anthropic:claude-sonnet-4-5");
const allAttachments = cell<Array<PromptAttachment>>([]);
const mentionable = schemaifyWish<MentionableCharm[]>("#mentionable");
const recentCharms = schemaifyWish<MentionableCharm[]>("#recent");

// Auto-attach the most recent charm (union with user attachments)
const attachmentsWithRecent = derive(
[allAttachments, recentCharms],
([userAttachments, recent]: [
Array<PromptAttachment>,
Array<MentionableCharm>,
]): Array<PromptAttachment> => {
const attachments = userAttachments || [];

// If there's a most recent charm, auto-inject it
if (recent && recent.length > 0) {
const mostRecent = recent[0];
const mostRecentName = mostRecent[NAME];

// Check if it's already in the attachments
const alreadyAttached = attachments.some(
(a) => a.type === "mention" && a.name === mostRecentName,
);

if (!alreadyAttached && mostRecentName) {
// Add the most recent charm to the beginning
const id = `attachment-auto-recent-${mostRecentName}`;
return [
{
id,
name: mostRecentName,
type: "mention" as const,
charm: mostRecent,
},
...attachments,
];
}
}

// Derive tools from attachments
const dynamicTools = derive(allAttachments, (attachments) => {
return attachments;
},
);

// Derive tools from attachments (including auto-attached recent charm)
const dynamicTools = derive(attachmentsWithRecent, (attachments) => {
const tools: Record<string, any> = {};

for (const attachment of attachments || []) {
Expand All @@ -296,16 +353,22 @@ export default recipe<ChatInput, ChatOutput>(
navigateToAttachment: {
description:
"Navigate to a mentionable by its ID in the attachments array.",
handler: navigateToAttachment({ allAttachments }),
handler: navigateToAttachment({
allAttachments: attachmentsWithRecent,
}),
},
listAttachments: {
description: "List all attachments in the attachments array.",
handler: listAttachments({ allAttachments }),
handler: listAttachments({ allAttachments: attachmentsWithRecent }),
},
listMentionable: {
description: "List all mentionable NAMEs in the space.",
handler: listMentionable({ mentionable }),
},
listRecent: {
description: "List all recently viewed charms in the space.",
handler: listRecent({ recentCharms }),
},
addAttachment: {
description:
"Add a new attachment to the attachments array by its mentionable NAME.",
Expand All @@ -330,8 +393,9 @@ export default recipe<ChatInput, ChatOutput>(

const { addMessage, cancelGeneration, pending, flattenedTools } = llmDialog(
{
system:
"You are a polite but efficient assistant. Think Star Trek computer - helpful and professional without unnecessary conversation. Let your actions speak for themselves.\n\nTool usage priority:\n- Search this space first: listMentionable → addAttachment to access items\n- Search externally only when clearly needed: searchWeb for current events, external information, or when nothing relevant exists in the space\n\nBe matter-of-fact. Prefer action to explanation.",
system: derive(system, (s) =>
s ??
"You are a polite but efficient assistant."),
messages,
tools: mergedTools,
model,
Expand Down Expand Up @@ -362,7 +426,10 @@ export default recipe<ChatInput, ChatOutput>(
$mentionable={mentionable}
modelItems={items}
$model={model}
onct-send={sendMessage({ addMessage, allAttachments })}
onct-send={sendMessage({
addMessage,
allAttachments: attachmentsWithRecent,
})}
onct-stop={cancelGeneration}
onct-attachment-add={addAttachment({ allAttachments })}
onct-attachment-remove={removeAttachment({ allAttachments })}
Expand All @@ -389,7 +456,7 @@ export default recipe<ChatInput, ChatOutput>(
const attachmentsAndTools = (
<ct-hstack align="center" gap="1">
<ct-attachments-bar
attachments={allAttachments}
attachments={attachmentsWithRecent}
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Oct 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Auto-attached charms rendered by the bar can no longer be removed: the bar now reads from attachmentsWithRecent, but removeAttachment still mutates only the base allAttachments cell, so deleting the auto entry is impossible and the chip reappears immediately.

Prompt for AI agents
Address the following comment on packages/patterns/chatbot.tsx at line 459:

<comment>Auto-attached charms rendered by the bar can no longer be removed: the bar now reads from attachmentsWithRecent, but removeAttachment still mutates only the base allAttachments cell, so deleting the auto entry is impossible and the chip reappears immediately.</comment>

<file context>
@@ -389,7 +456,7 @@ export default recipe&lt;ChatInput, ChatOutput&gt;(
       &lt;ct-hstack align=&quot;center&quot; gap=&quot;1&quot;&gt;
         &lt;ct-attachments-bar
-          attachments={allAttachments}
+          attachments={attachmentsWithRecent}
           removable
           onct-remove={removeAttachment({ allAttachments })}
</file context>
Fix with Cubic

removable
onct-remove={removeAttachment({ allAttachments })}
/>
Expand Down Expand Up @@ -431,7 +498,7 @@ export default recipe<ChatInput, ChatOutput>(
}),
cancelGeneration,
title,
attachments: allAttachments,
attachments: attachmentsWithRecent,
tools: flattenedTools,
ui: {
chatLog,
Expand Down
2 changes: 2 additions & 0 deletions packages/patterns/omnibox-fab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ export default recipe<OmniboxFABInput>(
"OmniboxFAB",
({ mentionable: _mentionable }) => {
const omnibot = Chatbot({
system:
"You are a polite but efficient assistant. Think Star Trek computer - helpful and professional without unnecessary conversation. Let your actions speak for themselves.\n\nTool usage priority:\n- Search this space first: listMentionable → addAttachment to access items\n- Search externally only when clearly needed: searchWeb for current events, external information, or when nothing relevant exists in the space\n\nBe matter-of-fact. Prefer action to explanation.",
messages: [],
tools: {
searchWeb: {
Expand Down