{ "of:baedreiahv63wxwgaem4hzjkizl4qncfgvca7pj5cvdon7cukumfon3ioye": { "application/json": { "ba4jcbh6lzzotnm42ta6zwopiqiq5lgo7dugaeoujgagwxidvxmujvw6c": { "is": { "value": [ { "/": { "link@1": { "path": [], "id": "of:baedreihffyh4263hchjbcx4vqsyctgf6frmgaryrflbdtkezhzzyn5itde", "space": "did:key:z6MkkGMscCkDFETV5efoTSEybcVfo8muPQUp7qMa3mUGC4mF" } } }, { "/": { "link@1": { "path": [], "id": "of:baedreibl64qzbhgkvpuxbfc657ugjeyidc62hixjybt5dpci2ddkkhs26m", "space": "did:key:z6MkkGMscCkDFETV5efoTSEybcVfo8muPQUp7qMa3mUGC4mF" } } }, { "/": { "link@1": { "path": [], "id": "of:baedreidvk42ywcn6n6ucdpfg2pvi5o6p3njkk6rltyvydkkbjcco5m4wcu", "space": "did:key:z6MkkGMscCkDFETV5efoTSEybcVfo8muPQUp7qMa3mUGC4mF" } } }, { "/": { "link@1": { "path": [], "id": "of:baedreibninu5twfm72l6gs5sbaghg6gdtmn2365encfoinebrvnsmavrr4", "space": "did:key:z6MkkGMscCkDFETV5efoTSEybcVfo8muPQUp7qMa3mUGC4mF" } } }, { "/": { "link@1": { "path": [], "id": "of:baedreibyolwzasa3njbwrwexvtolf7b7x5wh5lkwkepa5jbc6paghoc5ua", "space": "did:key:z6MkkGMscCkDFETV5efoTSEybcVfo8muPQUp7qMa3mUGC4mF" } } }, { "/": { "link@1": { "path": [], "id": "of:baedreigyxxqptxd2vlfwxnhzdwdha32i4ou5onzw6ruunaqudg5u42agva", "space": "did:key:z6MkkGMscCkDFETV5efoTSEybcVfo8muPQUp7qMa3mUGC4mF" } } } ] }, "since": 90 } } }, "of:baedreihffyh4263hchjbcx4vqsyctgf6frmgaryrflbdtkezhzzyn5itde": { "application/json": { "ba4jcbpoemlwuyl4oy3nf7ns7q4jzih2nzufd7pmroo6z6qscbmmonvz4": { "is": { "source": { "/": "baedreiaonvkt2jjlnav7hj7y6aknizkoxywahej6u4zqswof2jhe6yqtue" }, "value": { "$NAME": { "$alias": { "path": [ "internal", "$NAME" ], "cell": { "/": "baedreiaonvkt2jjlnav7hj7y6aknizkoxywahej6u4zqswof2jhe6yqtue" } } }, "$UI": { "type": "vnode", "name": "ct-screen", "props": { }, "children": [ { "type": "vnode", "name": "ct-keybind", "props": { "code": "KeyN", "alt": true, "preventDefault": true, "onct-keybind": { "$alias": { "path": [ "internal", "__#4stream" ], "schema": { "type": "object", "properties": { }, "additionalProperties": false }, "rootSchema": { "type": "object", "properties": { }, "additionalProperties": false }, "cell": { "/": "baedreiaonvkt2jjlnav7hj7y6aknizkoxywahej6u4zqswof2jhe6yqtue" } } } }, "children": [] }, { "type": "vnode", "name": "ct-vstack", "props": { "gap": "4", "padding": "6" }, "children": [ { "type": "vnode", "name": "ct-hstack", "props": { "gap": "2", "align": "center" }, "children": [ { "type": "vnode", "name": "h3", "props": { }, "children": [ "Quicklaunch:" ] }, { "type": "vnode", "name": "ct-button", "props": { "onClick": { "$alias": { "path": [ "internal", "$event" ], "schema": { "type": "object", "properties": { }, "additionalProperties": false }, "rootSchema": { "type": "object", "properties": { }, "additionalProperties": false }, "cell": { "/": "baedreiaonvkt2jjlnav7hj7y6aknizkoxywahej6u4zqswof2jhe6yqtue" } } } }, "children": [ "📂 Chat List" ] }, { "type": "vnode", "name": "ct-button", "props": { "onClick": { "$alias": { "path": [ "internal", "__#5stream" ], "schema": { "type": "object", "properties": { }, "additionalProperties": false }, "rootSchema": { "type": "object", "properties": { }, "additionalProperties": false }, "cell": { "/": "baedreiaonvkt2jjlnav7hj7y6aknizkoxywahej6u4zqswof2jhe6yqtue" } } } }, "children": [ "💬 Chatbot" ] }, { "type": "vnode", "name": "ct-button", "props": { "onClick": { "$alias": { "path": [ "internal", "__#0stream" ], "schema": { "type": "object", "properties": { }, "additionalProperties": false }, "rootSchema": { "type": "object", "properties": { }, "additionalProperties": false }, "cell": { "/": "baedreiaonvkt2jjlnav7hj7y6aknizkoxywahej6u4zqswof2jhe6yqtue" } } } }, "children": [ "📝 Chatbot Outliner" ] }, { "type": "vnode", "name": "ct-button", "props": { "onClick": { "$alias": { "path": [ "internal", "__#1stream" ], "schema": { "type": "object", "properties": { }, "additionalProperties": false }, "rootSchema": { "type": "object", "properties": { }, "additionalProperties": false }, "cell": { "/": "baedreiaonvkt2jjlnav7hj7y6aknizkoxywahej6u4zqswof2jhe6yqtue" } } } }, "children": [ "📄 Note" ] } ] }, { "type": "vnode", "name": "h2", "props": { }, "children": [ "Charms (", { "$alias": { "path": [ "internal", "__#2" ], "cell": { "/": "baedreiaonvkt2jjlnav7hj7y6aknizkoxywahej6u4zqswof2jhe6yqtue" } } }, ")" ] }, { "type": "vnode", "name": "ct-table", "props": { "full-width": true, "hover": true }, "children": [ { "type": "vnode", "name": "thead", "props": { }, "children": [ { "type": "vnode", "name": "tr", "props": { }, "children": [ { "type": "vnode", "name": "th", "props": { }, "children": [ "Charm Name" ] }, { "type": "vnode", "name": "th", "props": { }, "children": [ "Actions" ] } ] } ] }, { "type": "vnode", "name": "tbody", "props": { }, "children": [ { "$alias": { "path": [ "internal", "__#3" ], "cell": { "/": "baedreiaonvkt2jjlnav7hj7y6aknizkoxywahej6u4zqswof2jhe6yqtue" } } } ] } ] } ] } ] } } }, "since": 1 } } }, "of:baedreiaonvkt2jjlnav7hj7y6aknizkoxywahej6u4zqswof2jhe6yqtue": { "application/json": { "ba4jcbzc2lvlflhxcxxwnquxf2qtz3k56un573e2cyjzc23pmr4it7fkh": { "is": { "value": { "$TYPE": "ba4jcb4prfy3g4uxr7jnm6ci6jgyeuutzoqebdsbx5isxoiejvxyfp6am", "resultRef": { "/": { "link@1": { "path": [], "id": "of:baedreihffyh4263hchjbcx4vqsyctgf6frmgaryrflbdtkezhzzyn5itde" } } }, "internal": { "$event": { "$stream": true }, "__#0stream": { "$stream": true }, "__#1stream": { "$stream": true }, "__#4stream": { "$stream": true }, "__#5stream": { "$stream": true }, "$NAME": "DefaultCharmList (6)", "__#2": 6, "__#3": { "/": { "link@1": { "path": [], "id": "of:baedreieihgsgvlmhz43c4zozooeyokvkudejkntefpglpz3vwq5odsan2e" } } } }, "spell": { "/": { "link@1": { "id": "of:baedreiad5fq3onjttfqdd6ege2gfs5u4myvuimauebcwijlfpo2kxcouvy" } } }, "argument": { "allCharms": { "/": { "link@1": { "path": [], "id": "of:baedreiahv63wxwgaem4hzjkizl4qncfgvca7pj5cvdon7cukumfon3ioye", "space": "did:key:z6MkkGMscCkDFETV5efoTSEybcVfo8muPQUp7qMa3mUGC4mF" } } } } } }, "since": 94 } } }, "of:baedreiad5fq3onjttfqdd6ege2gfs5u4myvuimauebcwijlfpo2kxcouvy": { "application/json": { "ba4jcaptvazfqxe2zletz66l6dq2judrkw3cwrdfsgpawcyxstuouphnv": { "is": { "value": { "id": "ba4jcb4prfy3g4uxr7jnm6ci6jgyeuutzoqebdsbx5isxoiejvxyfp6am", "program": { "main": "/default-app.tsx", "files": [ { "name": "/default-app.tsx", "contents": "/// \nimport {\n Cell,\n Default,\n derive,\n h,\n handler,\n NAME,\n navigateTo,\n Opaque,\n OpaqueRef,\n recipe,\n str,\n UI,\n} from \"commontools\";\n\n// Import recipes we want to be launchable from the default app.\nimport Chatbot from \"./chatbot.tsx\";\nimport ChatbotOutliner from \"./chatbot-outliner.tsx\";\nimport { type MentionableCharm } from \"./chatbot-note-composed.tsx\";\nimport { default as Note } from \"./note.tsx\";\nimport ChatList from \"./chatbot-list-view.tsx\";\n\nexport type Charm = {\n [NAME]?: string;\n [UI]?: unknown;\n [key: string]: any;\n};\n\ntype CharmsListInput = {\n allCharms: Default;\n};\n\n// Recipe returns only UI, no data outputs (only symbol properties)\ninterface CharmsListOutput {\n [key: string]: unknown;\n}\n\nconst visit = handler<\n Record,\n { charm: any }\n>((_, state) => {\n return navigateTo(state.charm);\n}, { proxy: true });\n\nconst removeCharm = handler<\n Record,\n {\n charm: any;\n allCharms: Cell;\n }\n>((_, state) => {\n const charmName = state.charm[NAME];\n const allCharmsValue = state.allCharms.get();\n const index = allCharmsValue.findIndex((c: any) => c[NAME] === charmName);\n\n if (index !== -1) {\n const charmListCopy = [...allCharmsValue];\n console.log(\"charmListCopy before\", charmListCopy);\n charmListCopy.splice(index, 1);\n console.log(\"charmListCopy after\", charmListCopy);\n state.allCharms.set(charmListCopy);\n }\n});\n\nconst spawnChatList = handler<\n Record,\n { allCharms: Cell }\n>((_, state) => {\n return navigateTo(ChatList({\n selectedCharm: { charm: undefined },\n charmsList: [],\n allCharms: state.allCharms, // we should handle empty here\n }));\n});\n\nconst spawnChatbot = handler<\n Record,\n Record\n>((_, state) => {\n return navigateTo(Chatbot({\n messages: [],\n tools: undefined,\n }));\n});\n\nconst spawnChatbotOutliner = handler<\n Record,\n { allCharms: Cell }\n>((_, state) => {\n return navigateTo(ChatbotOutliner({\n title: \"Chatbot Outliner\",\n expandChat: false,\n messages: [],\n outline: {\n root: { body: \"\", children: [], attachments: [] },\n },\n allCharms: state.allCharms,\n }));\n});\n\nconst spawnNote = handler<\n Record,\n { allCharms: Cell }\n>((_, state) => {\n return navigateTo(Note({\n title: \"New Note\",\n content: \"\",\n allCharms: state.allCharms,\n }));\n});\n\nexport default recipe(\n \"DefaultCharmList\",\n ({ allCharms }) => {\n return {\n [NAME]: str`DefaultCharmList (${allCharms.length})`,\n [UI]: (\n \n ,\n })}\n />\n\n \n {/* Quick Launch Toolbar */}\n \n Quicklaunch:\n ,\n })}\n >\n 📂 Chat List\n \n \n 💬 Chatbot\n \n \n 📝 Chatbot Outliner\n \n ,\n })}\n >\n 📄 Note\n \n \n\n Charms ({allCharms.length})\n\n \n \n \n Charm Name\n Actions\n \n \n \n {derive(allCharms, (allCharms) =>\n allCharms.map((charm: any) => (\n \n {charm[NAME] || \"Untitled Charm\"}\n \n \n \n Visit\n \n \n Remove\n \n \n \n \n )))}\n \n \n \n \n ),\n };\n },\n);\n" }, { "name": "/chatbot.tsx", "contents": "/// \nimport {\n BuiltInLLMMessage,\n Cell,\n cell,\n Default,\n derive,\n fetchData,\n generateObject,\n h,\n handler,\n ifElse,\n JSONSchema,\n lift,\n llm,\n llmDialog,\n NAME,\n recipe,\n str,\n Stream,\n UI,\n} from \"commontools\";\n\nconst sendMessage = handler<\n { detail: { message: string } },\n {\n addMessage: Stream;\n }\n>((event, { addMessage }) => {\n addMessage.send({\n role: \"user\",\n content: [{ type: \"text\", text: event.detail.message }],\n });\n});\n\nconst clearChat = handler(\n (\n _: never,\n { messages, pending }: {\n messages: Cell>;\n pending: Cell;\n },\n ) => {\n messages.set([]);\n pending.set(false);\n },\n);\n\ntype ChatInput = {\n messages: Default, []>;\n tools: any;\n theme?: any;\n};\n\ntype ChatOutput = {\n messages: Array;\n pending: boolean | undefined;\n addMessage: Stream;\n cancelGeneration: Stream;\n title?: string;\n};\n\nexport const TitleGenerator = recipe<\n { model?: string; messages: Array }\n>(\"Title Generator\", ({ model, messages }) => {\n const titleMessages = derive(messages, (m) => {\n if (!m || m.length === 0) return \"\";\n\n const messageCount = 2;\n const selectedMessages = m.slice(0, messageCount).filter(Boolean);\n\n if (selectedMessages.length === 0) return \"\";\n\n return selectedMessages.map((msg) => JSON.stringify(msg)).join(\"\\n\");\n });\n\n const { result } = generateObject({\n system:\n \"Generate at most a 3-word title based on the following content, respond with NOTHING but the literal title text.\",\n prompt: titleMessages,\n model,\n schema: {\n type: \"object\",\n properties: {\n title: {\n type: \"string\",\n description: \"The title of the chat\",\n },\n },\n required: [\"title\"],\n },\n });\n\n const title = derive(result, (t) => {\n return t?.title || \"Untitled Chat\";\n });\n\n return title;\n});\n\nexport default recipe(\n \"Chat\",\n ({ messages, tools, theme }) => {\n const model = cell(\"anthropic:claude-sonnet-4-5\");\n\n const { addMessage, cancelGeneration, pending } = llmDialog({\n system: \"You are a helpful assistant with some tools.\",\n messages,\n tools,\n model,\n });\n\n const { result } = fetchData({\n url: \"/api/ai/llm/models\",\n mode: \"json\",\n });\n\n const items = derive(result, (models) => {\n if (!models) return [];\n const items = Object.keys(models as any).map((key) => ({\n label: key,\n value: key,\n }));\n return items;\n });\n\n const title = TitleGenerator({ model, messages });\n\n return {\n [NAME]: title,\n [UI]: (\n \n \n {title}\n \n \n\n \n \n \n\n \n \n \n \n \n ),\n messages,\n pending,\n addMessage,\n cancelGeneration,\n title,\n };\n },\n);\n" }, { "name": "/chatbot-outliner.tsx", "contents": "/// \nimport {\n BuiltInLLMMessage,\n Cell,\n cell,\n Default,\n derive,\n fetchData,\n getRecipeEnvironment,\n h,\n handler,\n ID,\n ifElse,\n JSONSchema,\n lift,\n llm,\n llmDialog,\n NAME,\n navigateTo,\n OpaqueRef,\n recipe,\n str,\n Stream,\n UI,\n} from \"commontools\";\n\nimport Chat from \"./chatbot.tsx\";\n\ntype Charm = any;\n\ntype OutlinerNode = {\n body: Default;\n children: Default;\n attachments: Default[], []>;\n};\n\ntype Outliner = {\n root: OutlinerNode;\n};\n\ntype PageResult = {\n outline: Default<\n Outliner,\n { root: { body: \"\"; children: []; attachments: [] } }\n >;\n};\n\nexport type PageInput = {\n outline: Outliner;\n allCharms: Cell;\n};\n\nconst handleCharmLinkClick = handler<\n {\n detail: {\n charm: Cell;\n };\n },\n Record\n>(({ detail }, _) => {\n return navigateTo(detail.charm);\n});\n\nexport const Page = recipe(\n \"Page\",\n ({ outline, allCharms }) => {\n return {\n [NAME]: \"Page\",\n [UI]: (\n \n ),\n outline,\n };\n },\n);\n\ntype LLMTestInput = {\n title: Default;\n messages: Default, []>;\n expandChat: Default;\n outline: Default<\n Outliner,\n { root: { body: \"Untitled Page\"; children: []; attachments: [] } }\n >;\n allCharms: Cell;\n};\n\ntype LLMTestResult = {\n messages: Default, []>;\n};\n\n// put a node at the end of the outline (by appending to root.children)\nconst appendOutlinerNode = handler<\n {\n /** The text content/title of the outliner node to be appended */\n body: string;\n /** A cell to store the result message indicating success or error */\n result: Cell;\n },\n { outline: Cell }\n>(\n (args, state) => {\n try {\n (state.outline.key(\"root\").key(\"children\")).push({\n body: args.body,\n children: [],\n attachments: [],\n });\n\n args.result.set(\n `${state.outline.key(\"root\").key(\"children\").get().length} nodes`,\n );\n } catch (error) {\n args.result.set(`Error: ${(error as any)?.message || \"\"}`);\n }\n },\n);\n\nexport default recipe(\n \"Outliner\",\n ({ title, expandChat, messages, outline, allCharms }) => {\n const tools = {\n appendOutlinerNode: {\n description: \"Add a new outliner node.\",\n inputSchema: {\n type: \"object\",\n properties: {\n body: {\n type: \"string\",\n description: \"The title of the new node.\",\n },\n },\n required: [\"body\"],\n } as JSONSchema,\n handler: appendOutlinerNode({ outline }),\n },\n };\n\n const chat = Chat({ messages, tools });\n const { addMessage, cancelGeneration, pending } = chat;\n\n return {\n [NAME]: title,\n [UI]: (\n \n \n \n \n Show Chat\n \n \n\n \n \n \n \n \n\n \n \n \n \n \n \n\n {ifElse(\n expandChat,\n chat,\n null,\n )}\n \n \n ),\n messages,\n };\n },\n);\n" }, { "name": "/chatbot-note-composed.tsx", "contents": "/// \nimport {\n BuiltInLLMMessage,\n Cell,\n cell,\n Default,\n derive,\n fetchData,\n getRecipeEnvironment,\n h,\n handler,\n ID,\n ifElse,\n JSONSchema,\n lift,\n llm,\n llmDialog,\n NAME,\n navigateTo,\n OpaqueRef,\n recipe,\n str,\n Stream,\n toSchema,\n UI,\n} from \"commontools\";\n\nimport Chat from \"./chatbot.tsx\";\nimport Note from \"./note.tsx\";\nimport Tools, {\n addListItem,\n calculator,\n ListItem,\n readListItems,\n readWebpage,\n searchWeb,\n} from \"./common-tools.tsx\";\n\nexport type MentionableCharm = {\n [NAME]: string;\n content?: string;\n mentioned?: MentionableCharm[];\n};\n\n// export type ChatbotNoteInput = {\n// content: Default;\n// allCharms?: Cell;\n// };\n\nconst handleCharmLinkClick = handler<\n {\n detail: {\n charm: Cell;\n };\n },\n Record\n>(({ detail }, _) => {\n return navigateTo(detail.charm);\n});\n\nconst handleCharmLinkClicked = handler(\n (_: any, { charm }: { charm: Cell }) => {\n return navigateTo(charm);\n },\n);\n\ntype ChatbotNoteInput = {\n title: Default;\n messages: Default, []>;\n content: Default;\n allCharms: Cell;\n};\n\ntype ChatbotNoteResult = {\n messages: Default, []>;\n mentioned: Default, []>;\n backlinks: Default, []>;\n content: Default;\n note: any;\n chat: any;\n list: Default;\n};\n\nconst newNote = handler<\n {\n /** The text content of the note */\n title: string;\n content?: string;\n /** A cell to store the result message indicating success or error */\n result: Cell;\n },\n { allCharms: Cell }\n>(\n (args, state) => {\n try {\n const n = Note({\n title: args.title,\n content: args.content || \"\",\n allCharms: state.allCharms,\n });\n\n args.result.set(\n `Created note ${args.title}!`,\n );\n\n state.allCharms.push(n as unknown as MentionableCharm);\n } catch (error) {\n args.result.set(`Error: ${(error as any)?.message || \"\"}`);\n }\n },\n);\n\n// put a note at the end of the outline (by appending to root.children)\nconst editNote = handler<\n {\n /** The text content of the note */\n body: string;\n /** A cell to store the result message indicating success or error */\n result: Cell;\n },\n { content: Cell }\n>(\n (args, state) => {\n try {\n state.content.set(args.body);\n\n args.result.set(\n `Updated note!`,\n );\n } catch (error) {\n args.result.set(`Error: ${(error as any)?.message || \"\"}`);\n }\n },\n);\n\nconst readNote = handler<\n {\n /** A cell to store the result text */\n result: Cell;\n },\n { content: string }\n>(\n (args, state) => {\n try {\n args.result.set(state.content);\n } catch (error) {\n args.result.set(`Error: ${(error as any)?.message || \"\"}`);\n }\n },\n);\n\nconst listMentionable = handler<\n {\n /** A cell to store the result text */\n result: Cell;\n },\n { allCharms: { [NAME]: string }[] }\n>(\n (args, state) => {\n try {\n const namesList = state.allCharms.map((charm) => charm[NAME]);\n args.result.set(JSON.stringify(namesList));\n } catch (error) {\n args.result.set(`Error: ${(error as any)?.message || \"\"}`);\n }\n },\n);\n\nconst readNoteByIndex = handler<\n {\n /** A cell to store the result text */\n index: number;\n result: Cell;\n },\n { allCharms: { [NAME]: string; content?: string }[] }\n>(\n (args, state) => {\n try {\n args.result.set(\n state.allCharms[args.index]?.content || \"No content found\",\n );\n } catch (error) {\n args.result.set(`Error: ${(error as any)?.message || \"\"}`);\n }\n },\n);\n\nconst editNoteByIndex = handler<\n {\n /** The index of the note to edit */\n index: number;\n /** The new text content of the note */\n body: string;\n /** A cell to store the result message indicating success or error */\n result: Cell;\n },\n { allCharms: Cell }\n>(\n (args, state) => {\n try {\n const charms = state.allCharms.get();\n if (args.index < 0 || args.index >= charms.length) {\n args.result.set(`Error: Invalid index ${args.index}`);\n return;\n }\n\n state.allCharms.key(args.index).key(\"content\").set(args.body);\n args.result.set(`Updated note at index ${args.index}!`);\n } catch (error) {\n args.result.set(`Error: ${(error as any)?.message || \"\"}`);\n }\n },\n);\n\nconst navigateToNote = handler<\n {\n /** The index of the note to navigate to */\n index: number;\n /** A cell to store the result message indicating success or error */\n result: Cell;\n },\n { allCharms: Cell }\n>(\n (args, state) => {\n try {\n const charms = state.allCharms.get();\n if (args.index < 0 || args.index >= charms.length) {\n args.result.set(`Error: Invalid index ${args.index}`);\n return;\n }\n\n const targetCharm = charms[args.index];\n args.result.set(`Navigating to note: ${targetCharm[NAME]}`);\n\n return navigateTo(state.allCharms.key(args.index));\n } catch (error) {\n args.result.set(`Error: ${(error as any)?.message || \"\"}`);\n }\n },\n);\n\nexport default recipe(\n \"Chatbot + Note\",\n ({ title, messages, content, allCharms }) => {\n const list = cell([]);\n\n const tools = {\n searchWeb: {\n pattern: searchWeb,\n },\n readWebpage: {\n pattern: readWebpage,\n },\n calculator: {\n pattern: calculator,\n },\n addListItem: {\n handler: addListItem({ list }),\n },\n readListItems: {\n handler: readListItems({ list }),\n },\n editActiveNote: {\n description: \"Modify the shared note.\",\n inputSchema: {\n type: \"object\",\n properties: {\n body: {\n type: \"string\",\n description: \"The content of the note.\",\n },\n },\n required: [\"body\"],\n } as JSONSchema,\n handler: editNote({ content }),\n },\n readActiveNote: {\n description: \"Read the currently focused note.\",\n inputSchema: {\n type: \"object\",\n properties: {},\n required: [],\n } as JSONSchema,\n handler: readNote({ content }),\n },\n listNotes: {\n description:\n \"List all mentionable note titles (read the body with readNoteByIndex).\",\n inputSchema: {\n type: \"object\",\n properties: {},\n required: [],\n } as JSONSchema,\n handler: listMentionable({\n allCharms: allCharms as unknown as OpaqueRef,\n }),\n },\n readNoteByIndex: {\n description:\n \"Read the body of a note by its index in the listNotes() list.\",\n inputSchema: {\n type: \"object\",\n properties: {\n index: {\n type: \"number\",\n description: \"The index of the note in the notes list.\",\n },\n },\n required: [\"index\"],\n } as JSONSchema,\n handler: readNoteByIndex({\n allCharms: allCharms as unknown as OpaqueRef,\n }),\n },\n editNoteByIndex: {\n description:\n \"Edit the body of a note by its index in the listNotes() list.\",\n inputSchema: {\n type: \"object\",\n properties: {\n index: {\n type: \"number\",\n description: \"The index of the note in the notes list.\",\n },\n body: {\n type: \"string\",\n description: \"The new content of the note.\",\n },\n },\n required: [\"index\", \"body\"],\n } as JSONSchema,\n handler: editNoteByIndex({\n allCharms: allCharms as unknown as OpaqueRef,\n }),\n },\n navigateToNote: {\n description: \"Navigate to a note by its index in the listNotes() list.\",\n inputSchema: {\n type: \"object\",\n properties: {\n index: {\n type: \"number\",\n description: \"The index of the note in the notes list.\",\n },\n },\n required: [\"index\"],\n } as JSONSchema,\n handler: navigateToNote({\n allCharms: allCharms as unknown as OpaqueRef,\n }),\n },\n newNote: {\n description: \"Read the shared note.\",\n inputSchema: {\n type: \"object\",\n properties: {\n title: {\n type: \"string\",\n description: \"The title of the note.\",\n },\n content: {\n type: \"string\",\n description: \"The content of the note.\",\n },\n },\n required: [\"title\"],\n } as JSONSchema,\n handler: newNote({\n allCharms: allCharms as unknown as OpaqueRef,\n }),\n },\n };\n\n const chat = Chat({ messages, tools });\n const note = Note({ title, content, allCharms });\n\n return {\n [NAME]: title,\n chat,\n note,\n content,\n messages,\n mentioned: note.mentioned,\n backlinks: note.backlinks,\n list,\n };\n },\n);\n" }, { "name": "/note.tsx", "contents": "/// \nimport {\n Cell,\n cell,\n Default,\n derive,\n h,\n handler,\n lift,\n NAME,\n navigateTo,\n OpaqueRef,\n recipe,\n toSchema,\n UI,\n} from \"commontools\";\n\nexport type MentionableCharm = {\n [NAME]: string;\n content?: string;\n mentioned?: MentionableCharm[];\n};\n\ntype Input = {\n title: Default;\n content: Default;\n allCharms: Cell;\n};\n\ntype Output = {\n mentioned: Default, []>;\n content: Default;\n backlinks: Default, []>;\n};\n\nconst updateTitle = handler<\n { detail: { value: string } },\n { title: Cell }\n>(\n (event, state) => {\n state.title.set(event.detail?.value ?? \"\");\n },\n);\n\nconst updateContent = handler<\n { detail: { value: string } },\n { content: Cell }\n>(\n (event, state) => {\n state.content.set(event.detail?.value ?? \"\");\n },\n);\n\nconst handleCharmLinkClick = handler<\n {\n detail: {\n charm: Cell;\n };\n },\n Record\n>(({ detail }, _) => {\n return navigateTo(detail.charm);\n});\n\nconst handleNewBacklink = handler<\n {\n detail: {\n text: string;\n charmId: any;\n charm: Cell;\n navigate: boolean;\n };\n },\n {\n allCharms: Cell;\n }\n>(({ detail }, { allCharms }) => {\n console.log(\"new charm\", detail.text, detail.charmId);\n\n if (detail.navigate) {\n return navigateTo(detail.charm);\n } else {\n allCharms.push(detail.charm as unknown as MentionableCharm);\n }\n});\n\nconst handleCharmLinkClicked = handler(\n (_: any, { charm }: { charm: Cell }) => {\n return navigateTo(charm);\n },\n);\n\nconst Note = recipe(\n \"Note\",\n ({ title, content, allCharms }) => {\n const mentioned = cell([]);\n\n const computeBacklinks = lift<\n { allCharms: Cell; content: Cell },\n MentionableCharm[]\n >(\n ({ allCharms, content }) => {\n const cs = allCharms.get();\n if (!cs) return [];\n\n const self = cs.find((c) => c.content === content.get());\n\n const results = self\n ? cs.filter((c) =>\n c.mentioned?.some((m) => m.content === self.content) ?? false\n )\n : [];\n\n return results;\n },\n );\n\n const backlinks: OpaqueRef = computeBacklinks({\n allCharms,\n content: content as unknown as Cell, // TODO(bf): this is valid, but types complain\n });\n\n // The only way to serialize a pattern, apparently?\n const pattern = derive(undefined, () => JSON.stringify(Note));\n\n return {\n [NAME]: title,\n [UI]: (\n \n \n \n \n\n \n \n ),\n title,\n content,\n mentioned,\n backlinks,\n };\n },\n);\n\nexport default Note;\n" }, { "name": "/chatbot-list-view.tsx", "contents": "/// \nimport {\n Cell,\n cell,\n Default,\n derive,\n h,\n handler,\n ID,\n ifElse,\n lift,\n NAME,\n navigateTo,\n OpaqueRef,\n recipe,\n toSchema,\n UI,\n} from \"commontools\";\n\nimport Chat from \"./chatbot-note-composed.tsx\";\nimport { ListItem } from \"./common-tools.tsx\";\n\nexport type MentionableCharm = {\n [NAME]: string;\n content?: string;\n mentioned?: MentionableCharm[];\n};\n\ntype CharmEntry = {\n [ID]: string; // randomId is a string\n local_id: string; // same as ID but easier to access\n charm: any;\n};\n\ntype Input = {\n selectedCharm: Default<{ charm: any }, { charm: undefined }>;\n charmsList: Default;\n allCharms: Cell;\n theme?: {\n accentColor: Default;\n fontFace: Default;\n borderRadius: Default;\n };\n};\n\ntype Output = {\n selectedCharm: Default<{ charm: any }, { charm: undefined }>;\n};\n\nconst removeChat = handler<\n unknown,\n {\n charmsList: Cell;\n id: string;\n selectedCharm: Cell>;\n }\n>(\n (\n _,\n { charmsList, id, selectedCharm },\n ) => {\n const list = charmsList.get();\n const index = list.findIndex((entry) => entry.local_id === id);\n if (index === -1) return;\n\n const removed = list[index];\n const next = [...list];\n next.splice(index, 1);\n charmsList.set(next);\n\n // If we removed the currently selected charm, choose a new selection.\n const current = selectedCharm.get();\n if (current?.charm === removed.charm) {\n const replacement = next[index] ?? next[index - 1];\n if (replacement) {\n selectedCharm.set({ charm: replacement.charm });\n } else {\n selectedCharm.set({ charm: undefined as unknown as any });\n }\n }\n },\n);\n\n// this will be called whenever charm or selectedCharm changes\n// pass isInitialized to make sure we dont call this each time\n// we change selectedCharm, otherwise creates a loop\nconst storeCharm = lift(\n toSchema<{\n charm: any;\n selectedCharm: Cell>;\n charmsList: Cell;\n allCharms: Cell;\n theme?: {\n accentColor: Default;\n fontFace: Default;\n borderRadius: Default;\n };\n isInitialized: Cell;\n }>(),\n undefined,\n ({ charm, selectedCharm, charmsList, isInitialized, allCharms }) => { // Not including `allCharms` is a compile error...\n if (!isInitialized.get()) {\n console.log(\n \"storeCharm storing charm:\",\n charm,\n );\n selectedCharm.set({ charm });\n\n // create the chat charm with a custom name including a random suffix\n const randomId = Math.random().toString(36).substring(2, 10); // Random 8-char string\n charmsList.push({ [ID]: randomId, local_id: randomId, charm });\n\n isInitialized.set(true);\n return charm;\n } else {\n console.log(\"storeCharm: already initialized\");\n }\n return undefined;\n },\n);\n\nconst populateChatList = lift(\n toSchema<{\n charmsList: CharmEntry[];\n allCharms: Cell;\n selectedCharm: Cell<{ charm: any }>;\n }>(),\n undefined,\n (\n { charmsList, allCharms, selectedCharm },\n ) => {\n if (charmsList.length === 0) {\n const isInitialized = cell(false);\n return storeCharm({\n charm: Chat({\n title: \"New Chat\",\n messages: [],\n content: \"\",\n allCharms,\n }),\n selectedCharm,\n charmsList,\n allCharms,\n isInitialized: isInitialized as unknown as Cell,\n });\n }\n\n return charmsList;\n },\n);\n\nconst createChatRecipe = handler<\n unknown,\n {\n selectedCharm: Cell<{ charm: any }>;\n charmsList: Cell;\n allCharms: Cell;\n }\n>(\n (_, { selectedCharm, charmsList, allCharms }) => {\n const isInitialized = cell(false);\n\n const charm = Chat({\n title: \"New Chat\",\n messages: [],\n content: \"\",\n allCharms,\n });\n // store the charm ref in a cell (pass isInitialized to prevent recursive calls)\n return storeCharm({\n charm,\n selectedCharm,\n charmsList: charmsList as unknown as OpaqueRef,\n allCharms,\n isInitialized: isInitialized as unknown as Cell,\n });\n },\n);\n\nconst selectCharm = handler<\n unknown,\n { selectedCharm: Cell<{ charm: any }>; charm: any }\n>(\n (_, { selectedCharm, charm }) => {\n console.log(\"selectCharm: updating selectedCharm to \", charm);\n selectedCharm.set({ charm });\n return selectedCharm;\n },\n);\n\nconst logCharmsList = lift<\n { charmsList: Cell },\n Cell\n>(\n ({ charmsList }) => {\n console.log(\"logCharmsList: \", charmsList.get());\n return charmsList;\n },\n);\n\nconst handleCharmLinkClicked = handler(\n (_: any, { charm }: { charm: Cell }) => {\n return navigateTo(charm);\n },\n);\n\nconst combineLists = lift(\n (\n { allCharms, charmsList }: { allCharms: any[]; charmsList: CharmEntry[] },\n ) => {\n return [...charmsList.map((c) => c.charm), ...allCharms];\n },\n);\n\nconst getSelectedCharm = lift<\n { entry: { charm: any | undefined } },\n {\n chat: unknown;\n note: unknown;\n list: ListItem[];\n backlinks: MentionableCharm[];\n mentioned: MentionableCharm[];\n } | undefined\n>(\n ({ entry }) => {\n return entry?.charm;\n },\n);\n\nconst getCharmName = lift(({ charm }: { charm: any }) => {\n return charm?.[NAME] || \"Unknown\";\n});\n\n// create the named cell inside the recipe body, so we do it just once\nexport default recipe(\n \"Launcher\",\n ({ selectedCharm, charmsList, allCharms, theme }) => {\n logCharmsList({ charmsList: charmsList as unknown as Cell });\n\n populateChatList({\n selectedCharm: selectedCharm as unknown as Cell<\n Pick\n >,\n charmsList,\n allCharms,\n });\n\n const combined = combineLists({\n allCharms: allCharms as unknown as any[],\n charmsList,\n });\n\n const selected = getSelectedCharm({ entry: selectedCharm });\n\n const localTheme = theme ?? {\n accentColor: cell(\"#3b82f6\"),\n fontFace: cell(\"system-ui, -apple-system, sans-serif\"),\n borderRadius: cell(\"0.5rem\"),\n };\n\n return {\n [NAME]: \"Launcher\",\n [UI]: (\n \n \n \n \n \n \n Create New Chat\n alt+N\n \n \n \n\n {/* Keyboard shortcuts */}\n \n \n \n {/* workaround: this seems to correctly start the sub-recipes on a refresh while directly rendering does not */}\n {/* this should be fixed after the builder-refactor (DX1) */}\n \n \n \n \n \n \n\n \n\n \n \n \n \n ),\n selectedCharm,\n charmsList,\n };\n },\n);\n" }, { "name": "/common-tools.tsx", "contents": "/// \nimport {\n BuiltInLLMMessage,\n BuiltInLLMTool,\n Cell,\n cell,\n Default,\n derive,\n fetchData,\n h,\n handler,\n ifElse,\n llmDialog,\n NAME,\n recipe,\n Stream,\n UI,\n} from \"commontools\";\n\n///// COMMON TOOLS (get it?) ////\n\n/**\n * Calculate the result of a mathematical expression.\n * Supports +, -, *, /, and parentheses.\n */\ntype CalculatorRequest = {\n /** The mathematical expression to evaluate. */\n expression: string;\n};\n\nexport const calculator = recipe<\n CalculatorRequest,\n string | { error: string }\n>(\"Calculator\", ({ expression }) => {\n return derive(expression, (expr) => {\n const sanitized = expr.replace(/[^0-9+\\-*/().\\s]/g, \"\");\n let result;\n try {\n result = Function(`\"use strict\"; return (${sanitized})`)();\n } catch (error) {\n result = { error: (error as any)?.message || \"\" };\n }\n return result;\n });\n});\n\n/** Add an item to the list. */\ntype AddListItemRequest = {\n /** The item to add to the list. */\n item: string;\n result: Cell;\n};\n\n/** Read all items from the list. */\ntype ReadListItemsRequest = {\n result: Cell;\n};\n\nexport type ListItem = {\n title: string;\n};\n\nexport const addListItem = handler<\n AddListItemRequest,\n { list: Cell }\n>(\n (args, state) => {\n try {\n state.list.push({ title: args.item });\n args.result.set(`${state.list.get().length} items`);\n } catch (error) {\n args.result.set(`Error: ${(error as any)?.message || \"\"}`);\n }\n },\n);\n\nexport const readListItems = handler<\n ReadListItemsRequest,\n { list: ListItem[] }\n>(\n (args, state) => {\n try {\n const items = state.list;\n if (items.length === 0) {\n args.result.set(\"The list is empty\");\n } else {\n const itemList = items.map((item, index) =>\n `${index + 1}. ${item.title}`\n ).join(\"\\n\");\n args.result.set(`List items (${items.length} total):\\n${itemList}`);\n }\n } catch (error) {\n args.result.set(`Error: ${(error as any)?.message || \"\"}`);\n }\n },\n);\n\n/** Search the web for information. */\ntype SearchQuery = {\n /** The query to search the web for. */\n query: string;\n};\n\ntype SearchWebResult = {\n results: {\n title: string;\n url: string;\n description: string;\n }[];\n};\n\nexport const searchWeb = recipe<\n SearchQuery,\n SearchWebResult | { error: string }\n>(\"Search Web\", ({ query }) => {\n const { result, error } = fetchData({\n url: \"/api/agent-tools/web-search\",\n mode: \"json\",\n options: {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n },\n body: {\n query,\n max_results: 5,\n },\n },\n });\n\n // TODO(seefeld): Should we instead return { result, error }? Or allocate a\n // special [ERROR] for errors? Ideally this isn\\'t specific to using recipes as\n // tools but a general pattern.\n return ifElse(error, { error }, result);\n});\n\n/** Read and extract content from a specific webpage URL. */\ntype ReadWebRequest = {\n /** The URL of the webpage to read and extract content from. */\n url: string;\n};\n\ntype ReadWebResult = {\n content: string;\n metadata: {\n title?: string;\n author?: string;\n date?: string;\n word_count: number;\n };\n};\n\nexport const readWebpage = recipe<\n ReadWebRequest,\n ReadWebResult | { error: string }\n>(\"Read Webpage\", ({ url }) => {\n const { result, error } = fetchData({\n url: \"/api/agent-tools/web-read\",\n mode: \"json\",\n options: {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n },\n body: {\n url,\n max_tokens: 4000,\n include_code: true,\n },\n },\n });\n\n return ifElse(error, { error }, result);\n});\n\ntype ToolsInput = {\n list: ListItem[];\n};\n\nexport default recipe(\"Tools\", ({ list }) => {\n const tools: Record = {\n search_web: {\n pattern: searchWeb,\n },\n read_webpage: {\n pattern: readWebpage,\n },\n calculator: {\n pattern: calculator,\n },\n addListItem: {\n handler: addListItem({ list }),\n },\n };\n\n return { tools, list };\n});\n" } ] }, "spec": "recipe" } }, "since": 0 } } }, "of:baedreieihgsgvlmhz43c4zozooeyokvkudejkntefpglpz3vwq5odsan2e": { "application/json": { "ba4jcabiglpjllxplgrrg7h6fgqhdak5fsvaye7g4joomjfu5mzj7xbt3": { "is": { "source": { "/": "baedreidfn3ztoouromccp4sqiqspbigzvenj7diywcvmmxf2jw7z5a5dju" }, "value": [ { "type": "vnode", "name": "tr", "props": { }, "children": [ { "type": "vnode", "name": "td", "props": { }, "children": [ "DefaultCharmList (6)" ] }, { "type": "vnode", "name": "td", "props": { }, "children": [ { "type": "vnode", "name": "ct-hstack", "props": { "gap": "2" }, "children": [ { "type": "vnode", "name": "ct-button", "props": { "size": "sm", "onClick": { "$alias": { "path": [ "internal", "$event" ], "schema": { "type": "object", "properties": { }, "additionalProperties": false }, "rootSchema": { "type": "object", "properties": { }, "additionalProperties": false }, "cell": { "/": "baedreidfn3ztoouromccp4sqiqspbigzvenj7diywcvmmxf2jw7z5a5dju" } } } }, "children": [ "Visit" ] }, { "type": "vnode", "name": "ct-button", "props": { "size": "sm", "variant": "destructive", "onClick": { "$alias": { "path": [ "internal", "__#10stream" ], "schema": { "type": "object", "properties": { }, "additionalProperties": false }, "rootSchema": { "type": "object", "properties": { }, "additionalProperties": false }, "cell": { "/": "baedreidfn3ztoouromccp4sqiqspbigzvenj7diywcvmmxf2jw7z5a5dju" } } } }, "children": [ "Remove" ] } ] } ] } ] }, { "type": "vnode", "name": "tr", "props": { }, "children": [ { "type": "vnode", "name": "td", "props": { }, "children": [ "Launcher" ] }, { "type": "vnode", "name": "td", "props": { }, "children": [ { "type": "vnode", "name": "ct-hstack", "props": { "gap": "2" }, "children": [ { "type": "vnode", "name": "ct-button", "props": { "size": "sm", "onClick": { "$alias": { "path": [ "internal", "__#0stream" ], "schema": { "type": "object", "properties": { }, "additionalProperties": false }, "rootSchema": { "type": "object", "properties": { }, "additionalProperties": false }, "cell": { "/": "baedreidfn3ztoouromccp4sqiqspbigzvenj7diywcvmmxf2jw7z5a5dju" } } } }, "children": [ "Visit" ] }, { "type": "vnode", "name": "ct-button", "props": { "size": "sm", "variant": "destructive", "onClick": { "$alias": { "path": [ "internal", "__#1stream" ], "schema": { "type": "object", "properties": { }, "additionalProperties": false }, "rootSchema": { "type": "object", "properties": { }, "additionalProperties": false }, "cell": { "/": "baedreidfn3ztoouromccp4sqiqspbigzvenj7diywcvmmxf2jw7z5a5dju" } } } }, "children": [ "Remove" ] } ] } ] } ] }, { "type": "vnode", "name": "tr", "props": { }, "children": [ { "type": "vnode", "name": "td", "props": { }, "children": [ "one" ] }, { "type": "vnode", "name": "td", "props": { }, "children": [ { "type": "vnode", "name": "ct-hstack", "props": { "gap": "2" }, "children": [ { "type": "vnode", "name": "ct-button", "props": { "size": "sm", "onClick": { "$alias": { "path": [ "internal", "__#2stream" ], "schema": { "type": "object", "properties": { }, "additionalProperties": false }, "rootSchema": { "type": "object", "properties": { }, "additionalProperties": false }, "cell": { "/": "baedreidfn3ztoouromccp4sqiqspbigzvenj7diywcvmmxf2jw7z5a5dju" } } } }, "children": [ "Visit" ] }, { "type": "vnode", "name": "ct-button", "props": { "size": "sm", "variant": "destructive", "onClick": { "$alias": { "path": [ "internal", "__#3stream" ], "schema": { "type": "object", "properties": { }, "additionalProperties": false }, "rootSchema": { "type": "object", "properties": { }, "additionalProperties": false }, "cell": { "/": "baedreidfn3ztoouromccp4sqiqspbigzvenj7diywcvmmxf2jw7z5a5dju" } } } }, "children": [ "Remove" ] } ] } ] } ] }, { "type": "vnode", "name": "tr", "props": { }, "children": [ { "type": "vnode", "name": "td", "props": { }, "children": [ "two" ] }, { "type": "vnode", "name": "td", "props": { }, "children": [ { "type": "vnode", "name": "ct-hstack", "props": { "gap": "2" }, "children": [ { "type": "vnode", "name": "ct-button", "props": { "size": "sm", "onClick": { "$alias": { "path": [ "internal", "__#4stream" ], "schema": { "type": "object", "properties": { }, "additionalProperties": false }, "rootSchema": { "type": "object", "properties": { }, "additionalProperties": false }, "cell": { "/": "baedreidfn3ztoouromccp4sqiqspbigzvenj7diywcvmmxf2jw7z5a5dju" } } } }, "children": [ "Visit" ] }, { "type": "vnode", "name": "ct-button", "props": { "size": "sm", "variant": "destructive", "onClick": { "$alias": { "path": [ "internal", "__#5stream" ], "schema": { "type": "object", "properties": { }, "additionalProperties": false }, "rootSchema": { "type": "object", "properties": { }, "additionalProperties": false }, "cell": { "/": "baedreidfn3ztoouromccp4sqiqspbigzvenj7diywcvmmxf2jw7z5a5dju" } } } }, "children": [ "Remove" ] } ] } ] } ] }, { "type": "vnode", "name": "tr", "props": { }, "children": [ { "type": "vnode", "name": "td", "props": { }, "children": [ "three" ] }, { "type": "vnode", "name": "td", "props": { }, "children": [ { "type": "vnode", "name": "ct-hstack", "props": { "gap": "2" }, "children": [ { "type": "vnode", "name": "ct-button", "props": { "size": "sm", "onClick": { "$alias": { "path": [ "internal", "__#6stream" ], "schema": { "type": "object", "properties": { }, "additionalProperties": false }, "rootSchema": { "type": "object", "properties": { }, "additionalProperties": false }, "cell": { "/": "baedreidfn3ztoouromccp4sqiqspbigzvenj7diywcvmmxf2jw7z5a5dju" } } } }, "children": [ "Visit" ] }, { "type": "vnode", "name": "ct-button", "props": { "size": "sm", "variant": "destructive", "onClick": { "$alias": { "path": [ "internal", "__#7stream" ], "schema": { "type": "object", "properties": { }, "additionalProperties": false }, "rootSchema": { "type": "object", "properties": { }, "additionalProperties": false }, "cell": { "/": "baedreidfn3ztoouromccp4sqiqspbigzvenj7diywcvmmxf2jw7z5a5dju" } } } }, "children": [ "Remove" ] } ] } ] } ] }, { "type": "vnode", "name": "tr", "props": { }, "children": [ { "type": "vnode", "name": "td", "props": { }, "children": [ "four" ] }, { "type": "vnode", "name": "td", "props": { }, "children": [ { "type": "vnode", "name": "ct-hstack", "props": { "gap": "2" }, "children": [ { "type": "vnode", "name": "ct-button", "props": { "size": "sm", "onClick": { "$alias": { "path": [ "internal", "__#8stream" ], "schema": { "type": "object", "properties": { }, "additionalProperties": false }, "rootSchema": { "type": "object", "properties": { }, "additionalProperties": false }, "cell": { "/": "baedreidfn3ztoouromccp4sqiqspbigzvenj7diywcvmmxf2jw7z5a5dju" } } } }, "children": [ "Visit" ] }, { "type": "vnode", "name": "ct-button", "props": { "size": "sm", "variant": "destructive", "onClick": { "$alias": { "path": [ "internal", "__#9stream" ], "schema": { "type": "object", "properties": { }, "additionalProperties": false }, "rootSchema": { "type": "object", "properties": { }, "additionalProperties": false }, "cell": { "/": "baedreidfn3ztoouromccp4sqiqspbigzvenj7diywcvmmxf2jw7z5a5dju" } } } }, "children": [ "Remove" ] } ] } ] } ] } ] }, "since": 96 } } }, "of:baedreidfn3ztoouromccp4sqiqspbigzvenj7diywcvmmxf2jw7z5a5dju": { "application/json": { "ba4jcaxr4hmppcrvidrvp2dlynonnsymakjek24ch6awlkyccxt7jww76": { "is": { "value": { "$TYPE": "ba4jcbmwg4wrwkea6vw7pc7agu5dn3iockwv2g5vzdquavlueltwsseaj", "resultRef": { "/": { "link@1": { "path": [], "id": "of:baedreieihgsgvlmhz43c4zozooeyokvkudejkntefpglpz3vwq5odsan2e" } } }, "internal": { "$event": { "$stream": true }, "__#0stream": { "$stream": true }, "__#1stream": { "$stream": true }, "__#2stream": { "$stream": true }, "__#3stream": { "$stream": true }, "__#4stream": { "$stream": true }, "__#5stream": { "$stream": true }, "__#6stream": { "$stream": true }, "__#7stream": { "$stream": true }, "__#8stream": { "$stream": true }, "__#9stream": { "$stream": true }, "__#10stream": { "$stream": true } }, "spell": { "/": { "link@1": { "id": "of:baedreialup6quosxdoqitoe77j4nmoxjnfhbmexxg7w26h57zm5efz6fdu" } } }, "argument": { } } }, "since": 96 } } }, "of:baedreibl64qzbhgkvpuxbfc657ugjeyidc62hixjybt5dpci2ddkkhs26m": { "application/json": { "ba4jcao7dyu5ju4wyovrxttgby75p35fzoptz5gowhnz432kscf2j5kje": { "is": { "source": { "/": "baedreibyee7wugohld5chzqxgckibuluga64i6zswmx7zqz67v2xp3mk3u" }, "value": { "$NAME": "Launcher", "$UI": { "type": "vnode", "name": "ct-theme", "props": { "theme": { "$alias": { "path": [ "argument", "theme" ], "schema": { "type": "object", "properties": { "accentColor": { "type": "string", "default": "#3b82f6" }, "fontFace": { "type": "string", "default": "system-ui, -apple-system, sans-serif" }, "borderRadius": { "type": "string", "default": "0.5rem" } }, "required": [ "accentColor", "fontFace", "borderRadius" ] }, "rootSchema": { "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", "properties": { "selectedCharm": { "type": "object", "properties": { "charm": true }, "required": [ "charm" ], "default": { } }, "charmsList": { "type": "array", "items": { "$ref": "#/$defs/CharmEntry" }, "default": [] }, "allCharms": { "type": "array", "items": true }, "theme": { "type": "object", "properties": { "accentColor": { "type": "string", "default": "#3b82f6" }, "fontFace": { "type": "string", "default": "system-ui, -apple-system, sans-serif" }, "borderRadius": { "type": "string", "default": "0.5rem" } }, "required": [ "accentColor", "fontFace", "borderRadius" ] } }, "required": [ "selectedCharm", "charmsList", "allCharms" ], "$defs": { "CharmEntry": { "type": "object", "properties": { "local_id": { "type": "string" }, "charm": true }, "required": [ "local_id", "charm" ] } } }, "cell": { "/": "baedreibyee7wugohld5chzqxgckibuluga64i6zswmx7zqz67v2xp3mk3u" } } } }, "children": [ { "type": "vnode", "name": "ct-screen", "props": { }, "children": [ { "type": "vnode", "name": "div", "props": { "slot": "header" }, "children": [ { "type": "vnode", "name": "ct-toolbar", "props": { "dense": true, "sticky": true }, "children": [ { "type": "vnode", "name": "div", "props": { "slot": "start" }, "children": [ { "type": "vnode", "name": "ct-button", "props": { "id": "new-chat-btn", "onClick": { "$alias": { "path": [ "internal", "__#4stream" ], "schema": true, "rootSchema": true, "cell": { "/": "baedreibyee7wugohld5chzqxgckibuluga64i6zswmx7zqz67v2xp3mk3u" } } } }, "children": [ "Create New Chat", { "type": "vnode", "name": "ct-kbd", "props": { }, "children": [ "alt+N" ] } ] } ] } ] }, { "type": "vnode", "name": "ct-keybind", "props": { "code": "KeyN", "alt": true, "preventDefault": true, "onct-keybind": { "$alias": { "path": [ "internal", "$event" ], "schema": true, "rootSchema": true, "cell": { "/": "baedreibyee7wugohld5chzqxgckibuluga64i6zswmx7zqz67v2xp3mk3u" } } } }, "children": [] } ] }, { "type": "vnode", "name": "ct-autolayout", "props": { "leftOpen": true, "rightOpen": false, "tabNames": [ "Chat", "Note" ] }, "children": [ { "type": "vnode", "name": "ct-screen", "props": { }, "children": [ { "type": "vnode", "name": "ct-render", "props": { "$cell": { "$alias": { "path": [ "internal", "list", "chat" ], "cell": { "/": "baedreibyee7wugohld5chzqxgckibuluga64i6zswmx7zqz67v2xp3mk3u" } } } }, "children": [] } ] }, { "type": "vnode", "name": "ct-screen", "props": { }, "children": [ { "type": "vnode", "name": "ct-render", "props": { "$cell": { "$alias": { "path": [ "internal", "list", "note" ], "cell": { "/": "baedreibyee7wugohld5chzqxgckibuluga64i6zswmx7zqz67v2xp3mk3u" } } } }, "children": [] } ] }, { "type": "vnode", "name": "aside", "props": { "slot": "left" }, "children": [ { "type": "vnode", "name": "div", "props": { }, "children": [ { "type": "vnode", "name": "ct-heading", "props": { "level": 3 }, "children": [ "Chat List" ] } ] }, { "type": "vnode", "name": "div", "props": { "role": "list" }, "children": [ { "$alias": { "path": [ "internal", "__#5" ], "cell": { "/": "baedreibyee7wugohld5chzqxgckibuluga64i6zswmx7zqz67v2xp3mk3u" } } } ] } ] }, { "type": "vnode", "name": "aside", "props": { "slot": "right" }, "children": [ { "$alias": { "path": [ "internal", "__#2" ], "cell": { "/": "baedreibyee7wugohld5chzqxgckibuluga64i6zswmx7zqz67v2xp3mk3u" } } }, { "type": "vnode", "name": "ct-collapsible", "props": { }, "children": [ { "type": "vnode", "name": "ct-heading", "props": { "slot": "trigger", "level": 5, "no-margin": true }, "children": [ "Theme" ] }, { "type": "vnode", "name": "ct-vstack", "props": { "style": "padding: 0.5rem 0; gap: 0.5rem;" }, "children": [ { "type": "vnode", "name": "ct-vstack", "props": { }, "children": [ { "type": "vnode", "name": "ct-text", "props": { }, "children": [ "Font Family" ] }, { "type": "vnode", "name": "ct-select", "props": { "items": [ { "label": "System", "value": "system-ui, -apple-system, sans-serif" }, { "label": "Monospace", "value": "ui-monospace, Consolas, monospace" }, { "label": "Serif", "value": "Georgia, Times, serif" }, { "label": "Sans Serif", "value": "Arial, Helvetica, sans-serif" } ], "$value": { "$alias": { "path": [ "argument", "theme", "fontFace" ], "schema": { "type": "string", "default": "system-ui, -apple-system, sans-serif" }, "rootSchema": { "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", "properties": { "selectedCharm": { "type": "object", "properties": { "charm": true }, "required": [ "charm" ], "default": { } }, "charmsList": { "type": "array", "items": { "$ref": "#/$defs/CharmEntry" }, "default": [] }, "allCharms": { "type": "array", "items": true }, "theme": { "type": "object", "properties": { "accentColor": { "type": "string", "default": "#3b82f6" }, "fontFace": { "type": "string", "default": "system-ui, -apple-system, sans-serif" }, "borderRadius": { "type": "string", "default": "0.5rem" } }, "required": [ "accentColor", "fontFace", "borderRadius" ] } }, "required": [ "selectedCharm", "charmsList", "allCharms" ], "$defs": { "CharmEntry": { "type": "object", "properties": { "local_id": { "type": "string" }, "charm": true }, "required": [ "local_id", "charm" ] } } }, "cell": { "/": "baedreibyee7wugohld5chzqxgckibuluga64i6zswmx7zqz67v2xp3mk3u" } } } }, "children": [] } ] }, { "type": "vnode", "name": "ct-vstack", "props": { }, "children": [ { "type": "vnode", "name": "ct-text", "props": { }, "children": [ "Accent Color" ] }, { "type": "vnode", "name": "ct-select", "props": { "items": [ { "label": "Blue", "value": "#3b82f6" }, { "label": "Purple", "value": "#8b5cf6" }, { "label": "Green", "value": "#10b981" }, { "label": "Red", "value": "#ef4444" }, { "label": "Orange", "value": "#f97316" }, { "label": "Pink", "value": "#ec4899" }, { "label": "Indigo", "value": "#6366f1" }, { "label": "Teal", "value": "#14b8a6" } ], "$value": { "$alias": { "path": [ "argument", "theme", "accentColor" ], "schema": { "type": "string", "default": "#3b82f6" }, "rootSchema": { "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", "properties": { "selectedCharm": { "type": "object", "properties": { "charm": true }, "required": [ "charm" ], "default": { } }, "charmsList": { "type": "array", "items": { "$ref": "#/$defs/CharmEntry" }, "default": [] }, "allCharms": { "type": "array", "items": true }, "theme": { "type": "object", "properties": { "accentColor": { "type": "string", "default": "#3b82f6" }, "fontFace": { "type": "string", "default": "system-ui, -apple-system, sans-serif" }, "borderRadius": { "type": "string", "default": "0.5rem" } }, "required": [ "accentColor", "fontFace", "borderRadius" ] } }, "required": [ "selectedCharm", "charmsList", "allCharms" ], "$defs": { "CharmEntry": { "type": "object", "properties": { "local_id": { "type": "string" }, "charm": true }, "required": [ "local_id", "charm" ] } } }, "cell": { "/": "baedreibyee7wugohld5chzqxgckibuluga64i6zswmx7zqz67v2xp3mk3u" } } } }, "children": [] } ] }, { "type": "vnode", "name": "ct-vstack", "props": { }, "children": [ { "type": "vnode", "name": "ct-text", "props": { }, "children": [ "Border Radius" ] }, { "type": "vnode", "name": "ct-select", "props": { "items": [ { "label": "None", "value": "0px" }, { "label": "Small", "value": "0.25rem" }, { "label": "Medium", "value": "0.5rem" }, { "label": "Large", "value": "0.75rem" }, { "label": "Extra Large", "value": "1rem" }, { "label": "Rounded", "value": "1.5rem" } ], "$value": { "$alias": { "path": [ "argument", "theme", "borderRadius" ], "schema": { "type": "string", "default": "0.5rem" }, "rootSchema": { "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", "properties": { "selectedCharm": { "type": "object", "properties": { "charm": true }, "required": [ "charm" ], "default": { } }, "charmsList": { "type": "array", "items": { "$ref": "#/$defs/CharmEntry" }, "default": [] }, "allCharms": { "type": "array", "items": true }, "theme": { "type": "object", "properties": { "accentColor": { "type": "string", "default": "#3b82f6" }, "fontFace": { "type": "string", "default": "system-ui, -apple-system, sans-serif" }, "borderRadius": { "type": "string", "default": "0.5rem" } }, "required": [ "accentColor", "fontFace", "borderRadius" ] } }, "required": [ "selectedCharm", "charmsList", "allCharms" ], "$defs": { "CharmEntry": { "type": "object", "properties": { "local_id": { "type": "string" }, "charm": true }, "required": [ "local_id", "charm" ] } } }, "cell": { "/": "baedreibyee7wugohld5chzqxgckibuluga64i6zswmx7zqz67v2xp3mk3u" } } } }, "children": [] } ] } ] } ] } ] } ] } ] } ] }, "selectedCharm": { "$alias": { "path": [ "argument", "selectedCharm" ], "schema": { "type": "object", "properties": { "charm": true }, "required": [ "charm" ], "default": { } }, "rootSchema": { "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", "properties": { "selectedCharm": { "type": "object", "properties": { "charm": true }, "required": [ "charm" ], "default": { } }, "charmsList": { "type": "array", "items": { "$ref": "#/$defs/CharmEntry" }, "default": [] }, "allCharms": { "type": "array", "items": true }, "theme": { "type": "object", "properties": { "accentColor": { "type": "string", "default": "#3b82f6" }, "fontFace": { "type": "string", "default": "system-ui, -apple-system, sans-serif" }, "borderRadius": { "type": "string", "default": "0.5rem" } }, "required": [ "accentColor", "fontFace", "borderRadius" ] } }, "required": [ "selectedCharm", "charmsList", "allCharms" ], "$defs": { "CharmEntry": { "type": "object", "properties": { "local_id": { "type": "string" }, "charm": true }, "required": [ "local_id", "charm" ] } } }, "cell": { "/": "baedreibyee7wugohld5chzqxgckibuluga64i6zswmx7zqz67v2xp3mk3u" } } }, "charmsList": { "$alias": { "path": [ "argument", "charmsList" ], "schema": { "type": "array", "items": { "$ref": "#/$defs/CharmEntry" }, "default": [] }, "rootSchema": { "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", "properties": { "selectedCharm": { "type": "object", "properties": { "charm": true }, "required": [ "charm" ], "default": { } }, "charmsList": { "type": "array", "items": { "$ref": "#/$defs/CharmEntry" }, "default": [] }, "allCharms": { "type": "array", "items": true }, "theme": { "type": "object", "properties": { "accentColor": { "type": "string", "default": "#3b82f6" }, "fontFace": { "type": "string", "default": "system-ui, -apple-system, sans-serif" }, "borderRadius": { "type": "string", "default": "0.5rem" } }, "required": [ "accentColor", "fontFace", "borderRadius" ] } }, "required": [ "selectedCharm", "charmsList", "allCharms" ], "$defs": { "CharmEntry": { "type": "object", "properties": { "local_id": { "type": "string" }, "charm": true }, "required": [ "local_id", "charm" ] } } }, "cell": { "/": "baedreibyee7wugohld5chzqxgckibuluga64i6zswmx7zqz67v2xp3mk3u" } } } } }, "since": 11 } } }, "of:baedreibyee7wugohld5chzqxgckibuluga64i6zswmx7zqz67v2xp3mk3u": { "application/json": { "ba4jcbhfr2tyzidydfnga5qzitmilvjy4exc5oyke2mih3sxqczls6aki": { "is": { "value": { "$TYPE": "ba4jcakiq2gsbkigfeo5q6ime4d4we5sqgr6gwdnopq4dl2fmss65o4bm", "resultRef": { "/": { "link@1": { "path": [], "id": "of:baedreibl64qzbhgkvpuxbfc657ugjeyidc62hixjybt5dpci2ddkkhs26m" } } }, "internal": { "$event": { "$stream": true }, "__#4stream": { "$stream": true }, "__#7": { "/": { "link@1": { "path": [ "argument", "charmsList" ], "id": "of:baedreibyee7wugohld5chzqxgckibuluga64i6zswmx7zqz67v2xp3mk3u", "space": "did:key:z6MkkGMscCkDFETV5efoTSEybcVfo8muPQUp7qMa3mUGC4mF" } } }, "__#6": { "/": { "link@1": { "path": [ "argument", "charmsList" ], "id": "of:baedreibyee7wugohld5chzqxgckibuluga64i6zswmx7zqz67v2xp3mk3u", "space": "did:key:z6MkkGMscCkDFETV5efoTSEybcVfo8muPQUp7qMa3mUGC4mF" } } }, "__#3": [ { "/": { "link@1": { "path": [], "id": "of:baedreih3s7r744kith3ntkg6oooklw2sy2vyycy22zlmm4l7rnizislpqa", "space": "did:key:z6MkkGMscCkDFETV5efoTSEybcVfo8muPQUp7qMa3mUGC4mF" } } }, { "/": { "link@1": { "path": [], "id": "of:baedreihffyh4263hchjbcx4vqsyctgf6frmgaryrflbdtkezhzzyn5itde", "space": "did:key:z6MkkGMscCkDFETV5efoTSEybcVfo8muPQUp7qMa3mUGC4mF" } } }, { "/": { "link@1": { "path": [], "id": "of:baedreibl64qzbhgkvpuxbfc657ugjeyidc62hixjybt5dpci2ddkkhs26m", "space": "did:key:z6MkkGMscCkDFETV5efoTSEybcVfo8muPQUp7qMa3mUGC4mF" } } }, { "/": { "link@1": { "path": [], "id": "of:baedreidvk42ywcn6n6ucdpfg2pvi5o6p3njkk6rltyvydkkbjcco5m4wcu", "space": "did:key:z6MkkGMscCkDFETV5efoTSEybcVfo8muPQUp7qMa3mUGC4mF" } } }, { "/": { "link@1": { "path": [], "id": "of:baedreibninu5twfm72l6gs5sbaghg6gdtmn2365encfoinebrvnsmavrr4", "space": "did:key:z6MkkGMscCkDFETV5efoTSEybcVfo8muPQUp7qMa3mUGC4mF" } } }, { "/": { "link@1": { "path": [], "id": "of:baedreibyolwzasa3njbwrwexvtolf7b7x5wh5lkwkepa5jbc6paghoc5ua", "space": "did:key:z6MkkGMscCkDFETV5efoTSEybcVfo8muPQUp7qMa3mUGC4mF" } } }, { "/": { "link@1": { "path": [], "id": "of:baedreigyxxqptxd2vlfwxnhzdwdha32i4ou5onzw6ruunaqudg5u42agva", "space": "did:key:z6MkkGMscCkDFETV5efoTSEybcVfo8muPQUp7qMa3mUGC4mF" } } } ], "__#0": { "/": { "link@1": { "path": [], "id": "of:baedreid6gonxx2y2poyw67zd636ijtv5iwc76kqfhhdd5zn4kbdpfr7tpe", "space": "did:key:z6MkkGMscCkDFETV5efoTSEybcVfo8muPQUp7qMa3mUGC4mF" } } }, "__#1": { "/": { "link@1": { "path": [], "id": "of:baedreihfxjcb2la3k4kuof4uvlgr4zdoll3mgksfi5vf2zmf7pnvgrk2tu", "space": "did:key:z6MkkGMscCkDFETV5efoTSEybcVfo8muPQUp7qMa3mUGC4mF" } } }, "__#5": { "/": { "link@1": { "path": [], "id": "of:baedreicv3mllykrlg2ahbgffulevsda4qxjipyuntyfs7fdi4nzrxfiw6u", "space": "did:key:z6MkkGMscCkDFETV5efoTSEybcVfo8muPQUp7qMa3mUGC4mF" } } }, "__#2": { "/": { "link@1": { "path": [], "id": "of:baedreihsyzn7myhp646sdiyb4ixi76jihj27hsgg3mcmlea6hqz72zaexe", "space": "did:key:z6MkkGMscCkDFETV5efoTSEybcVfo8muPQUp7qMa3mUGC4mF" } } }, "list": { "/": { "link@1": { "path": [], "id": "of:baedreih3s7r744kith3ntkg6oooklw2sy2vyycy22zlmm4l7rnizislpqa", "space": "did:key:z6MkkGMscCkDFETV5efoTSEybcVfo8muPQUp7qMa3mUGC4mF" } } } }, "spell": { "/": { "link@1": { "id": "of:baedreie4tj7mfuztjpercxelbluysbd3omwxwa47cckwludt3ayhabtjru" } } }, "argument": { "selectedCharm": { "charm": { "/": { "link@1": { "path": [], "id": "of:baedreih3s7r744kith3ntkg6oooklw2sy2vyycy22zlmm4l7rnizislpqa", "space": "did:key:z6MkkGMscCkDFETV5efoTSEybcVfo8muPQUp7qMa3mUGC4mF" } } } }, "charmsList": [ { "/": { "link@1": { "path": [], "id": "of:baedreiboekykwgdbufsup3shixvj6g2lbzlxy666g5lbhvdivuj7x375ae" } } } ], "allCharms": { "$alias": { "cell": { "/": "baedreiahv63wxwgaem4hzjkizl4qncfgvca7pj5cvdon7cukumfon3ioye" }, "path": [] } }, "theme": { "accentColor": "#3b82f6", "fontFace": "system-ui, -apple-system, sans-serif", "borderRadius": "0.5rem" } } } }, "since": 95 } } }, "of:baedreie4tj7mfuztjpercxelbluysbd3omwxwa47cckwludt3ayhabtjru": { "application/json": { "ba4jcbu5hn6fzsenqsllsfejs5vtahh7orfvwsxk7rlkkown5hpo4fxzk": { "is": { "value": { "id": "ba4jcakiq2gsbkigfeo5q6ime4d4we5sqgr6gwdnopq4dl2fmss65o4bm", "program": { "main": "/chatbot-list-view.tsx", "mainExport": "default", "files": [ { "name": "/default-app.tsx", "contents": "/// \nimport {\n Cell,\n Default,\n derive,\n h,\n handler,\n NAME,\n navigateTo,\n Opaque,\n OpaqueRef,\n recipe,\n str,\n UI,\n} from \"commontools\";\n\n// Import recipes we want to be launchable from the default app.\nimport Chatbot from \"./chatbot.tsx\";\nimport ChatbotOutliner from \"./chatbot-outliner.tsx\";\nimport { type MentionableCharm } from \"./chatbot-note-composed.tsx\";\nimport { default as Note } from \"./note.tsx\";\nimport ChatList from \"./chatbot-list-view.tsx\";\n\nexport type Charm = {\n [NAME]?: string;\n [UI]?: unknown;\n [key: string]: any;\n};\n\ntype CharmsListInput = {\n allCharms: Default;\n};\n\n// Recipe returns only UI, no data outputs (only symbol properties)\ninterface CharmsListOutput {\n [key: string]: unknown;\n}\n\nconst visit = handler<\n Record,\n { charm: any }\n>((_, state) => {\n return navigateTo(state.charm);\n}, { proxy: true });\n\nconst removeCharm = handler<\n Record,\n {\n charm: any;\n allCharms: Cell;\n }\n>((_, state) => {\n const charmName = state.charm[NAME];\n const allCharmsValue = state.allCharms.get();\n const index = allCharmsValue.findIndex((c: any) => c[NAME] === charmName);\n\n if (index !== -1) {\n const charmListCopy = [...allCharmsValue];\n console.log(\"charmListCopy before\", charmListCopy);\n charmListCopy.splice(index, 1);\n console.log(\"charmListCopy after\", charmListCopy);\n state.allCharms.set(charmListCopy);\n }\n});\n\nconst spawnChatList = handler<\n Record,\n { allCharms: Cell }\n>((_, state) => {\n return navigateTo(ChatList({\n selectedCharm: { charm: undefined },\n charmsList: [],\n allCharms: state.allCharms, // we should handle empty here\n }));\n});\n\nconst spawnChatbot = handler<\n Record,\n Record\n>((_, state) => {\n return navigateTo(Chatbot({\n messages: [],\n tools: undefined,\n }));\n});\n\nconst spawnChatbotOutliner = handler<\n Record,\n { allCharms: Cell }\n>((_, state) => {\n return navigateTo(ChatbotOutliner({\n title: \"Chatbot Outliner\",\n expandChat: false,\n messages: [],\n outline: {\n root: { body: \"\", children: [], attachments: [] },\n },\n allCharms: state.allCharms,\n }));\n});\n\nconst spawnNote = handler<\n Record,\n { allCharms: Cell }\n>((_, state) => {\n return navigateTo(Note({\n title: \"New Note\",\n content: \"\",\n allCharms: state.allCharms,\n }));\n});\n\nexport default recipe(\n \"DefaultCharmList\",\n ({ allCharms }) => {\n return {\n [NAME]: str`DefaultCharmList (${allCharms.length})`,\n [UI]: (\n \n ,\n })}\n />\n\n \n {/* Quick Launch Toolbar */}\n \n Quicklaunch:\n ,\n })}\n >\n 📂 Chat List\n \n \n 💬 Chatbot\n \n \n 📝 Chatbot Outliner\n \n ,\n })}\n >\n 📄 Note\n \n \n\n Charms ({allCharms.length})\n\n \n \n \n Charm Name\n Actions\n \n \n \n {derive(allCharms, (allCharms) =>\n allCharms.map((charm: any) => (\n \n {charm[NAME] || \"Untitled Charm\"}\n \n \n \n Visit\n \n \n Remove\n \n \n \n \n )))}\n \n \n \n \n ),\n };\n },\n);\n" }, { "name": "/chatbot.tsx", "contents": "/// \nimport {\n BuiltInLLMMessage,\n Cell,\n cell,\n Default,\n derive,\n fetchData,\n generateObject,\n h,\n handler,\n ifElse,\n JSONSchema,\n lift,\n llm,\n llmDialog,\n NAME,\n recipe,\n str,\n Stream,\n UI,\n} from \"commontools\";\n\nconst sendMessage = handler<\n { detail: { message: string } },\n {\n addMessage: Stream;\n }\n>((event, { addMessage }) => {\n addMessage.send({\n role: \"user\",\n content: [{ type: \"text\", text: event.detail.message }],\n });\n});\n\nconst clearChat = handler(\n (\n _: never,\n { messages, pending }: {\n messages: Cell>;\n pending: Cell;\n },\n ) => {\n messages.set([]);\n pending.set(false);\n },\n);\n\ntype ChatInput = {\n messages: Default, []>;\n tools: any;\n theme?: any;\n};\n\ntype ChatOutput = {\n messages: Array;\n pending: boolean | undefined;\n addMessage: Stream;\n cancelGeneration: Stream;\n title?: string;\n};\n\nexport const TitleGenerator = recipe<\n { model?: string; messages: Array }\n>(\"Title Generator\", ({ model, messages }) => {\n const titleMessages = derive(messages, (m) => {\n if (!m || m.length === 0) return \"\";\n\n const messageCount = 2;\n const selectedMessages = m.slice(0, messageCount).filter(Boolean);\n\n if (selectedMessages.length === 0) return \"\";\n\n return selectedMessages.map((msg) => JSON.stringify(msg)).join(\"\\n\");\n });\n\n const { result } = generateObject({\n system:\n \"Generate at most a 3-word title based on the following content, respond with NOTHING but the literal title text.\",\n prompt: titleMessages,\n model,\n schema: {\n type: \"object\",\n properties: {\n title: {\n type: \"string\",\n description: \"The title of the chat\",\n },\n },\n required: [\"title\"],\n },\n });\n\n const title = derive(result, (t) => {\n return t?.title || \"Untitled Chat\";\n });\n\n return title;\n});\n\nexport default recipe(\n \"Chat\",\n ({ messages, tools, theme }) => {\n const model = cell(\"anthropic:claude-sonnet-4-5\");\n\n const { addMessage, cancelGeneration, pending } = llmDialog({\n system: \"You are a helpful assistant with some tools.\",\n messages,\n tools,\n model,\n });\n\n const { result } = fetchData({\n url: \"/api/ai/llm/models\",\n mode: \"json\",\n });\n\n const items = derive(result, (models) => {\n if (!models) return [];\n const items = Object.keys(models as any).map((key) => ({\n label: key,\n value: key,\n }));\n return items;\n });\n\n const title = TitleGenerator({ model, messages });\n\n return {\n [NAME]: title,\n [UI]: (\n \n \n {title}\n \n \n\n \n \n \n\n \n \n \n \n \n ),\n messages,\n pending,\n addMessage,\n cancelGeneration,\n title,\n };\n },\n);\n" }, { "name": "/chatbot-outliner.tsx", "contents": "/// \nimport {\n BuiltInLLMMessage,\n Cell,\n cell,\n Default,\n derive,\n fetchData,\n getRecipeEnvironment,\n h,\n handler,\n ID,\n ifElse,\n JSONSchema,\n lift,\n llm,\n llmDialog,\n NAME,\n navigateTo,\n OpaqueRef,\n recipe,\n str,\n Stream,\n UI,\n} from \"commontools\";\n\nimport Chat from \"./chatbot.tsx\";\n\ntype Charm = any;\n\ntype OutlinerNode = {\n body: Default;\n children: Default;\n attachments: Default[], []>;\n};\n\ntype Outliner = {\n root: OutlinerNode;\n};\n\ntype PageResult = {\n outline: Default<\n Outliner,\n { root: { body: \"\"; children: []; attachments: [] } }\n >;\n};\n\nexport type PageInput = {\n outline: Outliner;\n allCharms: Cell;\n};\n\nconst handleCharmLinkClick = handler<\n {\n detail: {\n charm: Cell;\n };\n },\n Record\n>(({ detail }, _) => {\n return navigateTo(detail.charm);\n});\n\nexport const Page = recipe(\n \"Page\",\n ({ outline, allCharms }) => {\n return {\n [NAME]: \"Page\",\n [UI]: (\n \n ),\n outline,\n };\n },\n);\n\ntype LLMTestInput = {\n title: Default;\n messages: Default, []>;\n expandChat: Default;\n outline: Default<\n Outliner,\n { root: { body: \"Untitled Page\"; children: []; attachments: [] } }\n >;\n allCharms: Cell;\n};\n\ntype LLMTestResult = {\n messages: Default, []>;\n};\n\n// put a node at the end of the outline (by appending to root.children)\nconst appendOutlinerNode = handler<\n {\n /** The text content/title of the outliner node to be appended */\n body: string;\n /** A cell to store the result message indicating success or error */\n result: Cell;\n },\n { outline: Cell }\n>(\n (args, state) => {\n try {\n (state.outline.key(\"root\").key(\"children\")).push({\n body: args.body,\n children: [],\n attachments: [],\n });\n\n args.result.set(\n `${state.outline.key(\"root\").key(\"children\").get().length} nodes`,\n );\n } catch (error) {\n args.result.set(`Error: ${(error as any)?.message || \"\"}`);\n }\n },\n);\n\nexport default recipe(\n \"Outliner\",\n ({ title, expandChat, messages, outline, allCharms }) => {\n const tools = {\n appendOutlinerNode: {\n description: \"Add a new outliner node.\",\n inputSchema: {\n type: \"object\",\n properties: {\n body: {\n type: \"string\",\n description: \"The title of the new node.\",\n },\n },\n required: [\"body\"],\n } as JSONSchema,\n handler: appendOutlinerNode({ outline }),\n },\n };\n\n const chat = Chat({ messages, tools });\n const { addMessage, cancelGeneration, pending } = chat;\n\n return {\n [NAME]: title,\n [UI]: (\n \n \n \n \n Show Chat\n \n \n\n \n \n \n \n \n\n \n \n \n \n \n \n\n {ifElse(\n expandChat,\n chat,\n null,\n )}\n \n \n ),\n messages,\n };\n },\n);\n" }, { "name": "/chatbot-note-composed.tsx", "contents": "/// \nimport {\n BuiltInLLMMessage,\n Cell,\n cell,\n Default,\n derive,\n fetchData,\n getRecipeEnvironment,\n h,\n handler,\n ID,\n ifElse,\n JSONSchema,\n lift,\n llm,\n llmDialog,\n NAME,\n navigateTo,\n OpaqueRef,\n recipe,\n str,\n Stream,\n toSchema,\n UI,\n} from \"commontools\";\n\nimport Chat from \"./chatbot.tsx\";\nimport Note from \"./note.tsx\";\nimport Tools, {\n addListItem,\n calculator,\n ListItem,\n readListItems,\n readWebpage,\n searchWeb,\n} from \"./common-tools.tsx\";\n\nexport type MentionableCharm = {\n [NAME]: string;\n content?: string;\n mentioned?: MentionableCharm[];\n};\n\n// export type ChatbotNoteInput = {\n// content: Default;\n// allCharms?: Cell;\n// };\n\nconst handleCharmLinkClick = handler<\n {\n detail: {\n charm: Cell;\n };\n },\n Record\n>(({ detail }, _) => {\n return navigateTo(detail.charm);\n});\n\nconst handleCharmLinkClicked = handler(\n (_: any, { charm }: { charm: Cell }) => {\n return navigateTo(charm);\n },\n);\n\ntype ChatbotNoteInput = {\n title: Default;\n messages: Default, []>;\n content: Default;\n allCharms: Cell;\n};\n\ntype ChatbotNoteResult = {\n messages: Default, []>;\n mentioned: Default, []>;\n backlinks: Default, []>;\n content: Default;\n note: any;\n chat: any;\n list: Default;\n};\n\nconst newNote = handler<\n {\n /** The text content of the note */\n title: string;\n content?: string;\n /** A cell to store the result message indicating success or error */\n result: Cell;\n },\n { allCharms: Cell }\n>(\n (args, state) => {\n try {\n const n = Note({\n title: args.title,\n content: args.content || \"\",\n allCharms: state.allCharms,\n });\n\n args.result.set(\n `Created note ${args.title}!`,\n );\n\n state.allCharms.push(n as unknown as MentionableCharm);\n } catch (error) {\n args.result.set(`Error: ${(error as any)?.message || \"\"}`);\n }\n },\n);\n\n// put a note at the end of the outline (by appending to root.children)\nconst editNote = handler<\n {\n /** The text content of the note */\n body: string;\n /** A cell to store the result message indicating success or error */\n result: Cell;\n },\n { content: Cell }\n>(\n (args, state) => {\n try {\n state.content.set(args.body);\n\n args.result.set(\n `Updated note!`,\n );\n } catch (error) {\n args.result.set(`Error: ${(error as any)?.message || \"\"}`);\n }\n },\n);\n\nconst readNote = handler<\n {\n /** A cell to store the result text */\n result: Cell;\n },\n { content: string }\n>(\n (args, state) => {\n try {\n args.result.set(state.content);\n } catch (error) {\n args.result.set(`Error: ${(error as any)?.message || \"\"}`);\n }\n },\n);\n\nconst listMentionable = handler<\n {\n /** A cell to store the result text */\n result: Cell;\n },\n { allCharms: { [NAME]: string }[] }\n>(\n (args, state) => {\n try {\n const namesList = state.allCharms.map((charm) => charm[NAME]);\n args.result.set(JSON.stringify(namesList));\n } catch (error) {\n args.result.set(`Error: ${(error as any)?.message || \"\"}`);\n }\n },\n);\n\nconst readNoteByIndex = handler<\n {\n /** A cell to store the result text */\n index: number;\n result: Cell;\n },\n { allCharms: { [NAME]: string; content?: string }[] }\n>(\n (args, state) => {\n try {\n args.result.set(\n state.allCharms[args.index]?.content || \"No content found\",\n );\n } catch (error) {\n args.result.set(`Error: ${(error as any)?.message || \"\"}`);\n }\n },\n);\n\nconst editNoteByIndex = handler<\n {\n /** The index of the note to edit */\n index: number;\n /** The new text content of the note */\n body: string;\n /** A cell to store the result message indicating success or error */\n result: Cell;\n },\n { allCharms: Cell }\n>(\n (args, state) => {\n try {\n const charms = state.allCharms.get();\n if (args.index < 0 || args.index >= charms.length) {\n args.result.set(`Error: Invalid index ${args.index}`);\n return;\n }\n\n state.allCharms.key(args.index).key(\"content\").set(args.body);\n args.result.set(`Updated note at index ${args.index}!`);\n } catch (error) {\n args.result.set(`Error: ${(error as any)?.message || \"\"}`);\n }\n },\n);\n\nconst navigateToNote = handler<\n {\n /** The index of the note to navigate to */\n index: number;\n /** A cell to store the result message indicating success or error */\n result: Cell;\n },\n { allCharms: Cell }\n>(\n (args, state) => {\n try {\n const charms = state.allCharms.get();\n if (args.index < 0 || args.index >= charms.length) {\n args.result.set(`Error: Invalid index ${args.index}`);\n return;\n }\n\n const targetCharm = charms[args.index];\n args.result.set(`Navigating to note: ${targetCharm[NAME]}`);\n\n return navigateTo(state.allCharms.key(args.index));\n } catch (error) {\n args.result.set(`Error: ${(error as any)?.message || \"\"}`);\n }\n },\n);\n\nexport default recipe(\n \"Chatbot + Note\",\n ({ title, messages, content, allCharms }) => {\n const list = cell([]);\n\n const tools = {\n searchWeb: {\n pattern: searchWeb,\n },\n readWebpage: {\n pattern: readWebpage,\n },\n calculator: {\n pattern: calculator,\n },\n addListItem: {\n handler: addListItem({ list }),\n },\n readListItems: {\n handler: readListItems({ list }),\n },\n editActiveNote: {\n description: \"Modify the shared note.\",\n inputSchema: {\n type: \"object\",\n properties: {\n body: {\n type: \"string\",\n description: \"The content of the note.\",\n },\n },\n required: [\"body\"],\n } as JSONSchema,\n handler: editNote({ content }),\n },\n readActiveNote: {\n description: \"Read the currently focused note.\",\n inputSchema: {\n type: \"object\",\n properties: {},\n required: [],\n } as JSONSchema,\n handler: readNote({ content }),\n },\n listNotes: {\n description:\n \"List all mentionable note titles (read the body with readNoteByIndex).\",\n inputSchema: {\n type: \"object\",\n properties: {},\n required: [],\n } as JSONSchema,\n handler: listMentionable({\n allCharms: allCharms as unknown as OpaqueRef,\n }),\n },\n readNoteByIndex: {\n description:\n \"Read the body of a note by its index in the listNotes() list.\",\n inputSchema: {\n type: \"object\",\n properties: {\n index: {\n type: \"number\",\n description: \"The index of the note in the notes list.\",\n },\n },\n required: [\"index\"],\n } as JSONSchema,\n handler: readNoteByIndex({\n allCharms: allCharms as unknown as OpaqueRef,\n }),\n },\n editNoteByIndex: {\n description:\n \"Edit the body of a note by its index in the listNotes() list.\",\n inputSchema: {\n type: \"object\",\n properties: {\n index: {\n type: \"number\",\n description: \"The index of the note in the notes list.\",\n },\n body: {\n type: \"string\",\n description: \"The new content of the note.\",\n },\n },\n required: [\"index\", \"body\"],\n } as JSONSchema,\n handler: editNoteByIndex({\n allCharms: allCharms as unknown as OpaqueRef,\n }),\n },\n navigateToNote: {\n description: \"Navigate to a note by its index in the listNotes() list.\",\n inputSchema: {\n type: \"object\",\n properties: {\n index: {\n type: \"number\",\n description: \"The index of the note in the notes list.\",\n },\n },\n required: [\"index\"],\n } as JSONSchema,\n handler: navigateToNote({\n allCharms: allCharms as unknown as OpaqueRef,\n }),\n },\n newNote: {\n description: \"Read the shared note.\",\n inputSchema: {\n type: \"object\",\n properties: {\n title: {\n type: \"string\",\n description: \"The title of the note.\",\n },\n content: {\n type: \"string\",\n description: \"The content of the note.\",\n },\n },\n required: [\"title\"],\n } as JSONSchema,\n handler: newNote({\n allCharms: allCharms as unknown as OpaqueRef,\n }),\n },\n };\n\n const chat = Chat({ messages, tools });\n const note = Note({ title, content, allCharms });\n\n return {\n [NAME]: title,\n chat,\n note,\n content,\n messages,\n mentioned: note.mentioned,\n backlinks: note.backlinks,\n list,\n };\n },\n);\n" }, { "name": "/note.tsx", "contents": "/// \nimport {\n Cell,\n cell,\n Default,\n derive,\n h,\n handler,\n lift,\n NAME,\n navigateTo,\n OpaqueRef,\n recipe,\n toSchema,\n UI,\n} from \"commontools\";\n\nexport type MentionableCharm = {\n [NAME]: string;\n content?: string;\n mentioned?: MentionableCharm[];\n};\n\ntype Input = {\n title: Default;\n content: Default;\n allCharms: Cell;\n};\n\ntype Output = {\n mentioned: Default, []>;\n content: Default;\n backlinks: Default, []>;\n};\n\nconst updateTitle = handler<\n { detail: { value: string } },\n { title: Cell }\n>(\n (event, state) => {\n state.title.set(event.detail?.value ?? \"\");\n },\n);\n\nconst updateContent = handler<\n { detail: { value: string } },\n { content: Cell }\n>(\n (event, state) => {\n state.content.set(event.detail?.value ?? \"\");\n },\n);\n\nconst handleCharmLinkClick = handler<\n {\n detail: {\n charm: Cell;\n };\n },\n Record\n>(({ detail }, _) => {\n return navigateTo(detail.charm);\n});\n\nconst handleNewBacklink = handler<\n {\n detail: {\n text: string;\n charmId: any;\n charm: Cell;\n navigate: boolean;\n };\n },\n {\n allCharms: Cell;\n }\n>(({ detail }, { allCharms }) => {\n console.log(\"new charm\", detail.text, detail.charmId);\n\n if (detail.navigate) {\n return navigateTo(detail.charm);\n } else {\n allCharms.push(detail.charm as unknown as MentionableCharm);\n }\n});\n\nconst handleCharmLinkClicked = handler(\n (_: any, { charm }: { charm: Cell }) => {\n return navigateTo(charm);\n },\n);\n\nconst Note = recipe(\n \"Note\",\n ({ title, content, allCharms }) => {\n const mentioned = cell([]);\n\n const computeBacklinks = lift<\n { allCharms: Cell; content: Cell },\n MentionableCharm[]\n >(\n ({ allCharms, content }) => {\n const cs = allCharms.get();\n if (!cs) return [];\n\n const self = cs.find((c) => c.content === content.get());\n\n const results = self\n ? cs.filter((c) =>\n c.mentioned?.some((m) => m.content === self.content) ?? false\n )\n : [];\n\n return results;\n },\n );\n\n const backlinks: OpaqueRef = computeBacklinks({\n allCharms,\n content: content as unknown as Cell, // TODO(bf): this is valid, but types complain\n });\n\n // The only way to serialize a pattern, apparently?\n const pattern = derive(undefined, () => JSON.stringify(Note));\n\n return {\n [NAME]: title,\n [UI]: (\n \n \n \n \n\n \n \n ),\n title,\n content,\n mentioned,\n backlinks,\n };\n },\n);\n\nexport default Note;\n" }, { "name": "/chatbot-list-view.tsx", "contents": "/// \nimport {\n Cell,\n cell,\n Default,\n derive,\n h,\n handler,\n ID,\n ifElse,\n lift,\n NAME,\n navigateTo,\n OpaqueRef,\n recipe,\n toSchema,\n UI,\n} from \"commontools\";\n\nimport Chat from \"./chatbot-note-composed.tsx\";\nimport { ListItem } from \"./common-tools.tsx\";\n\nexport type MentionableCharm = {\n [NAME]: string;\n content?: string;\n mentioned?: MentionableCharm[];\n};\n\ntype CharmEntry = {\n [ID]: string; // randomId is a string\n local_id: string; // same as ID but easier to access\n charm: any;\n};\n\ntype Input = {\n selectedCharm: Default<{ charm: any }, { charm: undefined }>;\n charmsList: Default;\n allCharms: Cell;\n theme?: {\n accentColor: Default;\n fontFace: Default;\n borderRadius: Default;\n };\n};\n\ntype Output = {\n selectedCharm: Default<{ charm: any }, { charm: undefined }>;\n};\n\nconst removeChat = handler<\n unknown,\n {\n charmsList: Cell;\n id: string;\n selectedCharm: Cell>;\n }\n>(\n (\n _,\n { charmsList, id, selectedCharm },\n ) => {\n const list = charmsList.get();\n const index = list.findIndex((entry) => entry.local_id === id);\n if (index === -1) return;\n\n const removed = list[index];\n const next = [...list];\n next.splice(index, 1);\n charmsList.set(next);\n\n // If we removed the currently selected charm, choose a new selection.\n const current = selectedCharm.get();\n if (current?.charm === removed.charm) {\n const replacement = next[index] ?? next[index - 1];\n if (replacement) {\n selectedCharm.set({ charm: replacement.charm });\n } else {\n selectedCharm.set({ charm: undefined as unknown as any });\n }\n }\n },\n);\n\n// this will be called whenever charm or selectedCharm changes\n// pass isInitialized to make sure we dont call this each time\n// we change selectedCharm, otherwise creates a loop\nconst storeCharm = lift(\n toSchema<{\n charm: any;\n selectedCharm: Cell>;\n charmsList: Cell;\n allCharms: Cell;\n theme?: {\n accentColor: Default;\n fontFace: Default;\n borderRadius: Default;\n };\n isInitialized: Cell;\n }>(),\n undefined,\n ({ charm, selectedCharm, charmsList, isInitialized, allCharms }) => { // Not including `allCharms` is a compile error...\n if (!isInitialized.get()) {\n console.log(\n \"storeCharm storing charm:\",\n charm,\n );\n selectedCharm.set({ charm });\n\n // create the chat charm with a custom name including a random suffix\n const randomId = Math.random().toString(36).substring(2, 10); // Random 8-char string\n charmsList.push({ [ID]: randomId, local_id: randomId, charm });\n\n isInitialized.set(true);\n return charm;\n } else {\n console.log(\"storeCharm: already initialized\");\n }\n return undefined;\n },\n);\n\nconst populateChatList = lift(\n toSchema<{\n charmsList: CharmEntry[];\n allCharms: Cell;\n selectedCharm: Cell<{ charm: any }>;\n }>(),\n undefined,\n (\n { charmsList, allCharms, selectedCharm },\n ) => {\n if (charmsList.length === 0) {\n const isInitialized = cell(false);\n return storeCharm({\n charm: Chat({\n title: \"New Chat\",\n messages: [],\n content: \"\",\n allCharms,\n }),\n selectedCharm,\n charmsList,\n allCharms,\n isInitialized: isInitialized as unknown as Cell,\n });\n }\n\n return charmsList;\n },\n);\n\nconst createChatRecipe = handler<\n unknown,\n {\n selectedCharm: Cell<{ charm: any }>;\n charmsList: Cell;\n allCharms: Cell;\n }\n>(\n (_, { selectedCharm, charmsList, allCharms }) => {\n const isInitialized = cell(false);\n\n const charm = Chat({\n title: \"New Chat\",\n messages: [],\n content: \"\",\n allCharms,\n });\n // store the charm ref in a cell (pass isInitialized to prevent recursive calls)\n return storeCharm({\n charm,\n selectedCharm,\n charmsList: charmsList as unknown as OpaqueRef,\n allCharms,\n isInitialized: isInitialized as unknown as Cell,\n });\n },\n);\n\nconst selectCharm = handler<\n unknown,\n { selectedCharm: Cell<{ charm: any }>; charm: any }\n>(\n (_, { selectedCharm, charm }) => {\n console.log(\"selectCharm: updating selectedCharm to \", charm);\n selectedCharm.set({ charm });\n return selectedCharm;\n },\n);\n\nconst logCharmsList = lift<\n { charmsList: Cell },\n Cell\n>(\n ({ charmsList }) => {\n console.log(\"logCharmsList: \", charmsList.get());\n return charmsList;\n },\n);\n\nconst handleCharmLinkClicked = handler(\n (_: any, { charm }: { charm: Cell }) => {\n return navigateTo(charm);\n },\n);\n\nconst combineLists = lift(\n (\n { allCharms, charmsList }: { allCharms: any[]; charmsList: CharmEntry[] },\n ) => {\n return [...charmsList.map((c) => c.charm), ...allCharms];\n },\n);\n\nconst getSelectedCharm = lift<\n { entry: { charm: any | undefined } },\n {\n chat: unknown;\n note: unknown;\n list: ListItem[];\n backlinks: MentionableCharm[];\n mentioned: MentionableCharm[];\n } | undefined\n>(\n ({ entry }) => {\n return entry?.charm;\n },\n);\n\nconst getCharmName = lift(({ charm }: { charm: any }) => {\n return charm?.[NAME] || \"Unknown\";\n});\n\n// create the named cell inside the recipe body, so we do it just once\nexport default recipe(\n \"Launcher\",\n ({ selectedCharm, charmsList, allCharms, theme }) => {\n logCharmsList({ charmsList: charmsList as unknown as Cell });\n\n populateChatList({\n selectedCharm: selectedCharm as unknown as Cell<\n Pick\n >,\n charmsList,\n allCharms,\n });\n\n const combined = combineLists({\n allCharms: allCharms as unknown as any[],\n charmsList,\n });\n\n const selected = getSelectedCharm({ entry: selectedCharm });\n\n const localTheme = theme ?? {\n accentColor: cell(\"#3b82f6\"),\n fontFace: cell(\"system-ui, -apple-system, sans-serif\"),\n borderRadius: cell(\"0.5rem\"),\n };\n\n return {\n [NAME]: \"Launcher\",\n [UI]: (\n \n \n