/// import { Cell, computed, handler, ifElse, NAME, navigateTo, pattern, patternTool, UI, when, wish, } from "commontools"; import Chatbot from "../chatbot.tsx"; import { calculator, fetchAndRunPattern, listPatternIndex, navigateToPattern, readWebpage, searchWeb, } from "./common-tools.tsx"; import { MentionableCharm } from "./backlinks-index.tsx"; interface OmniboxFABInput { mentionable: Cell; } const toggle = handler }>((_, { value }) => { value.set(!value.get()); }); const closeFab = handler }>( (_, { fabExpanded }) => { fabExpanded.set(false); }, ); const dismissPeek = handler< any, { peekDismissedIndex: Cell; assistantMessageCount: number } >((_, { peekDismissedIndex, assistantMessageCount }) => { // Store the current assistant message count so we know which message was dismissed peekDismissedIndex.set(assistantMessageCount); }); /** Wish for a #tag or a custom query with optional linked context. Automatically navigates to the result. */ type WishToolParameters = { query: string; context?: Record }; const wishTool = pattern( ({ query, context }) => { const wishResult = wish({ query, context, }); // Navigate to wishResult.result (the actual cell), not the entire wish state object return when(wishResult.result, navigateTo(wishResult.result)); }, ); export default pattern( (_) => { 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- For patterns: listPatternIndex first\n- For existing pages/notes/content: listRecent or listMentionable to identify what they're referencing\n- Attach relevant items to conversation after instantiation/retrieval if they support ongoing tasks\n- Remove attachments when no longer relevant\n- Search web only as last resort when nothing exists in the space\n\nBe matter-of-fact. Prefer action to explanation.", tools: { searchWeb: { pattern: searchWeb, }, readWebpage: { pattern: readWebpage, }, calculator: { pattern: calculator, }, fetchAndRunPattern: patternTool(fetchAndRunPattern), listPatternIndex: patternTool(listPatternIndex), navigateTo: patternTool(navigateToPattern), wishAndNavigate: patternTool(wishTool), }, }); const fabExpanded = Cell.of(false); const showHistory = Cell.of(false); const peekDismissedIndex = Cell.of(-1); // Track which message index was dismissed // Derive assistant message count for dismiss tracking const assistantMessageCount = computed(() => { return omnibot.messages.filter((m) => m.role === "assistant").length; }); // Derive latest assistant message for peek const latestAssistantMessage = computed(() => { if (!omnibot.messages || omnibot.messages.length === 0) return null; for (let i = omnibot.messages.length - 1; i >= 0; i--) { const msg = omnibot.messages[i]; if (msg.role === "assistant") { const content = typeof msg.content === "string" ? msg.content : msg.content.map((part: any) => { if (part.type === "text") return part.text; return ""; }).join(""); return content; } } return null; }); return { [NAME]: "OmniboxFAB", messages: omnibot.messages, [UI]: ( fabExpanded.get())} variant="primary" position="bottom-right" pending={omnibot.pending} $previewMessage={latestAssistantMessage} onct-fab-backdrop-click={closeFab({ fabExpanded })} onct-fab-escape={closeFab({ fabExpanded })} onClick={toggle({ value: fabExpanded })} >
{/* Chevron at top - the "handle" for the drawer */}
showHistory.get())} loading={omnibot.pending} onct-toggle={toggle({ value: showHistory })} />
{ const show = showHistory.get(); return `flex: ${ show ? "1" : "0" }; min-height: 0; display: flex; flex-direction: column; opacity: ${ show ? "1" : "0" }; max-height: ${ show ? "480px" : "0" }; overflow: hidden; transition: opacity 300ms ease, max-height 400ms cubic-bezier(0.34, 1.56, 0.64, 1), flex 400ms cubic-bezier(0.34, 1.56, 0.64, 1); pointer-events: ${ show ? "auto" : "none" };`; })} >
{omnibot.ui.attachmentsAndTools}
{omnibot.ui.chatLog}
{ifElse( computed(() => { const show = showHistory.get(); const dismissedIdx = peekDismissedIndex.get(); return !show && latestAssistantMessage && assistantMessageCount !== dismissedIdx; }),
×
, null, )} {/* Prompt input - always at bottom */}
{omnibot.ui.promptInput}
), fabExpanded, }; }, );