///
import {
BuiltInLLMMessage,
Cell,
Default,
handler,
NAME,
navigateTo,
OpaqueRef,
pattern,
wish,
} from "commontools";
import Chat from "../chatbot.tsx";
import Note from "../notes/note.tsx";
// Simple random ID generator (crypto.randomUUID not available in pattern env)
const generateId = () =>
`${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 11)}`;
import {
addListItem,
calculator,
ListItem,
readListItems,
readWebpage,
searchWeb,
} from "../system/common-tools.tsx";
import { type MentionableCharm } from "../system/backlinks-index.tsx";
type ChatbotNoteInput = {
title?: Cell>;
messages?: Cell, []>>;
};
type ChatbotNoteResult = {
messages: Default, []>;
chat: any;
list: Default;
// Optional: expose sub-charms as mentionable targets
mentionable?: MentionableCharm[];
};
const newNote = handler<
{
/** The text content of the note */
title: string;
content?: string;
/** A cell to store the result message indicating success or error */
result: Cell;
},
{ allCharms: Cell; index: any }
>(
(args, _) => {
try {
const n = Note({
title: args.title,
content: args.content ?? "",
noteId: generateId(),
});
args.result.set(
`Created note ${args.title}`,
);
// TODO(bf): we have to navigate here until DX1 lands
// then we go back to pushing to allCharms
return navigateTo(n);
} catch (error) {
args.result.set(`Error: ${(error as any)?.message || ""}`);
}
},
);
const listMentionable = handler<
{
/** A cell to store the result text */
result: Cell;
},
{ mentionable: MentionableCharm[] }
>(
(args, state) => {
try {
const namesList = state.mentionable.map((charm) => charm[NAME]);
args.result.set(JSON.stringify(namesList));
} catch (error) {
args.result.set(`Error: ${(error as any)?.message || ""}`);
}
},
);
const readContentByIndex = handler<
{
/** A cell to store the result text */
index: number;
result: Cell;
},
{ allNotes: Note[] }
>(
(args, state) => {
try {
args.result.set(
state.allNotes[args.index]?.content || "No content found",
);
} catch (error) {
args.result.set(`Error: ${(error as any)?.message || ""}`);
}
},
);
type Note = MentionableCharm & { content: string };
const editContentByIndex = handler<
{
/** The index of the note to edit */
index: number;
/** The new text content of the note */
body: string;
/** A cell to store the result message indicating success or error */
result: Cell;
},
{ allNotes: Cell }
>(
(args, state) => {
try {
const charms = state.allNotes.get();
if (args.index < 0 || args.index >= charms.length) {
args.result.set(`Error: Invalid index ${args.index}`);
return;
}
state.allNotes.key(args.index).key("content").set(args.body);
args.result.set(`Updated note at index ${args.index}!`);
} catch (error) {
args.result.set(`Error: ${(error as any)?.message || ""}`);
}
},
);
const navigateToNote = handler<
{
/** The index of the note to navigate to */
index: number;
/** A cell to store the result message indicating success or error */
result: Cell;
},
{ allCharms: Cell }
>(
(args, state) => {
try {
const charms = state.allCharms.get();
if (args.index < 0 || args.index >= charms.length) {
args.result.set(`Error: Invalid index ${args.index}`);
return;
}
const targetCharm = charms[args.index];
args.result.set(`Navigating to note: ${targetCharm[NAME]}`);
return navigateTo(state.allCharms.key(args.index));
} catch (error) {
args.result.set(`Error: ${(error as any)?.message || ""}`);
}
},
);
type BacklinksIndex = {
mentionable: MentionableCharm[];
};
export default pattern(
({ title, messages }) => {
const allCharms = wish>("#allCharms");
const index = wish>(
"#default/backlinksIndex",
);
const mentionable = wish>(
"#mentionable",
);
const list = Cell.of([]);
const tools = {
searchWeb: {
pattern: searchWeb,
},
readWebpage: {
pattern: readWebpage,
},
calculator: {
pattern: calculator,
},
addListItem: {
handler: addListItem({ list }),
},
readListItems: {
handler: readListItems({ list }),
},
listMentionable: {
description:
"List all mentionable items titles (read the body with readNoteByIndex).",
handler: listMentionable({ mentionable }),
},
readContentByIndex: {
description:
"Read the content of a mentionable by its index in the listMentionable() list (if possible)",
handler: readContentByIndex({
allNotes: mentionable as unknown as OpaqueRef,
}),
},
editContentByIndex: {
description:
"Edit the content of a mentionable by its index in the listMentionable() list (if possible)",
handler: editContentByIndex({
allNotes: mentionable as unknown as OpaqueRef,
}),
},
navigateToNote: {
description:
"Navigate to a mentionable by its index in the listMentionable() list.",
handler: navigateToNote({
allCharms: allCharms as unknown as OpaqueRef,
}),
},
newNote: {
description: "Create a new note instance",
handler: newNote({
allCharms: allCharms as unknown as OpaqueRef,
index: index as unknown as OpaqueRef,
}),
},
};
const chat = Chat({ messages, tools });
return {
[NAME]: title,
chat,
messages,
list,
// Expose sub-charms as mentionable targets
mentionable: [chat as unknown as MentionableCharm],
};
},
);