Skip to content

Commit 7647d78

Browse files
authored
Add ct-toast and use it for omnibot (#1934)
* Add `ct-toast` and use it for `omnibot` * Use timestamp as ID * Reset if messages cleared
1 parent 69c51cb commit 7647d78

File tree

6 files changed

+844
-4
lines changed

6 files changed

+844
-4
lines changed

packages/patterns/default-app.tsx

Lines changed: 72 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
/// <cts-enable />
22
import {
3+
BuiltInLLMMessage,
34
Cell,
45
cell,
56
derive,
67
handler,
78
ifElse,
9+
lift,
810
NAME,
911
navigateTo,
1012
recipe,
@@ -101,6 +103,52 @@ const toggle = handler<any, { value: Cell<boolean> }>((_, { value }) => {
101103
value.set(!value.get());
102104
});
103105

106+
const messagesToNotifications = lift<
107+
{
108+
messages: BuiltInLLMMessage[];
109+
seen: Cell<number>;
110+
notifications: Cell<{ text: string; timestamp: number }[]>;
111+
}
112+
>(({ messages, seen, notifications }) => {
113+
if (messages.length > 0) {
114+
if (seen.get() >= messages.length) {
115+
// If messages length went backwards, reset seen counter
116+
if (seen.get() > messages.length) {
117+
seen.set(0);
118+
} else {
119+
return;
120+
}
121+
}
122+
123+
const latestMessage = messages[messages.length - 1];
124+
if (latestMessage.role === "assistant") {
125+
const contentText = typeof latestMessage.content === "string"
126+
? latestMessage.content
127+
: latestMessage.content.map((part) => {
128+
if (part.type === "text") {
129+
return part.text;
130+
} else if (part.type === "tool-call") {
131+
return `Tool call: ${part.toolName}`;
132+
} else if (part.type === "tool-result") {
133+
return part.output.type === "text"
134+
? part.output.value
135+
: JSON.stringify(part.output.value);
136+
} else if (part.type === "image") {
137+
return "[Image]";
138+
}
139+
return "";
140+
}).join("");
141+
142+
notifications.push({
143+
text: contentText,
144+
timestamp: Date.now(),
145+
});
146+
147+
seen.set(messages.length);
148+
}
149+
}
150+
});
151+
104152
export default recipe<CharmsListInput, CharmsListOutput>(
105153
"DefaultCharmList",
106154
(_) => {
@@ -110,6 +158,8 @@ export default recipe<CharmsListInput, CharmsListOutput>(
110158
);
111159
const index = BacklinksIndex({ allCharms });
112160
const fabExpanded = cell(false);
161+
const notifications = cell<{ text: string; timestamp: number }[]>([]);
162+
const seen = cell<number>(0);
113163

114164
const omnibot = Chatbot({
115165
messages: [],
@@ -126,6 +176,14 @@ export default recipe<CharmsListInput, CharmsListOutput>(
126176
},
127177
});
128178

179+
messagesToNotifications({
180+
messages: omnibot.messages,
181+
seen: seen as unknown as Cell<number>,
182+
notifications: notifications as unknown as Cell<
183+
{ id: string; text: string; timestamp: number }[]
184+
>,
185+
});
186+
129187
return {
130188
backlinksIndex: index,
131189
[NAME]: str`DefaultCharmList (${allCharms.length})`,
@@ -215,10 +273,20 @@ export default recipe<CharmsListInput, CharmsListOutput>(
215273
{omnibot.ui.chatLog as any}
216274
</div>
217275
),
218-
fabUI: ifElse(
219-
fabExpanded,
220-
omnibot.ui.promptInput,
221-
<ct-button onClick={toggle({ value: fabExpanded })}></ct-button>,
276+
fabUI: (
277+
<>
278+
<ct-toast-stack
279+
$notifications={notifications}
280+
position="top-right"
281+
auto-dismiss={5000}
282+
max-toasts={5}
283+
/>
284+
{ifElse(
285+
fabExpanded,
286+
omnibot.ui.promptInput,
287+
<ct-button onClick={toggle({ value: fabExpanded })}></ct-button>,
288+
)}
289+
</>
222290
),
223291
};
224292
},

0 commit comments

Comments
 (0)