/// import { BuiltInLLMMessage, Cell, computed, Default, fetchData, generateObject, handler, llmDialog, NAME, pattern, patternTool, Stream, UI, VNode, wish, } from "commontools"; import { type MentionableCharm } from "./system/backlinks-index.tsx"; const sendMessage = handler< { detail: { text: string; attachments: Array; mentions: Array; message: string; // Backward compatibility }; }, { addMessage: Stream; } >((event, { addMessage }) => { const { text } = event.detail; // Send the message as-is. Any markdown links like [name](/of:...) // are just text that the LLM can parse and use with addAttachment() tool. addMessage.send({ role: "user", content: [{ type: "text" as const, text }], }); }); const clearChat = handler( ( _: never, { messages, pending }: { messages: Cell>; pending: Cell; }, ) => { messages.set([]); pending.set(false); }, ); type ChatInput = { messages?: Cell, []>>; tools?: any; theme?: any; system?: string; }; type PromptAttachment = { id: string; name: string; type: "file" | "clipboard" | "mention"; data?: any; // File | Blob | string charm?: any; removable?: boolean; // Whether this attachment can be removed }; type ChatOutput = { messages: Array; pending: boolean | undefined; addMessage: Stream; clearChat: Stream; cancelGeneration: Stream; title?: string; pinnedCells: Array; tools: any; ui: { chatLog: VNode; promptInput: VNode; attachmentsAndTools: VNode; }; }; export const TitleGenerator = pattern< { model?: string; messages: Array } >(({ model, messages }) => { const previewMessage = computed(() => { if (!messages || messages.length === 0) return ""; const firstMessage = messages[0]; if (!firstMessage) return ""; return JSON.stringify(firstMessage); }); const { result } = generateObject({ system: "Generate at most a 3-word title based on the following content, respond with NOTHING but the literal title text.", prompt: previewMessage, model, schema: { type: "object", properties: { title: { type: "string", description: "The title of the chat", }, }, required: ["title"], }, }); const title = computed(() => { return result?.title || "Untitled Chat"; }); return title; }); const listMentionable = pattern< { mentionable: Array }, { result: Array<{ label: string; cell: Cell }> } >( ({ mentionable }) => { const result = mentionable.map((charm) => ({ label: charm[NAME]!, cell: charm, })); return { result }; }, ); const listRecent = pattern< { recentCharms: Array }, { result: Array<{ label: string; cell: Cell }> } >( ({ recentCharms }) => { const namesList = recentCharms.map((charm) => ({ label: charm[NAME]!, cell: charm, })); return { result: namesList }; }, ); export default pattern( ({ messages, tools, theme, system }) => { const model = Cell.of("anthropic:claude-sonnet-4-5"); const mentionable = wish("#mentionable"); const recentCharms = wish("#recent"); const assistantTools = { listMentionable: patternTool(listMentionable, { mentionable }), listRecent: patternTool(listRecent, { recentCharms }), }; // Merge static and assistant tools const mergedTools = computed(() => ({ ...tools, ...assistantTools, })); const latest = computed(() => recentCharms[0]); const latestName = computed(() => recentCharms[0]?.[NAME] ?? "latest"); const { addMessage, cancelGeneration, pending, flattenedTools, pinnedCells, } = llmDialog( { system: computed(() => { return system ?? "You are a polite but efficient assistant."; }), messages, tools: mergedTools, model, context: computed(() => ({ [latestName]: latest, })), }, ); const { result } = fetchData({ url: "/api/ai/llm/models", mode: "json", }); const items = computed(() => { if (!result) return []; const items = Object.keys(result as any).map((key) => ({ label: key, value: key, })); return items; }); const title = TitleGenerator({ model, messages }); const promptInput = ( ); const chatLog = ( ); const attachmentsAndTools = ( Clear ); return { [NAME]: title, [UI]: ( {title} {attachmentsAndTools} {chatLog} {promptInput} ), messages, pending, addMessage, clearChat: clearChat({ messages, pending, }), cancelGeneration, title, pinnedCells, tools: flattenedTools, ui: { chatLog, promptInput, attachmentsAndTools, }, }; }, );