11# Copyright 2025 © BeeAI a Series of LF Projects, LLC
22# SPDX-License-Identifier: Apache-2.0
33
4+ import asyncio
45import os
56import random
67import re
2930]
3031
3132
33+ def generate_recipe_response (recipe_title : str , description : str , closing_message : str ) -> str :
34+ """Generate a recipe response with the given title, description, and closing message."""
35+ return f"""\
36+ { description }
37+
38+ ```recipe
39+ # { recipe_title }
40+
41+ ## Ingredients
42+ - bread (1 slice)
43+ - butter (1 slice)
44+
45+ ## Instructions
46+ 1. Cut a slice of bread.
47+ 2. Cut a slice of butter.
48+ 3. Spread the slice of butter on the slice of bread.
49+ ```
50+
51+ { closing_message }
52+ """
53+
54+
3255@server .agent (
3356 name = "Canvas example agent" ,
3457)
@@ -49,81 +72,85 @@ async def artifacts_agent(
4972 print (f"Canvas Edit Request: { canvas_edit_request } " )
5073
5174 if canvas_edit_request :
52- recipe_title = "Canvas Recipe EDITED"
53-
5475 original_recipe = (
5576 canvas_edit_request .artifact .parts [0 ].root .text
5677 if isinstance (canvas_edit_request .artifact .parts [0 ].root , TextPart )
5778 else ""
5879 )
5980 edited_part = original_recipe [canvas_edit_request .start_index : canvas_edit_request .end_index ]
60- description = f"You requested to edit this part:\n \n *{ edited_part } *\n \n "
61-
62- response = f"""\
63- { description }
64-
65- ```recipe
66- # Canvas Recipe EDITED
67-
68- ## Ingredients
69- - bread (1 slice)
70- - butter (1 slice)
71-
72- ## Instructions
73- 1. Cut a slice of bread.
74- 2. Cut a slice of butter.
75- 3. Spread the slice of butter on the slice of bread.
76- ```
77-
78- Enjoy your edited meal!
79- """
81+ description = f"You requested to edit this part:\n \n { edited_part } \n \n "
82+ recipe_title = "Canvas Recipe EDITED"
83+ closing_message = "Enjoy your edited meal!"
8084 else :
8185 recipe_title = random .choice (RECIPE_TITLES )
8286 description = "Here's your recipe:"
87+ closing_message = "Enjoy your meal!"
8388
84- response = f"""\
85- { description }
86-
87- ```recipe
88- # { recipe_title }
89-
90- ## Ingredients
91- - bread (1 slice)
92- - butter (1 slice)
93-
94- ## Instructions
95- 1. Cut a slice of bread.
96- 2. Cut a slice of butter.
97- 3. Spread the slice of butter on the slice of bread.
98- ```
99-
100- Enjoy your meal!
101- """
89+ response = generate_recipe_response (recipe_title , description , closing_message )
10290
10391 match = re .compile (r"```recipe\n(.*?)\n```" , re .DOTALL ).search (response )
104- print (
105- f"Match: { match } \n pre_text: { response [: match .start () if match else 'N/A' ]} \n post_text: { response [match .end () if match else 'N/A' :]} "
106- )
10792
10893 if not match :
10994 yield response
11095 return
11196
97+ await asyncio .sleep (1 )
98+
11299 if pre_text := response [: match .start ()].strip ():
113100 message = AgentMessage (text = pre_text )
114101 yield message
115102 await context .store (message )
116103
104+ await asyncio .sleep (1 )
105+
117106 recipe_content = match .group (1 ).strip ()
118107 first_line = recipe_content .split ("\n " , 1 )[0 ]
108+
109+ # Extract the title and remove it from content if it's a heading
110+ if first_line .startswith ("#" ):
111+ artifact_name = first_line .lstrip ("# " ).strip ()
112+ # Remove the first line from recipe_content if there's more content
113+ recipe_content = recipe_content .split ("\n " , 1 )[1 ].strip () if "\n " in recipe_content else recipe_content
114+ else :
115+ artifact_name = "Recipe"
116+
117+ # Split recipe content into x chunks for streaming
118+ num_chunks = 8
119+ content_length = len (recipe_content )
120+ chunk_size = content_length // num_chunks
121+ chunks = []
122+
123+ for i in range (num_chunks ):
124+ start = i * chunk_size
125+ # Last chunk gets any remaining characters
126+ end = content_length if i == num_chunks - 1 else (i + 1 ) * chunk_size
127+ chunks .append (recipe_content [start :end ])
128+
119129 artifact = AgentArtifact (
120- # artifact_id='recipe-artifact-1',
121- name = first_line .lstrip ("# " ).strip () if first_line .startswith ("#" ) else "Recipe" ,
130+ name = artifact_name ,
122131 parts = [TextPart (text = recipe_content )],
123132 )
124- yield artifact
125133 await context .store (artifact )
126134
135+ # Send first chunk with artifact_id to establish the artifact
136+ first_artifact = AgentArtifact (
137+ artifact_id = artifact .artifact_id ,
138+ name = artifact_name ,
139+ parts = [TextPart (text = chunks [0 ])],
140+ )
141+ yield first_artifact
142+
143+ # Send remaining chunks using the same artifact_id
144+ for chunk in chunks [1 :]:
145+ chunk_artifact = AgentArtifact (
146+ artifact_id = artifact .artifact_id ,
147+ name = artifact_name ,
148+ parts = [TextPart (text = chunk )],
149+ )
150+ yield chunk_artifact
151+ await context .store (chunk_artifact )
152+ await asyncio .sleep (0.3 )
153+
127154 if post_text := response [match .end () :].strip ():
128155 message = AgentMessage (text = post_text )
129156 yield message
0 commit comments