11/// <cts-enable />
22import {
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+
104152export 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