Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 104 additions & 30 deletions packages/patterns/chatbot-list-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,18 @@ import {
lift,
NAME,
navigateTo,
OpaqueRef,
recipe,
UI,
} from "commontools";

import Chat from "./chatbot.tsx";
import Chat from "./chatbot-note-composed.tsx";

export type MentionableCharm = {
[NAME]: string;
content?: string;
mentioned?: MentionableCharm[];
};

type CharmEntry = {
[ID]: string; // randomId is a string
Expand All @@ -26,9 +33,12 @@ type CharmEntry = {
type Input = {
selectedCharm: Default<{ charm: any }, { charm: undefined }>;
charmsList: Default<CharmEntry[], []>;
allCharms: Cell<any[]>;
};

type Output = Input;
type Output = {
selectedCharm: Default<{ charm: any }, { charm: undefined }>;
};

// this will be called whenever charm or selectedCharm changes
// pass isInitialized to make sure we dont call this each time
Expand Down Expand Up @@ -64,7 +74,7 @@ const storeCharm = lift(
if (!isInitialized.get()) {
console.log(
"storeCharm storing charm:",
JSON.stringify(charm),
charm,
);
selectedCharm.set({ charm });

Expand All @@ -83,14 +93,24 @@ const storeCharm = lift(

const createChatRecipe = handler<
unknown,
{ selectedCharm: Cell<{ charm: any }>; charmsList: Cell<CharmEntry[]> }
{
selectedCharm: Cell<{ charm: any }>;
charmsList: Cell<CharmEntry[]>;
allCharms: Cell<any[]>;
}
>(
(_, { selectedCharm, charmsList }) => {
(_, { selectedCharm, charmsList, allCharms }) => {
const isInitialized = cell(false);

const charm = Chat({
title: "New Chat",
messages: [],
tools: undefined,
expandChat: false,
content: "",
allCharms: [
...allCharms.get(),
...charmsList.get().map((i) => i.charm),
] as unknown as OpaqueRef<Cell<MentionableCharm[]>>, // TODO(bf): types...
});
// store the charm ref in a cell (pass isInitialized to prevent recursive calls)
return storeCharm({ charm, selectedCharm, charmsList, isInitialized });
Expand Down Expand Up @@ -132,42 +152,96 @@ const logCharmsList = lift(
},
);

const handleCharmLinkClicked = handler(
(_: any, { charm }: { charm: Cell<MentionableCharm> }) => {
return navigateTo(charm);
},
);

// create the named cell inside the recipe body, so we do it just once
export default recipe<Input, Output>(
"Launcher",
({ selectedCharm, charmsList }) => {
({ selectedCharm, charmsList, allCharms }) => {
logCharmsList({ charmsList });

return {
[NAME]: "Launcher",
[UI]: (
<div>
<ct-button onClick={createChatRecipe({ selectedCharm, charmsList })}>
Create New Chat
</ct-button>

<div>
<h3>Chat List</h3>
<ct-screen>
<div slot="header">
<ct-button
onClick={createChatRecipe({
selectedCharm,
charmsList,
allCharms: allCharms as unknown as any,
})}
>
Create New Chat
</ct-button>
</div>
<div>
{charmsList.map((charmEntry, i) => (
<ct-autolayout tabNames={["Chat", "Tools"]}>
{
selectedCharm.charm.chat[UI] // workaround: CT-987
}
{
selectedCharm.charm.note[UI] // workaround: CT-987
}

<aside slot="left">
<div>
index={i} chat ID: {charmEntry.local_id}
<ct-button
onClick={selectCharm({
selectedCharm: selectedCharm,
charm: charmEntry.charm,
})}
>
LOAD
</ct-button>
<h3>Chat List</h3>
</div>
))}
</div>
<div>
{charmsList.map((charmEntry, i) => (
<div>
index={i} chat ID: {charmEntry.local_id}
<ct-button
onClick={selectCharm({
selectedCharm: selectedCharm,
charm: charmEntry.charm,
})}
>
LOAD
</ct-button>
</div>
))}
</div>
</aside>

<div>--- end chat list ---</div>
<div>{selectedCharm.charm}</div>
</div>
<aside slot="right">
{ifElse(
selectedCharm.charm,
<>
<div>
<label>Backlinks</label>
<ct-vstack>
{selectedCharm?.charm?.backlinks?.map((
charm: MentionableCharm,
) => (
<ct-button onClick={handleCharmLinkClicked({ charm })}>
{charm[NAME]}
</ct-button>
))}
</ct-vstack>
</div>
<details>
<summary>Mentioned Charms</summary>
<ct-vstack>
{selectedCharm?.charm?.mentioned?.map((
charm: MentionableCharm,
) => (
<ct-button onClick={handleCharmLinkClicked({ charm })}>
{charm[NAME]}
</ct-button>
))}
</ct-vstack>
</details>
</>,
null,
)}
</aside>
</ct-autolayout>
</ct-screen>
),
selectedCharm,
charmsList,
Expand Down
163 changes: 163 additions & 0 deletions packages/patterns/chatbot-note-composed.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
/// <cts-enable />
import {
BuiltInLLMMessage,
Cell,
cell,
Default,
derive,
fetchData,
getRecipeEnvironment,
h,
handler,
ID,
ifElse,
JSONSchema,
lift,
llm,
llmDialog,
NAME,
navigateTo,
OpaqueRef,
recipe,
str,
Stream,
toSchema,
UI,
} from "commontools";

import Chat from "./chatbot.tsx";
import Note from "./note.tsx";

export type MentionableCharm = {
[NAME]: string;
content?: string;
mentioned?: MentionableCharm[];
};

type NoteResult = {
content: Default<string, "">;
};

export type NoteInput = {
content: Default<string, "">;
allCharms: Cell<MentionableCharm[]>;
};

const handleCharmLinkClick = handler<
{
detail: {
charm: Cell<MentionableCharm>;
};
},
Record<string, never>
>(({ detail }, _) => {
return navigateTo(detail.charm);
});

const handleCharmLinkClicked = handler(
(_: any, { charm }: { charm: Cell<MentionableCharm> }) => {
return navigateTo(charm);
},
);

type LLMTestInput = {
title: Default<string, "LLM Test">;
messages: Default<Array<BuiltInLLMMessage>, []>;
expandChat: Default<boolean, false>;
content: Default<string, "">;
allCharms: Cell<MentionableCharm[]>;
};

type LLMTestResult = {
messages: Default<Array<BuiltInLLMMessage>, []>;
mentioned: Default<Array<MentionableCharm>, []>;
backlinks: Default<Array<MentionableCharm>, []>;
content: Default<string, "">;
note: any;
chat: any;
};

// put a note at the end of the outline (by appending to root.children)
const editNote = handler<
{
/** The text content of the note */
body: string;
/** A cell to store the result message indicating success or error */
result: Cell<string>;
},
{ content: Cell<string> }
>(
(args, state) => {
try {
state.content.set(args.body);

args.result.set(
`Updated note!`,
);
} catch (error) {
args.result.set(`Error: ${(error as any)?.message || "<error>"}`);
}
},
);

const readNote = handler<
{
/** A cell to store the result text */
result: Cell<string>;
},
{ content: string }
>(
(args, state) => {
try {
args.result.set(state.content);
} catch (error) {
args.result.set(`Error: ${(error as any)?.message || "<error>"}`);
}
},
);

export default recipe<LLMTestInput, LLMTestResult>(
"Note",
({ title, expandChat, messages, content, allCharms }) => {
const tools = {
editNote: {
description: "Modify the shared note.",
inputSchema: {
type: "object",
properties: {
body: {
type: "string",
description: "The content of the note.",
},
},
required: ["body"],
} as JSONSchema,
handler: editNote({ content }),
},
readNote: {
description: "Read the shared note.",
inputSchema: {
type: "object",
properties: {},
required: [],
} as JSONSchema,
handler: readNote({ content }),
},
};

const chat = Chat({ messages, tools });
const { addMessage, cancelGeneration, pending } = chat;

const note = Note({ title, content, allCharms });

return {
[NAME]: title,
chat,
note,
content,
messages,
mentioned: note.mentioned,
backlinks: note.backlinks,
};
},
);
Loading
Loading