diff --git a/packages/patterns/chatbot.tsx b/packages/patterns/chatbot.tsx index fdf45c9fe..6a602d6c0 100644 --- a/packages/patterns/chatbot.tsx +++ b/packages/patterns/chatbot.tsx @@ -122,6 +122,7 @@ type ChatInput = { messages: Default, []>; tools: any; theme?: any; + system?: string; }; type PromptAttachment = { @@ -268,15 +269,71 @@ const listMentionable = handler< }, ); +const listRecent = handler< + { + /** A cell to store the result text */ + result: Cell; + }, + { 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 || ""}`); + } + }, +); + export default recipe( "Chat", - ({ messages, tools, theme }) => { + ({ messages, tools, theme, system }) => { const model = cell("anthropic:claude-sonnet-4-5"); const allAttachments = cell>([]); const mentionable = schemaifyWish("#mentionable"); + const recentCharms = schemaifyWish("#recent"); + + // Auto-attach the most recent charm (union with user attachments) + const attachmentsWithRecent = derive( + [allAttachments, recentCharms], + ([userAttachments, recent]: [ + Array, + Array, + ]): Array => { + 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 = {}; for (const attachment of attachments || []) { @@ -296,16 +353,22 @@ export default recipe( 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.", @@ -330,8 +393,9 @@ export default recipe( 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, @@ -362,7 +426,10 @@ export default recipe( $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 })} @@ -389,7 +456,7 @@ export default recipe( const attachmentsAndTools = ( @@ -431,7 +498,7 @@ export default recipe( }), cancelGeneration, title, - attachments: allAttachments, + attachments: attachmentsWithRecent, tools: flattenedTools, ui: { chatLog, diff --git a/packages/patterns/omnibox-fab.tsx b/packages/patterns/omnibox-fab.tsx index 5b23a4613..366a86825 100644 --- a/packages/patterns/omnibox-fab.tsx +++ b/packages/patterns/omnibox-fab.tsx @@ -39,6 +39,8 @@ export default recipe( "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: {