forked from CopilotKit/CopilotKit
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathfiles.json
More file actions
663 lines (663 loc) · 363 KB
/
Copy pathfiles.json
File metadata and controls
663 lines (663 loc) · 363 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
{
"crewai_agentic_chat": {
"files": [
{
"name": "agent.py",
"content": "\"\"\"\nA simple agentic chat flow.\n\"\"\"\n\nfrom crewai.flow.flow import Flow, start\nfrom litellm import completion\nfrom copilotkit.crewai import copilotkit_stream, CopilotKitState\n\nclass AgenticChatFlow(Flow[CopilotKitState]):\n\n @start()\n async def chat(self):\n system_prompt = \"You are a helpful assistant.\"\n\n # 1. Run the model and stream the response\n # Note: In order to stream the response, wrap the completion call in\n # copilotkit_stream and set stream=True.\n response = await copilotkit_stream(\n completion(\n\n # 1.1 Specify the model to use\n model=\"openai/gpt-4o\",\n messages=[\n {\n \"role\": \"system\", \n \"content\": system_prompt\n },\n *self.state.messages\n ],\n\n # 1.2 Bind the available tools to the model\n tools=[\n *self.state.copilotkit.actions,\n ],\n\n # 1.3 Disable parallel tool calls to avoid race conditions,\n # enable this for faster performance if you want to manage\n # the complexity of running tool calls in parallel.\n parallel_tool_calls=False,\n stream=True\n )\n )\n\n message = response.choices[0].message\n\n # 2. Append the message to the messages in state\n self.state.messages.append(message)\n\n",
"path": "crewai_agentic_chat/agent.py",
"language": "python",
"type": "file"
},
{
"name": "page.tsx",
"content": "\"use client\";\nimport React, { useState } from \"react\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport { CopilotKit, useCopilotAction, useCopilotChat } from \"@copilotkit/react-core\";\nimport { CopilotChat, useCopilotChatSuggestions } from \"@copilotkit/react-ui\";\nimport { chatSuggestions, initialPrompt } from \"@/lib/prompts\";\nimport { AGENT_TYPE } from \"../../../src/config\"\nconst AgenticChat: React.FC = () => {\n return (\n <CopilotKit\n runtimeUrl={AGENT_TYPE == \"general\" ? \"/api/copilotkit?crewai=true\" : \"/api/copilotkit\"}\n showDevConsole={false}\n agent=\"agentic_chat\"\n >\n <Chat />\n </CopilotKit>\n );\n};\n\nconst Chat = () => {\n const [background, setBackground] = useState<string>(\"--copilot-kit-background-color\");\n\n useCopilotAction({\n name: \"change_background\",\n description:\n \"Change the background color of the chat. Can be anything that the CSS background attribute accepts. Regular colors, linear of radial gradients etc.\",\n parameters: [\n {\n name: \"background\",\n type: \"string\",\n description: \"The background. Prefer gradients.\",\n },\n ],\n handler: ({ background }) => {\n console.log(\"background\", background);\n setBackground(background);\n },\n });\n\n useCopilotChatSuggestions({\n instructions: chatSuggestions.agenticChat,\n // className : \"bg-gray-100\"\n })\n\n\n return (\n <div\n className=\"flex justify-center items-center h-full w-full\"\n style={{ background }}\n >\n <div className=\"w-8/10 h-8/10 rounded-lg \">\n <CopilotChat\n className=\"h-full w-full rounded-2xl py-6\"\n labels={{ initial: initialPrompt.agenticChat }}\n />\n </div>\n </div>\n );\n};\n\nexport default AgenticChat;\n",
"path": "crewai_agentic_chat/page.tsx",
"language": "typescript",
"type": "file"
},
{
"name": "style.css",
"content": ".copilotKitInput {\n border-bottom-left-radius: 0.75rem;\n border-bottom-right-radius: 0.75rem;\n border-top-left-radius: 0.75rem;\n border-top-right-radius: 0.75rem;\n border: 1px solid var(--copilot-kit-separator-color) !important;\n}\n \n.copilotKitChat {\n background-color: #fff !important;\n}\n \n",
"path": "crewai_agentic_chat/style.css",
"language": "css",
"type": "file"
},
{
"name": "README.mdx",
"content": "# 🤖 Agentic Chat with Frontend Tools\n\n## What This Demo Shows\n\nThis demo showcases CopilotKit's **agentic chat** capabilities with **frontend tool integration**:\n1. **Natural Conversation**: Chat with your Copilot in a familiar chat interface\n2. **Frontend Tool Execution**: The Copilot can directly interacts with your UI by calling frontend functions\n3. **Seamless Integration**: Tools defined in the frontend and automatically discovered and made available to the agent\n\n## How to Interact\n\nTry asking your Copilot to:\n- \"Can you change the background color to something more vibrant?\"\n- \"Make the background a blue to purple gradient\"\n- \"Set the background to a sunset-themed gradient\"\n- \"Change it back to a simple light color\"\n\nYou can also chat about other topics - the agent will respond conversationally while having the ability to use your UI tools when appropriate.\n\n## ✨ Frontend Tool Integration in Action\n\n**What's happening technically:**\n- The React component defines a frontend function using `useCopilotAction`\n- CopilotKit automatically exposes this function to the agent\n- When you make a request, the agent determines whether to use the tool\n- The agent calls the function with the appropriate parameters\n- The UI immediately updates in response\n\n**What you'll see in this demo:**\n- The Copilot understands requests to change the background\n- It generates CSS values for colors and gradients\n- When it calls the tool, the background changes instantly\n- The agent provides a conversational response about the changes it made\n\nThis technique of exposing frontend functions to your Copilot can be extended to any UI manipulation you want to enable, from theme changes to data filtering, navigation, or complex UI state management! ",
"path": "crewai_agentic_chat/README.mdx",
"language": "markdown",
"type": "file"
}
]
},
"crewai_agentic_generative_ui": {
"files": [
{
"name": "agent.py",
"content": "\"\"\"\nAn example demonstrating agentic generative UI.\n\"\"\"\n\nimport json\nimport asyncio\nfrom crewai.flow.flow import Flow, start, router, listen, or_\nfrom copilotkit.crewai import (\n copilotkit_stream,\n CopilotKitState,\n copilotkit_predict_state,\n copilotkit_emit_state\n)\nfrom litellm import completion\nfrom pydantic import BaseModel\nfrom typing import Literal, List\n\n# This tool simulates performing a task on the server.\n# The tool call will be streamed to the frontend as it is being generated.\nPERFORM_TASK_TOOL = {\n \"type\": \"function\",\n \"function\": {\n \"name\": \"generate_task_steps\",\n \"description\": \"Make up 10 steps (only a couple of words per step) that are required for a task. The step should be in gerund form (i.e. Digging hole, opening door, ...)\",\n \"parameters\": {\n \"type\": \"object\",\n \"properties\": {\n \"steps\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"properties\": {\n \"description\": {\n \"type\": \"string\",\n \"description\": \"The text of the step in gerund form\"\n },\n \"status\": {\n \"type\": \"string\",\n \"enum\": [\"pending\"],\n \"description\": \"The status of the step, always 'pending'\"\n }\n },\n \"required\": [\"description\", \"status\"]\n },\n \"description\": \"An array of 10 step objects, each containing text and status\"\n }\n },\n \"required\": [\"steps\"]\n }\n }\n}\n\nclass TaskStep(BaseModel):\n description: str\n status: Literal[\"pending\", \"completed\"]\n\nclass AgentState(CopilotKitState):\n \"\"\"\n Here we define the state of the agent\n\n In this instance, we're inheriting from CopilotKitState, which will bring in\n the CopilotKitState fields. We're also adding a custom field, `steps`,\n which will be used to store the steps of the task.\n \"\"\"\n steps: List[TaskStep] = []\n\n\nclass AgenticGenerativeUIFlow(Flow[AgentState]):\n \"\"\"\n This is a sample flow that uses the CopilotKit framework to create a chat agent.\n \"\"\"\n\n \n @start()\n async def start_flow(self):\n \"\"\"\n This is the entry point for the flow.\n \"\"\"\n self.state.steps = []\n\n @router(or_(start_flow, \"simulate_task\"))\n async def chat(self):\n \"\"\"\n Standard chat node.\n \"\"\"\n system_prompt = \"\"\"\n You are a helpful assistant assisting with any task. \n When asked to do something, you MUST call the function `generate_task_steps`\n that was provided to you.\n If you called the function, you MUST NOT repeat the steps in your next response to the user.\n Just give a very brief summary (one sentence) of what you did with some emojis. \n Always say you actually did the steps, not merely generated them.\n \"\"\"\n\n # 1. Here we specify that we want to stream the tool call to generate_task_steps\n # to the frontend as state.\n await copilotkit_predict_state({\n \"steps\": {\n \"tool\": \"generate_task_steps\",\n \"tool_argument\": \"steps\"\n }\n })\n\n # 2. Run the model and stream the response\n # Note: In order to stream the response, wrap the completion call in\n # copilotkit_stream and set stream=True.\n response = await copilotkit_stream(\n completion(\n\n # 2.1 Specify the model to use\n model=\"openai/gpt-4o\",\n messages=[\n {\n \"role\": \"system\", \n \"content\": system_prompt\n },\n *self.state.messages\n ],\n\n # 2.2 Bind the tools to the model\n tools=[\n *self.state.copilotkit.actions,\n PERFORM_TASK_TOOL\n ],\n\n # 2.3 Disable parallel tool calls to avoid race conditions,\n # enable this for faster performance if you want to manage\n # the complexity of running tool calls in parallel.\n parallel_tool_calls=False,\n stream=True\n )\n )\n\n message = response.choices[0].message\n\n # 3. Append the message to the messages in state\n self.state.messages.append(message)\n\n # 4. Handle tool call\n if message.get(\"tool_calls\"):\n tool_call = message[\"tool_calls\"][0]\n tool_call_id = tool_call[\"id\"]\n tool_call_name = tool_call[\"function\"][\"name\"]\n tool_call_args = json.loads(tool_call[\"function\"][\"arguments\"])\n\n if tool_call_name == \"generate_task_steps\":\n # Convert each step in the JSON array to a TaskStep instance\n self.state.steps = [TaskStep(**step) for step in tool_call_args[\"steps\"]]\n\n # 4.1 Append the result to the messages in state\n self.state.messages.append({\n \"role\": \"tool\",\n \"content\": \"Steps executed.\",\n \"tool_call_id\": tool_call_id\n })\n return \"route_simulate_task\"\n\n # 5. If our tool was not called, return to the end route\n return \"route_end\"\n\n @listen(\"route_simulate_task\")\n async def simulate_task(self):\n \"\"\"\n Simulate the task.\n \"\"\"\n for step in self.state.steps:\n # simulate executing the step\n await asyncio.sleep(1)\n step.status = \"completed\"\n await copilotkit_emit_state(self.state)\n\n @listen(\"route_end\")\n async def end(self):\n \"\"\"\n End the flow.\n \"\"\"",
"path": "crewai_agentic_generative_ui/agent.py",
"language": "python",
"type": "file"
},
{
"name": "page.tsx",
"content": "\"use client\";\nimport React from \"react\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport { CopilotKit, useCoAgentStateRender } from \"@copilotkit/react-core\";\nimport { CopilotChat, useCopilotChatSuggestions } from \"@copilotkit/react-ui\";\nimport { chatSuggestions, initialPrompt } from \"@/lib/prompts\";\nimport { AGENT_TYPE } from \"@/config\";\nconst AgenticGenerativeUI: React.FC = () => {\n return (\n <CopilotKit\n runtimeUrl={AGENT_TYPE == \"general\" ? \"/api/copilotkit?crewai=true\" : \"/api/copilotkit\"}\n showDevConsole={false}\n agent=\"agentic_generative_ui\"\n >\n <Chat />\n </CopilotKit>\n );\n};\n\ninterface AgentState {\n steps: {\n description: string;\n status: \"pending\" | \"completed\";\n }[];\n}\n\nconst Chat = () => {\n useCoAgentStateRender<AgentState>({\n name: \"agentic_generative_ui\",\n render: ({ state }) => {\n if (!state.steps || state.steps.length === 0) {\n return null;\n }\n\n return (\n <div className=\"flex\">\n <div className=\"bg-gray-100 rounded-lg w-[500px] p-4 text-black space-y-2\">\n {state.steps.map((step, index) => {\n if (step.status === \"completed\") {\n return (\n <div key={index} className=\"text-sm\">\n ✓ {step.description}\n </div>\n );\n } else if (\n step.status === \"pending\" &&\n index === state.steps.findIndex((s) => s.status === \"pending\")\n ) {\n return (\n <div\n key={index}\n className=\"text-3xl font-bold text-slate-700\"\n >\n <Spinner />\n {step.description}\n </div>\n );\n } else {\n return (\n <div key={index} className=\"text-sm\">\n <Spinner />\n {step.description}\n </div>\n );\n }\n })}\n </div>\n </div>\n );\n },\n });\n\n\n useCopilotChatSuggestions({\n instructions: chatSuggestions.agenticGenerativeUI,\n })\n\n return (\n <div className=\"flex justify-center items-center h-full w-full\">\n <div className=\"w-8/10 h-8/10 rounded-lg\">\n <CopilotChat\n className=\"h-full rounded-2xl\"\n labels={{ initial: initialPrompt.agenticGenerativeUI }}\n />\n </div>\n </div>\n );\n};\n\nfunction Spinner() {\n return (\n <svg\n className=\"mr-2 size-3 animate-spin text-slate-500\"\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n >\n <circle\n className=\"opacity-25\"\n cx=\"12\"\n cy=\"12\"\n r=\"10\"\n stroke=\"currentColor\"\n strokeWidth=\"4\"\n ></circle>\n <path\n className=\"opacity-75\"\n fill=\"currentColor\"\n d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z\"\n ></path>\n </svg>\n );\n}\n\nexport default AgenticGenerativeUI;\n",
"path": "crewai_agentic_generative_ui/page.tsx",
"language": "typescript",
"type": "file"
},
{
"name": "style.css",
"content": ".copilotKitInput {\n border-bottom-left-radius: 0.75rem;\n border-bottom-right-radius: 0.75rem;\n border-top-left-radius: 0.75rem;\n border-top-right-radius: 0.75rem;\n border: 1px solid var(--copilot-kit-separator-color) !important;\n}\n\n.copilotKitChat {\n background-color: #fff !important;\n}\n",
"path": "crewai_agentic_generative_ui/style.css",
"language": "css",
"type": "file"
},
{
"name": "README.mdx",
"content": "# 🚀 Agentic Generative UI Task Executor\n\n## What This Demo Shows\n\nThis demo showcases CopilotKit's **agentic generative UI** capabilities:\n1. **Real-time Status Updates**: The Copilot provides live feedback as it works through complex tasks\n2. **Long-running Task Execution**: See how agents can handle extended processes with continuous feedback\n3. **Dynamic UI Generation**: The interface updates in real-time to reflect the agent's progress\n\n## How to Interact\n\nSimply ask your Copilot to perform any moderately complex task:\n- \"Make me a sandwich\"\n- \"Plan a vacation to Japan\"\n- \"Create a weekly workout routine\"\n\nThe Copilot will break down the task into steps and begin \"executing\" them, providing real-time status updates as it progresses.\n\n## ✨ Agentic Generative UI in Action\n\n**What's happening technically:**\n- The agent analyzes your request and creates a detailed execution plan\n- Each step is processed sequentially with realistic timing\n- Status updates are streamed to the frontend using CopilotKit's streaming capabilities\n- The UI dynamically renders these updates without page refreshes\n- The entire flow is managed by the agent, requiring no manual intervention\n\n**What you'll see in this demo:**\n- The Copilot breaks your task into logical steps\n- A status indicator shows the current progress\n- Each step is highlighted as it's being executed\n- Detailed status messages explain what's happening at each moment\n- Upon completion, you receive a summary of the task execution\n\nThis pattern of providing real-time progress for long-running tasks is perfect for scenarios where users benefit from transparency into complex processes - from data analysis to content creation, system configurations, or multi-stage workflows! ",
"path": "crewai_agentic_generative_ui/README.mdx",
"language": "markdown",
"type": "file"
}
]
},
"crewai_human_in_the_loop": {
"files": [
{
"name": "agent.py",
"content": "\"\"\"\nAn example demonstrating agentic generative UI.\n\"\"\"\n\nfrom crewai.flow.flow import Flow, start, router, listen\nfrom copilotkit.crewai import (\n copilotkit_stream, \n CopilotKitState, \n)\nfrom litellm import completion\nfrom pydantic import BaseModel\nfrom typing import Literal, List\n\n# This tool simulates performing a task on the server.\n# The tool call will be streamed to the frontend as it is being generated.\nDEFINE_TASK_TOOL = {\n \"type\": \"function\",\n \"function\": {\n \"name\": \"generate_task_steps\",\n \"description\": \"Make up 10 steps (only a couple of words per step) that are required for a task. The step should be in imperative form (i.e. Dig hole, Open door, ...)\",\n \"parameters\": {\n \"type\": \"object\",\n \"properties\": {\n \"steps\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"properties\": {\n \"description\": {\n \"type\": \"string\",\n \"description\": \"The text of the step in imperative form\"\n },\n \"status\": {\n \"type\": \"string\",\n \"enum\": [\"enabled\"],\n \"description\": \"The status of the step, always 'enabled'\"\n }\n },\n \"required\": [\"description\", \"status\"]\n },\n \"description\": \"An array of 10 step objects, each containing text and status\"\n }\n },\n \"required\": [\"steps\"]\n }\n }\n}\n\nclass TaskStep(BaseModel):\n description: str\n status: Literal[\"enabled\", \"disabled\"]\n\nclass AgentState(CopilotKitState):\n \"\"\"\n Here we define the state of the agent\n\n In this instance, we're inheriting from CopilotKitState, which will bring in\n the CopilotKitState fields. We're also adding a custom field, `steps`,\n which will be used to store the steps of the task.\n \"\"\"\n steps: List[TaskStep] = []\n\n\nclass HumanInTheLoopFlow(Flow[AgentState]):\n \"\"\"\n This is a sample flow that uses the CopilotKit framework to create a chat agent.\n \"\"\"\n\n @start()\n @listen(\"route_follow_up\")\n async def start_flow(self):\n \"\"\"\n This is the entry point for the flow.\n \"\"\"\n\n @router(start_flow)\n async def chat(self):\n \"\"\"\n Standard chat node.\n \"\"\"\n system_prompt = \"\"\"\n You are a helpful assistant that can perform any task.\n You MUST call the `generate_task_steps` function when the user asks you to perform a task.\n When the function `generate_task_steps` is called, the user will decide to enable or disable a step.\n After the user has decided which steps to perform, provide a textual description of how you are performing the task.\n If the user has disabled a step, you are not allowed to perform that step.\n However, you should find a creative workaround to perform the task, and if an essential step is disabled, you can even use\n some humor in the description of how you are performing the task.\n Don't just repeat a list of steps, come up with a creative but short description (3 sentences max) of how you are performing the task.\n \"\"\"\n\n # 1. Run the model and stream the response\n # Note: In order to stream the response, wrap the completion call in\n # copilotkit_stream and set stream=True.\n response = await copilotkit_stream(\n completion(\n\n # 1.1 Specify the model to use\n model=\"openai/gpt-4o\",\n messages=[\n {\n \"role\": \"system\", \n \"content\": system_prompt\n },\n *self.state.messages\n ],\n\n # 1.2 Bind the tools to the model\n tools=[\n *self.state.copilotkit.actions,\n DEFINE_TASK_TOOL\n ],\n\n # 1.3 Disable parallel tool calls to avoid race conditions,\n # enable this for faster performance if you want to manage\n # the complexity of running tool calls in parallel.\n parallel_tool_calls=False,\n stream=True\n )\n )\n\n message = response.choices[0].message\n\n # 2. Append the message to the messages in state\n self.state.messages.append(message)\n\n return \"route_end\"\n\n @listen(\"route_end\")\n async def end(self):\n \"\"\"\n End the flow.\n \"\"\"\n",
"path": "crewai_human_in_the_loop/agent.py",
"language": "python",
"type": "file"
},
{
"name": "page.tsx",
"content": "\"use client\";\nimport React, { useState, useEffect } from \"react\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport { CopilotKit, useCopilotAction } from \"@copilotkit/react-core\";\nimport { CopilotChat, useCopilotChatSuggestions } from \"@copilotkit/react-ui\";\nimport { chatSuggestions, initialPrompt } from \"@/lib/prompts\";\nimport { AGENT_TYPE } from \"@/config\";\nconst HumanInTheLoop: React.FC = () => {\n return (\n <CopilotKit\n runtimeUrl={AGENT_TYPE == \"general\" ? \"/api/copilotkit?crewai=true\" : \"/api/copilotkit\"}\n showDevConsole={false}\n agent=\"human_in_the_loop\"\n >\n <Chat />\n </CopilotKit>\n );\n};\n\n\nconst Chat = () => {\n useCopilotAction({\n name: \"generate_task_steps\",\n parameters: [\n {\n name: \"steps\",\n type: \"object[]\",\n attributes: [\n {\n name: \"description\",\n type: \"string\",\n },\n {\n name: \"status\",\n type: \"string\",\n enum: [\"enabled\", \"disabled\", \"executing\"],\n },\n ],\n },\n ],\n renderAndWaitForResponse: ({ args, respond, status }) => {\n return <StepsFeedback args={args} respond={respond} status={status} />;\n },\n });\n\n\n useCopilotChatSuggestions({\n instructions: chatSuggestions.humanInTheLoop,\n })\n\n return (\n <div className=\"flex justify-center items-center h-full w-full\">\n <div className=\"w-8/10 h-8/10 rounded-lg\">\n <CopilotChat\n className=\"h-full rounded-2xl\"\n labels={{ initial: initialPrompt.humanInTheLoop }}\n />\n </div>\n </div>\n );\n};\n\nconst StepsFeedback = ({ args, respond, status }: { args: any, respond: any, status: any }) => {\n const [localSteps, setLocalSteps] = useState<\n {\n description: string;\n status: \"disabled\" | \"enabled\" | \"executing\";\n }[]\n >([]);\n const [newStep, setNewStep] = useState(\"\");\n\n useEffect(() => {\n if (status === \"executing\" && localSteps.length === 0) {\n setLocalSteps(args.steps);\n }\n }, [status, args.steps, localSteps]);\n\n if (args.steps === undefined || args.steps.length === 0) {\n return <></>;\n }\n\n const steps = localSteps.length > 0 ? localSteps : args.steps;\n\n const handleCheckboxChange = (index: number) => {\n setLocalSteps((prevSteps) =>\n prevSteps.map((step, i) =>\n i === index\n ? {\n ...step,\n status: step.status === \"enabled\" ? \"disabled\" : \"enabled\",\n }\n : step\n )\n );\n };\n\n const handleAddStep = () => {\n const trimmed = newStep.trim();\n if (trimmed.length === 0) return;\n setLocalSteps((prevSteps) => [\n ...prevSteps,\n { description: trimmed, status: \"enabled\" },\n ]);\n setNewStep(\"\");\n };\n\n const handleInputKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {\n if (e.key === \"Enter\") {\n handleAddStep();\n }\n };\n\n return (\n <div className=\"flex flex-col gap-4 w-[500px] bg-gray-100 rounded-lg p-8 mb-4\">\n <div className=\" text-black space-y-2\">\n <h2 className=\"text-lg font-bold mb-4\">Select Steps</h2>\n {steps.map((step: any, index: any) => (\n <div key={index} className=\"text-sm flex items-center\">\n <label className=\"flex items-center cursor-pointer\">\n <input\n type=\"checkbox\"\n checked={step.status === \"enabled\"}\n onChange={() => {\n if (respond) {\n handleCheckboxChange(index)\n }\n }}\n className=\"mr-2\"\n />\n <span\n className={\n step.status !== \"enabled\" && status != \"inProgress\"\n ? \"line-through\"\n : \"\"\n }\n >\n {step.description}\n </span>\n </label>\n </div>\n ))}\n <div className=\"flex items-center gap-2 mb-4\">\n <input\n type=\"text\"\n className=\"flex-1 rounded py-2 focus:outline-none\"\n placeholder=\"Add a new step...\"\n value={newStep}\n onChange={(e) => setNewStep(e.target.value)}\n onKeyDown={handleInputKeyDown}\n hidden={status != \"executing\"}\n />\n </div>\n {status === \"executing\" && (\n <button\n className=\"mt-4 bg-gradient-to-r from-purple-400 to-purple-600 text-white py-2 px-4 rounded cursor-pointer w-48 font-bold\"\n onClick={() => {\n const selectedSteps = localSteps\n .filter((step) => step.status === \"enabled\")\n .map((step) => step.description);\n respond(\n \"The user selected the following steps: \" +\n selectedSteps.join(\", \")\n );\n }}\n >\n ✨ Perform Steps\n </button>\n )}\n </div>\n </div>\n );\n};\n\nfunction Spinner() {\n return (\n <svg\n className=\"mr-2 size-3 animate-spin text-slate-500\"\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n >\n <circle\n className=\"opacity-25\"\n cx=\"12\"\n cy=\"12\"\n r=\"10\"\n stroke=\"currentColor\"\n strokeWidth=\"4\"\n ></circle>\n <path\n className=\"opacity-75\"\n fill=\"currentColor\"\n d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z\"\n ></path>\n </svg>\n );\n}\nexport default HumanInTheLoop;\n\n",
"path": "crewai_human_in_the_loop/page.tsx",
"language": "typescript",
"type": "file"
},
{
"name": "style.css",
"content": ".copilotKitInput {\n border-bottom-left-radius: 0.75rem;\n border-bottom-right-radius: 0.75rem;\n border-top-left-radius: 0.75rem;\n border-top-right-radius: 0.75rem;\n border: 1px solid var(--copilot-kit-separator-color) !important;\n}\n\n.copilotKitChat {\n background-color: #fff !important;\n}\n",
"path": "crewai_human_in_the_loop/style.css",
"language": "css",
"type": "file"
},
{
"name": "README.mdx",
"content": "# 🤝 Human-in-the-Loop Task Planner\n\n## What This Demo Shows\n\nThis demo showcases CopilotKit's **human-in-the-loop** capabilities:\n1. **Collaborative Planning**: The Copilot generates task steps and lets you decide which ones to perform\n2. **Interactive Decision Making**: Select or deselect steps to customize the execution plan\n3. **Adaptive Responses**: The Copilot adapts its execution based on your choices, even handling missing steps\n\n## How to Interact\n\nTry these steps to experience the demo:\n1. Ask your Copilot to help with a task, such as:\n - \"Make me a sandwich\"\n - \"Plan a weekend trip\"\n - \"Organize a birthday party\"\n - \"Start a garden\"\n\n2. Review the suggested steps provided by your Copilot\n\n3. Select or deselect steps using the checkboxes to customize the plan\n - Try removing essential steps to see how the Copilot adapts!\n\n4. Click \"Execute Plan\" to see the outcome based on your selections\n\n## ✨ Human-in-the-Loop Magic in Action\n\n**What's happening technically:**\n- The agent analyzes your request and breaks it down into logical steps\n- These steps are presented to you through a dynamic UI component\n- Your selections are captured as user input\n- The agent considers your choices when executing the plan\n- The agent adapts to missing steps with creative problem-solving\n\n**What you'll see in this demo:**\n- The Copilot provides a detailed, step-by-step plan for your task\n- You have complete control over which steps to include\n- If you remove essential steps, the Copilot provides entertaining and creative workarounds\n- The final execution reflects your choices, showing how human input shapes the outcome\n- Each response is tailored to your specific selections\n\nThis human-in-the-loop pattern creates a powerful collaborative experience where both human judgment and AI capabilities work together to achieve better results than either could alone! ",
"path": "crewai_human_in_the_loop/README.mdx",
"language": "markdown",
"type": "file"
}
]
},
"crewai_shared_state": {
"files": [
{
"name": "README.mdx",
"content": "# 🍳 Shared State Recipe Creator\n\n## What This Demo Shows\n\nThis demo showcases CopilotKit's **shared state** functionality - a powerful feature that enables bidirectional data flow between:\n1. **Frontend → Agent**: UI controls update the agent's context in real-time\n2. **Agent → Frontend**: The Copilot's recipe creations instantly update the UI components\n\nIt's like having a cooking buddy who not only listens to what you want but also updates your recipe card as you chat - no refresh needed! ✨\n\n## How to Interact\n\nMix and match any of these parameters (or none at all - it's up to you!):\n- **Skill Level**: Beginner to expert 👨🍳\n- **Cooking Time**: Quick meals or slow cooking ⏱️\n- **Special Preferences**: Dietary needs, flavor profiles, health goals 🥗\n- **Ingredients**: Items you want to include 🧅🥩🍄\n- **Instructions**: Any specific steps\n\nThen chat with your Copilot chef with prompts like:\n- \"I'm a beginner cook. Can you make me a quick dinner?\"\n- \"I need something spicy with chicken that takes under 30 minutes!\"\n\n## ✨ Shared State Magic in Action\n\n**What's happening technically:**\n- The UI and Copilot agent share the same state object (**Agent State = UI State**)\n- Changes from either side automatically update the other\n- Neither side needs to manually request updates from the other\n\n**What you'll see in this demo:**\n- Set cooking time to 20 minutes in the UI and watch the Copilot immediately respect your time constraint\n- Add ingredients through the UI and see them appear in your recipe\n- When the Copilot suggests new ingredients, watch them automatically appear in the UI ingredients list\n- Change your skill level and see how the Copilot adapts its instructions in real-time\n\nThis synchronized state creates a seamless experience where the agent always has your current preferences, and any updates to the recipe are instantly reflected in both places.\n\nThis shared state pattern can be applied to any application where you want your UI and Copilot to work together in perfect harmony! ",
"path": "crewai_shared_state/README.mdx",
"language": "markdown",
"type": "file"
},
{
"name": "agent.py",
"content": "\"\"\"\nA demo of shared state between the agent and CopilotKit.\n\"\"\"\n\nimport json\nfrom enum import Enum\nfrom typing import List, Optional\nfrom litellm import completion\nfrom pydantic import BaseModel, Field\nfrom crewai.flow.flow import Flow, start, router, listen\nfrom copilotkit.crewai import (\n copilotkit_stream, \n copilotkit_predict_state,\n CopilotKitState\n)\n\nclass SkillLevel(str, Enum):\n \"\"\"\n The level of skill required for the recipe.\n \"\"\"\n BEGINNER = \"Beginner\"\n INTERMEDIATE = \"Intermediate\"\n ADVANCED = \"Advanced\"\n\nclass CookingTime(str, Enum):\n \"\"\"\n The cooking time of the recipe.\n \"\"\"\n FIVE_MIN = \"5 min\"\n FIFTEEN_MIN = \"15 min\"\n THIRTY_MIN = \"30 min\"\n FORTY_FIVE_MIN = \"45 min\"\n SIXTY_PLUS_MIN = \"60+ min\"\n\nclass Ingredient(BaseModel):\n \"\"\"\n An ingredient with its details.\n \"\"\"\n icon: str = Field(..., description=\"Emoji icon representing the ingredient.\")\n name: str = Field(..., description=\"Name of the ingredient.\")\n amount: str = Field(..., description=\"Amount or quantity of the ingredient.\")\n\nGENERATE_RECIPE_TOOL = {\n \"type\": \"function\",\n \"function\": {\n \"name\": \"generate_recipe\",\n \"description\": \" \".join(\"\"\"Generate or modify an existing recipe. \n When creating a new recipe, specify all fields. \n When modifying, only fill optional fields if they need changes; \n otherwise, leave them empty.\"\"\".split()),\n \"parameters\": {\n \"type\": \"object\",\n \"properties\": {\n \"recipe\": {\n \"description\": \"The recipe object containing all details.\",\n \"type\": \"object\",\n \"properties\": {\n \"title\": {\n \"type\": \"string\",\n \"description\": \"The title of the recipe.\"\n },\n \"skill_level\": {\n \"type\": \"string\",\n \"enum\": [level.value for level in SkillLevel],\n \"description\": \"The skill level required for the recipe.\"\n },\n \"dietary_preferences\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n },\n \"description\": \"A list of dietary preferences (e.g., Vegetarian, Gluten-free).\"\n },\n \"cooking_time\": {\n \"type\": \"string\",\n \"enum\": [time.value for time in CookingTime],\n \"description\": \"The estimated cooking time for the recipe.\"\n },\n \"ingredients\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"properties\": {\n \"icon\": {\"type\": \"string\", \"description\": \"Emoji icon for the ingredient.\"},\n \"name\": {\"type\": \"string\", \"description\": \"Name of the ingredient.\"},\n \"amount\": {\"type\": \"string\", \"description\": \"Amount/quantity of the ingredient.\"}\n },\n \"required\": [\"icon\", \"name\", \"amount\"]\n },\n \"description\": \"A list of ingredients required for the recipe.\"\n },\n \"instructions\": {\n \"type\": \"array\",\n \"items\": {\"type\": \"string\"},\n \"description\": \"Step-by-step instructions for preparing the recipe.\"\n }\n },\n \"required\": [\"title\", \"skill_level\", \"cooking_time\", \"dietary_preferences\", \"ingredients\", \"instructions\"]\n }\n },\n \"required\": [\"recipe\"]\n }\n }\n}\n\nclass Recipe(BaseModel):\n \"\"\"\n A recipe.\n \"\"\"\n title: str\n skill_level: SkillLevel\n dietary_preferences: List[str] = Field(default_factory=list)\n cooking_time: CookingTime\n ingredients: List[Ingredient] = Field(default_factory=list)\n instructions: List[str] = Field(default_factory=list)\n\n\nclass AgentState(CopilotKitState):\n \"\"\"\n The state of the recipe.\n \"\"\"\n recipe: Optional[Recipe] = None\n\nclass SharedStateFlow(Flow[AgentState]):\n \"\"\"\n This is a sample flow that demonstrates shared state between the agent and CopilotKit.\n \"\"\"\n\n @start()\n @listen(\"route_follow_up\")\n async def start_flow(self):\n \"\"\"\n This is the entry point for the flow.\n \"\"\"\n\n @router(start_flow)\n async def chat(self):\n \"\"\"\n Standard chat node.\n \"\"\"\n \n system_prompt = f\"\"\"You are a helpful assistant for creating recipes. \n This is the current state of the recipe: {self.state.model_dump_json(indent=2)}\n You can modify the recipe by calling the generate_recipe tool.\n If you have just created or modified the recipe, just answer in one sentence what you did.\n \"\"\"\n\n # 1. Here we specify that we want to stream the tool call to generate_recipe\n # to the frontend as state.\n await copilotkit_predict_state({\n \"recipe\": {\n \"tool_name\": \"generate_recipe\",\n \"tool_argument\": \"recipe\"\n }\n })\n\n # 2. Run the model and stream the response\n # Note: In order to stream the response, wrap the completion call in\n # copilotkit_stream and set stream=True.\n response = await copilotkit_stream(\n completion(\n\n # 2.1 Specify the model to use\n model=\"openai/gpt-4o\",\n messages=[\n {\n \"role\": \"system\", \n \"content\": system_prompt\n },\n *self.state.messages\n ],\n\n # 2.2 Bind the tools to the model\n tools=[\n *self.state.copilotkit.actions,\n GENERATE_RECIPE_TOOL\n ],\n\n # 2.3 Disable parallel tool calls to avoid race conditions,\n # enable this for faster performance if you want to manage\n # the complexity of running tool calls in parallel.\n parallel_tool_calls=False,\n stream=True\n )\n )\n\n message = response.choices[0].message\n\n # 3. Append the message to the messages in state\n self.state.messages.append(message)\n\n # 4. Handle tool call\n if message.get(\"tool_calls\"):\n tool_call = message[\"tool_calls\"][0]\n tool_call_id = tool_call[\"id\"]\n tool_call_name = tool_call[\"function\"][\"name\"]\n tool_call_args = json.loads(tool_call[\"function\"][\"arguments\"])\n\n if tool_call_name == \"generate_recipe\":\n # Attempt to update the recipe state using the data from the tool call\n try:\n updated_recipe_data = tool_call_args[\"recipe\"]\n # Validate and update the state. Pydantic will raise an error if the structure is wrong.\n self.state.recipe = Recipe(**updated_recipe_data)\n\n # 4.1 Append the result to the messages in state\n self.state.messages.append({\n \"role\": \"tool\",\n \"content\": \"Recipe updated.\", # More accurate message\n \"tool_call_id\": tool_call_id\n })\n return \"route_follow_up\"\n except Exception as e:\n # Handle validation or other errors during update\n print(f\"Error updating recipe state: {e}\") # Log the error server-side\n # Optionally inform the user via a tool message, though it might be noisy\n # self.state.messages.append({\"role\": \"tool\", \"content\": f\"Error processing recipe update: {e}\", \"tool_call_id\": tool_call_id})\n return \"route_end\" # End the flow on error for now\n\n # 5. If our tool was not called, return to the end route\n return \"route_end\"\n\n @listen(\"route_end\")\n async def end(self):\n \"\"\"\n End the flow.\n \"\"\"",
"path": "crewai_shared_state/agent.py",
"language": "python",
"type": "file"
},
{
"name": "page.tsx",
"content": "\"use client\";\nimport { CopilotKit, useCoAgent, useCopilotChat } from \"@copilotkit/react-core\";\nimport { CopilotKitCSSProperties, CopilotSidebar, useCopilotChatSuggestions } from \"@copilotkit/react-ui\";\nimport { useState, useEffect, useRef } from \"react\";\nimport { Role, TextMessage } from \"@copilotkit/runtime-client-gql\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport { chatSuggestions, initialPrompt } from \"@/lib/prompts\";\nimport { AGENT_TYPE } from \"@/config\";\nenum SkillLevel {\n BEGINNER = \"Beginner\",\n INTERMEDIATE = \"Intermediate\",\n ADVANCED = \"Advanced\",\n}\n\nenum CookingTime {\n FiveMin = \"5 min\",\n FifteenMin = \"15 min\",\n ThirtyMin = \"30 min\",\n FortyFiveMin = \"45 min\",\n SixtyPlusMin = \"60+ min\",\n}\n\nconst cookingTimeValues = [\n { label: CookingTime.FiveMin, value: 0 },\n { label: CookingTime.FifteenMin, value: 1 },\n { label: CookingTime.ThirtyMin, value: 2 },\n { label: CookingTime.FortyFiveMin, value: 3 },\n { label: CookingTime.SixtyPlusMin, value: 4 },\n];\n\nconst dietaryOptions = [\n \"Vegetarian\",\n \"Nut-free\",\n \"Dairy-free\",\n \"Gluten-free\",\n \"Vegan\",\n \"Low-carb\"\n];\n\nexport default function SharedState() {\n return (\n \n <CopilotKit\n runtimeUrl={AGENT_TYPE == \"general\" ? \"/api/copilotkit?crewai=true\" : \"/api/copilotkit\"}\n showDevConsole={false}\n agent=\"shared_state\"\n >\n <div\n className=\"app-container\"\n style={{\n backgroundImage: \"url('./shared_state_background.png')\",\n backgroundAttachment: \"fixed\",\n backgroundSize: \"cover\",\n position: \"relative\",\n }}\n >\n <div \n style={{\n position: \"absolute\",\n top: 0,\n left: 0,\n right: 0,\n bottom: 0,\n backgroundColor: \"rgba(0, 0, 0, 0.4)\",\n backdropFilter: \"blur(5px)\",\n zIndex: 0,\n }}\n />\n <Recipe />\n {/* </div> */}\n <CopilotSidebar\n defaultOpen={true}\n labels={{\n title: \"AI Recipe Assistant\",\n initial: initialPrompt.sharedState,\n }}\n clickOutsideToClose={false}\n />\n </div>\n </CopilotKit>\n );\n}\n\ninterface Ingredient {\n icon: string;\n name: string;\n amount: string;\n}\n\ninterface Recipe {\n title: string;\n skill_level: SkillLevel;\n cooking_time: CookingTime;\n dietary_preferences: string[];\n ingredients: Ingredient[];\n instructions: string[];\n}\n\ninterface RecipeAgentState {\n recipe: Recipe;\n}\n\nconst INITIAL_STATE: RecipeAgentState = {\n recipe: {\n title: \"Make Your Recipe\",\n skill_level: SkillLevel.INTERMEDIATE,\n cooking_time: CookingTime.FortyFiveMin,\n dietary_preferences: [],\n ingredients: [\n { icon: \"🥕\", name: \"Carrots\", amount: \"3 large, grated\" },\n { icon: \"🌾\", name: \"All-Purpose Flour\", amount: \"2 cups\" },\n ],\n instructions: [\n \"Preheat oven to 350°F (175°C)\",\n ],\n },\n};\n\nfunction Recipe() {\n const { state: agentState, setState: setAgentState } =\n useCoAgent<RecipeAgentState>({\n name: \"shared_state\",\n initialState: INITIAL_STATE,\n });\n\n const [recipe, setRecipe] = useState(INITIAL_STATE.recipe);\n const { appendMessage, isLoading } = useCopilotChat();\n const [editingInstructionIndex, setEditingInstructionIndex] = useState<number | null>(null);\n const newInstructionRef = useRef<HTMLTextAreaElement>(null);\n\n const updateRecipe = (partialRecipe: Partial<Recipe>) => {\n setAgentState({\n ...agentState,\n recipe: {\n ...recipe,\n ...partialRecipe,\n },\n });\n setRecipe({\n ...recipe,\n ...partialRecipe,\n });\n };\n\n const newRecipeState = { ...recipe };\n const newChangedKeys = [];\n const changedKeysRef = useRef<string[]>([]);\n\n for (const key in recipe) {\n if (\n agentState &&\n agentState.recipe &&\n (agentState.recipe as any)[key] !== undefined &&\n (agentState.recipe as any)[key] !== null\n ) {\n let agentValue = (agentState.recipe as any)[key];\n const recipeValue = (recipe as any)[key];\n\n // Check if agentValue is a string and replace \\n with actual newlines\n if (typeof agentValue === \"string\") {\n agentValue = agentValue.replace(/\\\\n/g, \"\\n\");\n }\n\n if (JSON.stringify(agentValue) !== JSON.stringify(recipeValue)) {\n (newRecipeState as any)[key] = agentValue;\n newChangedKeys.push(key);\n }\n }\n }\n\n if (newChangedKeys.length > 0) {\n changedKeysRef.current = newChangedKeys;\n } else if (!isLoading) {\n changedKeysRef.current = [];\n }\n\n useEffect(() => {\n setRecipe(newRecipeState);\n }, [JSON.stringify(newRecipeState)]);\n\n const handleTitleChange = (event: React.ChangeEvent<HTMLInputElement>) => {\n updateRecipe({\n title: event.target.value,\n });\n };\n\n const handleSkillLevelChange = (\n event: React.ChangeEvent<HTMLSelectElement>\n ) => {\n updateRecipe({\n skill_level: event.target.value as SkillLevel,\n });\n };\n\n const handleDietaryChange = (preference: string, checked: boolean) => {\n if (checked) {\n updateRecipe({\n dietary_preferences: [...recipe.dietary_preferences, preference],\n });\n } else {\n updateRecipe({\n dietary_preferences: recipe.dietary_preferences.filter(\n (p) => p !== preference\n ),\n });\n }\n };\n\n const handleCookingTimeChange = (\n event: React.ChangeEvent<HTMLSelectElement>\n ) => {\n updateRecipe({\n cooking_time: cookingTimeValues[Number(event.target.value)].label,\n });\n };\n\n \n const addIngredient = () => {\n // Pick a random food emoji from our valid list\n updateRecipe({\n ingredients: [...recipe.ingredients, { icon: \"🍴\", name: \"\", amount: \"\" }],\n });\n };\n\n const updateIngredient = (index: number, field: keyof Ingredient, value: string) => {\n const updatedIngredients = [...recipe.ingredients];\n updatedIngredients[index] = {\n ...updatedIngredients[index],\n [field]: value,\n };\n updateRecipe({ ingredients: updatedIngredients });\n };\n\n const removeIngredient = (index: number) => {\n const updatedIngredients = [...recipe.ingredients];\n updatedIngredients.splice(index, 1);\n updateRecipe({ ingredients: updatedIngredients });\n };\n\n const addInstruction = () => {\n const newIndex = recipe.instructions.length;\n updateRecipe({\n instructions: [...recipe.instructions, \"\"],\n });\n // Set the new instruction as the editing one\n setEditingInstructionIndex(newIndex);\n \n // Focus the new instruction after render\n setTimeout(() => {\n const textareas = document.querySelectorAll('.instructions-container textarea');\n const newTextarea = textareas[textareas.length - 1] as HTMLTextAreaElement;\n if (newTextarea) {\n newTextarea.focus();\n }\n }, 50);\n };\n\n const updateInstruction = (index: number, value: string) => {\n const updatedInstructions = [...recipe.instructions];\n updatedInstructions[index] = value;\n updateRecipe({ instructions: updatedInstructions });\n };\n\n const removeInstruction = (index: number) => {\n const updatedInstructions = [...recipe.instructions];\n updatedInstructions.splice(index, 1);\n updateRecipe({ instructions: updatedInstructions });\n };\n\n // Simplified icon handler that defaults to a fork/knife for any problematic icons\n const getProperIcon = (icon: string | undefined): string => {\n // If icon is undefined return the default\n if (!icon) {\n return \"🍴\";\n }\n \n return icon;\n };\n\n useCopilotChatSuggestions({\n instructions: chatSuggestions.sharedState,\n })\n\n\n return (\n <form className=\"recipe-card\">\n {/* Recipe Title */}\n <div className=\"recipe-header\">\n <input\n type=\"text\"\n value={recipe.title || ''}\n onChange={handleTitleChange}\n className=\"recipe-title-input\"\n />\n \n <div className=\"recipe-meta\">\n <div className=\"meta-item\">\n <span className=\"meta-icon\">🕒</span>\n <select\n className=\"meta-select\"\n value={cookingTimeValues.find(t => t.label === recipe.cooking_time)?.value || 3}\n onChange={handleCookingTimeChange}\n style={{\n backgroundImage: 'url(\"data:image/svg+xml;charset=UTF-8,%3csvg xmlns=\\'http://www.w3.org/2000/svg\\' viewBox=\\'0 0 24 24\\' fill=\\'none\\' stroke=\\'%23555\\' stroke-width=\\'2\\' stroke-linecap=\\'round\\' stroke-linejoin=\\'round\\'%3e%3cpolyline points=\\'6 9 12 15 18 9\\'%3e%3c/polyline%3e%3c/svg%3e\")',\n backgroundRepeat: 'no-repeat',\n backgroundPosition: 'right 0px center',\n backgroundSize: '12px',\n appearance: 'none',\n WebkitAppearance: 'none'\n }}\n >\n {cookingTimeValues.map((time) => (\n <option key={time.value} value={time.value}>\n {time.label}\n </option>\n ))}\n </select>\n </div>\n \n <div className=\"meta-item\">\n <span className=\"meta-icon\">🏆</span>\n <select\n className=\"meta-select\"\n value={recipe.skill_level}\n onChange={handleSkillLevelChange}\n style={{\n backgroundImage: 'url(\"data:image/svg+xml;charset=UTF-8,%3csvg xmlns=\\'http://www.w3.org/2000/svg\\' viewBox=\\'0 0 24 24\\' fill=\\'none\\' stroke=\\'%23555\\' stroke-width=\\'2\\' stroke-linecap=\\'round\\' stroke-linejoin=\\'round\\'%3e%3cpolyline points=\\'6 9 12 15 18 9\\'%3e%3c/polyline%3e%3c/svg%3e\")',\n backgroundRepeat: 'no-repeat',\n backgroundPosition: 'right 0px center',\n backgroundSize: '12px',\n appearance: 'none',\n WebkitAppearance: 'none'\n }}\n >\n {Object.values(SkillLevel).map((level) => (\n <option key={level} value={level}>\n {level}\n </option>\n ))}\n </select>\n </div>\n </div>\n </div>\n\n {/* Dietary Preferences */}\n <div className=\"section-container relative\">\n {changedKeysRef.current.includes(\"dietary_preferences\") && <Ping />}\n <h2 className=\"section-title\">Dietary Preferences</h2>\n <div className=\"dietary-options\">\n {dietaryOptions.map((option) => (\n <label key={option} className=\"dietary-option\">\n <input\n type=\"checkbox\"\n checked={recipe.dietary_preferences.includes(option)}\n onChange={(e: React.ChangeEvent<HTMLInputElement>) => handleDietaryChange(option, e.target.checked)}\n />\n <span>{option}</span>\n </label>\n ))}\n </div>\n </div>\n\n {/* Ingredients */}\n <div className=\"section-container relative\">\n {changedKeysRef.current.includes(\"ingredients\") && <Ping />}\n <div className=\"section-header\">\n <h2 className=\"section-title\">Ingredients</h2>\n <button \n type=\"button\" \n className=\"add-button\"\n onClick={addIngredient}\n >\n + Add Ingredient\n </button>\n </div>\n <div className=\"ingredients-container\">\n {recipe.ingredients.map((ingredient, index) => (\n <div key={index} className=\"ingredient-card\">\n <div className=\"ingredient-icon\">{getProperIcon(ingredient.icon)}</div>\n <div className=\"ingredient-content\">\n <input\n type=\"text\"\n value={ingredient.name || ''}\n onChange={(e) => updateIngredient(index, \"name\", e.target.value)}\n placeholder=\"Ingredient name\"\n className=\"ingredient-name-input\"\n />\n <input\n type=\"text\"\n value={ingredient.amount || ''}\n onChange={(e) => updateIngredient(index, \"amount\", e.target.value)}\n placeholder=\"Amount\"\n className=\"ingredient-amount-input\"\n />\n </div>\n <button \n type=\"button\" \n className=\"remove-button\" \n onClick={() => removeIngredient(index)}\n aria-label=\"Remove ingredient\"\n >\n ×\n </button>\n </div>\n ))}\n </div>\n </div>\n\n {/* Instructions */}\n <div className=\"section-container relative\">\n {changedKeysRef.current.includes(\"instructions\") && <Ping />}\n <div className=\"section-header\">\n <h2 className=\"section-title\">Instructions</h2>\n <button \n type=\"button\" \n className=\"add-step-button\"\n onClick={addInstruction}\n >\n + Add Step\n </button>\n </div>\n <div className=\"instructions-container\">\n {recipe.instructions.map((instruction, index) => (\n <div key={index} className=\"instruction-item\">\n {/* Number Circle */}\n <div className=\"instruction-number\">\n {index + 1}\n </div>\n \n {/* Vertical Line */}\n {index < recipe.instructions.length - 1 && (\n <div className=\"instruction-line\" />\n )}\n \n {/* Instruction Content */}\n <div \n className={`instruction-content ${\n editingInstructionIndex === index \n ? 'instruction-content-editing' \n : 'instruction-content-default'\n }`}\n onClick={() => setEditingInstructionIndex(index)}\n >\n <textarea\n className=\"instruction-textarea\"\n value={instruction || ''}\n onChange={(e) => updateInstruction(index, e.target.value)}\n placeholder={!instruction ? \"Enter cooking instruction...\" : \"\"}\n onFocus={() => setEditingInstructionIndex(index)}\n onBlur={(e) => {\n // Only blur if clicking outside this instruction\n if (!e.relatedTarget || !e.currentTarget.contains(e.relatedTarget as Node)) {\n setEditingInstructionIndex(null);\n }\n }}\n />\n \n {/* Delete Button (only visible on hover) */}\n <button \n type=\"button\"\n className={`instruction-delete-btn ${\n editingInstructionIndex === index \n ? 'instruction-delete-btn-editing' \n : 'instruction-delete-btn-default'\n } remove-button`}\n onClick={(e) => {\n e.stopPropagation(); // Prevent triggering parent onClick\n removeInstruction(index);\n }}\n aria-label=\"Remove instruction\"\n >\n ×\n </button>\n </div>\n </div>\n ))}\n </div>\n </div>\n\n {/* Improve with AI Button */}\n <div className=\"action-container\">\n <button\n className={isLoading ? \"improve-button loading\" : \"improve-button\"}\n type=\"button\"\n onClick={() => {\n if (!isLoading) {\n appendMessage(\n new TextMessage({\n content: \"Improve the recipe\",\n role: Role.User,\n })\n );\n }\n }}\n disabled={isLoading}\n >\n {isLoading ? \"Please Wait...\" : \"Improve with AI\"}\n </button>\n </div>\n </form>\n );\n}\n\nfunction Ping() {\n return (\n <span className=\"ping-animation\">\n <span className=\"ping-circle\"></span>\n <span className=\"ping-dot\"></span>\n </span>\n );\n}\n",
"path": "crewai_shared_state/page.tsx",
"language": "typescript",
"type": "file"
},
{
"name": "style.css",
"content": ".copilotKitWindow {\n box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);\n}\n\n.copilotKitHeader {\n border-top-left-radius: 5px !important;\n background-color: #fff;\n color: #000;\n border-bottom: 0px;\n}\n\n/* Recipe App Styles */\n.app-container {\n min-height: 100vh;\n width: 100%;\n display: flex;\n align-items: center;\n justify-content: center;\n background-size: cover;\n background-position: center;\n background-repeat: no-repeat;\n background-attachment: fixed;\n position: relative;\n overflow: auto;\n}\n\n.recipe-card {\n background-color: rgba(255, 255, 255, 0.97);\n border-radius: 16px;\n box-shadow: 0 15px 30px rgba(0, 0, 0, 0.25), 0 5px 15px rgba(0, 0, 0, 0.15);\n width: 100%;\n max-width: 750px;\n margin: 20px auto;\n padding: 14px 32px;\n position: relative;\n z-index: 1;\n backdrop-filter: blur(5px);\n border: 1px solid rgba(255, 255, 255, 0.3);\n transition: transform 0.2s ease, box-shadow 0.2s ease;\n animation: fadeIn 0.5s ease-out forwards;\n box-sizing: border-box;\n overflow: hidden;\n}\n\n.recipe-card:hover {\n transform: translateY(-5px);\n box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3), 0 10px 20px rgba(0, 0, 0, 0.2);\n}\n\n/* Recipe Header */\n.recipe-header {\n margin-bottom: 24px;\n}\n\n.recipe-title-input {\n width: 100%;\n font-size: 24px;\n font-weight: bold;\n border: none;\n outline: none;\n padding: 8px 0;\n margin-bottom: 0px;\n}\n\n.recipe-meta {\n display: flex;\n align-items: center;\n gap: 20px;\n margin-top: 5px;\n margin-bottom: 14px;\n}\n\n.meta-item {\n display: flex;\n align-items: center;\n gap: 8px;\n color: #555;\n}\n\n.meta-icon {\n font-size: 20px;\n color: #777;\n}\n\n.meta-text {\n font-size: 15px;\n}\n\n/* Recipe Meta Selects */\n.meta-item select {\n border: none;\n background: transparent;\n font-size: 15px;\n color: #555;\n cursor: pointer;\n outline: none;\n padding-right: 18px;\n transition: color 0.2s, transform 0.1s;\n font-weight: 500;\n}\n\n.meta-item select:hover, \n.meta-item select:focus {\n color: #FF5722;\n}\n\n.meta-item select:active {\n transform: scale(0.98);\n}\n\n.meta-item select option {\n color: #333;\n background-color: white;\n font-weight: normal;\n padding: 8px;\n}\n\n/* Section Container */\n.section-container {\n margin-bottom: 20px;\n position: relative;\n width: 100%;\n}\n\n.section-title {\n font-size: 20px;\n font-weight: 700;\n margin-bottom: 20px;\n color: #333;\n position: relative;\n display: inline-block;\n}\n\n.section-title:after {\n content: \"\";\n position: absolute;\n bottom: -8px;\n left: 0;\n width: 40px;\n height: 3px;\n background-color: #ff7043;\n border-radius: 3px;\n}\n\n/* Dietary Preferences */\n.dietary-options {\n display: flex;\n flex-wrap: wrap;\n gap: 10px 16px;\n margin-bottom: 16px;\n width: 100%;\n}\n\n.dietary-option {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n cursor: pointer;\n margin-bottom: 4px;\n}\n\n.dietary-option input {\n cursor: pointer;\n}\n\n/* Ingredients */\n.ingredients-container {\n display: flex;\n flex-wrap: wrap;\n gap: 10px;\n margin-bottom: 15px;\n width: 100%;\n box-sizing: border-box;\n}\n\n.ingredient-card {\n display: flex;\n align-items: center;\n background-color: rgba(255, 255, 255, 0.9);\n border-radius: 12px;\n padding: 12px;\n margin-bottom: 10px;\n box-shadow: 0 4px 10px rgba(0, 0, 0, 0.08);\n position: relative;\n transition: all 0.2s ease;\n border: 1px solid rgba(240, 240, 240, 0.8);\n width: calc(33.333% - 7px);\n box-sizing: border-box;\n}\n\n.ingredient-card:hover {\n transform: translateY(-2px);\n box-shadow: 0 6px 15px rgba(0, 0, 0, 0.12);\n}\n\n.ingredient-card .remove-button {\n position: absolute;\n right: 10px;\n top: 10px;\n background: none;\n border: none;\n color: #ccc;\n font-size: 16px;\n cursor: pointer;\n display: none;\n padding: 0;\n width: 24px;\n height: 24px;\n line-height: 1;\n}\n\n.ingredient-card:hover .remove-button {\n display: block;\n}\n\n.ingredient-icon {\n font-size: 24px;\n margin-right: 12px;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 40px;\n height: 40px;\n background-color: #f7f7f7;\n border-radius: 50%;\n flex-shrink: 0;\n}\n\n.ingredient-content {\n flex: 1;\n display: flex;\n flex-direction: column;\n gap: 3px;\n min-width: 0;\n}\n\n.ingredient-name-input, \n.ingredient-amount-input {\n border: none;\n background: transparent;\n outline: none;\n width: 100%;\n padding: 0;\n text-overflow: ellipsis;\n overflow: hidden;\n white-space: nowrap;\n}\n\n.ingredient-name-input {\n font-weight: 500;\n font-size: 14px;\n}\n\n.ingredient-amount-input {\n font-size: 13px;\n color: #666;\n}\n\n.ingredient-name-input::placeholder,\n.ingredient-amount-input::placeholder {\n color: #aaa;\n}\n\n.remove-button {\n background: none;\n border: none;\n color: #999;\n font-size: 20px;\n cursor: pointer;\n padding: 0;\n width: 28px;\n height: 28px;\n display: flex;\n align-items: center;\n justify-content: center;\n margin-left: 10px;\n}\n\n.remove-button:hover {\n color: #FF5722;\n}\n\n/* Instructions */\n.instructions-container {\n display: flex;\n flex-direction: column;\n gap: 6px;\n position: relative;\n margin-bottom: 12px;\n width: 100%;\n}\n\n.instruction-item {\n position: relative;\n display: flex;\n width: 100%;\n box-sizing: border-box;\n margin-bottom: 8px;\n align-items: flex-start;\n}\n\n.instruction-number {\n display: flex;\n align-items: center;\n justify-content: center;\n min-width: 26px;\n height: 26px;\n background-color: #ff7043;\n color: white;\n border-radius: 50%;\n font-weight: 600;\n flex-shrink: 0;\n box-shadow: 0 2px 4px rgba(255, 112, 67, 0.3);\n z-index: 1;\n font-size: 13px;\n margin-top: 2px;\n}\n\n.instruction-line {\n position: absolute;\n left: 13px; /* Half of the number circle width */\n top: 22px;\n bottom: -18px;\n width: 2px;\n background: linear-gradient(to bottom, #ff7043 60%, rgba(255, 112, 67, 0.4));\n z-index: 0;\n}\n\n.instruction-content {\n background-color: white;\n border-radius: 10px;\n padding: 10px 14px;\n margin-left: 12px;\n flex-grow: 1;\n transition: all 0.2s ease;\n box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);\n border: 1px solid rgba(240, 240, 240, 0.8);\n position: relative;\n width: calc(100% - 38px);\n box-sizing: border-box;\n display: flex;\n align-items: center;\n}\n\n.instruction-content-editing {\n background-color: #fff9f6;\n box-shadow: 0 6px 16px rgba(0, 0, 0, 0.12), 0 0 0 2px rgba(255, 112, 67, 0.2);\n}\n\n.instruction-content:hover {\n transform: translateY(-2px);\n box-shadow: 0 6px 16px rgba(0, 0, 0, 0.12);\n}\n\n.instruction-textarea {\n width: 100%;\n background: transparent;\n border: none;\n resize: vertical;\n font-family: inherit;\n font-size: 14px;\n line-height: 1.4;\n min-height: 20px;\n outline: none;\n padding: 0;\n margin: 0;\n}\n\n.instruction-delete-btn {\n position: absolute;\n background: none;\n border: none;\n color: #ccc;\n font-size: 16px;\n cursor: pointer;\n display: none;\n padding: 0;\n width: 20px;\n height: 20px;\n line-height: 1;\n top: 50%;\n transform: translateY(-50%);\n right: 8px;\n}\n\n.instruction-content:hover .instruction-delete-btn {\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n/* Action Button */\n.action-container {\n display: flex;\n justify-content: center;\n margin-top: 40px;\n padding-bottom: 20px;\n position: relative;\n}\n\n.improve-button {\n background-color: #ff7043;\n border: none;\n color: white;\n border-radius: 30px;\n font-size: 18px;\n font-weight: 600;\n padding: 14px 28px;\n cursor: pointer;\n transition: all 0.3s ease;\n box-shadow: 0 4px 15px rgba(255, 112, 67, 0.4);\n display: flex;\n align-items: center;\n justify-content: center;\n text-align: center;\n position: relative;\n min-width: 180px;\n}\n\n.improve-button:hover {\n background-color: #ff5722;\n transform: translateY(-2px);\n box-shadow: 0 8px 20px rgba(255, 112, 67, 0.5);\n}\n\n.improve-button.loading {\n background-color: #ff7043;\n opacity: 0.8;\n cursor: not-allowed;\n padding-left: 42px; /* Reduced padding to bring text closer to icon */\n padding-right: 22px; /* Balance the button */\n justify-content: flex-start; /* Left align text for better alignment with icon */\n}\n\n.improve-button.loading:after {\n content: \"\"; /* Add space between icon and text */\n display: inline-block;\n width: 8px; /* Width of the space */\n}\n\n.improve-button:before {\n content: \"\";\n background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83'/%3E%3C/svg%3E\");\n width: 20px; /* Slightly smaller icon */\n height: 20px;\n background-repeat: no-repeat;\n background-size: contain;\n position: absolute;\n left: 16px; /* Slightly adjusted */\n top: 50%;\n transform: translateY(-50%);\n display: none;\n}\n\n.improve-button.loading:before {\n display: block;\n animation: spin 1.5s linear infinite;\n}\n\n@keyframes spin {\n 0% { transform: translateY(-50%) rotate(0deg); }\n 100% { transform: translateY(-50%) rotate(360deg); }\n}\n\n/* Ping Animation */\n.ping-animation {\n position: absolute;\n display: flex;\n width: 12px;\n height: 12px;\n top: 0;\n right: 0;\n}\n\n.ping-circle {\n position: absolute;\n display: inline-flex;\n width: 100%;\n height: 100%;\n border-radius: 50%;\n background-color: #38BDF8;\n opacity: 0.75;\n animation: ping 1.5s cubic-bezier(0, 0, 0.2, 1) infinite;\n}\n\n.ping-dot {\n position: relative;\n display: inline-flex;\n width: 12px;\n height: 12px;\n border-radius: 50%;\n background-color: #0EA5E9;\n}\n\n@keyframes ping {\n 75%, 100% {\n transform: scale(2);\n opacity: 0;\n }\n}\n\n/* Instruction hover effects */\n.instruction-item:hover .instruction-delete-btn {\n display: flex !important;\n}\n\n/* Add some subtle animations */\n@keyframes fadeIn {\n from { opacity: 0; transform: translateY(20px); }\n to { opacity: 1; transform: translateY(0); }\n}\n\n/* Better center alignment for the recipe card */\n.recipe-card-container {\n display: flex;\n justify-content: center;\n width: 100%;\n position: relative;\n z-index: 1;\n margin: 0 auto;\n box-sizing: border-box;\n}\n\n/* Add Buttons */\n.add-button {\n background-color: transparent;\n color: #FF5722;\n border: 1px dashed #FF5722;\n border-radius: 8px;\n padding: 10px 16px;\n cursor: pointer;\n font-weight: 500;\n display: inline-block;\n font-size: 14px;\n margin-bottom: 0;\n}\n\n.add-step-button {\n background-color: transparent;\n color: #FF5722;\n border: 1px dashed #FF5722;\n border-radius: 6px;\n padding: 6px 12px;\n cursor: pointer;\n font-weight: 500;\n font-size: 13px;\n}\n\n/* Section Headers */\n.section-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 12px;\n}\n",
"path": "crewai_shared_state/style.css",
"language": "css",
"type": "file"
},
{
"name": "README.mdx",
"content": "# 🍳 Shared State Recipe Creator\n\n## What This Demo Shows\n\nThis demo showcases CopilotKit's **shared state** functionality - a powerful feature that enables bidirectional data flow between:\n1. **Frontend → Agent**: UI controls update the agent's context in real-time\n2. **Agent → Frontend**: The Copilot's recipe creations instantly update the UI components\n\nIt's like having a cooking buddy who not only listens to what you want but also updates your recipe card as you chat - no refresh needed! ✨\n\n## How to Interact\n\nMix and match any of these parameters (or none at all - it's up to you!):\n- **Skill Level**: Beginner to expert 👨🍳\n- **Cooking Time**: Quick meals or slow cooking ⏱️\n- **Special Preferences**: Dietary needs, flavor profiles, health goals 🥗\n- **Ingredients**: Items you want to include 🧅🥩🍄\n- **Instructions**: Any specific steps\n\nThen chat with your Copilot chef with prompts like:\n- \"I'm a beginner cook. Can you make me a quick dinner?\"\n- \"I need something spicy with chicken that takes under 30 minutes!\"\n\n## ✨ Shared State Magic in Action\n\n**What's happening technically:**\n- The UI and Copilot agent share the same state object (**Agent State = UI State**)\n- Changes from either side automatically update the other\n- Neither side needs to manually request updates from the other\n\n**What you'll see in this demo:**\n- Set cooking time to 20 minutes in the UI and watch the Copilot immediately respect your time constraint\n- Add ingredients through the UI and see them appear in your recipe\n- When the Copilot suggests new ingredients, watch them automatically appear in the UI ingredients list\n- Change your skill level and see how the Copilot adapts its instructions in real-time\n\nThis synchronized state creates a seamless experience where the agent always has your current preferences, and any updates to the recipe are instantly reflected in both places.\n\nThis shared state pattern can be applied to any application where you want your UI and Copilot to work together in perfect harmony! ",
"path": "crewai_shared_state/README.mdx",
"language": "markdown",
"type": "file"
}
]
},
"crewai_predictive_state_updates": {
"files": [
{
"name": "agent.py",
"content": "\"\"\"\nA demo of predictive state updates.\n\"\"\"\n\nimport json\nimport uuid\nfrom typing import Optional\nfrom litellm import completion\nfrom crewai.flow.flow import Flow, start, router, listen\nfrom copilotkit.crewai import (\n copilotkit_stream, \n copilotkit_predict_state,\n CopilotKitState\n)\n\nWRITE_DOCUMENT_TOOL = {\n \"type\": \"function\",\n \"function\": {\n \"name\": \"write_document\",\n \"description\": \" \".join(\"\"\"\n Write a document. Use markdown formatting to format the document.\n It's good to format the document extensively so it's easy to read.\n You can use all kinds of markdown.\n However, do not use italic or strike-through formatting, it's reserved for another purpose.\n You MUST write the full document, even when changing only a few words.\n When making edits to the document, try to make them minimal - do not change every word.\n Keep stories SHORT!\n \"\"\".split()),\n \"parameters\": {\n \"type\": \"object\",\n \"properties\": {\n \"document\": {\n \"type\": \"string\",\n \"description\": \"The document to write\"\n },\n },\n }\n }\n}\n\n\nclass AgentState(CopilotKitState):\n \"\"\"\n The state of the agent.\n \"\"\"\n document: Optional[str] = None\n\nclass PredictiveStateUpdatesFlow(Flow[AgentState]):\n \"\"\"\n This is a sample flow that demonstrates predictive state updates.\n \"\"\"\n\n @start()\n @listen(\"route_follow_up\")\n async def start_flow(self):\n \"\"\"\n This is the entry point for the flow.\n \"\"\"\n\n @router(start_flow)\n async def chat(self):\n \"\"\"\n Standard chat node.\n \"\"\"\n system_prompt = f\"\"\"\n You are a helpful assistant for writing documents. \n To write the document, you MUST use the write_document tool.\n You MUST write the full document, even when changing only a few words.\n When you wrote the document, DO NOT repeat it as a message. \n Just briefly summarize the changes you made. 2 sentences max.\n This is the current state of the document: ----\\n {self.state.document}\\n-----\n \"\"\"\n\n # 1. Here we specify that we want to stream the tool call to write_document\n # to the frontend as state.\n await copilotkit_predict_state({\n \"document\": {\n \"tool_name\": \"write_document\",\n \"tool_argument\": \"document\"\n }\n })\n\n # 2. Run the model and stream the response\n # Note: In order to stream the response, wrap the completion call in\n # copilotkit_stream and set stream=True.\n response = await copilotkit_stream(\n completion(\n\n # 2.1 Specify the model to use\n model=\"openai/gpt-4o\",\n messages=[\n {\n \"role\": \"system\", \n \"content\": system_prompt\n },\n *self.state.messages\n ],\n\n # 2.2 Bind the tools to the model\n tools=[\n *self.state.copilotkit.actions,\n WRITE_DOCUMENT_TOOL\n ],\n\n # 2.3 Disable parallel tool calls to avoid race conditions,\n # enable this for faster performance if you want to manage\n # the complexity of running tool calls in parallel.\n parallel_tool_calls=False,\n stream=True\n )\n )\n\n message = response.choices[0].message\n\n # 3. Append the message to the messages in state\n self.state.messages.append(message)\n\n # 4. Handle tool call\n if message.get(\"tool_calls\"):\n tool_call = message[\"tool_calls\"][0]\n tool_call_id = tool_call[\"id\"]\n tool_call_name = tool_call[\"function\"][\"name\"]\n tool_call_args = json.loads(tool_call[\"function\"][\"arguments\"])\n\n if tool_call_name == \"write_document\":\n self.state.document = tool_call_args[\"document\"]\n\n # 4.1 Append the result to the messages in state\n self.state.messages.append({\n \"role\": \"tool\",\n \"content\": \"Document written.\",\n \"tool_call_id\": tool_call_id\n })\n\n # 4.2 Append a tool call to confirm changes\n self.state.messages.append({\n \"role\": \"assistant\",\n \"content\": \"\",\n \"tool_calls\": [{\n \"id\": str(uuid.uuid4()),\n \"function\": {\n \"name\": \"confirm_changes\",\n \"arguments\": \"{}\"\n }\n }]\n })\n\n return \"route_end\"\n\n # 5. If our tool was not called, return to the end route\n return \"route_end\"\n\n @listen(\"route_end\")\n async def end(self):\n \"\"\"\n End the flow.\n \"\"\"\n",
"path": "crewai_predictive_state_updates/agent.py",
"language": "python",
"type": "file"
},
{
"name": "page.tsx",
"content": "\"use client\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\n\nimport MarkdownIt from \"markdown-it\";\n\nimport { diffWords } from \"diff\";\nimport { useEditor, EditorContent } from \"@tiptap/react\";\nimport StarterKit from \"@tiptap/starter-kit\";\nimport { useEffect, useState } from \"react\";\nimport {\n CopilotKit,\n useCoAgent,\n useCopilotAction,\n useCopilotChat,\n} from \"@copilotkit/react-core\";\nimport { CopilotSidebar, useCopilotChatSuggestions } from \"@copilotkit/react-ui\";\nimport { chatSuggestions, initialPrompt } from \"@/lib/prompts\";\nimport { AGENT_TYPE } from \"@/config\";\nconst extensions = [StarterKit];\n\nexport default function PredictiveStateUpdates() {\n\n return (\n <CopilotKit\n runtimeUrl={AGENT_TYPE == \"general\" ? \"/api/copilotkit?crewai=true\" : \"/api/copilotkit\"}\n showDevConsole={false}\n agent=\"predictive_state_updates\"\n >\n <div\n className=\"min-h-screen w-full\"\n style={\n {\n // \"--copilot-kit-primary-color\": \"#222\",\n // \"--copilot-kit-separator-color\": \"#CCC\",\n } as React.CSSProperties\n }\n >\n <CopilotSidebar\n defaultOpen={true}\n labels={{\n title: \"AI Document Editor\",\n initial: initialPrompt.predictiveStateUpdates,\n }}\n clickOutsideToClose={false}\n >\n <DocumentEditor />\n </CopilotSidebar>\n </div>\n </CopilotKit>\n );\n}\n\ninterface AgentState {\n document: string;\n}\n\nconst DocumentEditor = () => {\n const editor = useEditor({\n extensions,\n immediatelyRender: false,\n editorProps: {\n attributes: { class: \"min-h-screen p-10\" },\n },\n });\n const [placeholderVisible, setPlaceholderVisible] = useState(false);\n const [currentDocument, setCurrentDocument] = useState(\"\");\n const { isLoading } = useCopilotChat();\n\n const {\n state: agentState,\n setState: setAgentState,\n nodeName,\n } = useCoAgent<AgentState>({\n name: \"predictive_state_updates\",\n initialState: {\n document: \"\",\n },\n });\n\n useEffect(() => {\n if (isLoading) {\n setCurrentDocument(editor?.getText() || \"\");\n }\n editor?.setEditable(!isLoading);\n }, [isLoading]);\n\n useEffect(() => {\n if (nodeName == \"end\") {\n // set the text one final time when loading is done\n if (\n currentDocument.trim().length > 0 &&\n currentDocument !== agentState?.document\n ) {\n const newDocument = agentState?.document || \"\";\n const diff = diffPartialText(currentDocument, newDocument, true);\n const markdown = fromMarkdown(diff);\n editor?.commands.setContent(markdown);\n }\n }\n }, [nodeName]);\n\n useEffect(() => {\n if (isLoading) {\n if (currentDocument.trim().length > 0) {\n const newDocument = agentState?.document || \"\";\n const diff = diffPartialText(currentDocument, newDocument);\n const markdown = fromMarkdown(diff);\n editor?.commands.setContent(markdown);\n } else {\n const markdown = fromMarkdown(agentState?.document || \"\");\n editor?.commands.setContent(markdown);\n }\n }\n }, [agentState?.document]);\n\n const text = editor?.getText() || \"\";\n\n useEffect(() => {\n setPlaceholderVisible(text.length === 0);\n\n if (!isLoading) {\n setCurrentDocument(text);\n setAgentState({\n document: text,\n });\n }\n }, [text]);\n\n useCopilotAction({\n name: \"confirm_changes\",\n renderAndWaitForResponse: ({ args, respond, status }) =>\n <ConfirmChanges\n args={args}\n respond={respond}\n status={status}\n onReject={() => {\n editor?.commands.setContent(fromMarkdown(currentDocument));\n setAgentState({ document: currentDocument });\n }}\n onConfirm={() => {\n editor?.commands.setContent(fromMarkdown(agentState?.document || \"\"));\n setCurrentDocument(agentState?.document || \"\");\n setAgentState({ document: agentState?.document || \"\", });\n }}\n />\n });\n useCopilotChatSuggestions({\n instructions: chatSuggestions.predictiveStateUpdates,\n })\n return (\n <div className=\"relative min-h-screen w-full\">\n {placeholderVisible && (\n <div className=\"absolute top-6 left-6 m-4 pointer-events-none text-gray-400\">\n Write whatever you want here in Markdown format...\n </div>\n )}\n <EditorContent editor={editor} />\n </div>\n );\n};\n\n\ninterface ConfirmChangesProps {\n args: any;\n respond: any;\n status: any;\n onReject: () => void;\n onConfirm: () => void;\n}\n\nfunction ConfirmChanges({ args, respond, status, onReject, onConfirm }: ConfirmChangesProps) {\n const [accepted, setAccepted] = useState<boolean | null>(null);\n return (\n <div className=\"bg-white p-6 rounded shadow-lg border border-gray-200 mt-5 mb-5\">\n <h2 className=\"text-lg font-bold mb-4\">Confirm Changes</h2>\n <p className=\"mb-6\">Do you want to accept the changes?</p>\n {accepted === null && (\n <div className=\"flex justify-end space-x-4\">\n <button\n className={`bg-gray-200 text-black py-2 px-4 rounded disabled:opacity-50 ${status === \"executing\" ? \"cursor-pointer\" : \"cursor-default\"\n }`}\n disabled={status !== \"executing\"}\n onClick={() => {\n if (respond) {\n setAccepted(false);\n onReject();\n respond({ accepted: false });\n }\n }}\n >\n Reject\n </button>\n <button\n className={`bg-black text-white py-2 px-4 rounded disabled:opacity-50 ${status === \"executing\" ? \"cursor-pointer\" : \"cursor-default\"\n }`}\n disabled={status !== \"executing\"}\n onClick={() => {\n if (respond) {\n setAccepted(true);\n onConfirm();\n respond({ accepted: true });\n }\n }}\n >\n Confirm\n </button>\n </div>\n )}\n {accepted !== null && (\n <div className=\"flex justify-end\">\n <div className=\"mt-4 bg-gray-200 text-black py-2 px-4 rounded inline-block\">\n {accepted ? \"✓ Accepted\" : \"✗ Rejected\"}\n </div>\n </div>\n )}\n </div>\n );\n}\n\nfunction fromMarkdown(text: string) {\n const md = new MarkdownIt({\n typographer: true,\n html: true,\n });\n\n return md.render(text);\n}\n\nfunction diffPartialText(\n oldText: string,\n newText: string,\n isComplete: boolean = false\n) {\n let oldTextToCompare = oldText;\n if (oldText.length > newText.length && !isComplete) {\n // make oldText shorter\n oldTextToCompare = oldText.slice(0, newText.length);\n }\n\n const changes = diffWords(oldTextToCompare, newText);\n\n let result = \"\";\n changes.forEach((part) => {\n if (part.added) {\n result += `<em>${part.value}</em>`;\n } else if (part.removed) {\n result += `<s>${part.value}</s>`;\n } else {\n result += part.value;\n }\n });\n\n if (oldText.length > newText.length && !isComplete) {\n result += oldText.slice(newText.length);\n }\n\n return result;\n}\n\nfunction isAlpha(text: string) {\n return /[a-zA-Z\\u00C0-\\u017F]/.test(text.trim());\n}\n",
"path": "crewai_predictive_state_updates/page.tsx",
"language": "typescript",
"type": "file"
},
{
"name": "style.css",
"content": "/* Basic editor styles */\n.tiptap-container {\n height: 100vh; /* Full viewport height */\n width: 100vw; /* Full viewport width */\n display: flex;\n flex-direction: column;\n}\n\n.tiptap {\n flex: 1; /* Take up remaining space */\n overflow: auto; /* Allow scrolling if content overflows */\n}\n\n.tiptap :first-child {\n margin-top: 0;\n}\n\n/* List styles */\n.tiptap ul,\n.tiptap ol {\n padding: 0 1rem;\n margin: 1.25rem 1rem 1.25rem 0.4rem;\n}\n\n.tiptap ul li p,\n.tiptap ol li p {\n margin-top: 0.25em;\n margin-bottom: 0.25em;\n}\n\n/* Heading styles */\n.tiptap h1,\n.tiptap h2,\n.tiptap h3,\n.tiptap h4,\n.tiptap h5,\n.tiptap h6 {\n line-height: 1.1;\n margin-top: 2.5rem;\n text-wrap: pretty;\n font-weight: bold;\n}\n\n.tiptap h1,\n.tiptap h2,\n.tiptap h3,\n.tiptap h4,\n.tiptap h5,\n.tiptap h6 {\n margin-top: 3.5rem;\n margin-bottom: 1.5rem;\n}\n\n.tiptap p {\n margin-bottom: 1rem;\n}\n\n.tiptap h1 {\n font-size: 1.4rem;\n}\n\n.tiptap h2 {\n font-size: 1.2rem;\n}\n\n.tiptap h3 {\n font-size: 1.1rem;\n}\n\n.tiptap h4,\n.tiptap h5,\n.tiptap h6 {\n font-size: 1rem;\n}\n\n/* Code and preformatted text styles */\n.tiptap code {\n background-color: var(--purple-light);\n border-radius: 0.4rem;\n color: var(--black);\n font-size: 0.85rem;\n padding: 0.25em 0.3em;\n}\n\n.tiptap pre {\n background: var(--black);\n border-radius: 0.5rem;\n color: var(--white);\n font-family: \"JetBrainsMono\", monospace;\n margin: 1.5rem 0;\n padding: 0.75rem 1rem;\n}\n\n.tiptap pre code {\n background: none;\n color: inherit;\n font-size: 0.8rem;\n padding: 0;\n}\n\n.tiptap blockquote {\n border-left: 3px solid var(--gray-3);\n margin: 1.5rem 0;\n padding-left: 1rem;\n}\n\n.tiptap hr {\n border: none;\n border-top: 1px solid var(--gray-2);\n margin: 2rem 0;\n}\n\n.tiptap s {\n background-color: #f9818150;\n padding: 2px;\n font-weight: bold;\n color: rgba(0, 0, 0, 0.7);\n}\n\n.tiptap em {\n background-color: #b2f2bb;\n padding: 2px;\n font-weight: bold;\n font-style: normal;\n}\n\n.copilotKitWindow {\n box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);\n}\n\n",
"path": "crewai_predictive_state_updates/style.css",
"language": "css",
"type": "file"
},
{
"name": "README.mdx",
"content": "# 📝 Predictive State Updates Document Editor\n\n## What This Demo Shows\n\nThis demo showcases CopilotKit's **predictive state updates** for real-time document collaboration:\n1. **Live Document Editing**: Watch as your Copilot makes changes to a document in real-time\n2. **Diff Visualization**: See exactly what's being changed as it happens\n3. **Streaming Updates**: Changes are displayed character-by-character as the Copilot works\n\n## How to Interact\n\nTry these interactions with the collaborative document editor:\n- \"Fix the grammar and typos in this document\"\n- \"Make this text more professional\"\n- \"Add a section about [topic]\"\n- \"Summarize this content in bullet points\"\n- \"Change the tone to be more casual\"\n\nWatch as the Copilot processes your request and edits the document in real-time right before your eyes.\n\n## ✨ Predictive State Updates in Action\n\n**What's happening technically:**\n- The document state is shared between your UI and the Copilot\n- As the Copilot generates content, changes are streamed to the UI\n- Each modification is visualized with additions and deletions\n- The UI renders these changes progressively, without waiting for completion\n- All edits are tracked and displayed in a visually intuitive way\n\n**What you'll see in this demo:**\n- Text changes are highlighted in different colors (green for additions, red for deletions)\n- The document updates character-by-character, creating a typing-like effect\n- You can see the Copilot's thought process as it refines the content\n- The final document seamlessly incorporates all changes\n- The experience feels collaborative, as if someone is editing alongside you\n\nThis pattern of real-time collaborative editing with diff visualization is perfect for document editors, code review tools, content creation platforms, or any application where users benefit from seeing exactly how content is being transformed! ",
"path": "crewai_predictive_state_updates/README.mdx",
"language": "markdown",
"type": "file"
}
]
},
"crewai_tool_based_generative_ui": {
"files": [
{
"name": "agent.py",
"content": "\"\"\"\nAn example demonstrating tool-based generative UI.\n\"\"\"\n\nfrom crewai.flow.flow import Flow, start\nfrom copilotkit.crewai import copilotkit_stream, CopilotKitState\nfrom litellm import completion\n\n\n# This tool generates a haiku on the server.\n# The tool call will be streamed to the frontend as it is being generated.\nGENERATE_HAIKU_TOOL = {\n \"type\": \"function\",\n \"function\": {\n \"name\": \"generate_haiku\",\n \"description\": \"Generate a haiku in Japanese and its English translation\",\n \"parameters\": {\n \"type\": \"object\",\n \"properties\": {\n \"japanese\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n },\n \"description\": \"An array of three lines of the haiku in Japanese\"\n },\n \"english\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n },\n \"description\": \"An array of three lines of the haiku in English\"\n },\n \"image_names\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n },\n \"description\": \"Names of 3 relevant images from the provided list\"\n }\n },\n \"required\": [\"japanese\", \"english\", \"image_names\"]\n }\n }\n}\n\n\nclass ToolBasedGenerativeUIFlow(Flow[CopilotKitState]):\n \"\"\"\n A flow that demonstrates tool-based generative UI.\n \"\"\"\n\n @start()\n async def chat(self):\n \"\"\"\n The main function handling chat and tool calls.\n \"\"\"\n system_prompt = \"You assist the user in generating a haiku. When generating a haiku using the 'generate_haiku' tool, you MUST also select exactly 3 image filenames from the following list that are most relevant to the haiku's content or theme. Return the filenames in the 'image_names' parameter. Dont provide the relavent image names in your final response to the user. \"\n\n\n # 1. Run the model and stream the response\n # Note: In order to stream the response, wrap the completion call in\n # copilotkit_stream and set stream=True.\n response = await copilotkit_stream(\n completion(\n\n # 1.1 Specify the model to use\n model=\"openai/gpt-4o\",\n messages=[\n {\n \"role\": \"system\", \n \"content\": system_prompt\n },\n *self.state.messages\n ],\n\n # 1.2 Bind the available tools to the model\n tools=[ GENERATE_HAIKU_TOOL ],\n\n # 1.3 Disable parallel tool calls to avoid race conditions,\n # enable this for faster performance if you want to manage\n # the complexity of running tool calls in parallel.\n parallel_tool_calls=False,\n stream=True\n )\n )\n\n message = response.choices[0].message\n\n # 2. Append the message to the messages in state\n self.state.messages.append(message)\n\n",
"path": "crewai_tool_based_generative_ui/agent.py",
"language": "python",
"type": "file"
},
{
"name": "page.tsx",
"content": "\"use client\";\nimport { CopilotKit, useCopilotAction } from \"@copilotkit/react-core\";\nimport { CopilotKitCSSProperties, CopilotSidebar, useCopilotChatSuggestions } from \"@copilotkit/react-ui\";\nimport { useState, useEffect } from \"react\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport { chatSuggestions, initialPrompt } from \"@/lib/prompts\";\nimport HaikuCard from \"./HaikuCard\";\nimport { AGENT_TYPE } from \"@/config\";\n// List of known valid image filenames (should match agent.py)\nconst VALID_IMAGE_NAMES = [\n \"Osaka_Castle_Turret_Stone_Wall_Pine_Trees_Daytime.jpg\",\n \"Tokyo_Skyline_Night_Tokyo_Tower_Mount_Fuji_View.jpg\",\n \"Itsukushima_Shrine_Miyajima_Floating_Torii_Gate_Sunset_Long_Exposure.jpg\",\n \"Takachiho_Gorge_Waterfall_River_Lush_Greenery_Japan.jpg\",\n \"Bonsai_Tree_Potted_Japanese_Art_Green_Foliage.jpeg\",\n \"Shirakawa-go_Gassho-zukuri_Thatched_Roof_Village_Aerial_View.jpg\",\n \"Ginkaku-ji_Silver_Pavilion_Kyoto_Japanese_Garden_Pond_Reflection.jpg\",\n \"Senso-ji_Temple_Asakusa_Cherry_Blossoms_Kimono_Umbrella.jpg\",\n \"Cherry_Blossoms_Sakura_Night_View_City_Lights_Japan.jpg\",\n \"Mount_Fuji_Lake_Reflection_Cherry_Blossoms_Sakura_Spring.jpg\"\n];\n\nexport default function AgenticChat() {\n return (\n <CopilotKit\n showDevConsole={false}\n runtimeUrl={AGENT_TYPE == \"general\" ? \"/api/copilotkit?crewai=true\" : \"/api/copilotkit\"}\n agent=\"tool_based_generative_ui\"\n >\n <div\n className=\"min-h-screen w-full flex items-center justify-center page-background\"\n style={\n {\n \"--copilot-kit-primary-color\": \"#222\",\n \"--copilot-kit-separator-color\": \"#CCC\",\n } as CopilotKitCSSProperties\n }\n >\n <Haiku />\n <CopilotSidebar\n defaultOpen={true}\n labels={{\n title: \"Haiku Generator\",\n initial: initialPrompt.toolCallingGenerativeUI,\n }}\n clickOutsideToClose={false}\n />\n </div>\n </CopilotKit>\n );\n}\n\ninterface Haiku {\n japanese: string[];\n english: string[];\n image_names: string[];\n selectedImage: string | null;\n}\n\n\nfunction Haiku() {\n const [haikus, setHaikus] = useState<Haiku[]>([{\n japanese: [\"仮の句よ\", \"まっさらながら\", \"花を呼ぶ\"],\n english: [\n \"A placeholder verse—\",\n \"even in a blank canvas,\",\n \"it beckons flowers.\",\n ],\n image_names: [],\n selectedImage: null,\n }])\n const [activeIndex, setActiveIndex] = useState(0);\n const [isJustApplied, setIsJustApplied] = useState(false);\n\n const validateAndCorrectImageNames = (rawNames: string[] | undefined): string[] | null => {\n if (!rawNames || rawNames.length !== 3) {\n return null;\n }\n\n const correctedNames: string[] = [];\n const usedValidNames = new Set<string>();\n\n for (const name of rawNames) {\n if (VALID_IMAGE_NAMES.includes(name) && !usedValidNames.has(name)) {\n correctedNames.push(name);\n usedValidNames.add(name);\n if (correctedNames.length === 3) break;\n }\n }\n\n if (correctedNames.length < 3) {\n const availableFallbacks = VALID_IMAGE_NAMES.filter(name => !usedValidNames.has(name));\n for (let i = availableFallbacks.length - 1; i > 0; i--) {\n const j = Math.floor(Math.random() * (i + 1));\n [availableFallbacks[i], availableFallbacks[j]] = [availableFallbacks[j], availableFallbacks[i]];\n }\n\n while (correctedNames.length < 3 && availableFallbacks.length > 0) {\n const fallbackName = availableFallbacks.pop();\n if (fallbackName) {\n correctedNames.push(fallbackName);\n }\n }\n }\n\n while (correctedNames.length < 3 && VALID_IMAGE_NAMES.length > 0) {\n const fallbackName = VALID_IMAGE_NAMES[Math.floor(Math.random() * VALID_IMAGE_NAMES.length)];\n correctedNames.push(fallbackName);\n }\n\n return correctedNames.slice(0, 3);\n };\n\n useCopilotAction({\n name: \"generate_haiku\",\n parameters: [\n {\n name: \"japanese\",\n type: \"string[]\",\n },\n {\n name: \"english\",\n type: \"string[]\",\n },\n {\n name: \"image_names\",\n type: \"string[]\",\n description: \"Names of 3 relevant images\",\n },\n ],\n followUp: false,\n handler: async ({ japanese, english, image_names }) => {\n const finalCorrectedImages = validateAndCorrectImageNames(image_names);\n const newHaiku = {\n japanese: japanese || [],\n english: english || [],\n image_names: finalCorrectedImages || [],\n selectedImage: finalCorrectedImages?.[0] || null,\n };\n console.log(finalCorrectedImages, \"finalCorrectedImages\");\n setHaikus(prev => [...prev, newHaiku]);\n setActiveIndex(haikus.length - 1);\n setIsJustApplied(true);\n setTimeout(() => setIsJustApplied(false), 600);\n return \"Haiku generated.\";\n },\n render: ({ args: generatedHaiku }) => {\n return (\n <HaikuCard generatedHaiku={generatedHaiku} setHaikus={setHaikus} haikus={haikus} />\n );\n },\n }, [haikus]);\n\n useCopilotChatSuggestions({\n instructions: chatSuggestions.toolCallingGenerativeUI,\n });\n return (\n <div className=\"flex h-screen w-full\">\n\n {/* Thumbnail List */}\n <div className=\"w-40 p-4 border-r border-gray-200 overflow-y-auto overflow-x-hidden\">\n {haikus.filter((haiku) => haiku.english[0] !== \"A placeholder verse—\").map((haiku, index) => (\n <div\n key={index}\n className={`haiku-card animated-fade-in mb-4 cursor-pointer ${index === activeIndex ? 'active' : ''}`}\n style={{\n width: '80px',\n transform: 'scale(0.2)',\n transformOrigin: 'top left',\n marginBottom: '-340px',\n opacity: index === activeIndex ? 1 : 0.5,\n transition: 'opacity 0.2s',\n }}\n onClick={() => setActiveIndex(index)}\n >\n {haiku.japanese.map((line, lineIndex) => (\n <div\n className=\"flex items-start gap-2 mb-2 haiku-line\"\n key={lineIndex}\n >\n <p className=\"text-2xl font-bold text-gray-600 w-auto\">{line}</p>\n <p className=\"text-xs font-light text-gray-500 w-auto\">{haiku.english?.[lineIndex]}</p>\n </div>\n ))}\n {haiku.image_names && haiku.image_names.length === 3 && (\n <div className=\"mt-2 flex gap-2 justify-center\">\n {haiku.image_names.map((imageName, imgIndex) => (\n <img\n style={{\n width: '110px',\n height: '110px',\n objectFit: 'cover',\n }}\n key={imageName}\n src={`/images/${imageName}`}\n alt={imageName || \"\"}\n className=\"haiku-card-image w-12 h-12 object-cover\"\n />\n ))}\n </div>\n )}\n </div>\n ))}\n </div>\n\n {/* Main Display */}\n {/* Add a margin to the left of margin-left: -48px; */}\n <div className=\"flex-1 p-8 flex items-center justify-center \" style={{ marginLeft: '-48px' }}>\n <div className=\"haiku-stack\">\n {haikus.filter((_haiku: Haiku, index: number) => {\n if (haikus.length == 1) return true;\n else return index == activeIndex + 1;\n }).map((haiku, index) => (\n <div\n key={index}\n className={`haiku-card animated-fade-in ${isJustApplied && index === activeIndex ? 'applied-flash' : ''} ${index === activeIndex ? 'active' : ''}`}\n style={{\n zIndex: index === activeIndex ? haikus.length : index,\n transform: `translateY(${index === activeIndex ? '0' : `${(index - activeIndex) * 20}px`}) scale(${index === activeIndex ? '1' : '0.95'})`,\n }}\n // onClick={() => setActiveIndex(index)}\n >\n {haiku.japanese.map((line, lineIndex) => (\n <div\n className=\"flex items-start gap-4 mb-4 haiku-line\"\n key={lineIndex}\n style={{ animationDelay: `${lineIndex * 0.1}s` }}\n >\n <p className=\"text-4xl font-bold text-gray-600 w-auto\">{line}</p>\n <p className=\"text-base font-light text-gray-500 w-auto\">{haiku.english?.[lineIndex]}</p>\n </div>\n ))}\n {haiku.image_names && haiku.image_names.length === 3 && (\n <div className=\"mt-6 flex gap-4 justify-center\">\n {haiku.image_names.map((imageName, imgIndex) => (\n <img\n key={imageName}\n src={`/images/${imageName}`}\n alt={imageName || \"\"}\n style={{\n width: '130px',\n height: '130px',\n objectFit: 'cover',\n }}\n className={(haiku.selectedImage === imageName) ? `suggestion-card-image-focus` : `haiku-card-image`}\n />\n ))}\n </div>\n )}\n </div>\n ))}\n </div>\n </div>\n </div>\n );\n}",
"path": "crewai_tool_based_generative_ui/page.tsx",
"language": "typescript",
"type": "file"
},
{
"name": "style.css",
"content": ".copilotKitWindow {\n box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);\n}\n\n.copilotKitHeader {\n border-top-left-radius: 5px !important;\n}\n\n.page-background {\n /* Darker gradient background */\n background: linear-gradient(170deg, #e9ecef 0%, #ced4da 100%); \n}\n\n@keyframes fade-scale-in {\n from {\n opacity: 0;\n transform: translateY(10px) scale(0.98);\n }\n to {\n opacity: 1;\n transform: translateY(0) scale(1);\n }\n}\n\n/* Updated card entry animation */\n@keyframes pop-in {\n 0% {\n opacity: 0;\n transform: translateY(15px) scale(0.95);\n }\n 70% {\n opacity: 1;\n transform: translateY(-2px) scale(1.02);\n }\n 100% {\n opacity: 1;\n transform: translateY(0) scale(1);\n }\n}\n\n/* Animation for subtle background gradient movement */\n@keyframes animated-gradient {\n 0% {\n background-position: 0% 50%;\n }\n 50% {\n background-position: 100% 50%;\n }\n 100% {\n background-position: 0% 50%;\n }\n}\n\n/* Animation for flash effect on apply */\n@keyframes flash-border-glow {\n 0% {\n /* Start slightly intensified */\n border-top-color: #ff5b4a !important;\n box-shadow: 0 10px 30px rgba(0, 0, 0, 0.07), \n inset 0 1px 2px rgba(0, 0, 0, 0.01), \n 0 0 25px rgba(255, 91, 74, 0.5);\n }\n 50% {\n /* Peak intensity */\n border-top-color: #ff4733 !important;\n box-shadow: 0 10px 30px rgba(0, 0, 0, 0.08), \n inset 0 1px 2px rgba(0, 0, 0, 0.01), \n 0 0 35px rgba(255, 71, 51, 0.7);\n }\n 100% {\n /* Return to default state appearance */\n border-top-color: #ff6f61 !important;\n box-shadow: 0 10px 30px rgba(0, 0, 0, 0.07), \n inset 0 1px 2px rgba(0, 0, 0, 0.01), \n 0 0 10px rgba(255, 111, 97, 0.15); \n }\n}\n\n/* Existing animation for haiku lines */\n@keyframes fade-slide-in {\n from {\n opacity: 0;\n transform: translateX(-15px);\n }\n to {\n opacity: 1;\n transform: translateX(0);\n }\n}\n\n.animated-fade-in { \n /* Use the new pop-in animation */\n animation: pop-in 0.6s ease-out forwards;\n}\n\n.haiku-card {\n /* Subtle animated gradient background */\n background: linear-gradient(120deg, #ffffff 0%, #fdfdfd 50%, #ffffff 100%);\n background-size: 200% 200%; \n animation: animated-gradient 10s ease infinite; \n\n /* === Explicit Border Override Attempt === */\n /* 1. Set the default grey border for all sides */\n border: 1px solid #dee2e6; \n \n /* 2. Explicitly override the top border immediately after */\n border-top: 10px solid #ff6f61 !important; /* Orange top - Added !important */\n /* === End Explicit Border Override Attempt === */\n \n padding: 2.5rem 3rem; \n border-radius: 20px; \n \n /* Default glow intensity */\n box-shadow: 0 10px 30px rgba(0, 0, 0, 0.07), \n inset 0 1px 2px rgba(0, 0, 0, 0.01), \n 0 0 15px rgba(255, 111, 97, 0.25); \n text-align: left;\n max-width: 745px; \n margin: 3rem auto;\n min-width: 600px;\n \n /* Transition */\n transition: transform 0.35s ease, box-shadow 0.35s ease, border-top-width 0.35s ease, border-top-color 0.35s ease;\n}\n\n.haiku-card:hover {\n transform: translateY(-8px) scale(1.03); \n /* Enhanced shadow + Glow */\n box-shadow: 0 15px 35px rgba(0, 0, 0, 0.1), \n inset 0 1px 2px rgba(0, 0, 0, 0.01), \n 0 0 25px rgba(255, 91, 74, 0.5); \n /* Modify only top border properties */\n border-top-width: 14px !important; /* Added !important */\n border-top-color: #ff5b4a !important; /* Added !important */\n}\n\n.haiku-card .flex {\n margin-bottom: 1.5rem;\n}\n\n.haiku-card .flex.haiku-line { /* Target the lines specifically */\n margin-bottom: 1.5rem;\n opacity: 0; /* Start hidden for animation */\n animation: fade-slide-in 0.5s ease-out forwards;\n /* animation-delay is set inline in page.tsx */\n}\n\n/* Remove previous explicit color overrides - rely on Tailwind */\n/* .haiku-card p.text-4xl {\n color: #212529; \n}\n\n.haiku-card p.text-base {\n color: #495057; \n} */\n\n.haiku-card.applied-flash {\n /* Apply the flash animation once */\n /* Note: animation itself has !important on border-top-color */\n animation: flash-border-glow 0.6s ease-out forwards; \n}\n\n/* Styling for images within the main haiku card */\n.haiku-card-image {\n width: 9.5rem; /* Increased size (approx w-48) */\n height: 9.5rem; /* Increased size (approx h-48) */\n object-fit: cover;\n border-radius: 1.5rem; /* rounded-xl */\n border: 1px solid #e5e7eb;\n /* Enhanced shadow with subtle orange hint */\n box-shadow: 0 8px 15px rgba(0, 0, 0, 0.1),\n 0 3px 6px rgba(0, 0, 0, 0.08),\n 0 0 10px rgba(255, 111, 97, 0.2);\n /* Inherit animation delay from inline style */\n animation-name: fadeIn;\n animation-duration: 0.5s;\n animation-fill-mode: both;\n}\n\n/* Styling for images within the suggestion card */\n.suggestion-card-image {\n width: 6.5rem; /* Increased slightly (w-20) */\n height: 6.5rem; /* Increased slightly (h-20) */\n object-fit: cover;\n border-radius: 1rem; /* Equivalent to rounded-md */\n border: 1px solid #d1d5db; /* Equivalent to border (using Tailwind gray-300) */\n margin-top: 0.5rem;\n /* Added shadow for suggestion images */\n box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1),\n 0 2px 4px rgba(0, 0, 0, 0.06);\n transition: all 0.2s ease-in-out; /* Added for smooth deselection */\n}\n\n/* Styling for the focused suggestion card image */\n.suggestion-card-image-focus {\n width: 6.5rem; \n height: 6.5rem; \n object-fit: cover;\n border-radius: 1rem; \n margin-top: 0.5rem;\n /* Highlight styles */\n border: 2px solid #ff6f61; /* Thicker, themed border */\n box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1), /* Base shadow for depth */\n 0 0 12px rgba(255, 111, 97, 0.6); /* Orange glow */\n transform: scale(1.05); /* Slightly scale up */\n transition: all 0.2s ease-in-out; /* Smooth transition for focus */\n}\n\n/* Styling for the suggestion card container in the sidebar */\n.suggestion-card {\n border: 1px solid #dee2e6; /* Same default border as haiku-card */\n border-top: 10px solid #ff6f61; /* Same orange top border */\n border-radius: 0.375rem; /* Default rounded-md */\n /* Note: background-color is set by Tailwind bg-gray-100 */\n /* Other styles like padding, margin, flex are handled by Tailwind */\n}\n\n.suggestion-image-container {\n display: flex;\n gap: 1rem;\n justify-content: space-between;\n width: 100%;\n height: 6.5rem;\n}\n\n",
"path": "crewai_tool_based_generative_ui/style.css",
"language": "css",
"type": "file"
},
{
"name": "README.mdx",
"content": "# 🪶 Tool-Based Generative UI Haiku Creator\n\n## What This Demo Shows\n\nThis demo showcases CopilotKit's **tool-based generative UI** capabilities:\n1. **Frontend Rendering of Tool Calls**: Backend tool calls are automatically rendered in the UI\n2. **Dynamic UI Generation**: The UI updates in real-time as the agent generates content\n3. **Elegant Content Presentation**: Complex structured data (haikus) are beautifully displayed\n\n## How to Interact\n\nChat with your Copilot and ask for haikus about different topics:\n- \"Create a haiku about nature\"\n- \"Write a haiku about technology\"\n- \"Generate a haiku about the changing seasons\"\n- \"Make a humorous haiku about programming\"\n\nEach request will trigger the agent to generate a haiku and display it in a visually appealing card format in the UI.\n\n## ✨ Tool-Based Generative UI in Action\n\n**What's happening technically:**\n- The agent processes your request and determines it should create a haiku\n- It calls a backend tool that returns structured haiku data\n- CopilotKit automatically renders this tool call in the frontend\n- The rendering is handled by the registered tool component in your React app\n- No manual state management is required to display the results\n\n**What you'll see in this demo:**\n- As you request a haiku, a beautifully formatted card appears in the UI\n- The haiku follows the traditional 5-7-5 syllable structure\n- Each haiku is presented with consistent styling\n- Multiple haikus can be generated in sequence\n- The UI adapts to display each new piece of content\n\nThis pattern of tool-based generative UI can be extended to create any kind of dynamic content - from data visualizations to interactive components, all driven by your Copilot's tool calls! ",
"path": "crewai_tool_based_generative_ui/README.mdx",
"language": "markdown",
"type": "file"
}
]
},
"crewai_crew_enterprise": {
"files": [
{
"name": "restaurant_finder_crew/src/config/__init__.py",
"content": "# This file is required to make the directory a Python package.\n# By including an __init__.py file, Python treats the directory as a package,\n# allowing you to import modules from this directory.\n# This file can be empty, or it can execute initialization code for the package.\n# In this case, it is left empty as a placeholder to signify the directory as a package.\n",
"path": "crewai_crew_enterprise/restaurant_finder_crew/src/config/__init__.py",
"language": "python",
"type": "file"
},
{
"name": "restaurant_finder_crew/src/config/agents.yaml",
"content": "restaurant_researcher:\n role: Restaurant Research Specialist\n goal: Find restaurants in specified locations and gather details about them\n backstory: >\n You are an expert at finding restaurants and understanding their characteristics.\n You have extensive knowledge of cuisines, dining styles, price points, and \n locations. You know how to search for the most relevant and highly-rated\n restaurants in any area.\n\nrecommendation_specialist:\n role: Restaurant Recommendation Specialist\n goal: Create personalized restaurant recommendations based on research\n backstory: >\n You are skilled at curating restaurant recommendations that match people's\n preferences. You have a talent for highlighting the unique aspects of restaurants\n and explaining why they would be good choices. You know how to present\n restaurant options in an engaging and helpful way.\n",
"path": "crewai_crew_enterprise/restaurant_finder_crew/src/config/agents.yaml",
"language": "yaml",
"type": "file"
},
{
"name": "restaurant_finder_crew/src/config/tasks.yaml",
"content": "search_restaurants_task:\n description: >\n Search for popular and highly-rated restaurants in {location}. Include a variety of \n cuisines, price points, and dining styles. For each restaurant, gather information\n about their cuisine, price range, ratings, popular dishes, and any unique features.\n expected_output: >\n A detailed list of at least 8 restaurants in {location}, with information about\n cuisine type, price range, ratings, signature dishes, and any standout characteristics.\n\npresent_recommendations_task:\n description: >\n Review the restaurants found in {location} and create a curated list of recommendations.\n Highlight what makes each restaurant special and why someone might want to visit.\n Present these recommendations in a friendly, conversational way. Then ask the user\n if they'd like more recommendations or if these options are sufficient.\n expected_output: >\n A well-organized list of restaurant recommendations with engaging descriptions,\n followed by a question asking if the user would like more recommendations.\n\n **Restaurant Recommendations**\n\n - **Restaurant Name**: Description of what makes this restaurant special.\n - **Cuisine**: Type of cuisine\n - **Price Range**: Price range\n - **Ratings**: Ratings\n - **Signature Dishes**: Signature dishes\n - **Standout Characteristics**: Any standout characteristics\n\n **Would you like more recommendations?**\n\nrespond_to_feedback_task:\n description: >\n Based on the user's response to the recommendations, either:\n 1. If they say \"thank you\" or indicate satisfaction, provide a friendly closing response\n along with the finalized list of recommendations.\n 2. If they ask for more recommendations, provide 3-5 additional restaurant options\n with different cuisines or characteristics from the first set.\n expected_output: >\n Either a gracious closing message with the finalized list of recommendations\n or a new set of restaurant recommendations that complement the initial suggestions.\n",
"path": "crewai_crew_enterprise/restaurant_finder_crew/src/config/tasks.yaml",
"language": "yaml",
"type": "file"
},
{
"name": "restaurant_finder_crew/src/tools/__init__.py",
"content": "",
"path": "crewai_crew_enterprise/restaurant_finder_crew/src/tools/__init__.py",
"language": "python",
"type": "file"
},
{
"name": "restaurant_finder_crew/src/tools/custom_tool.py",
"content": "from crewai_tools import BaseTool\n\n\nclass MyCustomTool(BaseTool):\n name: str = \"Name of my tool\"\n description: str = (\n \"Clear description for what this tool is useful for, you agent will need this information to use it.\"\n )\n\n def _run(self, argument: str) -> str:\n # Implementation goes here\n return \"this is an example of a tool output, ignore it and move along.\"\n",
"path": "crewai_crew_enterprise/restaurant_finder_crew/src/tools/custom_tool.py",
"language": "python",
"type": "file"
},
{
"name": "restaurant_finder_crew/src/crew.py",
"content": "from crewai_tools import ScrapeWebsiteTool, SerperDevTool\n\nfrom crewai import Agent, Crew, Process, Task\nfrom crewai.project import CrewBase, agent, crew, task\n\n\n@CrewBase\nclass SimilarCompanyFinderTemplateCrew:\n \"\"\"Restaurant Recommendation Crew\"\"\"\n\n agents_config = \"config/agents.yaml\"\n tasks_config = \"config/tasks.yaml\"\n\n @agent\n def restaurant_researcher(self) -> Agent:\n return Agent(\n config=self.agents_config[\"restaurant_researcher\"],\n tools=[SerperDevTool(), ScrapeWebsiteTool()],\n allow_delegation=False,\n verbose=True,\n )\n\n @agent\n def recommendation_specialist(self) -> Agent:\n return Agent(\n config=self.agents_config[\"recommendation_specialist\"],\n tools=[],\n allow_delegation=False,\n verbose=True,\n )\n\n @task\n def search_restaurants_task(self) -> Task:\n return Task(\n config=self.tasks_config[\"search_restaurants_task\"],\n agent=self.restaurant_researcher(),\n )\n\n @task\n def present_recommendations_task(self) -> Task:\n return Task(\n config=self.tasks_config[\"present_recommendations_task\"],\n agent=self.recommendation_specialist(),\n human_input=True,\n )\n\n @task\n def respond_to_feedback_task(self) -> Task:\n return Task(\n config=self.tasks_config[\"respond_to_feedback_task\"],\n agent=self.recommendation_specialist(),\n output_file=\"restaurant_recommendations.md\",\n )\n\n @crew\n def crew(self) -> Crew:\n \"\"\"Creates the Restaurant Recommendation crew\"\"\"\n return Crew(\n agents=self.agents, # Automatically created by the @agent decorator\n tasks=self.tasks, # Automatically created by the @task decorator\n process=Process.sequential,\n verbose=True,\n # process=Process.hierarchical, # In case you wanna use that instead https://docs.crewai.com/how-to/Hierarchical/\n )\n\n def run(self, inputs=None):\n \"\"\"Run the crew\n \n Args:\n inputs (dict, optional): Input parameters for the crew\n \"\"\"\n if inputs is None:\n inputs = {\n \"location\": \"San Francisco, CA\",\n }\n \n return self.crew().kickoff(inputs=inputs)\n",
"path": "crewai_crew_enterprise/restaurant_finder_crew/src/crew.py",
"language": "python",
"type": "file"
},
{
"name": "restaurant_finder_crew/src/main.py",
"content": "#!/usr/bin/env python\nimport sys\n\nfrom similar_company_finder_template.crew import SimilarCompanyFinderTemplateCrew\n\n# This main file is intended to be a way for your to run your\n# crew locally, so refrain from adding necessary logic into this file.\n# Replace with inputs you want to test with, it will automatically\n# interpolate any tasks and agents information\n\n\ndef run():\n \"\"\"\n Run the crew.\n \"\"\"\n inputs = {\n \"location\": \"San Francisco, CA\",\n }\n\n SimilarCompanyFinderTemplateCrew().run(inputs=inputs)\n\n\ndef train():\n \"\"\"\n Train the crew for a given number of iterations.\n \"\"\"\n inputs = {\"location\": \"New York, NY\"}\n try:\n SimilarCompanyFinderTemplateCrew().crew().train(\n n_iterations=int(sys.argv[1]), filename=sys.argv[2], inputs=inputs\n )\n\n except Exception as e:\n raise Exception(f\"An error occurred while training the crew: {e}\")\n\n\ndef replay():\n \"\"\"\n Replay the crew execution from a specific task.\n \"\"\"\n try:\n SimilarCompanyFinderTemplateCrew().crew().replay(task_id=sys.argv[1])\n\n except Exception as e:\n raise Exception(f\"An error occurred while replaying the crew: {e}\")\n\n\ndef test():\n \"\"\"\n Test the crew execution and returns the results.\n \"\"\"\n inputs = {\"location\": \"Chicago, IL\"}\n try:\n SimilarCompanyFinderTemplateCrew().crew().test(\n n_iterations=int(sys.argv[1]), openai_model_name=sys.argv[2], inputs=inputs\n )\n\n except Exception as e:\n raise Exception(f\"An error occurred while replaying the crew: {e}\")\n",
"path": "crewai_crew_enterprise/restaurant_finder_crew/src/main.py",
"language": "python",
"type": "file"
},
{
"name": "restaurant_finder_crew/pyproject.toml",
"content": "[project]\nname = \"similar_company_finder_template\"\nversion = \"0.1.0\"\ndescription = \"similar-company-finder-template using crewAI\"\nauthors = [\n { name = \"Your Name\", email = \"you@example.com\" },\n]\nrequires-python = \">=3.10,<=3.13\"\ndependencies = [\n \"crewai[tools]>=0.102.0\",\n \"langchain-core>=0.2.30\",\n]\n\n[project.scripts]\nsimilar_company_finder_template = \"similar_company_finder_template.main:run\"\nrun_crew = \"similar_company_finder_template.main:run\"\ntrain = \"similar_company_finder_template.main:train\"\nreplay = \"similar_company_finder_template.main:replay\"\ntest = \"similar_company_finder_template.main:test\"\n\n[build-system]\nrequires = [\n \"hatchling\",\n]\nbuild-backend = \"hatchling.build\"\n",
"path": "crewai_crew_enterprise/restaurant_finder_crew/pyproject.toml",
"language": "toml",
"type": "file"
},
{
"name": "restaurant_finder_crew/README.md",
"content": "# Crew Enterprise Demo\n\n_Prerequisites:_\n\n- [CopilotKit Cloud Account](https://cloud.copilotkit.ai)\n- [CrewAI Enterprise Account](https://www.crewai.com/enterprise)\n- [OpenAI Api Key](https://platform.openai.com/api-keys)\n\n#### 1. Setup\n\n- Deploy the demo crew located at `agent-py` on [Crew Enterprise](https://www.crewai.com/)\n- Register your Crew in [Copilot Cloud](https://cloud.copilotkit.ai/)\n- Copy `Cloud Public API Key` from Copilot Cloud\n\n\n\n### 2. Start the Frontend\n\nTo start the frontend, navigate to the `ui` directory and run the development server:\n\n```bash\n> cd ui\n> cp .env.example .env # Copy the example env file\n> # Edit the .env file with your Copilot Cloud Public API Key\n> pnpm install\n> pnpm run dev\n```\n\nThe `.env` file should contain:\n\n- `NEXT_PUBLIC_AGENT_NAME`: The name of your crew agent (default is `restaurant_finder_agent`)\n- `NEXT_PUBLIC_CPK_PUBLIC_API_KEY`: Your Copilot Cloud Public API Key\n",
"path": "crewai_crew_enterprise/restaurant_finder_crew/README.md",
"language": "markdown",
"type": "file"
},
{
"name": "restaurant_finder_crew/README.mdx",
"content": "## Build AI Agents with CopilotKit + CrewAI\n\n_Prerequisites:_\n\n- [CopilotKit Cloud Account](https://cloud.copilotkit.ai)\n- [CrewAI Enterprise Account](https://www.crewai.com/enterprise)\n- [OpenAI Api Key](https://platform.openai.com/api-keys)\n\n#### 1. Setup\n\n- Deploy the demo crew located at `agent-py` on [Crew Enterprise](https://www.crewai.com/)\n- Register your Crew in [Copilot Cloud](https://cloud.copilotkit.ai/)\n- Copy `Cloud Public API Key` from Copilot Cloud\n\n### 2. Start the Frontend\n\nTo start the frontend, navigate to the `ui` directory and run the development server:\n\n```sh\n> cd ui\n> cp .env.example .env # Copy the example env file\n> # Edit the .env file with your Copilot Cloud Public API Key\n> pnpm install\n> pnpm run dev\n```\n\nThe `.env` file should contain:\n\n- `NEXT_PUBLIC_AGENT_NAME`: The name of your crew agent (default is `restaurant_finder_agent`)\n- `NEXT_PUBLIC_CPK_PUBLIC_API_KEY`: Your Copilot Cloud Public API Key\n",
"path": "crewai_crew_enterprise/restaurant_finder_crew/README.mdx",
"language": "markdown",
"type": "file"
},
{
"name": "restaurant_finder_crew/code.tsx",
"content": "// @ts-nocheck\n\"use client\";\n\nimport AgentStatus from \"@/components/AgentStatus\";\nimport { DebugViewer } from \"@/components/DebugViewer\";\nimport FormattedContent, { formatContent } from \"@/components/FormattedContent\";\nimport {\n ResizableHandle,\n ResizablePanel,\n ResizablePanelGroup,\n} from \"@/components/ui/resizable\";\nimport { useGlobalContext } from \"@/context/GlobalContext\";\nimport { useInput } from \"@/hooks/useInput\";\nimport { useWindowSize } from \"@/hooks/useWindowSize\";\nimport { formatText } from \"@/lib/utils\";\nimport {\n useCoAgent,\n useCoAgentStateRender,\n useCopilotAction,\n useCopilotChat,\n} from \"@copilotkit/react-core\";\nimport {\n AgentState,\n CopilotChat,\n CopilotKitCSSProperties,\n DefaultResponseRenderer,\n DefaultStateRenderer,\n ResponseStatus,\n} from \"@copilotkit/react-ui\";\nimport { MessageRole, TextMessage } from \"@copilotkit/runtime-client-gql\";\nimport { useEffect, useState } from \"react\";\nimport ReactMarkdown from \"react-markdown\";\n\ninterface CrewFeedback {\n timestamp: string;\n id: string;\n task_id: string;\n task_output: string;\n}\n\n// Simple skeleton item for loading state\nconst SkeletonItem = () => (\n <div className=\"py-0.5 animate-pulse\">\n <div className=\"flex justify-between\">\n <div className=\"h-2.5 bg-gray-200 dark:bg-gray-700 rounded w-16\"></div>\n <div className=\"h-2 bg-gray-200 dark:bg-gray-700 rounded w-8\"></div>\n </div>\n <div className=\"mt-0.5 h-6 bg-gray-200 dark:bg-gray-700 rounded\"></div>\n </div>\n);\n\n// Application state extending the library AgentState\ninterface AppState extends AgentState {\n inputs: {\n location: string;\n [key: string]: string;\n };\n result: string;\n status: ResponseStatus;\n}\n\nexport default function Home() {\n const { location, setLocation } = useGlobalContext();\n const { appendMessage } = useCopilotChat();\n const { isMobile } = useWindowSize();\n const [direction, setDirection] = useState<\"horizontal\" | \"vertical\">(\n \"horizontal\"\n );\n\n useEffect(() => {\n setDirection(isMobile ? \"vertical\" : \"horizontal\");\n }, [isMobile]);\n\n const { state, name, running, setState } = useCoAgent<AppState>({\n name: \"restaurant_finder_agent\",\n initialState: {\n inputs: {\n location: location.city,\n },\n result: \"Final result will appear here\",\n },\n });\n\n /**\n * Appends a message to the chat with the given key and value\n * This would be used for programatically triggering Crew\n * @param key - The key of the input\n * @param value - The value of the input\n */\n const setInput = async (key: keyof AppState[\"inputs\"], value: string) => {\n setState({\n ...state,\n inputs: {\n ...state.inputs,\n [key as string]: value,\n },\n });\n setTimeout(async () => {\n await appendMessage(\n new TextMessage({\n content: `My ${String(key)} is ${value}`,\n role: MessageRole.Developer,\n })\n );\n }, 1000);\n };\n\n useInput({\n onInputSubmit: (city) => {\n setInput(\"location\", city);\n },\n });\n\n // Update location.city if state.inputs.location changes\n useEffect(() => {\n if (state?.inputs?.location && state.inputs.location !== location.city) {\n setLocation({ ...location, city: state.inputs.location });\n }\n }, [state?.inputs?.location, location, setLocation]);\n\n useCoAgentStateRender({\n name: \"restaurant_finder_agent\",\n render: ({\n state,\n status,\n }: {\n state: AppState;\n status: ResponseStatus;\n }) => {\n return (\n <DefaultStateRenderer\n state={state}\n status={status}\n defaultCollapsed={true}\n SkeletonLoader={SkeletonItem}\n StateItemRenderer={({ item }) => {\n return (\n <div key={item.id}>\n <div className=\"text-xs\">\n <div className=\"opacity-70\">\n {\"tool\" in item ? item.tool : item.name}\n </div>\n </div>\n\n {\"thought\" in item && item.thought && (\n <div className=\"mt-0.5 text-xs opacity-80\">\n {item.thought}\n </div>\n )}\n {\"result\" in item &&\n item.result !== undefined &&\n item.result !== null && (\n <div className=\"mt-0.5 text-xs\">\n <FormattedContent\n content={formatContent(item.result)}\n showJsonLabel={false}\n isCollapsed={true}\n />\n </div>\n )}\n {\"description\" in item && item.description && (\n <div className=\"mt-0.5 text-xs opacity-80\">\n {item.description}\n </div>\n )}\n </div>\n );\n }}\n />\n );\n },\n });\n\n useCopilotAction({\n name: \"crew_requesting_feedback\",\n renderAndWaitForResponse({ status, args, respond }) {\n const feedback = args as CrewFeedback;\n return (\n <DefaultResponseRenderer\n response={{\n id: feedback.id || String(Date.now()),\n content: feedback.task_output || String(feedback),\n metadata: feedback,\n }}\n onRespond={(input: string) => {\n respond?.(input);\n }}\n status={status as ResponseStatus}\n ContentRenderer={({ content }) => (\n <div className=\"text-sm text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800 rounded-md shadow-sm p-4 h-full overflow-y-auto whitespace-pre-line text-left\">\n <ReactMarkdown>{content}</ReactMarkdown>\n </div>\n )}\n />\n );\n },\n });\n\n const agentName = name.replace(/[^a-zA-Z0-9]/g, \" \");\n\n return (\n <div className=\"w-full h-full relative\">\n {/* Status Badge */}\n <AgentStatus running={running} state={{ status: state.status }} />\n\n {/* Debug Viewer - Fixed at bottom right corner of page */}\n <div className=\"fixed bottom-4 right-4 z-50\">\n <DebugViewer state={state} />\n </div>\n\n <ResizablePanelGroup direction={direction} className=\"w-full h-full\">\n <ResizablePanel defaultSize={60} minSize={30}>\n <div\n className=\"h-full relative overflow-y-auto\"\n style={\n {\n \"--copilot-kit-primary-color\": \"#4F4F4F\",\n } as CopilotKitCSSProperties\n }\n >\n <CopilotChat\n instructions={process.env.NEXT_PUBLIC_COPILOT_INSTRUCTIONS}\n className=\"h-full flex flex-col\"\n icons={{\n spinnerIcon: (\n <span className=\"h-5 w-5 text-gray-500 animate-pulse\">\n ...\n </span>\n ),\n }}\n />\n </div>\n </ResizablePanel>\n\n <ResizableHandle withHandle />\n\n <ResizablePanel defaultSize={40} minSize={25}>\n <div className=\"h-full overflow-y-auto bg-gray-50 dark:bg-gray-900 p-3\">\n <div className=\"flex flex-col h-full\">\n <div className=\"flex items-center justify-between mb-2\">\n <h1 className=\"text-lg font-medium text-gray-800 dark:text-gray-200\">\n {agentName}\n </h1>\n </div>\n\n <div className=\"h-full\">\n <div className=\"text-sm text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800 rounded-md shadow-sm p-4 h-full overflow-y-auto whitespace-pre-line\">\n <div\n dangerouslySetInnerHTML={{\n __html: formatText(state.result),\n }}\n />\n </div>\n </div>\n </div>\n </div>\n </ResizablePanel>\n </ResizablePanelGroup>\n </div>\n );\n}\n",
"path": "crewai_crew_enterprise/restaurant_finder_crew/code.tsx",
"language": "typescript",
"type": "file"
}
]
},
"langgraph_agentic_chat": {
"files": [
{
"name": "agent.py",
"content": "\"\"\"\nA simple agentic chat flow using LangGraph instead of CrewAI.\n\"\"\"\n\nfrom typing import Dict, List, Any, Optional\n\n# Updated imports for LangGraph\nfrom langchain_core.runnables import RunnableConfig\nfrom langgraph.graph import StateGraph, END, START\n# Updated imports for CopilotKit\nfrom copilotkit import CopilotKitState\nfrom copilotkit.langchain import copilotkit_customize_config\nfrom langgraph.types import Command\nfrom typing_extensions import Literal\nfrom langchain_openai import ChatOpenAI\nfrom langchain_core.messages import SystemMessage\nfrom langgraph.checkpoint.memory import MemorySaver\nfrom copilotkit.langgraph import (copilotkit_exit)\n\n\nclass AgentState(CopilotKitState):\n \"\"\"\n Here we define the state of the agent\n\n In this instance, we're inheriting from CopilotKitState, which will bring in\n the CopilotKitState fields. We're also adding a custom field, `language`,\n which will be used to set the language of the agent.\n \"\"\"\n\n\nasync def chat_node(state: AgentState, config: RunnableConfig):\n \"\"\"\n Standard chat node based on the ReAct design pattern. It handles:\n - The model to use (and binds in CopilotKit actions and the tools defined above)\n - The system prompt\n - Getting a response from the model\n - Handling tool calls\n\n For more about the ReAct design pattern, see: \n https://www.perplexity.ai/search/react-agents-NcXLQhreS0WDzpVaS4m9Cg\n \"\"\"\n \n # 1. Define the model\n model = ChatOpenAI(model=\"gpt-4o\")\n \n # Define config for the model\n if config is None:\n config = RunnableConfig(recursion_limit=25)\n else:\n # Use CopilotKit's custom config functions to properly set up streaming\n config = copilotkit_customize_config(config)\n\n # 2. Bind the tools to the model\n model_with_tools = model.bind_tools(\n [\n *state[\"copilotkit\"][\"actions\"],\n # your_tool_here\n ],\n\n # 2.1 Disable parallel tool calls to avoid race conditions,\n # enable this for faster performance if you want to manage\n # the complexity of running tool calls in parallel.\n parallel_tool_calls=False,\n )\n\n # 3. Define the system message by which the chat model will be run\n system_message = SystemMessage(\n content=f\"You are a helpful assistant. .\"\n )\n\n # 4. Run the model to generate a response\n response = await model_with_tools.ainvoke([\n system_message,\n *state[\"messages\"],\n ], config)\n\n # 6. We've handled all tool calls, so we can end the graph.\n await copilotkit_exit(config)\n return Command(\n goto=END,\n update={\n \"messages\": response\n }\n )\n\n# Define a new graph\nworkflow = StateGraph(AgentState)\nworkflow.add_node(\"chat_node\", chat_node)\nworkflow.set_entry_point(\"chat_node\")\n\n# Add explicit edges, matching the pattern in other examples\nworkflow.add_edge(START, \"chat_node\")\nworkflow.add_edge(\"chat_node\", END)\n\n# Compile the graph\nagentic_chat_graph = workflow.compile(checkpointer=MemorySaver())",
"path": "langgraph_agentic_chat/agent.py",
"language": "python",
"type": "file"
},
{
"name": "page.tsx",
"content": "\"use client\";\nimport React, { useState } from \"react\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport { CopilotKit, useCopilotAction } from \"@copilotkit/react-core\";\nimport { CopilotChat, useCopilotChatSuggestions } from \"@copilotkit/react-ui\";\nimport { chatSuggestions, initialPrompt } from \"@/lib/prompts\";\nimport { AGENT_TYPE } from \"@/config\";\nconst AgenticChat: React.FC = () => {\n return (\n <CopilotKit\n runtimeUrl={AGENT_TYPE == \"general\" ? \"/api/copilotkit?langgraph=true\" : \"/api/copilotkit\"}\n showDevConsole={false}\n agent=\"agentic_chat\"\n >\n <Chat />\n </CopilotKit>\n );\n};\n\nconst Chat = () => {\n const [background, setBackground] = useState<string>(\"#fefefe\");\n\n useCopilotAction({\n name: \"change_background\",\n description:\n \"Change the background color of the chat. Can be anything that the CSS background attribute accepts. Regular colors, linear of radial gradients etc.\",\n parameters: [\n {\n name: \"background\",\n type: \"string\",\n description: \"The background. Prefer gradients.\",\n },\n ],\n handler: ({ background }) => {\n setBackground(background);\n },\n });\n\n useCopilotChatSuggestions({\n instructions: chatSuggestions.agenticChat,\n })\n\n return (\n <div\n className=\"flex justify-center items-center h-full w-full\"\n style={{ background }}\n >\n <div className=\"w-8/10 h-8/10 rounded-lg\">\n <CopilotChat\n className=\"h-full w-full rounded-lg py-6\"\n labels={{ initial: initialPrompt.agenticChat }}\n />\n </div>\n </div>\n );\n};\n\nexport default AgenticChat;\n",
"path": "langgraph_agentic_chat/page.tsx",
"language": "typescript",
"type": "file"
},
{
"name": "style.css",
"content": ".copilotKitInput {\n border-bottom-left-radius: 0.75rem;\n border-bottom-right-radius: 0.75rem;\n border-top-left-radius: 0.75rem;\n border-top-right-radius: 0.75rem;\n border: 1px solid var(--copilot-kit-separator-color) !important;\n background-color: #fff;\n}\n\n.copilotKitChat {\n background-color: #fff !important;\n}\n",
"path": "langgraph_agentic_chat/style.css",
"language": "css",
"type": "file"
},
{
"name": "README.mdx",
"content": "# 🤖 Agentic Chat with Frontend Tools\n\n## What This Demo Shows\n\nThis demo showcases CopilotKit's **agentic chat** capabilities with **frontend tool integration**:\n1. **Natural Conversation**: Chat with your Copilot in a familiar chat interface\n2. **Frontend Tool Execution**: The Copilot can directly interacts with your UI by calling frontend functions\n3. **Seamless Integration**: Tools defined in the frontend and automatically discovered and made available to the agent\n\n## How to Interact\n\nTry asking your Copilot to:\n- \"Can you change the background color to something more vibrant?\"\n- \"Make the background a blue to purple gradient\"\n- \"Set the background to a sunset-themed gradient\"\n- \"Change it back to a simple light color\"\n\nYou can also chat about other topics - the agent will respond conversationally while having the ability to use your UI tools when appropriate.\n\n## ✨ Frontend Tool Integration in Action\n\n**What's happening technically:**\n- The React component defines a frontend function using `useCopilotAction`\n- CopilotKit automatically exposes this function to the agent\n- When you make a request, the agent determines whether to use the tool\n- The agent calls the function with the appropriate parameters\n- The UI immediately updates in response\n\n**What you'll see in this demo:**\n- The Copilot understands requests to change the background\n- It generates CSS values for colors and gradients\n- When it calls the tool, the background changes instantly\n- The agent provides a conversational response about the changes it made\n\nThis technique of exposing frontend functions to your Copilot can be extended to any UI manipulation you want to enable, from theme changes to data filtering, navigation, or complex UI state management! ",
"path": "langgraph_agentic_chat/README.mdx",
"language": "markdown",
"type": "file"
}
]
},
"langgraph_human_in_the_loop": {
"files": [
{
"name": "agent.py",
"content": "\"\"\"\nA LangGraph implementation of the human-in-the-loop agent.\n\"\"\"\n\nimport json\nfrom typing import Dict, List, Any\n\n# LangGraph imports\nfrom langchain_core.runnables import RunnableConfig\nfrom langgraph.graph import StateGraph, END, START\nfrom langgraph.types import Command, interrupt\nfrom langgraph.checkpoint.memory import MemorySaver\n\n# CopilotKit imports\nfrom copilotkit import CopilotKitState\nfrom copilotkit.langgraph import copilotkit_customize_config, copilotkit_emit_state, copilotkit_interrupt\n\n# LLM imports\nfrom langchain_openai import ChatOpenAI\nfrom langchain_core.messages import SystemMessage\nfrom copilotkit.langgraph import (copilotkit_exit)\n\nDEFINE_TASK_TOOL = {\n \"type\": \"function\",\n \"function\": {\n \"name\": \"generate_task_steps\",\n \"description\": \"Make up 10 steps (only a couple of words per step) that are required for a task. The step should be in imperative form (i.e. Dig hole, Open door, ...)\",\n \"parameters\": {\n \"type\": \"object\",\n \"properties\": {\n \"steps\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"properties\": {\n \"description\": {\n \"type\": \"string\",\n \"description\": \"The text of the step in imperative form\"\n },\n \"status\": {\n \"type\": \"string\",\n \"enum\": [\"enabled\"],\n \"description\": \"The status of the step, always 'enabled'\"\n }\n },\n \"required\": [\"description\", \"status\"]\n },\n \"description\": \"An array of 10 step objects, each containing text and status\"\n }\n },\n \"required\": [\"steps\"]\n }\n }\n}\n\nclass AgentState(CopilotKitState):\n \"\"\"\n The state of the agent.\n It inherits from CopilotKitState which provides the basic fields needed by CopilotKit.\n \"\"\"\n steps: List[Dict[str, str]] = []\n\nasync def start_flow(state: Dict[str, Any], config: RunnableConfig):\n \"\"\"\n This is the entry point for the flow.\n \"\"\"\n\n # Initialize steps list if not exists\n if \"steps\" not in state:\n state[\"steps\"] = []\n\n \n return Command(\n goto=\"chat_node\",\n update={\n \"messages\": state[\"messages\"],\n \"steps\": state[\"steps\"],\n }\n )\n\n\nasync def chat_node(state: Dict[str, Any], config: RunnableConfig):\n \"\"\"\n Standard chat node where the agent processes messages and generates responses.\n If task steps are defined, the user can enable/disable them using interrupts.\n \"\"\"\n system_prompt = \"\"\"\n You are a helpful assistant that can perform any task.\n You MUST call the `generate_task_steps` function when the user asks you to perform a task.\n Always make sure you will provide tasks based on the user query\n \"\"\"\n\n # Define the model\n model = ChatOpenAI(model=\"gpt-4o-mini\")\n \n # Define config for the model\n if config is None:\n config = RunnableConfig(recursion_limit=25)\n \n # Use CopilotKit's custom config functions to properly set up streaming for the steps state\n config = copilotkit_customize_config(\n config,\n emit_intermediate_state=[{\n \"state_key\": \"steps\",\n \"tool\": \"generate_task_steps\",\n \"tool_argument\": \"steps\"\n }],\n )\n\n # Bind the tools to the model\n model_with_tools = model.bind_tools(\n [\n *state[\"copilotkit\"][\"actions\"],\n DEFINE_TASK_TOOL\n ],\n # Disable parallel tool calls to avoid race conditions\n parallel_tool_calls=False,\n )\n\n # Run the model and generate a response\n response = await model_with_tools.ainvoke([\n SystemMessage(content=system_prompt),\n *state[\"messages\"],\n ], config)\n\n # Update messages with the response\n messages = state[\"messages\"] + [response]\n \n # Handle tool calls\n if hasattr(response, \"tool_calls\") and response.tool_calls and len(response.tool_calls) > 0:\n tool_call = response.tool_calls[0]\n # Extract tool call information\n if hasattr(tool_call, \"id\"):\n tool_call_id = tool_call.id\n tool_call_name = tool_call.name\n tool_call_args = tool_call.args if not isinstance(tool_call.args, str) else json.loads(tool_call.args)\n else:\n tool_call_id = tool_call.get(\"id\", \"\")\n tool_call_name = tool_call.get(\"name\", \"\")\n args = tool_call.get(\"args\", {})\n tool_call_args = args if not isinstance(args, str) else json.loads(args)\n\n if tool_call_name == \"generate_task_steps\":\n # Get the steps from the tool call\n steps_raw = tool_call_args.get(\"steps\", [])\n \n # Set initial status to \"enabled\" for all steps\n steps_data = []\n \n # Handle different potential formats of steps data\n if isinstance(steps_raw, list):\n for step in steps_raw:\n if isinstance(step, dict) and \"description\" in step:\n steps_data.append({\n \"description\": step[\"description\"],\n \"status\": \"enabled\"\n })\n elif isinstance(step, str):\n steps_data.append({\n \"description\": step,\n \"status\": \"enabled\"\n })\n \n # If no steps were processed correctly, return to END with the updated messages\n if not steps_data:\n await copilotkit_exit(config)\n return Command(\n goto=END,\n update={\n \"messages\": messages,\n \"steps\": state[\"steps\"],\n }\n )\n # Update steps in state and emit to frontend\n state[\"steps\"] = steps_data\n \n # Add a tool response to satisfy OpenAI's requirements\n tool_response = {\n \"role\": \"tool\",\n \"content\": \"Task steps generated.\",\n \"tool_call_id\": tool_call_id\n }\n \n messages = messages + [tool_response]\n\n # Move to the process_steps_node which will handle the interrupt and final response\n return Command(\n goto=\"process_steps_node\",\n update={\n \"messages\": messages,\n \"steps\": state[\"steps\"],\n }\n )\n \n # If no tool calls or not generate_task_steps, return to END with the updated messages\n await copilotkit_exit(config)\n return Command(\n goto=END,\n update={\n \"messages\": messages,\n \"steps\": state[\"steps\"],\n }\n )\n\n\nasync def process_steps_node(state: Dict[str, Any], config: RunnableConfig):\n \"\"\"\n This node handles the user interrupt for step customization and generates the final response.\n \"\"\"\n\n # Check if we already have a user_response in the state\n # This happens when the node restarts after an interrupt\n if \"user_response\" in state and state[\"user_response\"]:\n user_response = state[\"user_response\"]\n else:\n # Use LangGraph interrupt to get user input on steps\n # This will pause execution and wait for user input in the frontend\n user_response = interrupt({\"steps\": state[\"steps\"]})\n # Store the user response in state for when the node restarts\n state[\"user_response\"] = user_response\n \n # Generate the creative completion response\n final_prompt = \"\"\"\n Provide a textual description of how you are performing the task.\n If the user has disabled a step, you are not allowed to perform that step.\n However, you should find a creative workaround to perform the task, and if an essential step is disabled, you can even use\n some humor in the description of how you are performing the task.\n Don't just repeat a list of steps, come up with a creative but short description (3 sentences max) of how you are performing the task.\n \"\"\"\n \n final_response = await ChatOpenAI(model=\"gpt-4o\").ainvoke([\n SystemMessage(content=final_prompt),\n {\"role\": \"user\", \"content\": user_response}\n ], config)\n\n # Add the final response to messages\n messages = state[\"messages\"] + [final_response]\n \n # Clear the user_response from state to prepare for future interactions\n if \"user_response\" in state:\n state.pop(\"user_response\")\n \n # Return to END with the updated messages\n await copilotkit_exit(config)\n return Command(\n goto=END,\n update={\n \"messages\": messages,\n \"steps\": state[\"steps\"],\n }\n )\n\n\n# Define the graph\nworkflow = StateGraph(AgentState)\n\n# Add nodes\nworkflow.add_node(\"start_flow\", start_flow)\nworkflow.add_node(\"chat_node\", chat_node)\nworkflow.add_node(\"process_steps_node\", process_steps_node)\n\n# Add edges\nworkflow.set_entry_point(\"start_flow\")\nworkflow.add_edge(START, \"start_flow\")\nworkflow.add_edge(\"start_flow\", \"chat_node\")\n# workflow.add_edge(\"chat_node\", \"process_steps_node\") # Removed unconditional edge\nworkflow.add_edge(\"process_steps_node\", END)\n# workflow.add_edge(\"chat_node\", END) # Removed unconditional edge\n\n# Add conditional edges from chat_node\ndef should_continue(command: Command):\n if command.goto == \"process_steps_node\":\n return \"process_steps_node\"\n else:\n return END\n\nworkflow.add_conditional_edges(\n \"chat_node\",\n should_continue,\n {\n \"process_steps_node\": \"process_steps_node\",\n END: END,\n },\n)\n\n# Compile the graph\nhuman_in_the_loop_graph = workflow.compile(checkpointer=MemorySaver())",
"path": "langgraph_human_in_the_loop/agent.py",
"language": "python",
"type": "file"
},
{
"name": "page.tsx",
"content": "\"use client\";\nimport React, { useState, useEffect } from \"react\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport { CopilotKit, useLangGraphInterrupt } from \"@copilotkit/react-core\";\nimport { CopilotChat, useCopilotChatSuggestions } from \"@copilotkit/react-ui\";\nimport { initialPrompt, chatSuggestions } from \"@/lib/prompts\";\nimport { AGENT_TYPE } from \"@/config\";\nconst HumanInTheLoop: React.FC = () => {\n return (\n <CopilotKit\n runtimeUrl={AGENT_TYPE == \"general\" ? \"/api/copilotkit?langgraph=true\" : \"/api/copilotkit\"}\n showDevConsole={false}\n agent=\"human_in_the_loop\"\n >\n <Chat />\n </CopilotKit>\n );\n};\n\nconst Chat = () => {\n useLangGraphInterrupt({\n render: ({ event, resolve }) => {\n const [newStep, setNewStep] = useState(\"\");\n\n const handleAddStep = () => {\n const trimmed = newStep.trim();\n if (trimmed.length === 0) return;\n setLocalSteps((prevSteps) => [\n ...prevSteps,\n { description: trimmed, status: \"enabled\" },\n ]);\n setNewStep(\"\");\n };\n\n const handleInputKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {\n if (e.key === \"Enter\") {\n handleAddStep();\n }\n };\n // Ensure we have valid steps data\n let initialSteps = [];\n if (event.value && event.value.steps && Array.isArray(event.value.steps)) {\n initialSteps = event.value.steps.map((step: any) => ({\n description: typeof step === 'string' ? step : step.description || '',\n status: (typeof step === 'object' && step.status) ? step.status : 'enabled'\n }));\n }\n\n const [localSteps, setLocalSteps] = useState<\n {\n description: string;\n status: \"disabled\" | \"enabled\" | \"executing\";\n }[]\n >(initialSteps);\n\n const handleCheckboxChange = (index: number) => {\n setLocalSteps((prevSteps) =>\n prevSteps.map((step, i) =>\n i === index\n ? {\n ...step,\n status: step.status === \"enabled\" ? \"disabled\" : \"enabled\",\n }\n : step\n )\n );\n };\n\n return (\n <div className=\"flex flex-col gap-4 w-[500px] bg-gray-100 rounded-lg p-8 mb-4\">\n <div className=\"text-black space-y-2\">\n <h2 className=\"text-lg font-bold mb-4\">Select Steps</h2>\n {localSteps.map((step, index) => (\n <div key={index} className=\"text-sm flex items-center\">\n <label className=\"flex items-center cursor-pointer\">\n <input\n type=\"checkbox\"\n checked={step.status === \"enabled\"}\n onChange={() => handleCheckboxChange(index)}\n className=\"mr-2\"\n />\n <span\n className={\n step.status !== \"enabled\" ? \"line-through\" : \"\"\n }\n >\n {step.description}\n </span>\n </label>\n </div>\n ))}\n <div className=\"flex items-center gap-2 mb-4\">\n <input\n type=\"text\"\n className=\"flex-1 rounded py-2 focus:outline-none\"\n placeholder=\"Add a new step...\"\n value={newStep}\n onChange={(e) => setNewStep(e.target.value)}\n onKeyDown={handleInputKeyDown}\n // hidden={status != \"executing\"}\n />\n </div>\n <button\n className=\"mt-4 bg-gradient-to-r from-purple-400 to-purple-600 text-white py-2 px-4 rounded cursor-pointer w-48 font-bold\"\n onClick={() => {\n const selectedSteps = localSteps\n .filter((step) => step.status === \"enabled\")\n .map((step) => step.description);\n resolve(\"The user selected the following steps: \" + selectedSteps.join(\", \"));\n }}\n >\n ✨ Perform Steps\n </button>\n </div>\n </div>\n );\n },\n });\n useCopilotChatSuggestions({\n instructions: chatSuggestions.humanInTheLoop,\n })\n return (\n <div className=\"flex justify-center items-center h-screen w-screen\">\n <div className=\"w-8/10 h-8/10\">\n <CopilotChat\n className=\"h-full rounded-lg\"\n labels={{ initial: initialPrompt.humanInTheLoop }}\n />\n </div>\n </div>\n );\n};\n\nexport default HumanInTheLoop;\n",
"path": "langgraph_human_in_the_loop/page.tsx",
"language": "typescript",
"type": "file"
},
{
"name": "style.css",
"content": ".copilotKitInput {\n border-bottom-left-radius: 0.75rem;\n border-bottom-right-radius: 0.75rem;\n border-top-left-radius: 0.75rem;\n border-top-right-radius: 0.75rem;\n border: 1px solid var(--copilot-kit-separator-color) !important;\n background-color: #fff;\n}\n\n.copilotKitChat {\n background-color: #fff !important;\n}\n",
"path": "langgraph_human_in_the_loop/style.css",
"language": "css",
"type": "file"
},
{
"name": "README.mdx",
"content": "# 🤝 Human-in-the-Loop Task Planner\n\n## What This Demo Shows\n\nThis demo showcases CopilotKit's **human-in-the-loop** capabilities:\n1. **Collaborative Planning**: The Copilot generates task steps and lets you decide which ones to perform\n2. **Interactive Decision Making**: Select or deselect steps to customize the execution plan\n3. **Adaptive Responses**: The Copilot adapts its execution based on your choices, even handling missing steps\n\n## How to Interact\n\nTry these steps to experience the demo:\n1. Ask your Copilot to help with a task, such as:\n - \"Make me a sandwich\"\n - \"Plan a weekend trip\"\n - \"Organize a birthday party\"\n - \"Start a garden\"\n\n2. Review the suggested steps provided by your Copilot\n\n3. Select or deselect steps using the checkboxes to customize the plan\n - Try removing essential steps to see how the Copilot adapts!\n\n4. Click \"Execute Plan\" to see the outcome based on your selections\n\n## ✨ Human-in-the-Loop Magic in Action\n\n**What's happening technically:**\n- The agent analyzes your request and breaks it down into logical steps\n- These steps are presented to you through a dynamic UI component\n- Your selections are captured as user input\n- The agent considers your choices when executing the plan\n- The agent adapts to missing steps with creative problem-solving\n\n**What you'll see in this demo:**\n- The Copilot provides a detailed, step-by-step plan for your task\n- You have complete control over which steps to include\n- If you remove essential steps, the Copilot provides entertaining and creative workarounds\n- The final execution reflects your choices, showing how human input shapes the outcome\n- Each response is tailored to your specific selections\n\nThis human-in-the-loop pattern creates a powerful collaborative experience where both human judgment and AI capabilities work together to achieve better results than either could alone! ",
"path": "langgraph_human_in_the_loop/README.mdx",
"language": "markdown",
"type": "file"
}
]
},
"langgraph_agentic_generative_ui": {
"files": [
{
"name": "agent.py",
"content": "\"\"\"\nAn example demonstrating agentic generative UI using LangGraph.\n\"\"\"\n\nimport json\nimport asyncio\nfrom typing import Dict, List, Any, Optional, Literal\n# LangGraph imports\nfrom langchain_core.runnables import RunnableConfig\nfrom langgraph.graph import StateGraph, END, START\nfrom langgraph.types import Command\nfrom langgraph.checkpoint.memory import MemorySaver\n\n# CopilotKit imports\nfrom copilotkit import CopilotKitState\nfrom copilotkit.langgraph import (\n copilotkit_customize_config,\n copilotkit_emit_state\n)\nfrom copilotkit.langgraph import (copilotkit_exit)\n\n# OpenAI imports\nfrom langchain_openai import ChatOpenAI\nfrom langchain_core.messages import SystemMessage\n\n# This tool simulates performing a task on the server.\n# The tool call will be streamed to the frontend as it is being generated.\nPERFORM_TASK_TOOL = {\n \"type\": \"function\",\n \"function\": {\n \"name\": \"generate_task_steps_generative_ui\",\n \"description\": \"Make up 10 steps (only a couple of words per step) that are required for a task. The step should be in gerund form (i.e. Digging hole, opening door, ...)\",\n \"parameters\": {\n \"type\": \"object\",\n \"properties\": {\n \"steps\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"properties\": {\n \"description\": {\n \"type\": \"string\",\n \"description\": \"The text of the step in gerund form\"\n },\n \"status\": {\n \"type\": \"string\",\n \"enum\": [\"pending\"],\n \"description\": \"The status of the step, always 'pending'\"\n }\n },\n \"required\": [\"description\", \"status\"]\n },\n \"description\": \"An array of 10 step objects, each containing text and status\"\n }\n },\n \"required\": [\"steps\"]\n }\n }\n}\n\n\nclass AgentState(CopilotKitState):\n \"\"\"\n Here we define the state of the agent\n\n In this instance, we're inheriting from CopilotKitState, which will bring in\n the CopilotKitState fields. We're also adding a custom field, `steps`,\n which will be used to store the steps of the task.\n \"\"\"\n steps: List[dict] = []\n\n\nasync def start_flow(state: AgentState, config: RunnableConfig):\n \"\"\"\n This is the entry point for the flow.\n \"\"\"\n\n if \"steps\" not in state:\n state[\"steps\"] = []\n\n return Command(\n goto=\"chat_node\",\n update={\n \"messages\": state[\"messages\"],\n \"steps\": state[\"steps\"]\n }\n )\n\n\nasync def chat_node(state: AgentState, config: RunnableConfig):\n \"\"\"\n Standard chat node.\n \"\"\"\n system_prompt = \"\"\"\n You are a helpful assistant assisting with any task. \n When asked to do something, you MUST call the function `generate_task_steps_generative_ui`\n that was provided to you.\n If you called the function, you MUST NOT repeat the steps in your next response to the user.\n Just give a very brief summary (one sentence) of what you did with some emojis. \n Always say you actually did the steps, not merely generated them.\n \"\"\"\n\n # Define the model\n model = ChatOpenAI(model=\"gpt-4o\")\n \n # Define config for the model with emit_intermediate_state to stream tool calls to frontend\n if config is None:\n config = RunnableConfig(recursion_limit=25)\n \n # Use CopilotKit's custom config to set up streaming for the generate_task_steps_generative_ui tool\n # This is equivalent to copilotkit_predict_state in the CrewAI version\n config = copilotkit_customize_config(\n config,\n emit_intermediate_state=[{\n \"state_key\": \"steps\",\n \"tool\": \"generate_task_steps_generative_ui\",\n \"tool_argument\": \"steps\",\n }],\n ) \n\n # Bind the tools to the model\n model_with_tools = model.bind_tools(\n [\n # *state[\"copilotkit\"][\"actions\"]\n PERFORM_TASK_TOOL\n ],\n # Disable parallel tool calls to avoid race conditions\n parallel_tool_calls=False,\n )\n\n # Run the model to generate a response\n response = await model_with_tools.ainvoke([\n SystemMessage(content=system_prompt),\n *state[\"messages\"],\n ], config)\n\n messages = state[\"messages\"] + [response]\n\n # Extract any tool calls from the response\n if hasattr(response, \"tool_calls\") and response.tool_calls and len(response.tool_calls) > 0:\n tool_call = response.tool_calls[0]\n \n # Handle tool_call as a dictionary rather than an object\n if isinstance(tool_call, dict):\n tool_call_id = tool_call[\"id\"]\n tool_call_name = tool_call[\"name\"]\n tool_call_args = tool_call[\"args\"]\n else:\n # Handle as an object (backward compatibility)\n tool_call_id = tool_call.id\n tool_call_name = tool_call.name\n tool_call_args = tool_call.args\n\n if tool_call_name == \"generate_task_steps_generative_ui\":\n steps = [{\"description\": step[\"description\"], \"status\": step[\"status\"]} for step in tool_call_args[\"steps\"]]\n \n # Add the tool response to messages\n tool_response = {\n \"role\": \"tool\",\n \"content\": \"Steps executed.\",\n \"tool_call_id\": tool_call_id\n }\n\n messages = messages + [tool_response]\n\n # Return Command to route to simulate_task_node\n for i, step in enumerate(steps):\n # simulate executing the step\n await asyncio.sleep(1)\n steps[i][\"status\"] = \"completed\"\n # Update the state with the completed step - using config as first parameter\n state[\"steps\"] = steps\n await copilotkit_emit_state(config, state)\n \n return Command(\n goto='start_flow',\n update={\n \"messages\": messages,\n \"steps\": state[\"steps\"]\n }\n )\n # If no tool was called, go to end (equivalent to \"route_end\" in CrewAI)\n await copilotkit_exit(config)\n return Command(\n goto=END,\n update={\n \"messages\": messages,\n \"steps\": state[\"steps\"]\n }\n )\n\n\n# Define the graph\nworkflow = StateGraph(AgentState)\n\n# Add nodes\nworkflow.add_node(\"start_flow\", start_flow)\nworkflow.add_node(\"chat_node\", chat_node)\n\n# Add edges (equivalent to the routing in CrewAI)\nworkflow.set_entry_point(\"start_flow\")\nworkflow.add_edge(START, \"start_flow\")\nworkflow.add_edge(\"start_flow\", \"chat_node\")\nworkflow.add_edge(\"chat_node\", END)\n\n# Compile the graph\ngraph = workflow.compile(checkpointer=MemorySaver())\n\n# For compatibility with server code that might expect this clas",
"path": "langgraph_agentic_generative_ui/agent.py",
"language": "python",
"type": "file"
},
{
"name": "page.tsx",
"content": "\"use client\";\nimport React from \"react\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport { CopilotKit, useCoAgentStateRender } from \"@copilotkit/react-core\";\nimport { CopilotChat, useCopilotChatSuggestions } from \"@copilotkit/react-ui\";\nimport { initialPrompt, chatSuggestions } from \"@/lib/prompts\";\nimport { AGENT_TYPE } from \"@/config\";\nconst AgenticGenerativeUI: React.FC = () => {\n return (\n <CopilotKit\n runtimeUrl={AGENT_TYPE == \"general\" ? \"/api/copilotkit?langgraph=true\" : \"/api/copilotkit\"}\n showDevConsole={false}\n agent=\"agentic_generative_ui\"\n >\n <Chat />\n </CopilotKit>\n );\n};\n\ninterface AgentState {\n steps: {\n description: string;\n status: \"pending\" | \"completed\";\n }[];\n}\n\nconst Chat = () => {\n useCoAgentStateRender<AgentState>({\n name: \"agentic_generative_ui\",\n render: ({ state }) => {\n if (!state.steps || state.steps.length === 0) {\n return null;\n }\n\n return (\n <div className=\"flex\">\n <div className=\"bg-gray-100 rounded-lg w-[500px] p-4 text-black space-y-2\">\n {state.steps.map((step, index) => {\n if (step.status === \"completed\") {\n return (\n <div key={index} className=\"text-sm\">\n ✓ {step.description}\n </div>\n );\n } else if (\n step.status === \"pending\" &&\n index === state.steps.findIndex((s) => s.status === \"pending\")\n ) {\n return (\n <div\n key={index}\n className=\"text-3xl font-bold text-slate-700\"\n >\n <Spinner />\n {step.description}\n </div>\n );\n } else {\n return (\n <div key={index} className=\"text-sm\">\n <Spinner />\n {step.description}\n </div>\n );\n }\n })}\n </div>\n </div>\n );\n },\n });\n useCopilotChatSuggestions({\n instructions: chatSuggestions.agenticGenerativeUI,\n })\n return (\n <div className=\"flex justify-center items-center h-screen w-screen\">\n <div className=\"w-8/10 h-8/10\">\n <CopilotChat\n className=\"h-full rounded-lg\"\n labels={{ initial: initialPrompt.agenticGenerativeUI }}\n />\n </div>\n </div>\n );\n};\n\nfunction Spinner() {\n return (\n <svg\n className=\"mr-2 size-3 animate-spin text-slate-500\"\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n >\n <circle\n className=\"opacity-25\"\n cx=\"12\"\n cy=\"12\"\n r=\"10\"\n stroke=\"currentColor\"\n strokeWidth=\"4\"\n ></circle>\n <path\n className=\"opacity-75\"\n fill=\"currentColor\"\n d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z\"\n ></path>\n </svg>\n );\n}\n\nexport default AgenticGenerativeUI;\n",
"path": "langgraph_agentic_generative_ui/page.tsx",
"language": "typescript",
"type": "file"
},
{
"name": "style.css",
"content": ".copilotKitInput {\n border-bottom-left-radius: 0.75rem;\n border-bottom-right-radius: 0.75rem;\n border-top-left-radius: 0.75rem;\n border-top-right-radius: 0.75rem;\n border: 1px solid var(--copilot-kit-separator-color) !important;\n background-color: #fff;\n}\n\n.copilotKitChat {\n background-color: #fff !important;\n}\n",
"path": "langgraph_agentic_generative_ui/style.css",
"language": "css",
"type": "file"
},
{
"name": "README.mdx",
"content": "# 🚀 Agentic Generative UI Task Executor\n\n## What This Demo Shows\n\nThis demo showcases CopilotKit's **agentic generative UI** capabilities:\n1. **Real-time Status Updates**: The Copilot provides live feedback as it works through complex tasks\n2. **Long-running Task Execution**: See how agents can handle extended processes with continuous feedback\n3. **Dynamic UI Generation**: The interface updates in real-time to reflect the agent's progress\n\n## How to Interact\n\nSimply ask your Copilot to perform any moderately complex task:\n- \"Make me a sandwich\"\n- \"Plan a vacation to Japan\"\n- \"Create a weekly workout routine\"\n\nThe Copilot will break down the task into steps and begin \"executing\" them, providing real-time status updates as it progresses.\n\n## ✨ Agentic Generative UI in Action\n\n**What's happening technically:**\n- The agent analyzes your request and creates a detailed execution plan\n- Each step is processed sequentially with realistic timing\n- Status updates are streamed to the frontend using CopilotKit's streaming capabilities\n- The UI dynamically renders these updates without page refreshes\n- The entire flow is managed by the agent, requiring no manual intervention\n\n**What you'll see in this demo:**\n- The Copilot breaks your task into logical steps\n- A status indicator shows the current progress\n- Each step is highlighted as it's being executed\n- Detailed status messages explain what's happening at each moment\n- Upon completion, you receive a summary of the task execution\n\nThis pattern of providing real-time progress for long-running tasks is perfect for scenarios where users benefit from transparency into complex processes - from data analysis to content creation, system configurations, or multi-stage workflows! ",
"path": "langgraph_agentic_generative_ui/README.mdx",
"language": "markdown",
"type": "file"
}
]
},
"langgraph_tool_based_generative_ui": {
"files": [
{
"name": "agent.py",
"content": "\"\"\"\nAn example demonstrating tool-based generative UI using LangGraph.\n\"\"\"\n\nfrom typing import Dict, List, Any, Optional\n\n# LangGraph imports\nfrom langchain_core.runnables import RunnableConfig\nfrom langgraph.graph import StateGraph, END, START\nfrom langgraph.types import Command\nfrom langgraph.checkpoint.memory import MemorySaver\n\n# CopilotKit imports\nfrom copilotkit import CopilotKitState\nfrom copilotkit.langgraph import copilotkit_customize_config\n\n# OpenAI imports\nfrom langchain_openai import ChatOpenAI\nfrom langchain_core.messages import SystemMessage\nfrom copilotkit.langgraph import (copilotkit_exit)\n\n# List of available images (modify path if needed)\nIMAGE_LIST = [\n \"Osaka_Castle_Turret_Stone_Wall_Pine_Trees_Daytime.jpg\",\n \"Tokyo_Skyline_Night_Tokyo_Tower_Mount_Fuji_View.jpg\",\n \"Itsukushima_Shrine_Miyajima_Floating_Torii_Gate_Sunset_Long_Exposure.jpg\",\n \"Takachiho_Gorge_Waterfall_River_Lush_Greenery_Japan.jpg\",\n \"Bonsai_Tree_Potted_Japanese_Art_Green_Foliage.jpeg\",\n \"Shirakawa-go_Gassho-zukuri_Thatched_Roof_Village_Aerial_View.jpg\",\n \"Ginkaku-ji_Silver_Pavilion_Kyoto_Japanese_Garden_Pond_Reflection.jpg\",\n \"Senso-ji_Temple_Asakusa_Cherry_Blossoms_Kimono_Umbrella.jpg\",\n \"Cherry_Blossoms_Sakura_Night_View_City_Lights_Japan.jpg\",\n \"Mount_Fuji_Lake_Reflection_Cherry_Blossoms_Sakura_Spring.jpg\"\n]\n\n# This tool generates a haiku on the server.\n# The tool call will be streamed to the frontend as it is being generated.\nGENERATE_HAIKU_TOOL = {\n \"type\": \"function\",\n \"function\": {\n \"name\": \"generate_haiku\",\n \"description\": \"Generate a haiku in Japanese and its English translation. Also select exactly 3 relevant images from the provided list based on the haiku's theme.\",\n \"parameters\": {\n \"type\": \"object\",\n \"properties\": {\n \"japanese\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n },\n \"description\": \"An array of three lines of the haiku in Japanese\"\n },\n \"english\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n },\n \"description\": \"An array of three lines of the haiku in English\"\n },\n \"image_names\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n },\n \"description\": \"An array of EXACTLY THREE image filenames from the provided list that are most relevant to the haiku.\"\n }\n },\n \"required\": [\"japanese\", \"english\", \"image_names\"]\n }\n }\n}\n\n\nasync def chat_node(state: CopilotKitState, config: RunnableConfig):\n \"\"\"\n The main function handling chat and tool calls.\n \"\"\"\n # Prepare the image list string for the prompt\n image_list_str = \"\\n\".join([f\"- {img}\" for img in IMAGE_LIST])\n\n system_prompt = f\"\"\"You assist the user in generating a haiku.\nWhen generating a haiku using the 'generate_haiku' tool, you MUST also select exactly 3 image filenames from the following list that are most relevant to the haiku's content or theme. Return the filenames in the 'image_names' parameter.\n\nAvailable images:\n{image_list_str}\n\nDont provide the relavent image names in your final response to the user.\n\"\"\"\n\n # Define the model\n model = ChatOpenAI(model=\"gpt-4o\")\n \n # Define config for the model\n if config is None:\n config = RunnableConfig(recursion_limit=25)\n \n # Use CopilotKit's custom config to set up streaming\n config = copilotkit_customize_config(config)\n\n # Bind the tools to the model\n model_with_tools = model.bind_tools(\n [GENERATE_HAIKU_TOOL],\n # Disable parallel tool calls to avoid race conditions\n parallel_tool_calls=False,\n )\n\n # Run the model to generate a response\n response = await model_with_tools.ainvoke([\n SystemMessage(content=system_prompt),\n *state[\"messages\"],\n ], config)\n\n # Return Command to end with updated messages\n await copilotkit_exit(config)\n return Command(\n goto=END,\n update={\n \"messages\": state[\"messages\"] + [response]\n }\n )\n\n# Define the graph\nworkflow = StateGraph(CopilotKitState)\n\n# Add nodes\nworkflow.add_node(\"chat_node\", chat_node)\n\n# Add edges\nworkflow.set_entry_point(\"chat_node\")\nworkflow.add_edge(START, \"chat_node\")\nworkflow.add_edge(\"chat_node\", END)\n\n# Compile the graph\ntool_based_generative_ui_graph = workflow.compile(checkpointer=MemorySaver())\n\n",
"path": "langgraph_tool_based_generative_ui/agent.py",
"language": "python",
"type": "file"
},
{
"name": "page.tsx",
"content": "\"use client\";\nimport { CopilotKit, useCopilotAction } from \"@copilotkit/react-core\";\nimport { CopilotKitCSSProperties, CopilotSidebar, useCopilotChatSuggestions } from \"@copilotkit/react-ui\";\nimport { useState, useEffect } from \"react\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport { chatSuggestions, initialPrompt } from \"@/lib/prompts\";\nimport HaikuCard from \"./HaikuCard\";\nimport { AGENT_TYPE } from \"@/config\";\n// List of known valid image filenames (should match agent.py)\nconst VALID_IMAGE_NAMES = [\n \"Osaka_Castle_Turret_Stone_Wall_Pine_Trees_Daytime.jpg\",\n \"Tokyo_Skyline_Night_Tokyo_Tower_Mount_Fuji_View.jpg\",\n \"Itsukushima_Shrine_Miyajima_Floating_Torii_Gate_Sunset_Long_Exposure.jpg\",\n \"Takachiho_Gorge_Waterfall_River_Lush_Greenery_Japan.jpg\",\n \"Bonsai_Tree_Potted_Japanese_Art_Green_Foliage.jpeg\",\n \"Shirakawa-go_Gassho-zukuri_Thatched_Roof_Village_Aerial_View.jpg\",\n \"Ginkaku-ji_Silver_Pavilion_Kyoto_Japanese_Garden_Pond_Reflection.jpg\",\n \"Senso-ji_Temple_Asakusa_Cherry_Blossoms_Kimono_Umbrella.jpg\",\n \"Cherry_Blossoms_Sakura_Night_View_City_Lights_Japan.jpg\",\n \"Mount_Fuji_Lake_Reflection_Cherry_Blossoms_Sakura_Spring.jpg\"\n];\n\nexport default function AgenticChat() {\n return (\n <CopilotKit\n runtimeUrl={AGENT_TYPE == \"general\" ? \"/api/copilotkit?langgraph=true\" : \"/api/copilotkit\"}\n showDevConsole={false}\n agent=\"tool_based_generative_ui\"\n >\n <div\n className=\"min-h-screen w-full flex items-center justify-center page-background\"\n style={\n {\n \"--copilot-kit-primary-color\": \"#222\",\n \"--copilot-kit-separator-color\": \"#CCC\",\n } as CopilotKitCSSProperties\n }\n >\n <Haiku />\n <CopilotSidebar\n defaultOpen={true}\n labels={{\n title: \"Haiku Generator\",\n initial: initialPrompt.toolCallingGenerativeUI,\n }}\n clickOutsideToClose={false}\n />\n </div>\n </CopilotKit>\n );\n}\n\ninterface Haiku { \n japanese: string[];\n english: string[];\n image_names: string[];\n selectedImage: string | null;\n}\n\n\nfunction Haiku() {\n const [haikus, setHaikus] = useState<Haiku[]>([{\n japanese: [\"仮の句よ\", \"まっさらながら\", \"花を呼ぶ\"],\n english: [\n \"A placeholder verse—\",\n \"even in a blank canvas,\",\n \"it beckons flowers.\",\n ],\n image_names: [],\n selectedImage: null,\n }])\n const [activeIndex, setActiveIndex] = useState(0);\n const [isJustApplied, setIsJustApplied] = useState(false);\n\n const validateAndCorrectImageNames = (rawNames: string[] | undefined): string[] | null => {\n if (!rawNames || rawNames.length !== 3) {\n return null;\n }\n\n const correctedNames: string[] = [];\n const usedValidNames = new Set<string>();\n\n for (const name of rawNames) {\n if (VALID_IMAGE_NAMES.includes(name) && !usedValidNames.has(name)) {\n correctedNames.push(name);\n usedValidNames.add(name);\n if (correctedNames.length === 3) break;\n }\n }\n\n if (correctedNames.length < 3) {\n const availableFallbacks = VALID_IMAGE_NAMES.filter(name => !usedValidNames.has(name));\n for (let i = availableFallbacks.length - 1; i > 0; i--) {\n const j = Math.floor(Math.random() * (i + 1));\n [availableFallbacks[i], availableFallbacks[j]] = [availableFallbacks[j], availableFallbacks[i]];\n }\n\n while (correctedNames.length < 3 && availableFallbacks.length > 0) {\n const fallbackName = availableFallbacks.pop();\n if (fallbackName) {\n correctedNames.push(fallbackName);\n }\n }\n }\n\n while (correctedNames.length < 3 && VALID_IMAGE_NAMES.length > 0) {\n const fallbackName = VALID_IMAGE_NAMES[Math.floor(Math.random() * VALID_IMAGE_NAMES.length)];\n correctedNames.push(fallbackName);\n }\n\n return correctedNames.slice(0, 3);\n };\n\n useCopilotAction({\n name: \"generate_haiku\",\n parameters: [\n {\n name: \"japanese\",\n type: \"string[]\",\n },\n {\n name: \"english\",\n type: \"string[]\",\n },\n {\n name: \"image_names\",\n type: \"string[]\",\n description: \"Names of 3 relevant images\",\n },\n ],\n followUp: false,\n handler: async ({ japanese, english, image_names }) => {\n const finalCorrectedImages = validateAndCorrectImageNames(image_names);\n const newHaiku = {\n japanese: japanese || [],\n english: english || [],\n image_names: finalCorrectedImages || [],\n selectedImage: finalCorrectedImages?.[0] || null,\n };\n console.log(finalCorrectedImages, \"finalCorrectedImages\");\n setHaikus(prev => [...prev, newHaiku]);\n setActiveIndex(haikus.length - 1);\n setIsJustApplied(true);\n setTimeout(() => setIsJustApplied(false), 600);\n return \"Haiku generated.\";\n },\n render: ({ args: generatedHaiku }) => {\n return (\n <HaikuCard generatedHaiku={generatedHaiku} setHaikus={setHaikus} haikus={haikus} />\n );\n },\n }, [haikus]);\n\n useCopilotChatSuggestions({\n instructions: chatSuggestions.toolCallingGenerativeUI,\n });\n return (\n <div className=\"flex h-screen w-full\">\n \n {/* Thumbnail List */}\n <div className=\"w-40 p-4 border-r border-gray-200 overflow-y-auto overflow-x-hidden\">\n {haikus.filter((haiku) => haiku.english[0] !== \"A placeholder verse—\").map((haiku, index) => (\n <div\n key={index}\n className={`haiku-card animated-fade-in mb-4 cursor-pointer ${index === activeIndex ? 'active' : ''}`}\n style={{\n width: '80px',\n transform: 'scale(0.2)',\n transformOrigin: 'top left',\n marginBottom: '-340px',\n opacity: index === activeIndex ? 1 : 0.5,\n transition: 'opacity 0.2s',\n }}\n onClick={() => setActiveIndex(index)}\n >\n {haiku.japanese.map((line, lineIndex) => (\n <div\n className=\"flex items-start gap-2 mb-2 haiku-line\"\n key={lineIndex}\n >\n <p className=\"text-2xl font-bold text-gray-600 w-auto\">{line}</p>\n <p className=\"text-xs font-light text-gray-500 w-auto\">{haiku.english?.[lineIndex]}</p>\n </div>\n ))}\n {haiku.image_names && haiku.image_names.length === 3 && (\n <div className=\"mt-2 flex gap-2 justify-center\">\n {haiku.image_names.map((imageName, imgIndex) => (\n <img\n style={{\n width: '110px',\n height: '110px',\n objectFit: 'cover',\n }}\n key={imageName}\n src={`/images/${imageName}`}\n alt={imageName || \"\"}\n className=\"haiku-card-image w-12 h-12 object-cover\"\n />\n ))}\n </div>\n )}\n </div>\n ))}\n </div>\n\n {/* Main Display */}\n {/* Add a margin to the left of margin-left: -48px; */}\n <div className=\"flex-1 p-8 flex items-center justify-center \" style={{ marginLeft: '-48px' }}>\n <div className=\"haiku-stack\">\n {haikus.filter((_haiku: Haiku, index: number) => {\n if (haikus.length == 1) return true;\n else return index == activeIndex + 1;\n }).map((haiku, index) => (\n <div\n key={index}\n className={`haiku-card animated-fade-in ${isJustApplied && index === activeIndex ? 'applied-flash' : ''} ${index === activeIndex ? 'active' : ''}`}\n style={{\n zIndex: index === activeIndex ? haikus.length : index,\n transform: `translateY(${index === activeIndex ? '0' : `${(index - activeIndex) * 20}px`}) scale(${index === activeIndex ? '1' : '0.95'})`,\n }}\n // onClick={() => setActiveIndex(index)}\n >\n {haiku.japanese.map((line, lineIndex) => (\n <div\n className=\"flex items-start gap-4 mb-4 haiku-line\"\n key={lineIndex}\n style={{ animationDelay: `${lineIndex * 0.1}s` }}\n >\n <p className=\"text-4xl font-bold text-gray-600 w-auto\">{line}</p>\n <p className=\"text-base font-light text-gray-500 w-auto\">{haiku.english?.[lineIndex]}</p>\n </div>\n ))}\n {haiku.image_names && haiku.image_names.length === 3 && (\n <div className=\"mt-6 flex gap-4 justify-center\">\n {haiku.image_names.map((imageName, imgIndex) => (\n <img\n key={imageName}\n src={`/images/${imageName}`}\n alt={imageName || \"\"}\n style={{\n width: '130px',\n height: '130px',\n objectFit: 'cover',\n }}\n className={(haiku.selectedImage === imageName) ? `suggestion-card-image-focus` : `haiku-card-image`}\n />\n ))}\n </div>\n )}\n </div>\n ))}\n </div>\n </div>\n </div>\n );\n}",
"path": "langgraph_tool_based_generative_ui/page.tsx",
"language": "typescript",
"type": "file"
},
{
"name": "style.css",
"content": ".copilotKitWindow {\n box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);\n}\n\n.copilotKitHeader {\n border-top-left-radius: 5px !important;\n}\n\n.page-background {\n /* Darker gradient background */\n background: linear-gradient(170deg, #e9ecef 0%, #ced4da 100%); \n}\n\n@keyframes fade-scale-in {\n from {\n opacity: 0;\n transform: translateY(10px) scale(0.98);\n }\n to {\n opacity: 1;\n transform: translateY(0) scale(1);\n }\n}\n\n/* Updated card entry animation */\n@keyframes pop-in {\n 0% {\n opacity: 0;\n transform: translateY(15px) scale(0.95);\n }\n 70% {\n opacity: 1;\n transform: translateY(-2px) scale(1.02);\n }\n 100% {\n opacity: 1;\n transform: translateY(0) scale(1);\n }\n}\n\n/* Animation for subtle background gradient movement */\n@keyframes animated-gradient {\n 0% {\n background-position: 0% 50%;\n }\n 50% {\n background-position: 100% 50%;\n }\n 100% {\n background-position: 0% 50%;\n }\n}\n\n/* Animation for flash effect on apply */\n@keyframes flash-border-glow {\n 0% {\n /* Start slightly intensified */\n border-top-color: #ff5b4a !important;\n box-shadow: 0 10px 30px rgba(0, 0, 0, 0.07), \n inset 0 1px 2px rgba(0, 0, 0, 0.01), \n 0 0 25px rgba(255, 91, 74, 0.5);\n }\n 50% {\n /* Peak intensity */\n border-top-color: #ff4733 !important;\n box-shadow: 0 10px 30px rgba(0, 0, 0, 0.08), \n inset 0 1px 2px rgba(0, 0, 0, 0.01), \n 0 0 35px rgba(255, 71, 51, 0.7);\n }\n 100% {\n /* Return to default state appearance */\n border-top-color: #ff6f61 !important;\n box-shadow: 0 10px 30px rgba(0, 0, 0, 0.07), \n inset 0 1px 2px rgba(0, 0, 0, 0.01), \n 0 0 10px rgba(255, 111, 97, 0.15); \n }\n}\n\n/* Existing animation for haiku lines */\n@keyframes fade-slide-in {\n from {\n opacity: 0;\n transform: translateX(-15px);\n }\n to {\n opacity: 1;\n transform: translateX(0);\n }\n}\n\n.animated-fade-in { \n /* Use the new pop-in animation */\n animation: pop-in 0.6s ease-out forwards;\n}\n\n.haiku-card {\n /* Subtle animated gradient background */\n background: linear-gradient(120deg, #ffffff 0%, #fdfdfd 50%, #ffffff 100%);\n background-size: 200% 200%; \n animation: animated-gradient 10s ease infinite; \n\n /* === Explicit Border Override Attempt === */\n /* 1. Set the default grey border for all sides */\n border: 1px solid #dee2e6; \n \n /* 2. Explicitly override the top border immediately after */\n border-top: 10px solid #ff6f61 !important; /* Orange top - Added !important */\n /* === End Explicit Border Override Attempt === */\n \n padding: 2.5rem 3rem; \n border-radius: 20px; \n \n /* Default glow intensity */\n box-shadow: 0 10px 30px rgba(0, 0, 0, 0.07), \n inset 0 1px 2px rgba(0, 0, 0, 0.01), \n 0 0 15px rgba(255, 111, 97, 0.25); \n text-align: left;\n max-width: 745px; \n margin: 3rem auto;\n min-width: 600px;\n \n /* Transition */\n transition: transform 0.35s ease, box-shadow 0.35s ease, border-top-width 0.35s ease, border-top-color 0.35s ease;\n}\n\n.haiku-card:hover {\n transform: translateY(-8px) scale(1.03); \n /* Enhanced shadow + Glow */\n box-shadow: 0 15px 35px rgba(0, 0, 0, 0.1), \n inset 0 1px 2px rgba(0, 0, 0, 0.01), \n 0 0 25px rgba(255, 91, 74, 0.5); \n /* Modify only top border properties */\n border-top-width: 14px !important; /* Added !important */\n border-top-color: #ff5b4a !important; /* Added !important */\n}\n\n.haiku-card .flex {\n margin-bottom: 1.5rem;\n}\n\n.haiku-card .flex.haiku-line { /* Target the lines specifically */\n margin-bottom: 1.5rem;\n opacity: 0; /* Start hidden for animation */\n animation: fade-slide-in 0.5s ease-out forwards;\n /* animation-delay is set inline in page.tsx */\n}\n\n/* Remove previous explicit color overrides - rely on Tailwind */\n/* .haiku-card p.text-4xl {\n color: #212529; \n}\n\n.haiku-card p.text-base {\n color: #495057; \n} */\n\n.haiku-card.applied-flash {\n /* Apply the flash animation once */\n /* Note: animation itself has !important on border-top-color */\n animation: flash-border-glow 0.6s ease-out forwards; \n}\n\n/* Styling for images within the main haiku card */\n.haiku-card-image {\n width: 9.5rem; /* Increased size (approx w-48) */\n height: 9.5rem; /* Increased size (approx h-48) */\n object-fit: cover;\n border-radius: 1.5rem; /* rounded-xl */\n border: 1px solid #e5e7eb;\n /* Enhanced shadow with subtle orange hint */\n box-shadow: 0 8px 15px rgba(0, 0, 0, 0.1),\n 0 3px 6px rgba(0, 0, 0, 0.08),\n 0 0 10px rgba(255, 111, 97, 0.2);\n /* Inherit animation delay from inline style */\n animation-name: fadeIn;\n animation-duration: 0.5s;\n animation-fill-mode: both;\n}\n\n/* Styling for images within the suggestion card */\n.suggestion-card-image {\n width: 6.5rem; /* Increased slightly (w-20) */\n height: 6.5rem; /* Increased slightly (h-20) */\n object-fit: cover;\n border-radius: 1rem; /* Equivalent to rounded-md */\n border: 1px solid #d1d5db; /* Equivalent to border (using Tailwind gray-300) */\n margin-top: 0.5rem;\n /* Added shadow for suggestion images */\n box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1),\n 0 2px 4px rgba(0, 0, 0, 0.06);\n transition: all 0.2s ease-in-out; /* Added for smooth deselection */\n}\n\n/* Styling for the focused suggestion card image */\n.suggestion-card-image-focus {\n width: 6.5rem; \n height: 6.5rem; \n object-fit: cover;\n border-radius: 1rem; \n margin-top: 0.5rem;\n /* Highlight styles */\n border: 2px solid #ff6f61; /* Thicker, themed border */\n box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1), /* Base shadow for depth */\n 0 0 12px rgba(255, 111, 97, 0.6); /* Orange glow */\n transform: scale(1.05); /* Slightly scale up */\n transition: all 0.2s ease-in-out; /* Smooth transition for focus */\n}\n\n/* Styling for the suggestion card container in the sidebar */\n.suggestion-card {\n border: 1px solid #dee2e6; /* Same default border as haiku-card */\n border-top: 10px solid #ff6f61; /* Same orange top border */\n border-radius: 0.375rem; /* Default rounded-md */\n /* Note: background-color is set by Tailwind bg-gray-100 */\n /* Other styles like padding, margin, flex are handled by Tailwind */\n}\n\n.suggestion-image-container {\n display: flex;\n gap: 1rem;\n justify-content: space-between;\n width: 100%;\n height: 6.5rem;\n}\n\n",
"path": "langgraph_tool_based_generative_ui/style.css",
"language": "css",
"type": "file"
},
{
"name": "README.mdx",
"content": "# 🪶 Tool-Based Generative UI Haiku Creator\n\n## What This Demo Shows\n\nThis demo showcases CopilotKit's **tool-based generative UI** capabilities:\n1. **Frontend Rendering of Tool Calls**: Backend tool calls are automatically rendered in the UI\n2. **Dynamic UI Generation**: The UI updates in real-time as the agent generates content\n3. **Elegant Content Presentation**: Complex structured data (haikus) are beautifully displayed\n\n## How to Interact\n\nChat with your Copilot and ask for haikus about different topics:\n- \"Create a haiku about nature\"\n- \"Write a haiku about technology\"\n- \"Generate a haiku about the changing seasons\"\n- \"Make a humorous haiku about programming\"\n\nEach request will trigger the agent to generate a haiku and display it in a visually appealing card format in the UI.\n\n## ✨ Tool-Based Generative UI in Action\n\n**What's happening technically:**\n- The agent processes your request and determines it should create a haiku\n- It calls a backend tool that returns structured haiku data\n- CopilotKit automatically renders this tool call in the frontend\n- The rendering is handled by the registered tool component in your React app\n- No manual state management is required to display the results\n\n**What you'll see in this demo:**\n- As you request a haiku, a beautifully formatted card appears in the UI\n- The haiku follows the traditional 5-7-5 syllable structure\n- Each haiku is presented with consistent styling\n- Multiple haikus can be generated in sequence\n- The UI adapts to display each new piece of content\n\nThis pattern of tool-based generative UI can be extended to create any kind of dynamic content - from data visualizations to interactive components, all driven by your Copilot's tool calls! ",
"path": "langgraph_tool_based_generative_ui/README.mdx",
"language": "markdown",
"type": "file"
}
]
},
"langgraph_shared_state": {
"files": [
{
"name": "README.md",
"content": "# 🍳 Shared State Recipe Creator\n\n## What This Demo Shows\n\nThis demo showcases CopilotKit's **shared state** functionality - a powerful feature that enables bidirectional data flow between:\n1. **Frontend → Agent**: UI controls update the agent's context in real-time\n2. **Agent → Frontend**: The Copilot's recipe creations instantly update the UI components\n\nIt's like having a cooking buddy who not only listens to what you want but also updates your recipe card as you chat - no refresh needed! ✨\n\n## How to Interact\n\nMix and match any of these parameters (or none at all - it's up to you!):\n- **Skill Level**: Beginner to expert 👨🍳\n- **Cooking Time**: Quick meals or slow cooking ⏱️\n- **Special Preferences**: Dietary needs, flavor profiles, health goals 🥗\n- **Ingredients**: Items you want to include 🧅🥩🍄\n- **Instructions**: Any specific steps\n\nThen chat with your Copilot chef with prompts like:\n- \"I'm a beginner cook. Can you make me a quick dinner?\"\n- \"I need something spicy with chicken that takes under 30 minutes!\"\n\n## ✨ Shared State Magic in Action\n\n**What's happening technically:**\n- The UI and Copilot agent share the same state object (**Agent State = UI State**)\n- Changes from either side automatically update the other\n- Neither side needs to manually request updates from the other\n\n**What you'll see in this demo:**\n- Set cooking time to 20 minutes in the UI and watch the Copilot immediately respect your time constraint\n- Add ingredients through the UI and see them appear in your recipe\n- When the Copilot suggests new ingredients, watch them automatically appear in the UI ingredients list\n- Change your skill level and see how the Copilot adapts its instructions in real-time\n\nThis synchronized state creates a seamless experience where the agent always has your current preferences, and any updates to the recipe are instantly reflected in both places.\n\nThis shared state pattern can be applied to any application where you want your UI and Copilot to work together in perfect harmony!\n",
"path": "langgraph_shared_state/README.md",
"language": "markdown",
"type": "file"
},
{
"name": "agent.py",
"content": "\"\"\"\nA demo of shared state between the agent and CopilotKit using LangGraph.\n\"\"\"\n\nimport json\nfrom enum import Enum\nfrom typing import Dict, List, Any, Optional\n\n# LangGraph imports\nfrom langchain_core.runnables import RunnableConfig\nfrom langgraph.graph import StateGraph, END, START\nfrom langgraph.types import Command\nfrom langgraph.checkpoint.memory import MemorySaver\n\n# CopilotKit imports\nfrom copilotkit import CopilotKitState\nfrom copilotkit.langgraph import copilotkit_customize_config, copilotkit_emit_state\n\n# OpenAI imports\nfrom langchain_openai import ChatOpenAI\nfrom langchain_core.messages import SystemMessage\nfrom copilotkit.langgraph import (copilotkit_exit)\n\nclass SkillLevel(str, Enum):\n \"\"\"\n The level of skill required for the recipe.\n \"\"\"\n BEGINNER = \"Beginner\"\n INTERMEDIATE = \"Intermediate\"\n ADVANCED = \"Advanced\"\n\n\nclass SpecialPreferences(str, Enum):\n \"\"\"\n Special preferences for the recipe.\n \"\"\"\n HIGH_PROTEIN = \"High Protein\"\n LOW_CARB = \"Low Carb\"\n SPICY = \"Spicy\"\n BUDGET_FRIENDLY = \"Budget-Friendly\"\n ONE_POT_MEAL = \"One-Pot Meal\"\n VEGETARIAN = \"Vegetarian\"\n VEGAN = \"Vegan\"\n\n\nclass CookingTime(str, Enum):\n \"\"\"\n The cooking time of the recipe.\n \"\"\"\n FIVE_MIN = \"5 min\"\n FIFTEEN_MIN = \"15 min\"\n THIRTY_MIN = \"30 min\"\n FORTY_FIVE_MIN = \"45 min\"\n SIXTY_PLUS_MIN = \"60+ min\"\n\n\nGENERATE_RECIPE_TOOL = {\n \"type\": \"function\",\n \"function\": {\n \"name\": \"generate_recipe\",\n \"description\": \" \".join(\"\"\"Using the existing (if any) ingredients and instructions, proceed with the recipe to finish it.\n Make sure the recipe is complete. ALWAYS provide the entire recipe, not just the changes.\"\"\".split()),\n \"parameters\": {\n \"type\": \"object\",\n \"properties\": {\n \"recipe\": {\n \"type\": \"object\",\n \"properties\": {\n \"skill_level\": {\n \"type\": \"string\",\n \"enum\": [level.value for level in SkillLevel],\n \"description\": \"The skill level required for the recipe\"\n },\n \"special_preferences\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\",\n \"enum\": [preference.value for preference in SpecialPreferences]\n },\n \"description\": \"A list of special preferences for the recipe\"\n },\n \"cooking_time\": {\n \"type\": \"string\",\n \"enum\": [time.value for time in CookingTime],\n \"description\": \"The cooking time of the recipe\"\n },\n \"ingredients\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"properties\": {\n \"icon\": {\"type\": \"string\", \"description\": \"The icon emoji (not emoji code like '\\x1f35e', but the actual emoji like 🥕) of the ingredient\"},\n \"name\": {\"type\": \"string\"},\n \"amount\": {\"type\": \"string\"}\n }\n },\n \"description\": \"Entire list of ingredients for the recipe, including the new ingredients and the ones that are already in the recipe\"\n },\n \"instructions\": {\n \"type\": \"array\",\n \"items\": {\"type\": \"string\"},\n \"description\": \"Entire list of instructions for the recipe, including the new instructions and the ones that are already there\"\n },\n \"changes\": {\n \"type\": \"string\",\n \"description\": \"A description of the changes made to the recipe\"\n }\n },\n }\n },\n \"required\": [\"recipe\"]\n }\n }\n}\n\n\nclass AgentState(CopilotKitState):\n \"\"\"\n The state of the recipe.\n \"\"\"\n recipe: Optional[Dict[str, Any]] = None\n\n\nasync def start_flow(state: Dict[str, Any], config: RunnableConfig):\n \"\"\"\n This is the entry point for the flow.\n \"\"\"\n\n # Initialize recipe if not exists\n if \"recipe\" not in state or state[\"recipe\"] is None:\n state[\"recipe\"] = {\n \"skill_level\": SkillLevel.BEGINNER.value,\n \"special_preferences\": [],\n \"cooking_time\": CookingTime.FIFTEEN_MIN.value,\n \"ingredients\": [{\"icon\": \"🍴\", \"name\": \"Sample Ingredient\", \"amount\": \"1 unit\"}],\n \"instructions\": [\"First step instruction\"]\n }\n # Emit the initial state to ensure it's properly shared with the frontend\n await copilotkit_emit_state(config, state)\n \n return Command(\n goto=\"chat_node\",\n update={\n \"messages\": state[\"messages\"],\n \"recipe\": state[\"recipe\"]\n }\n )\n\n\nasync def chat_node(state: Dict[str, Any], config: RunnableConfig):\n \"\"\"\n Standard chat node.\n \"\"\"\n # Create a safer serialization of the recipe\n recipe_json = \"No recipe yet\"\n if \"recipe\" in state and state[\"recipe\"] is not None:\n try:\n recipe_json = json.dumps(state[\"recipe\"], indent=2)\n except Exception as e:\n recipe_json = f\"Error serializing recipe: {str(e)}\"\n \n system_prompt = f\"\"\"You are a helpful assistant for creating recipes. \n This is the current state of the recipe: {recipe_json}\n You can improve the recipe by calling the generate_recipe tool.\n \n IMPORTANT:\n 1. Create a recipe using the existing ingredients and instructions. Make sure the recipe is complete.\n 2. For ingredients, append new ingredients to the existing ones.\n 3. For instructions, append new steps to the existing ones.\n 4. 'ingredients' is always an array of objects with 'icon', 'name', and 'amount' fields\n 5. 'instructions' is always an array of strings\n\n If you have just created or modified the recipe, just answer in one sentence what you did. dont describe the recipe, just say what you did.\n \"\"\"\n\n # Define the model\n model = ChatOpenAI(model=\"gpt-4o-mini\")\n \n # Define config for the model\n if config is None:\n config = RunnableConfig(recursion_limit=25)\n \n # Use CopilotKit's custom config functions to properly set up streaming for the recipe state\n config = copilotkit_customize_config(\n config,\n emit_intermediate_state=[{\n \"state_key\": \"recipe\",\n \"tool\": \"generate_recipe\",\n \"tool_argument\": \"recipe\"\n }],\n )\n\n # Bind the tools to the model\n model_with_tools = model.bind_tools(\n [\n *state[\"copilotkit\"][\"actions\"],\n GENERATE_RECIPE_TOOL\n ],\n # Disable parallel tool calls to avoid race conditions\n parallel_tool_calls=False,\n )\n\n # Run the model and generate a response\n response = await model_with_tools.ainvoke([\n SystemMessage(content=system_prompt),\n *state[\"messages\"],\n ], config)\n\n # Update messages with the response\n messages = state[\"messages\"] + [response]\n \n # Handle tool calls\n if hasattr(response, \"tool_calls\") and response.tool_calls:\n tool_call = response.tool_calls[0]\n \n # Handle tool_call as a dictionary or an object\n if isinstance(tool_call, dict):\n tool_call_id = tool_call[\"id\"]\n tool_call_name = tool_call[\"name\"]\n # Check if args is already a dict or needs to be parsed\n if isinstance(tool_call[\"args\"], dict):\n tool_call_args = tool_call[\"args\"]\n else:\n tool_call_args = json.loads(tool_call[\"args\"])\n else:\n # Handle as an object\n tool_call_id = tool_call.id\n tool_call_name = tool_call.name\n # Check if args is already a dict or needs to be parsed\n if isinstance(tool_call.args, dict):\n tool_call_args = tool_call.args\n else:\n tool_call_args = json.loads(tool_call.args)\n\n if tool_call_name == \"generate_recipe\":\n # Update recipe state with tool_call_args\n recipe_data = tool_call_args[\"recipe\"]\n \n # If we have an existing recipe, update it\n if \"recipe\" in state and state[\"recipe\"] is not None:\n recipe = state[\"recipe\"]\n for key, value in recipe_data.items():\n if value is not None: # Only update fields that were provided\n recipe[key] = value\n else:\n # Create a new recipe\n recipe = {\n \"skill_level\": recipe_data.get(\"skill_level\", SkillLevel.BEGINNER.value),\n \"special_preferences\": recipe_data.get(\"special_preferences\", []),\n \"cooking_time\": recipe_data.get(\"cooking_time\", CookingTime.FIFTEEN_MIN.value),\n \"ingredients\": recipe_data.get(\"ingredients\", []),\n \"instructions\": recipe_data.get(\"instructions\", [])\n }\n \n # Add tool response to messages\n tool_response = {\n \"role\": \"tool\",\n \"content\": \"Recipe generated.\",\n \"tool_call_id\": tool_call_id\n }\n \n messages = messages + [tool_response]\n \n # Explicitly emit the updated state to ensure it's shared with frontend\n state[\"recipe\"] = recipe\n await copilotkit_emit_state(config, state)\n \n # Return command with updated recipe\n return Command(\n goto=\"start_flow\",\n update={\n \"messages\": messages,\n \"recipe\": recipe\n }\n )\n \n # If no tool was called, just update messages and go to end\n await copilotkit_exit(config)\n return Command(\n goto=END,\n update={\n \"messages\": messages,\n \"recipe\": state[\"recipe\"]\n }\n )\n\n\n# Define the graph\nworkflow = StateGraph(AgentState)\n\n# Add nodes\nworkflow.add_node(\"start_flow\", start_flow)\nworkflow.add_node(\"chat_node\", chat_node)\n\n# Add edges\nworkflow.set_entry_point(\"start_flow\")\nworkflow.add_edge(START, \"start_flow\")\nworkflow.add_edge(\"start_flow\", \"chat_node\")\nworkflow.add_edge(\"chat_node\", END)\n\n# Compile the graph\nshared_state_graph = workflow.compile(checkpointer=MemorySaver())",
"path": "langgraph_shared_state/agent.py",
"language": "python",
"type": "file"
},
{
"name": "page.tsx",
"content": "\"use client\";\nimport { CopilotKit, useCoAgent, useCopilotChat } from \"@copilotkit/react-core\";\nimport { CopilotKitCSSProperties, CopilotSidebar, useCopilotChatSuggestions } from \"@copilotkit/react-ui\";\nimport { useState, useEffect, useRef } from \"react\";\nimport { Role, TextMessage } from \"@copilotkit/runtime-client-gql\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport { initialPrompt, chatSuggestions } from \"@/lib/prompts\";\nimport { AGENT_TYPE } from \"@/config\";\nenum SkillLevel {\n BEGINNER = \"Beginner\",\n INTERMEDIATE = \"Intermediate\",\n ADVANCED = \"Advanced\",\n}\n\nenum CookingTime {\n FiveMin = \"5 min\",\n FifteenMin = \"15 min\",\n ThirtyMin = \"30 min\",\n FortyFiveMin = \"45 min\",\n SixtyPlusMin = \"60+ min\",\n}\n\nconst cookingTimeValues = [\n { label: CookingTime.FiveMin, value: 0 },\n { label: CookingTime.FifteenMin, value: 1 },\n { label: CookingTime.ThirtyMin, value: 2 },\n { label: CookingTime.FortyFiveMin, value: 3 },\n { label: CookingTime.SixtyPlusMin, value: 4 },\n];\n\nexport default function SharedState() {\n return (\n \n <CopilotKit\n runtimeUrl={AGENT_TYPE == \"general\" ? \"/api/copilotkit?langgraph=true\" : \"/api/copilotkit\"}\n showDevConsole={false}\n agent=\"shared_state\"\n >\n <div\n className=\"app-container\"\n style={{\n backgroundImage: \"url('./shared_state_background.png')\",\n backgroundAttachment: \"fixed\",\n backgroundSize: \"cover\",\n position: \"relative\",\n }}\n >\n <div \n style={{\n position: \"absolute\",\n top: 0,\n left: 0,\n right: 0,\n bottom: 0,\n backgroundColor: \"rgba(0, 0, 0, 0.4)\",\n backdropFilter: \"blur(5px)\",\n zIndex: 0,\n }}\n />\n <Recipe />\n {/* </div> */}\n <CopilotSidebar\n defaultOpen={true}\n labels={{\n title: \"AI Recipe Assistant\",\n initial: initialPrompt.sharedState,\n }}\n clickOutsideToClose={false}\n />\n </div>\n </CopilotKit>\n );\n}\n\ninterface Ingredient {\n icon: string;\n name: string;\n amount: string;\n}\n\nenum SpecialPreferences {\n HighProtein = \"High Protein\",\n LowCarb = \"Low Carb\",\n Spicy = \"Spicy\",\n BudgetFriendly = \"Budget-Friendly\",\n OnePotMeal = \"One-Pot Meal\",\n Vegetarian = \"Vegetarian\",\n Vegan = \"Vegan\",\n}\n\nconst dietaryOptions = Object.values(SpecialPreferences);\n\ninterface Recipe {\n title: string;\n skill_level: SkillLevel;\n cooking_time: CookingTime;\n special_preferences: SpecialPreferences[];\n ingredients: Ingredient[];\n instructions: string[];\n}\n\ninterface RecipeAgentState {\n recipe: Recipe;\n}\n\nconst INITIAL_STATE: RecipeAgentState = {\n recipe: {\n title: \"Make Your Recipe\",\n skill_level: SkillLevel.INTERMEDIATE,\n cooking_time: CookingTime.FortyFiveMin,\n special_preferences: [],\n ingredients: [\n { icon: \"🥕\", name: \"Carrots\", amount: \"3 large, grated\" },\n { icon: \"🌾\", name: \"All-Purpose Flour\", amount: \"2 cups\" },\n ],\n instructions: [\n \"Preheat oven to 350°F (175°C)\",\n ],\n },\n};\n\nfunction Recipe() {\n const { state: agentState, setState: setAgentState } =\n useCoAgent<RecipeAgentState>({\n name: \"shared_state\",\n initialState: INITIAL_STATE,\n });\n\n useCopilotChatSuggestions({\n instructions: chatSuggestions.sharedState,\n })\n\n const [recipe, setRecipe] = useState(INITIAL_STATE.recipe);\n const { appendMessage, isLoading } = useCopilotChat();\n const [editingInstructionIndex, setEditingInstructionIndex] = useState<number | null>(null);\n const newInstructionRef = useRef<HTMLTextAreaElement>(null);\n\n const updateRecipe = (partialRecipe: Partial<Recipe>) => {\n setAgentState({\n ...agentState,\n recipe: {\n ...recipe,\n ...partialRecipe,\n },\n });\n setRecipe({\n ...recipe,\n ...partialRecipe,\n });\n };\n\n const newRecipeState = { ...recipe };\n const newChangedKeys = [];\n const changedKeysRef = useRef<string[]>([]);\n\n for (const key in recipe) {\n if (\n agentState &&\n agentState.recipe &&\n (agentState.recipe as any)[key] !== undefined &&\n (agentState.recipe as any)[key] !== null\n ) {\n let agentValue = (agentState.recipe as any)[key];\n const recipeValue = (recipe as any)[key];\n\n // Check if agentValue is a string and replace \\n with actual newlines\n if (typeof agentValue === \"string\") {\n agentValue = agentValue.replace(/\\\\n/g, \"\\n\");\n }\n\n if (JSON.stringify(agentValue) !== JSON.stringify(recipeValue)) {\n (newRecipeState as any)[key] = agentValue;\n newChangedKeys.push(key);\n }\n }\n }\n\n if (newChangedKeys.length > 0) {\n changedKeysRef.current = newChangedKeys;\n } else if (!isLoading) {\n changedKeysRef.current = [];\n }\n\n useEffect(() => {\n setRecipe(newRecipeState);\n }, [JSON.stringify(newRecipeState)]);\n\n const handleTitleChange = (event: React.ChangeEvent<HTMLInputElement>) => {\n updateRecipe({\n title: event.target.value,\n });\n };\n\n const handleSkillLevelChange = (\n event: React.ChangeEvent<HTMLSelectElement>\n ) => {\n updateRecipe({\n skill_level: event.target.value as SkillLevel,\n });\n };\n\n const handleDietaryChange = (preference: SpecialPreferences, checked: boolean) => {\n if (checked) {\n updateRecipe({\n special_preferences: [...recipe.special_preferences, preference],\n });\n } else {\n updateRecipe({\n special_preferences: recipe.special_preferences.filter(\n (p) => p !== preference\n ),\n });\n }\n };\n\n const handleCookingTimeChange = (\n event: React.ChangeEvent<HTMLSelectElement>\n ) => {\n updateRecipe({\n cooking_time: cookingTimeValues[Number(event.target.value)].label,\n });\n };\n\n \n const addIngredient = () => {\n // Pick a random food emoji from our valid list\n updateRecipe({\n ingredients: [...recipe.ingredients, { icon: \"🍴\", name: \"\", amount: \"\" }],\n });\n };\n\n const updateIngredient = (index: number, field: keyof Ingredient, value: string) => {\n const updatedIngredients = [...recipe.ingredients];\n updatedIngredients[index] = {\n ...updatedIngredients[index],\n [field]: value,\n };\n updateRecipe({ ingredients: updatedIngredients });\n };\n\n const removeIngredient = (index: number) => {\n const updatedIngredients = [...recipe.ingredients];\n updatedIngredients.splice(index, 1);\n updateRecipe({ ingredients: updatedIngredients });\n };\n\n const addInstruction = () => {\n const newIndex = recipe.instructions.length;\n updateRecipe({\n instructions: [...recipe.instructions, \"\"],\n });\n // Set the new instruction as the editing one\n setEditingInstructionIndex(newIndex);\n \n // Focus the new instruction after render\n setTimeout(() => {\n const textareas = document.querySelectorAll('.instructions-container textarea');\n const newTextarea = textareas[textareas.length - 1] as HTMLTextAreaElement;\n if (newTextarea) {\n newTextarea.focus();\n }\n }, 50);\n };\n\n const updateInstruction = (index: number, value: string) => {\n const updatedInstructions = [...recipe.instructions];\n updatedInstructions[index] = value;\n updateRecipe({ instructions: updatedInstructions });\n };\n\n const removeInstruction = (index: number) => {\n const updatedInstructions = [...recipe.instructions];\n updatedInstructions.splice(index, 1);\n updateRecipe({ instructions: updatedInstructions });\n };\n\n // Simplified icon handler that defaults to a fork/knife for any problematic icons\n const getProperIcon = (icon: string | undefined): string => {\n // If icon is undefined return the default\n if (!icon) {\n return \"🍴\";\n }\n \n return icon;\n };\n\n console.log(recipe)\n\n return (\n <form className=\"recipe-card\">\n {/* Recipe Title */}\n <div className=\"recipe-header\">\n <input\n type=\"text\"\n value={recipe.title || ''}\n onChange={handleTitleChange}\n className=\"recipe-title-input\"\n />\n \n <div className=\"recipe-meta\">\n <div className=\"meta-item\">\n <span className=\"meta-icon\">🕒</span>\n <select\n className=\"meta-select\"\n value={cookingTimeValues.find(t => t.label === recipe.cooking_time)?.value || 3}\n onChange={handleCookingTimeChange}\n style={{\n backgroundImage: 'url(\"data:image/svg+xml;charset=UTF-8,%3csvg xmlns=\\'http://www.w3.org/2000/svg\\' viewBox=\\'0 0 24 24\\' fill=\\'none\\' stroke=\\'%23555\\' stroke-width=\\'2\\' stroke-linecap=\\'round\\' stroke-linejoin=\\'round\\'%3e%3cpolyline points=\\'6 9 12 15 18 9\\'%3e%3c/polyline%3e%3c/svg%3e\")',\n backgroundRepeat: 'no-repeat',\n backgroundPosition: 'right 0px center',\n backgroundSize: '12px',\n appearance: 'none',\n WebkitAppearance: 'none'\n }}\n >\n {cookingTimeValues.map((time) => (\n <option key={time.value} value={time.value}>\n {time.label}\n </option>\n ))}\n </select>\n </div>\n \n <div className=\"meta-item\">\n <span className=\"meta-icon\">🏆</span>\n <select\n className=\"meta-select\"\n value={recipe.skill_level}\n onChange={handleSkillLevelChange}\n style={{\n backgroundImage: 'url(\"data:image/svg+xml;charset=UTF-8,%3csvg xmlns=\\'http://www.w3.org/2000/svg\\' viewBox=\\'0 0 24 24\\' fill=\\'none\\' stroke=\\'%23555\\' stroke-width=\\'2\\' stroke-linecap=\\'round\\' stroke-linejoin=\\'round\\'%3e%3cpolyline points=\\'6 9 12 15 18 9\\'%3e%3c/polyline%3e%3c/svg%3e\")',\n backgroundRepeat: 'no-repeat',\n backgroundPosition: 'right 0px center',\n backgroundSize: '12px',\n appearance: 'none',\n WebkitAppearance: 'none'\n }}\n >\n {Object.values(SkillLevel).map((level) => (\n <option key={level} value={level}>\n {level}\n </option>\n ))}\n </select>\n </div>\n </div>\n </div>\n\n {/* Dietary Preferences */}\n <div className=\"section-container relative\">\n {changedKeysRef.current.includes(\"special_preferences\") && <Ping />}\n <h2 className=\"section-title\">Dietary Preferences</h2>\n <div className=\"dietary-options\">\n {dietaryOptions.map((option) => (\n <label key={option} className=\"dietary-option\">\n <input\n type=\"checkbox\"\n checked={recipe.special_preferences.includes(option)}\n onChange={(e: React.ChangeEvent<HTMLInputElement>) => handleDietaryChange(option, e.target.checked)}\n />\n <span>{option}</span>\n </label>\n ))}\n </div>\n </div>\n\n {/* Ingredients */}\n <div className=\"section-container relative\">\n {changedKeysRef.current.includes(\"ingredients\") && <Ping />}\n <div className=\"section-header\">\n <h2 className=\"section-title\">Ingredients</h2>\n <button \n type=\"button\" \n className=\"add-button\"\n onClick={addIngredient}\n >\n + Add Ingredient\n </button>\n </div>\n <div className=\"ingredients-container\">\n {recipe.ingredients.map((ingredient, index) => (\n <div key={index} className=\"ingredient-card\">\n <div className=\"ingredient-icon\">{getProperIcon(ingredient.icon)}</div>\n <div className=\"ingredient-content\">\n <input\n type=\"text\"\n value={ingredient.name || ''}\n onChange={(e) => updateIngredient(index, \"name\", e.target.value)}\n placeholder=\"Ingredient name\"\n className=\"ingredient-name-input\"\n />\n <input\n type=\"text\"\n value={ingredient.amount || ''}\n onChange={(e) => updateIngredient(index, \"amount\", e.target.value)}\n placeholder=\"Amount\"\n className=\"ingredient-amount-input\"\n />\n </div>\n <button \n type=\"button\" \n className=\"remove-button\" \n onClick={() => removeIngredient(index)}\n aria-label=\"Remove ingredient\"\n >\n ×\n </button>\n </div>\n ))}\n </div>\n </div>\n\n {/* Instructions */}\n <div className=\"section-container relative\">\n {changedKeysRef.current.includes(\"instructions\") && <Ping />}\n <div className=\"section-header\">\n <h2 className=\"section-title\">Instructions</h2>\n <button \n type=\"button\" \n className=\"add-step-button\"\n onClick={addInstruction}\n >\n + Add Step\n </button>\n </div>\n <div className=\"instructions-container\">\n {recipe.instructions.map((instruction, index) => (\n <div key={index} className=\"instruction-item\">\n {/* Number Circle */}\n <div className=\"instruction-number\">\n {index + 1}\n </div>\n \n {/* Vertical Line */}\n {index < recipe.instructions.length - 1 && (\n <div className=\"instruction-line\" />\n )}\n \n {/* Instruction Content */}\n <div \n className={`instruction-content ${\n editingInstructionIndex === index \n ? 'instruction-content-editing' \n : 'instruction-content-default'\n }`}\n onClick={() => setEditingInstructionIndex(index)}\n >\n <textarea\n className=\"instruction-textarea\"\n value={instruction || ''}\n onChange={(e) => updateInstruction(index, e.target.value)}\n placeholder={!instruction ? \"Enter cooking instruction...\" : \"\"}\n onFocus={() => setEditingInstructionIndex(index)}\n onBlur={(e) => {\n // Only blur if clicking outside this instruction\n if (!e.relatedTarget || !e.currentTarget.contains(e.relatedTarget as Node)) {\n setEditingInstructionIndex(null);\n }\n }}\n />\n \n {/* Delete Button (only visible on hover) */}\n <button \n type=\"button\"\n className={`instruction-delete-btn ${\n editingInstructionIndex === index \n ? 'instruction-delete-btn-editing' \n : 'instruction-delete-btn-default'\n } remove-button`}\n onClick={(e) => {\n e.stopPropagation(); // Prevent triggering parent onClick\n removeInstruction(index);\n }}\n aria-label=\"Remove instruction\"\n >\n ×\n </button>\n </div>\n </div>\n ))}\n </div>\n </div>\n\n {/* Improve with AI Button */}\n <div className=\"action-container\">\n <button\n className={isLoading ? \"improve-button loading\" : \"improve-button\"}\n type=\"button\"\n onClick={() => {\n if (!isLoading) {\n appendMessage(\n new TextMessage({\n content: \"Improve the recipe\",\n role: Role.User,\n })\n );\n }\n }}\n disabled={isLoading}\n >\n {isLoading ? \"Please Wait...\" : \"Improve with AI\"}\n </button>\n </div>\n </form>\n );\n}\n\nfunction Ping() {\n return (\n <span className=\"ping-animation\">\n <span className=\"ping-circle\"></span>\n <span className=\"ping-dot\"></span>\n </span>\n );\n}",
"path": "langgraph_shared_state/page.tsx",
"language": "typescript",
"type": "file"
},
{
"name": "style.css",
"content": ".copilotKitWindow {\n box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);\n}\n\n.copilotKitHeader {\n border-top-left-radius: 5px !important;\n background-color: #fff;\n color: #000;\n border-bottom: 0px;\n}\n\n/* Recipe App Styles */\n.app-container {\n min-height: 100vh;\n width: 100%;\n display: flex;\n align-items: center;\n justify-content: center;\n background-size: cover;\n background-position: center;\n background-repeat: no-repeat;\n background-attachment: fixed;\n position: relative;\n overflow: auto;\n}\n\n.recipe-card {\n background-color: rgba(255, 255, 255, 0.97);\n border-radius: 16px;\n box-shadow: 0 15px 30px rgba(0, 0, 0, 0.25), 0 5px 15px rgba(0, 0, 0, 0.15);\n width: 100%;\n max-width: 750px;\n margin: 20px auto;\n padding: 14px 32px;\n position: relative;\n z-index: 1;\n backdrop-filter: blur(5px);\n border: 1px solid rgba(255, 255, 255, 0.3);\n transition: transform 0.2s ease, box-shadow 0.2s ease;\n animation: fadeIn 0.5s ease-out forwards;\n box-sizing: border-box;\n overflow: hidden;\n}\n\n.recipe-card:hover {\n transform: translateY(-5px);\n box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3), 0 10px 20px rgba(0, 0, 0, 0.2);\n}\n\n/* Recipe Header */\n.recipe-header {\n margin-bottom: 24px;\n}\n\n.recipe-title-input {\n width: 100%;\n font-size: 24px;\n font-weight: bold;\n border: none;\n outline: none;\n padding: 8px 0;\n margin-bottom: 0px;\n}\n\n.recipe-meta {\n display: flex;\n align-items: center;\n gap: 20px;\n margin-top: 5px;\n margin-bottom: 14px;\n}\n\n.meta-item {\n display: flex;\n align-items: center;\n gap: 8px;\n color: #555;\n}\n\n.meta-icon {\n font-size: 20px;\n color: #777;\n}\n\n.meta-text {\n font-size: 15px;\n}\n\n/* Recipe Meta Selects */\n.meta-item select {\n border: none;\n background: transparent;\n font-size: 15px;\n color: #555;\n cursor: pointer;\n outline: none;\n padding-right: 18px;\n transition: color 0.2s, transform 0.1s;\n font-weight: 500;\n}\n\n.meta-item select:hover, \n.meta-item select:focus {\n color: #FF5722;\n}\n\n.meta-item select:active {\n transform: scale(0.98);\n}\n\n.meta-item select option {\n color: #333;\n background-color: white;\n font-weight: normal;\n padding: 8px;\n}\n\n/* Section Container */\n.section-container {\n margin-bottom: 20px;\n position: relative;\n width: 100%;\n}\n\n.section-title {\n font-size: 20px;\n font-weight: 700;\n margin-bottom: 20px;\n color: #333;\n position: relative;\n display: inline-block;\n}\n\n.section-title:after {\n content: \"\";\n position: absolute;\n bottom: -8px;\n left: 0;\n width: 40px;\n height: 3px;\n background-color: #ff7043;\n border-radius: 3px;\n}\n\n/* Dietary Preferences */\n.dietary-options {\n display: flex;\n flex-wrap: wrap;\n gap: 10px 16px;\n margin-bottom: 16px;\n width: 100%;\n}\n\n.dietary-option {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n cursor: pointer;\n margin-bottom: 4px;\n}\n\n.dietary-option input {\n cursor: pointer;\n}\n\n/* Ingredients */\n.ingredients-container {\n display: flex;\n flex-wrap: wrap;\n gap: 10px;\n margin-bottom: 15px;\n width: 100%;\n box-sizing: border-box;\n}\n\n.ingredient-card {\n display: flex;\n align-items: center;\n background-color: rgba(255, 255, 255, 0.9);\n border-radius: 12px;\n padding: 12px;\n margin-bottom: 10px;\n box-shadow: 0 4px 10px rgba(0, 0, 0, 0.08);\n position: relative;\n transition: all 0.2s ease;\n border: 1px solid rgba(240, 240, 240, 0.8);\n width: calc(33.333% - 7px);\n box-sizing: border-box;\n}\n\n.ingredient-card:hover {\n transform: translateY(-2px);\n box-shadow: 0 6px 15px rgba(0, 0, 0, 0.12);\n}\n\n.ingredient-card .remove-button {\n position: absolute;\n right: 10px;\n top: 10px;\n background: none;\n border: none;\n color: #ccc;\n font-size: 16px;\n cursor: pointer;\n display: none;\n padding: 0;\n width: 24px;\n height: 24px;\n line-height: 1;\n}\n\n.ingredient-card:hover .remove-button {\n display: block;\n}\n\n.ingredient-icon {\n font-size: 24px;\n margin-right: 12px;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 40px;\n height: 40px;\n background-color: #f7f7f7;\n border-radius: 50%;\n flex-shrink: 0;\n}\n\n.ingredient-content {\n flex: 1;\n display: flex;\n flex-direction: column;\n gap: 3px;\n min-width: 0;\n}\n\n.ingredient-name-input, \n.ingredient-amount-input {\n border: none;\n background: transparent;\n outline: none;\n width: 100%;\n padding: 0;\n text-overflow: ellipsis;\n overflow: hidden;\n white-space: nowrap;\n}\n\n.ingredient-name-input {\n font-weight: 500;\n font-size: 14px;\n}\n\n.ingredient-amount-input {\n font-size: 13px;\n color: #666;\n}\n\n.ingredient-name-input::placeholder,\n.ingredient-amount-input::placeholder {\n color: #aaa;\n}\n\n.remove-button {\n background: none;\n border: none;\n color: #999;\n font-size: 20px;\n cursor: pointer;\n padding: 0;\n width: 28px;\n height: 28px;\n display: flex;\n align-items: center;\n justify-content: center;\n margin-left: 10px;\n}\n\n.remove-button:hover {\n color: #FF5722;\n}\n\n/* Instructions */\n.instructions-container {\n display: flex;\n flex-direction: column;\n gap: 6px;\n position: relative;\n margin-bottom: 12px;\n width: 100%;\n}\n\n.instruction-item {\n position: relative;\n display: flex;\n width: 100%;\n box-sizing: border-box;\n margin-bottom: 8px;\n align-items: flex-start;\n}\n\n.instruction-number {\n display: flex;\n align-items: center;\n justify-content: center;\n min-width: 26px;\n height: 26px;\n background-color: #ff7043;\n color: white;\n border-radius: 50%;\n font-weight: 600;\n flex-shrink: 0;\n box-shadow: 0 2px 4px rgba(255, 112, 67, 0.3);\n z-index: 1;\n font-size: 13px;\n margin-top: 2px;\n}\n\n.instruction-line {\n position: absolute;\n left: 13px; /* Half of the number circle width */\n top: 22px;\n bottom: -18px;\n width: 2px;\n background: linear-gradient(to bottom, #ff7043 60%, rgba(255, 112, 67, 0.4));\n z-index: 0;\n}\n\n.instruction-content {\n background-color: white;\n border-radius: 10px;\n padding: 10px 14px;\n margin-left: 12px;\n flex-grow: 1;\n transition: all 0.2s ease;\n box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);\n border: 1px solid rgba(240, 240, 240, 0.8);\n position: relative;\n width: calc(100% - 38px);\n box-sizing: border-box;\n display: flex;\n align-items: center;\n}\n\n.instruction-content-editing {\n background-color: #fff9f6;\n box-shadow: 0 6px 16px rgba(0, 0, 0, 0.12), 0 0 0 2px rgba(255, 112, 67, 0.2);\n}\n\n.instruction-content:hover {\n transform: translateY(-2px);\n box-shadow: 0 6px 16px rgba(0, 0, 0, 0.12);\n}\n\n.instruction-textarea {\n width: 100%;\n background: transparent;\n border: none;\n resize: vertical;\n font-family: inherit;\n font-size: 14px;\n line-height: 1.4;\n min-height: 20px;\n outline: none;\n padding: 0;\n margin: 0;\n}\n\n.instruction-delete-btn {\n position: absolute;\n background: none;\n border: none;\n color: #ccc;\n font-size: 16px;\n cursor: pointer;\n display: none;\n padding: 0;\n width: 20px;\n height: 20px;\n line-height: 1;\n top: 50%;\n transform: translateY(-50%);\n right: 8px;\n}\n\n.instruction-content:hover .instruction-delete-btn {\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n/* Action Button */\n.action-container {\n display: flex;\n justify-content: center;\n margin-top: 40px;\n padding-bottom: 20px;\n position: relative;\n}\n\n.improve-button {\n background-color: #ff7043;\n border: none;\n color: white;\n border-radius: 30px;\n font-size: 18px;\n font-weight: 600;\n padding: 14px 28px;\n cursor: pointer;\n transition: all 0.3s ease;\n box-shadow: 0 4px 15px rgba(255, 112, 67, 0.4);\n display: flex;\n align-items: center;\n justify-content: center;\n text-align: center;\n position: relative;\n min-width: 180px;\n}\n\n.improve-button:hover {\n background-color: #ff5722;\n transform: translateY(-2px);\n box-shadow: 0 8px 20px rgba(255, 112, 67, 0.5);\n}\n\n.improve-button.loading {\n background-color: #ff7043;\n opacity: 0.8;\n cursor: not-allowed;\n padding-left: 42px; /* Reduced padding to bring text closer to icon */\n padding-right: 22px; /* Balance the button */\n justify-content: flex-start; /* Left align text for better alignment with icon */\n}\n\n.improve-button.loading:after {\n content: \"\"; /* Add space between icon and text */\n display: inline-block;\n width: 8px; /* Width of the space */\n}\n\n.improve-button:before {\n content: \"\";\n background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83'/%3E%3C/svg%3E\");\n width: 20px; /* Slightly smaller icon */\n height: 20px;\n background-repeat: no-repeat;\n background-size: contain;\n position: absolute;\n left: 16px; /* Slightly adjusted */\n top: 50%;\n transform: translateY(-50%);\n display: none;\n}\n\n.improve-button.loading:before {\n display: block;\n animation: spin 1.5s linear infinite;\n}\n\n@keyframes spin {\n 0% { transform: translateY(-50%) rotate(0deg); }\n 100% { transform: translateY(-50%) rotate(360deg); }\n}\n\n/* Ping Animation */\n.ping-animation {\n position: absolute;\n display: flex;\n width: 12px;\n height: 12px;\n top: 0;\n right: 0;\n}\n\n.ping-circle {\n position: absolute;\n display: inline-flex;\n width: 100%;\n height: 100%;\n border-radius: 50%;\n background-color: #38BDF8;\n opacity: 0.75;\n animation: ping 1.5s cubic-bezier(0, 0, 0.2, 1) infinite;\n}\n\n.ping-dot {\n position: relative;\n display: inline-flex;\n width: 12px;\n height: 12px;\n border-radius: 50%;\n background-color: #0EA5E9;\n}\n\n@keyframes ping {\n 75%, 100% {\n transform: scale(2);\n opacity: 0;\n }\n}\n\n/* Instruction hover effects */\n.instruction-item:hover .instruction-delete-btn {\n display: flex !important;\n}\n\n/* Add some subtle animations */\n@keyframes fadeIn {\n from { opacity: 0; transform: translateY(20px); }\n to { opacity: 1; transform: translateY(0); }\n}\n\n/* Better center alignment for the recipe card */\n.recipe-card-container {\n display: flex;\n justify-content: center;\n width: 100%;\n position: relative;\n z-index: 1;\n margin: 0 auto;\n box-sizing: border-box;\n}\n\n/* Add Buttons */\n.add-button {\n background-color: transparent;\n color: #FF5722;\n border: 1px dashed #FF5722;\n border-radius: 8px;\n padding: 10px 16px;\n cursor: pointer;\n font-weight: 500;\n display: inline-block;\n font-size: 14px;\n margin-bottom: 0;\n}\n\n.add-step-button {\n background-color: transparent;\n color: #FF5722;\n border: 1px dashed #FF5722;\n border-radius: 6px;\n padding: 6px 12px;\n cursor: pointer;\n font-weight: 500;\n font-size: 13px;\n}\n\n/* Section Headers */\n.section-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 12px;\n}",
"path": "langgraph_shared_state/style.css",
"language": "css",
"type": "file"
},
{
"name": "README.mdx",
"content": "# 🍳 Shared State Recipe Creator\n\n## What This Demo Shows\n\nThis demo showcases CopilotKit's **shared state** functionality - a powerful feature that enables bidirectional data flow between:\n1. **Frontend → Agent**: UI controls update the agent's context in real-time\n2. **Agent → Frontend**: The Copilot's recipe creations instantly update the UI components\n\nIt's like having a cooking buddy who not only listens to what you want but also updates your recipe card as you chat - no refresh needed! ✨\n\n## How to Interact\n\nMix and match any of these parameters (or none at all - it's up to you!):\n- **Skill Level**: Beginner to expert 👨🍳\n- **Cooking Time**: Quick meals or slow cooking ⏱️\n- **Special Preferences**: Dietary needs, flavor profiles, health goals 🥗\n- **Ingredients**: Items you want to include 🧅🥩🍄\n- **Instructions**: Any specific steps\n\nThen chat with your Copilot chef with prompts like:\n- \"I'm a beginner cook. Can you make me a quick dinner?\"\n- \"I need something spicy with chicken that takes under 30 minutes!\"\n\n## ✨ Shared State Magic in Action\n\n**What's happening technically:**\n- The UI and Copilot agent share the same state object (**Agent State = UI State**)\n- Changes from either side automatically update the other\n- Neither side needs to manually request updates from the other\n\n**What you'll see in this demo:**\n- Set cooking time to 20 minutes in the UI and watch the Copilot immediately respect your time constraint\n- Add ingredients through the UI and see them appear in your recipe\n- When the Copilot suggests new ingredients, watch them automatically appear in the UI ingredients list\n- Change your skill level and see how the Copilot adapts its instructions in real-time\n\nThis synchronized state creates a seamless experience where the agent always has your current preferences, and any updates to the recipe are instantly reflected in both places.\n\nThis shared state pattern can be applied to any application where you want your UI and Copilot to work together in perfect harmony! ",
"path": "langgraph_shared_state/README.mdx",
"language": "markdown",
"type": "file"
}
]
},
"langgraph_predictive_state_updates": {
"files": [
{
"name": "agent.py",
"content": "\"\"\"\nA demo of predictive state updates using LangGraph.\n\"\"\"\n\nimport json\nimport uuid\nfrom typing import Dict, List, Any, Optional\n\n# LangGraph imports\nfrom langchain_core.runnables import RunnableConfig\nfrom langgraph.graph import StateGraph, END, START\nfrom langgraph.types import Command\nfrom langgraph.checkpoint.memory import MemorySaver\n\n# CopilotKit imports\nfrom copilotkit import CopilotKitState\nfrom copilotkit.langgraph import (\n copilotkit_customize_config\n)\nfrom copilotkit.langgraph import (copilotkit_exit)\n# OpenAI imports\nfrom langchain_openai import ChatOpenAI\nfrom langchain_core.messages import SystemMessage\n\nWRITE_DOCUMENT_TOOL = {\n \"type\": \"function\",\n \"function\": {\n \"name\": \"write_document\",\n \"description\": \" \".join(\"\"\"\n Write a document. Use markdown formatting to format the document.\n It's good to format the document extensively so it's easy to read.\n You can use all kinds of markdown.\n However, do not use italic or strike-through formatting, it's reserved for another purpose.\n You MUST write the full document, even when changing only a few words.\n When making edits to the document, try to make them minimal - do not change every word.\n Keep stories SHORT!\n \"\"\".split()),\n \"parameters\": {\n \"type\": \"object\",\n \"properties\": {\n \"document\": {\n \"type\": \"string\",\n \"description\": \"The document to write\"\n },\n },\n }\n }\n}\n\n\nclass AgentState(CopilotKitState):\n \"\"\"\n The state of the agent.\n \"\"\"\n document: Optional[str] = None\n\n\nasync def start_flow(state: AgentState, config: RunnableConfig):\n \"\"\"\n This is the entry point for the flow.\n \"\"\"\n return Command(\n goto=\"chat_node\"\n )\n\n\nasync def chat_node(state: AgentState, config: RunnableConfig):\n \"\"\"\n Standard chat node.\n \"\"\"\n\n system_prompt = f\"\"\"\n You are a helpful assistant for writing documents. \n To write the document, you MUST use the write_document tool.\n You MUST write the full document, even when changing only a few words.\n When you wrote the document, DO NOT repeat it as a message. \n Just briefly summarize the changes you made. 2 sentences max.\n This is the current state of the document: ----\\n {state.get('document')}\\n-----\n \"\"\"\n\n # Define the model\n model = ChatOpenAI(model=\"gpt-4o\")\n \n # Define config for the model with emit_intermediate_state to stream tool calls to frontend\n if config is None:\n config = RunnableConfig(recursion_limit=25)\n \n # Use CopilotKit's custom config to set up streaming for the write_document tool\n # This is equivalent to copilotkit_predict_state in the CrewAI version\n config = copilotkit_customize_config(\n config,\n emit_intermediate_state=[{\n \"state_key\": \"document\",\n \"tool\": \"write_document\",\n \"tool_argument\": \"document\",\n }],\n )\n\n # Bind the tools to the model\n model_with_tools = model.bind_tools(\n [\n *state[\"copilotkit\"][\"actions\"],\n WRITE_DOCUMENT_TOOL\n ],\n # Disable parallel tool calls to avoid race conditions\n parallel_tool_calls=False,\n )\n\n # Run the model to generate a response\n response = await model_with_tools.ainvoke([\n SystemMessage(content=system_prompt),\n *state[\"messages\"],\n ], config)\n\n # Update messages with the response\n messages = state[\"messages\"] + [response]\n \n # Extract any tool calls from the response\n if hasattr(response, \"tool_calls\") and response.tool_calls:\n tool_call = response.tool_calls[0]\n \n # Handle tool_call as a dictionary or an object\n if isinstance(tool_call, dict):\n tool_call_id = tool_call[\"id\"]\n tool_call_name = tool_call[\"name\"]\n tool_call_args = tool_call[\"args\"]\n else:\n # Handle as an object (backward compatibility)\n tool_call_id = tool_call.id\n tool_call_name = tool_call.name\n tool_call_args = tool_call.args\n\n if tool_call_name == \"write_document\":\n # Add the tool response to messages\n tool_response = {\n \"role\": \"tool\",\n \"content\": \"Document written.\",\n \"tool_call_id\": tool_call_id\n }\n \n # Add confirmation tool call\n confirm_tool_call = {\n \"role\": \"assistant\",\n \"content\": \"\",\n \"tool_calls\": [{\n \"id\": str(uuid.uuid4()),\n \"function\": {\n \"name\": \"confirm_changes\",\n \"arguments\": \"{}\"\n }\n }]\n }\n \n messages = messages + [tool_response, confirm_tool_call]\n \n # Return Command to route to end\n await copilotkit_exit(config)\n return Command(\n goto=END,\n update={\n \"messages\": messages,\n \"document\": tool_call_args[\"document\"]\n }\n )\n \n # If no tool was called, go to end\n await copilotkit_exit(config)\n return Command(\n goto=END,\n update={\n \"messages\": messages\n }\n )\n\n\n# Define the graph\nworkflow = StateGraph(AgentState)\n\n# Add nodes\nworkflow.add_node(\"start_flow\", start_flow)\nworkflow.add_node(\"chat_node\", chat_node)\n\n# Add edges\nworkflow.set_entry_point(\"start_flow\")\nworkflow.add_edge(START, \"start_flow\")\nworkflow.add_edge(\"start_flow\", \"chat_node\")\nworkflow.add_edge(\"chat_node\", END)\n\n# Compile the graph\npredictive_state_updates_graph = workflow.compile(checkpointer=MemorySaver())\n",
"path": "langgraph_predictive_state_updates/agent.py",
"language": "python",
"type": "file"
},
{
"name": "page.tsx",
"content": "\"use client\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\n\nimport MarkdownIt from \"markdown-it\";\n\nimport { diffWords } from \"diff\";\nimport { useEditor, EditorContent } from \"@tiptap/react\";\nimport StarterKit from \"@tiptap/starter-kit\";\nimport { useEffect, useState } from \"react\";\nimport {\n CopilotKit,\n useCoAgent,\n useCopilotAction,\n useCopilotChat,\n} from \"@copilotkit/react-core\";\nimport { CopilotSidebar, useCopilotChatSuggestions } from \"@copilotkit/react-ui\";\nimport { initialPrompt, chatSuggestions } from \"@/lib/prompts\";\nimport { AGENT_TYPE } from \"@/config\";\nconst extensions = [StarterKit];\n\nexport default function PredictiveStateUpdates() {\n return (\n <CopilotKit\n runtimeUrl={AGENT_TYPE == \"general\" ? \"/api/copilotkit?langgraph=true\" : \"/api/copilotkit\"}\n showDevConsole={false}\n agent=\"predictive_state_updates\"\n >\n <div\n className=\"min-h-screen w-full\"\n style={\n {\n \"--copilot-kit-primary-color\": \"#222\",\n \"--copilot-kit-separator-color\": \"#CCC\",\n } as React.CSSProperties\n }\n >\n <CopilotSidebar\n defaultOpen={true}\n labels={{\n title: \"AI Document Editor\",\n initial: initialPrompt.predictiveStateUpdates,\n }}\n clickOutsideToClose={false}\n >\n <DocumentEditor />\n </CopilotSidebar>\n </div>\n </CopilotKit>\n );\n}\n\ninterface AgentState {\n document: string;\n}\n\nconst DocumentEditor = () => {\n const editor = useEditor({\n extensions,\n immediatelyRender: false,\n editorProps: {\n attributes: { class: \"min-h-screen p-10\" },\n },\n });\n const [placeholderVisible, setPlaceholderVisible] = useState(false);\n const [currentDocument, setCurrentDocument] = useState(\"\");\n const { isLoading } = useCopilotChat();\n\n const {\n state: agentState,\n setState: setAgentState,\n nodeName,\n } = useCoAgent<AgentState>({\n name: \"predictive_state_updates\",\n initialState: {\n document: \"\",\n },\n });\n\n useEffect(() => {\n if (isLoading) {\n setCurrentDocument(editor?.getText() || \"\");\n }\n editor?.setEditable(!isLoading);\n }, [isLoading]);\n\n useEffect(() => {\n if (nodeName == \"end\") {\n // set the text one final time when loading is done\n if (\n currentDocument.trim().length > 0 &&\n currentDocument !== agentState?.document\n ) {\n const newDocument = agentState?.document || \"\";\n const diff = diffPartialText(currentDocument, newDocument, true);\n const markdown = fromMarkdown(diff);\n editor?.commands.setContent(markdown);\n }\n }\n }, [nodeName]);\n\n useEffect(() => {\n if (isLoading) {\n if (currentDocument.trim().length > 0) {\n const newDocument = agentState?.document || \"\";\n const diff = diffPartialText(currentDocument, newDocument);\n const markdown = fromMarkdown(diff);\n editor?.commands.setContent(markdown);\n } else {\n const markdown = fromMarkdown(agentState?.document || \"\");\n editor?.commands.setContent(markdown);\n }\n }\n }, [agentState?.document]);\n\n const text = editor?.getText() || \"\";\n\n useEffect(() => {\n setPlaceholderVisible(text.length === 0);\n\n if (!isLoading) {\n setCurrentDocument(text);\n setAgentState({\n document: text,\n });\n }\n }, [text]);\n\n useCopilotAction({\n name: \"confirm_changes\",\n renderAndWaitForResponse: ({ args, respond, status }) => {\n const [accepted, setAccepted] = useState<boolean | null>(null);\n return (\n <div className=\"bg-white p-6 rounded shadow-lg border border-gray-200 mt-5 mb-5\">\n <h2 className=\"text-lg font-bold mb-4\">Confirm Changes</h2>\n <p className=\"mb-6\">Do you want to accept the changes?</p>\n {accepted === null && (\n <div className=\"flex justify-end space-x-4\">\n <button\n className={`bg-gray-200 text-black py-2 px-4 rounded disabled:opacity-50 ${\n status === \"executing\" ? \"cursor-pointer\" : \"cursor-default\"\n }`}\n disabled={status !== \"executing\"}\n onClick={() => {\n if (respond) {\n setAccepted(false);\n editor?.commands.setContent(fromMarkdown(currentDocument));\n setAgentState({\n document: currentDocument,\n });\n respond({ accepted: false });\n }\n }}\n >\n Reject\n </button>\n <button\n className={`bg-black text-white py-2 px-4 rounded disabled:opacity-50 ${\n status === \"executing\" ? \"cursor-pointer\" : \"cursor-default\"\n }`}\n disabled={status !== \"executing\"}\n onClick={() => {\n if (respond) {\n setAccepted(true);\n editor?.commands.setContent(\n fromMarkdown(agentState?.document || \"\")\n );\n setCurrentDocument(agentState?.document || \"\");\n setAgentState({\n document: agentState?.document || \"\",\n });\n respond({ accepted: true });\n }\n }}\n >\n Confirm\n </button>\n </div>\n )}\n {accepted !== null && (\n <div className=\"flex justify-end\">\n <div className=\"mt-4 bg-gray-200 text-black py-2 px-4 rounded inline-block\">\n {accepted ? \"✓ Accepted\" : \"✗ Rejected\"}\n </div>\n </div>\n )}\n </div>\n );\n },\n });\n\n useCopilotChatSuggestions({\n instructions: chatSuggestions.predictiveStateUpdates,\n })\n\n return (\n <div className=\"relative min-h-screen w-full\">\n {placeholderVisible && (\n <div className=\"absolute top-6 left-6 m-4 pointer-events-none text-gray-400\">\n Your content goes here...\n </div>\n )}\n <EditorContent editor={editor} />\n </div>\n );\n};\n\nfunction fromMarkdown(text: string) {\n const md = new MarkdownIt({\n typographer: true,\n html: true,\n });\n\n return md.render(text);\n}\n\nfunction diffPartialText(\n oldText: string,\n newText: string,\n isComplete: boolean = false\n) {\n let oldTextToCompare = oldText;\n if (oldText.length > newText.length && !isComplete) {\n // make oldText shorter\n oldTextToCompare = oldText.slice(0, newText.length);\n }\n\n const changes = diffWords(oldTextToCompare, newText);\n\n let result = \"\";\n changes.forEach((part) => {\n if (part.added) {\n result += `<em>${part.value}</em>`;\n } else if (part.removed) {\n result += `<s>${part.value}</s>`;\n } else {\n result += part.value;\n }\n });\n\n if (oldText.length > newText.length && !isComplete) {\n result += oldText.slice(newText.length);\n }\n\n return result;\n}\n\nfunction isAlpha(text: string) {\n return /[a-zA-Z\\u00C0-\\u017F]/.test(text.trim());\n}\n",
"path": "langgraph_predictive_state_updates/page.tsx",
"language": "typescript",
"type": "file"
},
{
"name": "style.css",
"content": "/* Basic editor styles */\n.tiptap-container {\n height: 100vh; /* Full viewport height */\n width: 100vw; /* Full viewport width */\n display: flex;\n flex-direction: column;\n}\n\n.tiptap {\n flex: 1; /* Take up remaining space */\n overflow: auto; /* Allow scrolling if content overflows */\n}\n\n.tiptap :first-child {\n margin-top: 0;\n}\n\n/* List styles */\n.tiptap ul,\n.tiptap ol {\n padding: 0 1rem;\n margin: 1.25rem 1rem 1.25rem 0.4rem;\n}\n\n.tiptap ul li p,\n.tiptap ol li p {\n margin-top: 0.25em;\n margin-bottom: 0.25em;\n}\n\n/* Heading styles */\n.tiptap h1,\n.tiptap h2,\n.tiptap h3,\n.tiptap h4,\n.tiptap h5,\n.tiptap h6 {\n line-height: 1.1;\n margin-top: 2.5rem;\n text-wrap: pretty;\n font-weight: bold;\n}\n\n.tiptap h1,\n.tiptap h2,\n.tiptap h3,\n.tiptap h4,\n.tiptap h5,\n.tiptap h6 {\n margin-top: 3.5rem;\n margin-bottom: 1.5rem;\n}\n\n.tiptap p {\n margin-bottom: 1rem;\n}\n\n.tiptap h1 {\n font-size: 1.4rem;\n}\n\n.tiptap h2 {\n font-size: 1.2rem;\n}\n\n.tiptap h3 {\n font-size: 1.1rem;\n}\n\n.tiptap h4,\n.tiptap h5,\n.tiptap h6 {\n font-size: 1rem;\n}\n\n/* Code and preformatted text styles */\n.tiptap code {\n background-color: var(--purple-light);\n border-radius: 0.4rem;\n color: var(--black);\n font-size: 0.85rem;\n padding: 0.25em 0.3em;\n}\n\n.tiptap pre {\n background: var(--black);\n border-radius: 0.5rem;\n color: var(--white);\n font-family: \"JetBrainsMono\", monospace;\n margin: 1.5rem 0;\n padding: 0.75rem 1rem;\n}\n\n.tiptap pre code {\n background: none;\n color: inherit;\n font-size: 0.8rem;\n padding: 0;\n}\n\n.tiptap blockquote {\n border-left: 3px solid var(--gray-3);\n margin: 1.5rem 0;\n padding-left: 1rem;\n}\n\n.tiptap hr {\n border: none;\n border-top: 1px solid var(--gray-2);\n margin: 2rem 0;\n}\n\n.tiptap s {\n background-color: #f9818150;\n padding: 2px;\n font-weight: bold;\n color: rgba(0, 0, 0, 0.7);\n}\n\n.tiptap em {\n background-color: #b2f2bb;\n padding: 2px;\n font-weight: bold;\n font-style: normal;\n}\n\n.copilotKitWindow {\n box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);\n}\n\n.copilotKitHeader {\n border-top-left-radius: 5px !important;\n background-color: #fff;\n color: #000;\n border-bottom: 0px;\n}\n",
"path": "langgraph_predictive_state_updates/style.css",
"language": "css",
"type": "file"
},
{
"name": "README.mdx",
"content": "# 📝 Predictive State Updates Document Editor\n\n## What This Demo Shows\n\nThis demo showcases CopilotKit's **predictive state updates** for real-time document collaboration:\n1. **Live Document Editing**: Watch as your Copilot makes changes to a document in real-time\n2. **Diff Visualization**: See exactly what's being changed as it happens\n3. **Streaming Updates**: Changes are displayed character-by-character as the Copilot works\n\n## How to Interact\n\nTry these interactions with the collaborative document editor:\n- \"Fix the grammar and typos in this document\"\n- \"Make this text more professional\"\n- \"Add a section about [topic]\"\n- \"Summarize this content in bullet points\"\n- \"Change the tone to be more casual\"\n\nWatch as the Copilot processes your request and edits the document in real-time right before your eyes.\n\n## ✨ Predictive State Updates in Action\n\n**What's happening technically:**\n- The document state is shared between your UI and the Copilot\n- As the Copilot generates content, changes are streamed to the UI\n- Each modification is visualized with additions and deletions\n- The UI renders these changes progressively, without waiting for completion\n- All edits are tracked and displayed in a visually intuitive way\n\n**What you'll see in this demo:**\n- Text changes are highlighted in different colors (green for additions, red for deletions)\n- The document updates character-by-character, creating a typing-like effect\n- You can see the Copilot's thought process as it refines the content\n- The final document seamlessly incorporates all changes\n- The experience feels collaborative, as if someone is editing alongside you\n\nThis pattern of real-time collaborative editing with diff visualization is perfect for document editors, code review tools, content creation platforms, or any application where users benefit from seeing exactly how content is being transformed! ",
"path": "langgraph_predictive_state_updates/README.mdx",
"language": "markdown",
"type": "file"
}
]
},
"langgraph_no_chat": {
"files": [
{
"name": "agent.py",
"content": "\"\"\"\nA LangGraph implementation of the human-in-the-loop agent.\n\"\"\"\n\nimport json\nfrom typing import Dict, List, Any\nimport asyncio\n# LangGraph imports\nfrom langchain_core.runnables import RunnableConfig\nfrom langgraph.graph import StateGraph, END, START\nfrom langgraph.types import Command, interrupt\nfrom langgraph.checkpoint.memory import MemorySaver\n\n# CopilotKit imports\nfrom copilotkit import CopilotKitState\nfrom copilotkit.langgraph import copilotkit_customize_config, copilotkit_emit_state, copilotkit_interrupt, copilotkit_emit_message\n\n# LLM imports\nfrom langchain_openai import ChatOpenAI\nfrom langchain_core.messages import SystemMessage\nfrom copilotkit.langgraph import (copilotkit_exit)\n\nclass AgentState(CopilotKitState):\n \"\"\"\n The state of the agent.\n It inherits from CopilotKitState which provides the basic fields needed by CopilotKit.\n \"\"\"\n\nasync def start_flow(state: AgentState, config: RunnableConfig):\n \"\"\"\n This is the entry point for the flow.\n \"\"\"\n \n await asyncio.sleep(2)\n return Command(\n goto=\"buffer_node\",\n update={\n \"messages\": state[\"messages\"],\n }\n )\n\n\nasync def buffer_node(state: AgentState, config: RunnableConfig):\n \"\"\"\n This is a buffer node.\n \"\"\"\n system_prompt = \"\"\"\n You are a helpful assistant that answers user's questions. Make sure the response is concise and to the point. The response format should strictly be in markdown format.\n \"\"\"\n await asyncio.sleep(1)\n\n \n # Define the model\n model = ChatOpenAI(model=\"gpt-4o-mini\")\n \n # Define config for the model\n if config is None:\n config = RunnableConfig(recursion_limit=25)\n # Use CopilotKit's custom config functions to properly set up streaming for the steps state\n config = copilotkit_customize_config(\n config\n )\n\n # Bind the tools to the model\n model_with_tools = model.bind_tools(\n tools=[],\n parallel_tool_calls=False,\n )\n\n # Run the model and generate a response\n response = await model_with_tools.ainvoke([\n SystemMessage(content=system_prompt),\n *state[\"messages\"],\n ], config)\n\n # Update messages with the response\n messages = state[\"messages\"] + [response]\n\n # If no tool calls or not generate_task_steps, return to END with the updated messages\n await copilotkit_exit(config)\n return Command(\n goto=\"confirming_response_node\",\n update={\n \"messages\": messages,\n }\n )\n\n\nasync def confirming_response_node(state: AgentState, config: RunnableConfig):\n \"\"\"\n This is a buffer node as well. Just the name is\n \"\"\"\n \n await asyncio.sleep(2)\n await copilotkit_exit(config)\n return Command(\n goto=\"reporting_node\",\n update={\n \"messages\": state[\"messages\"],\n }\n )\n\n\n\nasync def reporting_node(state: AgentState, config: RunnableConfig):\n \"\"\"\n This node handles the user interrupt for step customization and generates the final response.\n \"\"\"\n\n await asyncio.sleep(2)\n await copilotkit_exit(config)\n return Command(\n goto=END,\n update={\n \"messages\": state[\"messages\"]\n }\n )\n\n\n# Define the graph\nworkflow = StateGraph(AgentState)\n\n# Add nodes\nworkflow.add_node(\"start_flow\", start_flow)\nworkflow.add_node(\"buffer_node\", buffer_node)\nworkflow.add_node(\"confirming_response_node\", confirming_response_node)\nworkflow.add_node(\"reporting_node\", reporting_node)\n\n# Add edges\nworkflow.set_entry_point(\"start_flow\")\nworkflow.add_edge(START, \"start_flow\")\nworkflow.add_edge(\"start_flow\", \"buffer_node\")\nworkflow.add_edge(\"buffer_node\", \"confirming_response_node\")\nworkflow.add_edge(\"confirming_response_node\", \"reporting_node\")\nworkflow.add_edge(\"reporting_node\", END)\n\n\n# Compile the graph\nno_chat = workflow.compile(checkpointer=MemorySaver())\n",
"path": "langgraph_no_chat/agent.py",
"language": "python",
"type": "file"
},
{
"name": "page.tsx",
"content": "\"use client\";\nimport React, { useState, useEffect, useRef, ReactEventHandler } from \"react\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport { CopilotKit, useCoAgent, useCopilotAction, useCopilotChat } from \"@copilotkit/react-core\";\nimport { TextMessage, Role } from \"@copilotkit/runtime-client-gql\";\nimport { useCopilotChatSuggestions } from \"@copilotkit/react-ui\";\nimport { chatSuggestions } from \"@/lib/prompts\";\nimport { useTheme } from \"next-themes\";\nimport { AnimatePresence, motion } from \"framer-motion\"\nimport { samplePrompts } from \"./samplePrompts\";\nimport Loader from \"./Loader\";\nimport { AGENT_TYPE } from \"@/config\";\nconst AgenticChat: React.FC = () => {\n return (\n <CopilotKit\n runtimeUrl={\"/api/copilotkit?langgraph=true\"}\n showDevConsole={true}\n agent=\"no_chat\"\n >\n <Chat />\n </CopilotKit>\n );\n};\n\nconst Chat = () => {\n const { appendMessage, isLoading } = useCopilotChat();\n const [aiResponse, setAiResponse] = useState(\"\");\n const [prompt, setPrompt] = useState(\"\");\n const { theme } = useTheme()\n const [step, setStep] = React.useState(0);\n const [showVerticalWizard, setShowVerticalWizard] = React.useState(false);\n const [started, setStarted] = useState(false);\n const [completedSteps, setCompletedSteps] = useState<number[]>([]);\n\n const { nodeName, state } = useCoAgent({\n name: \"no_chat\",\n })\n\n useEffect(() => {\n console.log(state, \"state\", nodeName);\n\n if (nodeName && nodeName != \"start_flow\") {\n setShowVerticalWizard(true);\n // Update completed steps based on nodeName\n if (nodeName === \"buffer_node\") {\n setCompletedSteps([0]);\n } else if (nodeName === \"confirming_response_node\") {\n setCompletedSteps([0, 1]);\n } else if (nodeName === \"reporting_node\") {\n setCompletedSteps([0, 1, 2]);\n } else if (nodeName === \"__end__\") {\n setCompletedSteps([0, 1, 2, 3]);\n }\n }\n }, [nodeName]);\n\n // Main wizard steps\n const wizardSteps = [\n {\n title: \"Step 1\",\n content: `Asking the agent to start the process. <b><i>${prompt}</i></b>`\n },\n {\n title: \"Step 2\",\n content: \"Running the <b><i>buffer_node</i></b> to extract the answer from the model.\"\n },\n {\n title: \"Step 3\",\n content: \"Running the <b><i>confirming_response_node</i></b> to confirm the response from the model.\"\n },\n {\n title: \"Step 4\",\n content: \"Running the <b><i>reporting_node</i></b> to generate a response and send it to the user.\"\n }\n ];\n\n useEffect(() => {\n // console.log(state.messages,\"messages\");\n\n if (!isLoading && state?.messages?.length) {\n setAiResponse(state.messages[state.messages.length - 1].content);\n }\n }, [isLoading])\n\n\n function handleExecuteProcess(): void {\n try {\n let index = Math.floor(Math.random() * samplePrompts.length)\n setPrompt(samplePrompts[index]);\n appendMessage(new TextMessage({\n role: Role.User,\n content: samplePrompts[index]\n }));\n } catch (error) {\n console.error(error);\n }\n }\n\n return (\n <div\n className=\"flex justify-center items-center h-full w-full\"\n style={{ background: theme === \"dark\" ? \"#020817\" : \"#fefefe\" }}\n >\n <div className=\"w-8/10 h-8/10 rounded-lg agent-execution-view-container\" style={{ background: \"#fefefe\" }}>\n <div className=\"agent-execution-header\" >\n <h2 style={{ color: \"#000\" }}>No Chat Example</h2>\n </div>\n <div className={`agent-execution-body`} style={{ position: \"relative\", overflow: \"hidden\" }}>\n <AnimatePresence mode=\"wait\">\n {!started ? (\n <motion.div\n key=\"intro\"\n initial={{ opacity: 0, y: 40 }}\n animate={{ opacity: 1, y: 0 }}\n exit={{ opacity: 0, y: -40 }}\n transition={{ duration: 0.5 }}\n style={{\n width: \"100%\",\n height: \"100%\",\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n background: \"#f9fafb\",\n borderRadius: 16,\n boxShadow: \"0 4px 24px 0 rgba(30,41,59,0.08)\",\n padding: 48,\n flexDirection: \"column\",\n minHeight: 400,\n }}\n >\n <div style={{ textAlign: \"center\", maxWidth: 480 }}>\n <div style={{ fontSize: 48, marginBottom: 16, color: \"#10b981\" }}>🤖</div>\n <h2 style={{ fontSize: 28, fontWeight: 700, marginBottom: 12 }}>\n Welcome to the Agent Process Wizard\n </h2>\n <p style={{ fontSize: 18, color: \"#334155\", marginBottom: 32 }}>\n This wizard will guide you through the process of interacting with the AI agent. Click the button below to get started!\n </p>\n <button\n className=\"agent-execute-button\"\n style={{\n fontSize: 18,\n padding: \"14px 36px\",\n borderRadius: 8,\n background: \"#10b981\",\n color: \"#fff\",\n fontWeight: 600,\n border: \"none\",\n boxShadow: \"0 2px 8px 0 rgba(16,185,129,0.08)\",\n cursor: \"pointer\",\n transition: \"background 0.2s\"\n }}\n onClick={() => {\n setStarted(true);\n setShowVerticalWizard(true);\n setTimeout(() => handleExecuteProcess(), 400); // delay to allow animation\n }}\n disabled={isLoading}\n >\n {isLoading ? \"Processing...\" : \"Start Agent Process\"}\n </button>\n </div>\n </motion.div>\n ) : (\n <motion.div\n key=\"wizard\"\n initial={{ opacity: 0, y: 40 }}\n animate={{ opacity: 1, y: 0 }}\n exit={{ opacity: 0, y: -40 }}\n transition={{ duration: 0.5 }}\n style={{ width: \"100%\", height: \"100%\" }}\n >\n <AnimatePresence mode=\"wait\">\n <motion.div\n key=\"vertical-wizard\"\n initial={{ opacity: 0, y: 40 }}\n animate={{ opacity: 1, y: 0 }}\n exit={{ opacity: 0, y: -40 }}\n transition={{ duration: 0.5 }}\n style={{ width: \"100%\", height: \"100%\" }}\n >\n <div\n style={{\n background: \"#fefefe\",\n borderRadius: 12,\n boxShadow: \"0 4px 24px 0 rgba(30,41,59,0.08)\",\n padding: 32,\n minHeight: 800,\n width: \"100%\",\n display: \"flex\",\n flexDirection: \"row\",\n gap: 48,\n alignItems: \"flex-start\",\n justifyContent: \"flex-start\",\n }}\n >\n <div style={{ flex: 2 }}>\n {wizardSteps.map((stepObj, idx) => {\n // Determine step status\n const isCompleted = completedSteps.includes(idx);\n const isCurrent = !isCompleted && (completedSteps.length === idx);\n return (\n <div\n key={idx}\n style={{\n display: \"flex\",\n alignItems: \"flex-start\",\n position: \"relative\",\n minHeight: 56,\n marginBottom: idx < wizardSteps.length - 1 ? 0 : 0,\n paddingBottom: idx < wizardSteps.length - 1 ? 48 : 0,\n }}\n >\n <div style={{ display: \"flex\", flexDirection: \"column\", alignItems: \"center\", width: 48, position: \"relative\" }}>\n <div\n style={{\n width: 32,\n height: 32,\n borderRadius: \"50%\",\n background: isCompleted ? \"#10b981\" : isCurrent ? \"#cbd5e1\" : \"#cbd5e1\",\n color: isCompleted ? \"#fff\" : \"#64748b\",\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n fontWeight: 700,\n fontSize: 18,\n zIndex: 2,\n boxShadow: \"0 2px 8px 0 rgba(16,185,129,0.08)\",\n transition: \"background 0.3s ease, color 0.3s ease\",\n }}\n >\n {isCompleted ? (\n idx + 1\n ) : isCurrent ? (\n <div style={{ width: 16, height: 16, display: \"flex\", alignItems: \"center\", justifyContent: \"center\" }}>\n <Loader />\n </div>\n ) : (\n idx + 1\n )}\n </div>\n {idx < wizardSteps.length - 1 && (\n <div\n style={{\n width: 4,\n flex: 1,\n background: isCompleted ? \"#10b981\" : \"#cbd5e1\",\n minHeight: 48,\n marginTop: 0,\n transition: \"background 0.3s ease\",\n }}\n />\n )}\n </div>\n <div style={{ marginLeft: 32, flex: 1 }}>\n <h2 style={{ fontSize: 20, marginBottom: 4, fontWeight: 700 }}>\n {stepObj.title}\n </h2>\n <p style={{ fontSize: 16, marginBottom: 0 }} dangerouslySetInnerHTML={{ __html: stepObj.content }}>\n </p>\n </div>\n </div>\n );\n })}\n </div>\n <div\n style={{\n flex: 1,\n minWidth: 350,\n maxWidth: 420,\n background: \"#fff\",\n borderRadius: 16,\n boxShadow: \"0 2px 16px 0 rgba(30,41,59,0.10)\",\n padding: 32,\n marginLeft: 24,\n minHeight: 400,\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"flex-start\",\n justifyContent: \"flex-start\",\n }}\n >\n <h3 style={{ fontWeight: 700, fontSize: 18, marginBottom: 16 }}>AI Response</h3>\n <div style={{ color: \"#334155\", fontSize: 16 }}>\n {aiResponse}\n </div>\n </div>\n </div>\n </motion.div>\n </AnimatePresence>\n {showVerticalWizard && (\n <div\n style={{\n position: \"absolute\",\n left: 0,\n right: 0,\n bottom: 40,\n display: \"flex\",\n justifyContent: \"center\",\n pointerEvents: \"none\",\n zIndex: 10,\n }}\n >\n <button\n disabled={isLoading}\n style={{\n background: \"#0ea5e9\",\n color: \"#fff\",\n borderRadius: 6,\n padding: \"7px 24px\",\n fontSize: 18,\n cursor: \"pointer\",\n fontWeight: 600,\n pointerEvents: \"auto\",\n }}\n onClick={() => {\n setCompletedSteps([])\n setStarted(false);\n setAiResponse(\"\");\n setShowVerticalWizard(false);\n setStep(0);\n }}\n >\n Back to Start\n </button>\n </div>\n )}\n </motion.div>\n )}\n\n </AnimatePresence>\n </div>\n\n </div>\n </div >\n );\n};\n\nexport default AgenticChat;\n",
"path": "langgraph_no_chat/page.tsx",
"language": "typescript",
"type": "file"
},
{
"name": "style.css",
"content": "/* Make CopilotKit UI fill the screen */\n#root {\n width: 100vw;\n height: 100vh;\n display: flex;\n flex-direction: column;\n}\n\n/* AgentExecutionView Styles */\n.agent-execution-view-container {\n display: flex;\n flex-direction: column;\n height: 100%; /* Fill the container from page.tsx */\n width: 100%;\n background-color: #ffffff;\n border-radius: 8px; /* Consistent with current chat */\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);\n overflow: hidden; /* Contains the scrolling log area */\n}\n\n.agent-execution-header {\n padding: 16px 24px;\n border-bottom: 1px solid #e8e8e8;\n display: flex;\n justify-content: space-between;\n align-items: center;\n}\n\n.agent-execution-header h2 {\n margin: 0;\n font-size: 1.25rem;\n color: #333;\n}\n\n.agent-execute-button {\n padding: 10px 20px;\n font-size: 1rem;\n color: #fff;\n background-color: #007bff;\n border: none;\n border-radius: 5px;\n cursor: pointer;\n transition: background-color 0.2s ease;\n}\n\n.agent-execute-button:hover {\n background-color: #0056b3;\n}\n\n.agent-execute-button:disabled {\n background-color: #cccccc;\n cursor: not-allowed;\n}\n\n.process-log-area {\n flex-grow: 1;\n padding: 16px 24px;\n overflow-y: auto;\n background-color: #f9f9f9;\n font-family: \"SFMono-Regular\", Consolas, \"Liberation Mono\", Menlo, Courier, monospace;\n font-size: 0.9rem;\n line-height: 1.6;\n}\n\n.log-entry {\n padding: 8px 0;\n border-bottom: 1px dashed #eee;\n display: flex;\n flex-wrap: nowrap;\n}\n\n.log-entry:last-child {\n border-bottom: none;\n}\n\n.log-entry.system .log-entry-content {\n color: #888;\n font-style: italic;\n}\n\n.log-entry-role {\n min-width: 60px; /* Adjust as needed */\n font-weight: bold;\n margin-right: 12px;\n color: #555;\n}\n\n.log-entry.assistant .log-entry-role {\n color: #007bff; /* Blue for agent role */\n}\n\n.log-entry-content {\n flex-grow: 1;\n word-break: break-word;\n}\n\n.log-entry-timestamp {\n margin-left: 16px;\n font-size: 0.8em;\n color: #aaa;\n white-space: nowrap;\n} \n\n.copilotKitInput {\n border-bottom-left-radius: 0.75rem;\n border-bottom-right-radius: 0.75rem;\n border-top-left-radius: 0.75rem;\n border-top-right-radius: 0.75rem;\n border: 1px solid var(--copilot-kit-separator-color) !important;\n background-color: #fff;\n}\n\n.copilotKitChat {\n background-color: #fff !important;\n}\n\n/* Dotted background for agent-execution-body */\n.agent-execution-body {\n height: 100%;\n width: 100%;\n background-image: radial-gradient(var(--dot-color, #cbd5e1) 1px, transparent 1px);\n background-size: 20px 20px;\n}\n\n/* Support for dark mode if a parent sets [data-theme=dark] or .dark class */\n[data-theme=\"dark\"] .agent-execution-body,\n.dark .agent-execution-body {\n --dot-color: #b03c3c;\n}\n\n/* Default for light mode */\n.agent-execution-body {\n --dot-color: #cbd5e1;\n}\n\n/* styles.css */\n",
"path": "langgraph_no_chat/style.css",
"language": "css",
"type": "file"
},
{
"name": "README.mdx",
"content": "# 🤖 Agentic Chat with Frontend Tools\n\n## What This Demo Shows\n\nThis demo showcases CopilotKit's **agentic chat** capabilities with **frontend tool integration**:\n1. **Natural Conversation**: Chat with your Copilot in a familiar chat interface\n2. **Frontend Tool Execution**: The Copilot can directly interacts with your UI by calling frontend functions\n3. **Seamless Integration**: Tools defined in the frontend and automatically discovered and made available to the agent\n\n## How to Interact\n\nTry asking your Copilot to:\n- \"Can you change the background color to something more vibrant?\"\n- \"Make the background a blue to purple gradient\"\n- \"Set the background to a sunset-themed gradient\"\n- \"Change it back to a simple light color\"\n\nYou can also chat about other topics - the agent will respond conversationally while having the ability to use your UI tools when appropriate.\n\n## ✨ Frontend Tool Integration in Action\n\n**What's happening technically:**\n- The React component defines a frontend function using `useCopilotAction`\n- CopilotKit automatically exposes this function to the agent\n- When you make a request, the agent determines whether to use the tool\n- The agent calls the function with the appropriate parameters\n- The UI immediately updates in response\n\n**What you'll see in this demo:**\n- The Copilot understands requests to change the background\n- It generates CSS values for colors and gradients\n- When it calls the tool, the background changes instantly\n- The agent provides a conversational response about the changes it made\n\nThis technique of exposing frontend functions to your Copilot can be extended to any UI manipulation you want to enable, from theme changes to data filtering, navigation, or complex UI state management! ",
"path": "langgraph_no_chat/README.mdx",
"language": "markdown",
"type": "file"
}
]
},
"standard_agentic_chat": {
"files": [
{
"name": "page.tsx",
"content": "\"use client\";\nimport React, { useState } from \"react\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport { CopilotKit, useCopilotAction, useCopilotChat } from \"@copilotkit/react-core\";\nimport { CopilotChat, useCopilotChatSuggestions } from \"@copilotkit/react-ui\";\nimport { chatSuggestions, initialPrompt } from \"@/lib/prompts\";\nconst AgenticChat: React.FC = () => {\n return (\n <CopilotKit\n runtimeUrl=\"/api/copilotkit?standard=true\"\n showDevConsole={false}\n // agent=\"agentic_chat\"\n >\n <Chat />\n </CopilotKit>\n );\n};\n\nconst Chat = () => {\n const [background, setBackground] = useState<string>(\"--copilot-kit-background-color\");\n\n useCopilotAction({\n name: \"change_background\",\n description:\n \"Change the background color of the chat. Can be anything that the CSS background attribute accepts. Regular colors, linear of radial gradients etc.\",\n parameters: [\n {\n name: \"background\",\n type: \"string\",\n description: \"The background. Prefer gradients.\",\n },\n ],\n handler: ({ background }) => {\n console.log(\"background\", background);\n setBackground(background);\n },\n });\n\n useCopilotChatSuggestions({\n instructions: chatSuggestions.agenticChat,\n // className : \"bg-gray-100\"\n })\n\n\n return (\n <div\n className=\"flex justify-center items-center h-full w-full\"\n style={{ background }}\n >\n <div className=\"w-8/10 h-8/10 rounded-lg \">\n <CopilotChat\n className=\"h-full w-full rounded-2xl py-6\"\n labels={{ initial: initialPrompt.agenticChat }}\n />\n </div>\n </div>\n );\n};\n\nexport default AgenticChat;\n",
"path": "standard_agentic_chat/page.tsx",
"language": "typescript",
"type": "file"
},
{
"name": "style.css",
"content": ".copilotKitInput {\n border-bottom-left-radius: 0.75rem;\n border-bottom-right-radius: 0.75rem;\n border-top-left-radius: 0.75rem;\n border-top-right-radius: 0.75rem;\n border: 1px solid var(--copilot-kit-separator-color) !important;\n}\n \n.copilotKitChat {\n background-color: #fff !important;\n}\n \n",
"path": "standard_agentic_chat/style.css",
"language": "css",
"type": "file"
},
{
"name": "README.mdx",
"content": "# 🤖 Agentic Chat with Frontend Tools\n\n## What This Demo Shows\n\nThis demo showcases CopilotKit's **agentic chat** capabilities with **frontend tool integration**:\n1. **Natural Conversation**: Chat with your Copilot in a familiar chat interface\n2. **Frontend Tool Execution**: The Copilot can directly interacts with your UI by calling frontend functions\n3. **Seamless Integration**: Tools defined in the frontend and automatically discovered and made available to the agent\n\n## How to Interact\n\nTry asking your Copilot to:\n- \"Can you change the background color to something more vibrant?\"\n- \"Make the background a blue to purple gradient\"\n- \"Set the background to a sunset-themed gradient\"\n- \"Change it back to a simple light color\"\n\nYou can also chat about other topics - the agent will respond conversationally while having the ability to use your UI tools when appropriate.\n\n## ✨ Frontend Tool Integration in Action\n\n**What's happening technically:**\n- The React component defines a frontend function using `useCopilotAction`\n- CopilotKit automatically exposes this function to the agent\n- When you make a request, the agent determines whether to use the tool\n- The agent calls the function with the appropriate parameters\n- The UI immediately updates in response\n\n**What you'll see in this demo:**\n- The Copilot understands requests to change the background\n- It generates CSS values for colors and gradients\n- When it calls the tool, the background changes instantly\n- The agent provides a conversational response about the changes it made\n\nThis technique of exposing frontend functions to your Copilot can be extended to any UI manipulation you want to enable, from theme changes to data filtering, navigation, or complex UI state management! ",
"path": "standard_agentic_chat/README.mdx",
"language": "markdown",
"type": "file"
}
]
},
"standard_human_in_the_loop": {
"files": [
{
"name": "page.tsx",
"content": "\"use client\";\nimport React, { useState, useEffect } from \"react\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport { CopilotKit, useCopilotAction, useLangGraphInterrupt } from \"@copilotkit/react-core\";\nimport { CopilotChat, useCopilotChatSuggestions } from \"@copilotkit/react-ui\";\nimport { initialPrompt, chatSuggestions, instructions } from \"@/lib/prompts\";\nconst HumanInTheLoop: React.FC = () => {\n return (\n <CopilotKit\n runtimeUrl=\"/api/copilotkit?standard=true\"\n showDevConsole={false}\n >\n <Chat />\n </CopilotKit>\n );\n};\n\nconst Chat = () => {\n\n useCopilotAction({\n name: \"generate_task_steps\",\n description: \"Make up 10 steps (only a couple of words per step) that are required for a task. The step should be in imperative form (i.e. Dig hole, Open door, ...). When the user responds with selected steps, you must generate the summary with the selected steps.\",\n parameters: [\n {\n type: \"object[]\",\n name: \"items\",\n description: \"An array of 10 step objects, each containing text and status\",\n attributes: [\n {\n type: \"string\",\n name: \"description\",\n description: \"The text of the step in imperative form\"\n },\n {\n type: \"string\",\n name: \"status\",\n description: \"The status of the step, always 'enabled'\"\n }\n ]\n }\n ],\n renderAndWaitForResponse: ({ args, respond, status }) => {\n const [newStep, setNewStep] = useState(\"\");\n\n const handleAddStep = () => {\n const trimmed = newStep.trim();\n if (trimmed.length === 0) return;\n setLocalSteps((prevSteps) => [\n ...prevSteps,\n { description: trimmed, status: \"enabled\" },\n ]);\n setNewStep(\"\");\n };\n\n const handleInputKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {\n if (e.key === \"Enter\") {\n handleAddStep();\n }\n };\n\n useEffect(() => {\n console.log(args, \"args.steps\")\n setLocalSteps(args?.items || [])\n }, [args])\n const [localSteps, setLocalSteps] = useState<\n {\n description: string;\n status: string;\n }[]\n >(args?.items || [])\n const handleCheckboxChange = (index: number) => {\n debugger\n const newSteps = [...localSteps];\n newSteps[index].status = newSteps[index].status === \"enabled\" ? \"disabled\" : \"enabled\";\n setLocalSteps(newSteps);\n };\n return (localSteps) ? <div className=\"flex flex-col gap-4 w-[500px] bg-gray-100 rounded-lg p-8 mb-4\">\n <div className=\"text-black space-y-2\">\n <h2 className=\"text-lg font-bold mb-4\">Select Steps</h2>\n {localSteps.map((step: any, index: number) => (\n <div key={index} className=\"text-sm flex items-center\">\n <label className=\"flex items-center cursor-pointer\">\n <input\n type=\"checkbox\"\n checked={step.status == \"enabled\"}\n onChange={() => {\n if (respond) {\n handleCheckboxChange(index)\n }\n }}\n className=\"mr-2\"\n />\n <span\n className={\n step.status !== \"enabled\" ? \"line-through\" : \"\"\n }\n >\n {step.description}\n </span>\n </label>\n </div>\n ))}\n <div className=\"flex items-center gap-2 mb-4\">\n <input\n type=\"text\"\n className=\"flex-1 rounded py-2 focus:outline-none\"\n placeholder=\"Add a new step...\"\n value={newStep}\n onChange={(e) => setNewStep(e.target.value)}\n onKeyDown={handleInputKeyDown}\n hidden={status != \"executing\"}\n />\n </div>\n <button\n className=\"mt-4 bg-gradient-to-r from-purple-400 to-purple-600 text-white py-2 px-4 rounded cursor-pointer w-48 font-bold\"\n hidden={!respond}\n onClick={() => {\n if (respond) {\n console.log(`The user has selected the following steps: ${localSteps.filter((step) => step.status === \"enabled\").map((step) => step.description).join(\", \")}`)\n respond(`The user has selected the following steps: ${localSteps.filter((step) => step.status === \"enabled\").map((step) => step.description).join(\", \")}`)\n }\n }}\n >\n ✨ Perform Steps\n </button>\n </div>\n </div> : <></>\n }\n })\n\n\n useCopilotChatSuggestions({\n instructions: chatSuggestions.humanInTheLoop,\n })\n return (\n <div className=\"flex justify-center items-center h-screen w-screen\">\n <div className=\"w-8/10 h-8/10\">\n <CopilotChat\n className=\"h-full rounded-lg\"\n labels={{ initial: initialPrompt.humanInTheLoop }}\n instructions={instructions.humanInTheLoop}\n />\n </div>\n </div>\n );\n};\n\nexport default HumanInTheLoop;\n",
"path": "standard_human_in_the_loop/page.tsx",
"language": "typescript",
"type": "file"
},
{
"name": "style.css",
"content": ".copilotKitInput {\n border-bottom-left-radius: 0.75rem;\n border-bottom-right-radius: 0.75rem;\n border-top-left-radius: 0.75rem;\n border-top-right-radius: 0.75rem;\n border: 1px solid var(--copilot-kit-separator-color) !important;\n background-color: #fff;\n}\n\n.copilotKitChat {\n background-color: #fff !important;\n}\n",
"path": "standard_human_in_the_loop/style.css",
"language": "css",
"type": "file"
},
{
"name": "README.mdx",
"content": "# 🤝 Human-in-the-Loop Task Planner\n\n## What This Demo Shows\n\nThis demo showcases CopilotKit's **human-in-the-loop** capabilities:\n1. **Collaborative Planning**: The Copilot generates task steps and lets you decide which ones to perform\n2. **Interactive Decision Making**: Select or deselect steps to customize the execution plan\n3. **Adaptive Responses**: The Copilot adapts its execution based on your choices, even handling missing steps\n\n## How to Interact\n\nTry these steps to experience the demo:\n1. Ask your Copilot to help with a task, such as:\n - \"Make me a sandwich\"\n - \"Plan a weekend trip\"\n - \"Organize a birthday party\"\n - \"Start a garden\"\n\n2. Review the suggested steps provided by your Copilot\n\n3. Select or deselect steps using the checkboxes to customize the plan\n - Try removing essential steps to see how the Copilot adapts!\n\n4. Click \"Execute Plan\" to see the outcome based on your selections\n\n## ✨ Human-in-the-Loop Magic in Action\n\n**What's happening technically:**\n- The agent analyzes your request and breaks it down into logical steps\n- These steps are presented to you through a dynamic UI component\n- Your selections are captured as user input\n- The agent considers your choices when executing the plan\n- The agent adapts to missing steps with creative problem-solving\n\n**What you'll see in this demo:**\n- The Copilot provides a detailed, step-by-step plan for your task\n- You have complete control over which steps to include\n- If you remove essential steps, the Copilot provides entertaining and creative workarounds\n- The final execution reflects your choices, showing how human input shapes the outcome\n- Each response is tailored to your specific selections\n\nThis human-in-the-loop pattern creates a powerful collaborative experience where both human judgment and AI capabilities work together to achieve better results than either could alone! ",
"path": "standard_human_in_the_loop/README.mdx",
"language": "markdown",
"type": "file"
}
]
},
"standard_agentic_generative_ui": {
"files": [
{
"name": "page.tsx",
"content": "\"use client\";\nimport React from \"react\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport { CopilotKit, useCopilotAction } from \"@copilotkit/react-core\";\nimport { CopilotChat, useCopilotChatSuggestions } from \"@copilotkit/react-ui\";\nimport { initialPrompt, chatSuggestions, instructions } from \"@/lib/prompts\";\nimport { Steps } from \"./Steps\";\nconst AgenticGenerativeUI: React.FC = () => {\n return (\n <CopilotKit\n runtimeUrl=\"/api/copilotkit?standard=true\"\n showDevConsole\n >\n <Chat />\n </CopilotKit>\n );\n};\n\nconst Chat = () => {\n\n useCopilotAction({\n name: \"show_steps\",\n description: \"Show the steps to the user which was requested by the user\",\n parameters: [\n {\n name: \"steps\",\n type: \"object[]\",\n attributes: [\n { name: \"description\", type: \"string\" },\n { name: \"status\", type: \"string\", enum: [\"pending\", \"completed\"] }\n ]\n }\n ],\n followUp: false,\n renderAndWaitForResponse: ({ status, args, respond }) => {\n debugger\n console.log(status, \"stauts\", args)\n if (status === \"executing\" || status === \"complete\") {\n return (<Steps status={status} args={args} respond={respond} />)\n }\n else {\n return <></>\n }\n },\n // handler: async ({ steps }) => {\n // for (let i = 0; i < steps.length; i++) {\n // await delay(1000);\n // steps[i].status = \"completed\";\n // Optionally update agent state here for UI\n // }\n // return { steps };\n // }\n })\n\n useCopilotChatSuggestions({\n instructions: chatSuggestions.agenticGenerativeUI,\n })\n return (\n <div className=\"flex justify-center items-center h-screen w-screen\">\n <div className=\"w-8/10 h-8/10\">\n <CopilotChat\n instructions={instructions.agenticGenerativeUI}\n className=\"h-full rounded-lg\"\n labels={{ initial: initialPrompt.agenticGenerativeUI }}\n />\n </div>\n </div>\n );\n};\n\nexport function Spinner() {\n return (\n <svg\n className=\"mr-2 size-3 animate-spin text-slate-500\"\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n >\n <circle\n className=\"opacity-25\"\n cx=\"12\"\n cy=\"12\"\n r=\"10\"\n stroke=\"currentColor\"\n strokeWidth=\"4\"\n ></circle>\n <path\n className=\"opacity-75\"\n fill=\"currentColor\"\n d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z\"\n ></path>\n </svg>\n );\n}\n\nexport default AgenticGenerativeUI;\n",
"path": "standard_agentic_generative_ui/page.tsx",
"language": "typescript",
"type": "file"
},
{
"name": "style.css",
"content": ".copilotKitInput {\n border-bottom-left-radius: 0.75rem;\n border-bottom-right-radius: 0.75rem;\n border-top-left-radius: 0.75rem;\n border-top-right-radius: 0.75rem;\n border: 1px solid var(--copilot-kit-separator-color) !important;\n background-color: #fff;\n}\n\n.copilotKitChat {\n background-color: #fff !important;\n}\n",
"path": "standard_agentic_generative_ui/style.css",
"language": "css",
"type": "file"
},
{
"name": "README.mdx",
"content": "# 🚀 Agentic Generative UI Task Executor\n\n## What This Demo Shows\n\nThis demo showcases CopilotKit's **agentic generative UI** capabilities:\n1. **Real-time Status Updates**: The Copilot provides live feedback as it works through complex tasks\n2. **Long-running Task Execution**: See how agents can handle extended processes with continuous feedback\n3. **Dynamic UI Generation**: The interface updates in real-time to reflect the agent's progress\n\n## How to Interact\n\nSimply ask your Copilot to perform any moderately complex task:\n- \"Make me a sandwich\"\n- \"Plan a vacation to Japan\"\n- \"Create a weekly workout routine\"\n\nThe Copilot will break down the task into steps and begin \"executing\" them, providing real-time status updates as it progresses.\n\n## ✨ Agentic Generative UI in Action\n\n**What's happening technically:**\n- The agent analyzes your request and creates a detailed execution plan\n- Each step is processed sequentially with realistic timing\n- Status updates are streamed to the frontend using CopilotKit's streaming capabilities\n- The UI dynamically renders these updates without page refreshes\n- The entire flow is managed by the agent, requiring no manual intervention\n\n**What you'll see in this demo:**\n- The Copilot breaks your task into logical steps\n- A status indicator shows the current progress\n- Each step is highlighted as it's being executed\n- Detailed status messages explain what's happening at each moment\n- Upon completion, you receive a summary of the task execution\n\nThis pattern of providing real-time progress for long-running tasks is perfect for scenarios where users benefit from transparency into complex processes - from data analysis to content creation, system configurations, or multi-stage workflows! ",
"path": "standard_agentic_generative_ui/README.mdx",
"language": "markdown",
"type": "file"
}
]
},
"standard_tool_based_generative_ui": {
"files": [
{
"name": "page.tsx",
"content": "\"use client\";\nimport { CopilotKit, useCopilotAction } from \"@copilotkit/react-core\";\nimport { CopilotKitCSSProperties, CopilotSidebar, useCopilotChatSuggestions } from \"@copilotkit/react-ui\";\nimport { useState, useEffect } from \"react\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport { chatSuggestions, initialPrompt, instructions } from \"@/lib/prompts\";\nimport HaikuCard from \"./HaikuCard\";\n// List of known valid image filenames (should match agent.py)\nconst VALID_IMAGE_NAMES = [\n \"Osaka_Castle_Turret_Stone_Wall_Pine_Trees_Daytime.jpg\",\n \"Tokyo_Skyline_Night_Tokyo_Tower_Mount_Fuji_View.jpg\",\n \"Itsukushima_Shrine_Miyajima_Floating_Torii_Gate_Sunset_Long_Exposure.jpg\",\n \"Takachiho_Gorge_Waterfall_River_Lush_Greenery_Japan.jpg\",\n \"Bonsai_Tree_Potted_Japanese_Art_Green_Foliage.jpeg\",\n \"Shirakawa-go_Gassho-zukuri_Thatched_Roof_Village_Aerial_View.jpg\",\n \"Ginkaku-ji_Silver_Pavilion_Kyoto_Japanese_Garden_Pond_Reflection.jpg\",\n \"Senso-ji_Temple_Asakusa_Cherry_Blossoms_Kimono_Umbrella.jpg\",\n \"Cherry_Blossoms_Sakura_Night_View_City_Lights_Japan.jpg\",\n \"Mount_Fuji_Lake_Reflection_Cherry_Blossoms_Sakura_Spring.jpg\"\n];\n\nexport default function AgenticChat() {\n return (\n <CopilotKit\n runtimeUrl=\"/api/copilotkit?standard=true\"\n showDevConsole={false}\n // agent=\"tool_based_generative_ui\"\n >\n <div\n className=\"min-h-screen w-full flex items-center justify-center page-background\"\n style={\n {\n \"--copilot-kit-primary-color\": \"#222\",\n \"--copilot-kit-separator-color\": \"#CCC\",\n } as CopilotKitCSSProperties\n }\n >\n <Haiku />\n <CopilotSidebar\n defaultOpen={true}\n instructions={instructions.toolCallingGenerativeUI.replace(\"{IMAGE_NAMES}\", VALID_IMAGE_NAMES.join(\", \"))}\n labels={{\n title: \"Haiku Generator\",\n initial: initialPrompt.toolCallingGenerativeUI,\n }}\n clickOutsideToClose={false}\n />\n </div>\n </CopilotKit>\n );\n}\n\ninterface Haiku { \n japanese: string[];\n english: string[];\n image_names: string[];\n selectedImage: string | null;\n}\n\n\nfunction Haiku() {\n const [haikus, setHaikus] = useState<Haiku[]>([{\n japanese: [\"仮の句よ\", \"まっさらながら\", \"花を呼ぶ\"],\n english: [\n \"A placeholder verse—\",\n \"even in a blank canvas,\",\n \"it beckons flowers.\",\n ],\n image_names: [],\n selectedImage: null,\n }])\n const [activeIndex, setActiveIndex] = useState(0);\n const [isJustApplied, setIsJustApplied] = useState(false);\n\n useEffect(() => {\n console.log(instructions.toolCallingGenerativeUI.replace(\"{IMAGE_NAMES}\", VALID_IMAGE_NAMES.join(\", \")), \"INSTTTT\");\n }, []);\n const validateAndCorrectImageNames = (rawNames: string[] | undefined): string[] | null => {\n if (!rawNames || rawNames.length !== 3) {\n return null;\n }\n\n const correctedNames: string[] = [];\n const usedValidNames = new Set<string>();\n\n for (const name of rawNames) {\n if (VALID_IMAGE_NAMES.includes(name) && !usedValidNames.has(name)) {\n correctedNames.push(name);\n usedValidNames.add(name);\n if (correctedNames.length === 3) break;\n }\n }\n\n if (correctedNames.length < 3) {\n const availableFallbacks = VALID_IMAGE_NAMES.filter(name => !usedValidNames.has(name));\n for (let i = availableFallbacks.length - 1; i > 0; i--) {\n const j = Math.floor(Math.random() * (i + 1));\n [availableFallbacks[i], availableFallbacks[j]] = [availableFallbacks[j], availableFallbacks[i]];\n }\n\n while (correctedNames.length < 3 && availableFallbacks.length > 0) {\n const fallbackName = availableFallbacks.pop();\n if (fallbackName) {\n correctedNames.push(fallbackName);\n }\n }\n }\n\n while (correctedNames.length < 3 && VALID_IMAGE_NAMES.length > 0) {\n const fallbackName = VALID_IMAGE_NAMES[Math.floor(Math.random() * VALID_IMAGE_NAMES.length)];\n correctedNames.push(fallbackName);\n }\n\n return correctedNames.slice(0, 3);\n };\n\n useCopilotAction({\n name: \"generate_haiku\",\n parameters: [\n {\n name: \"japanese\",\n type: \"string[]\",\n },\n {\n name: \"english\",\n type: \"string[]\",\n },\n {\n name: \"image_names\",\n type: \"string[]\",\n description: \"Names of 3 relevant images\",\n },\n ],\n followUp: false,\n handler: async ({ japanese, english, image_names }) => {\n debugger\n const finalCorrectedImages = validateAndCorrectImageNames(image_names);\n const newHaiku = {\n japanese: japanese || [],\n english: english || [],\n image_names: finalCorrectedImages || [],\n selectedImage: finalCorrectedImages?.[0] || null,\n };\n console.log(finalCorrectedImages, \"finalCorrectedImages\");\n setHaikus(prev => [...prev, newHaiku]);\n setActiveIndex(haikus.length - 1);\n setIsJustApplied(true);\n setTimeout(() => setIsJustApplied(false), 600);\n return \"Haiku generated.\";\n },\n render: ({ args: generatedHaiku }) => {\n return (\n <HaikuCard generatedHaiku={generatedHaiku} setHaikus={setHaikus} haikus={haikus} />\n );\n },\n }, [haikus]);\n\n useCopilotChatSuggestions({\n instructions: chatSuggestions.toolCallingGenerativeUI,\n });\n return (\n <div className=\"flex h-screen w-full\">\n \n {/* Thumbnail List */}\n <div className=\"w-40 p-4 border-r border-gray-200 overflow-y-auto overflow-x-hidden\">\n {haikus.filter((haiku) => haiku.english[0] !== \"A placeholder verse—\").map((haiku, index) => (\n <div\n key={index}\n className={`haiku-card animated-fade-in mb-4 cursor-pointer ${index === activeIndex ? 'active' : ''}`}\n style={{\n width: '80px',\n transform: 'scale(0.2)',\n transformOrigin: 'top left',\n marginBottom: '-340px',\n opacity: index === activeIndex ? 1 : 0.5,\n transition: 'opacity 0.2s',\n }}\n onClick={() => setActiveIndex(index)}\n >\n {haiku.japanese.map((line, lineIndex) => (\n <div\n className=\"flex items-start gap-2 mb-2 haiku-line\"\n key={lineIndex}\n >\n <p className=\"text-2xl font-bold text-gray-600 w-auto\">{line}</p>\n <p className=\"text-xs font-light text-gray-500 w-auto\">{haiku.english?.[lineIndex]}</p>\n </div>\n ))}\n {haiku.image_names && haiku.image_names.length === 3 && (\n <div className=\"mt-2 flex gap-2 justify-center\">\n {haiku.image_names.map((imageName, imgIndex) => (\n <img\n style={{\n width: '110px',\n height: '110px',\n objectFit: 'cover',\n }}\n key={imageName}\n src={`/images/${imageName}`}\n alt={imageName || \"\"}\n className=\"haiku-card-image w-12 h-12 object-cover\"\n />\n ))}\n </div>\n )}\n </div>\n ))}\n </div>\n\n {/* Main Display */}\n {/* Add a margin to the left of margin-left: -48px; */}\n <div className=\"flex-1 p-8 flex items-center justify-center \" style={{ marginLeft: '-48px' }}>\n <div className=\"haiku-stack\">\n {haikus.filter((_haiku: Haiku, index: number) => {\n if (haikus.length == 1) return true;\n else return index == activeIndex + 1;\n }).map((haiku, index) => (\n <div\n key={index}\n className={`haiku-card animated-fade-in ${isJustApplied && index === activeIndex ? 'applied-flash' : ''} ${index === activeIndex ? 'active' : ''}`}\n style={{\n zIndex: index === activeIndex ? haikus.length : index,\n transform: `translateY(${index === activeIndex ? '0' : `${(index - activeIndex) * 20}px`}) scale(${index === activeIndex ? '1' : '0.95'})`,\n }}\n // onClick={() => setActiveIndex(index)}\n >\n {haiku.japanese.map((line, lineIndex) => (\n <div\n className=\"flex items-start gap-4 mb-4 haiku-line\"\n key={lineIndex}\n style={{ animationDelay: `${lineIndex * 0.1}s` }}\n >\n <p className=\"text-4xl font-bold text-gray-600 w-auto\">{line}</p>\n <p className=\"text-base font-light text-gray-500 w-auto\">{haiku.english?.[lineIndex]}</p>\n </div>\n ))}\n {haiku.image_names && haiku.image_names.length === 3 && (\n <div className=\"mt-6 flex gap-4 justify-center\">\n {haiku.image_names.map((imageName, imgIndex) => (\n <img\n key={imageName}\n src={`/images/${imageName}`}\n alt={imageName || \"\"}\n style={{\n width: '130px',\n height: '130px',\n objectFit: 'cover',\n }}\n className={(haiku.selectedImage === imageName) ? `suggestion-card-image-focus` : `haiku-card-image`}\n />\n ))}\n </div>\n )}\n </div>\n ))}\n </div>\n </div>\n </div>\n );\n}",
"path": "standard_tool_based_generative_ui/page.tsx",
"language": "typescript",
"type": "file"
},
{
"name": "style.css",
"content": ".copilotKitWindow {\n box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);\n}\n\n.copilotKitHeader {\n border-top-left-radius: 5px !important;\n}\n\n.page-background {\n /* Darker gradient background */\n background: linear-gradient(170deg, #e9ecef 0%, #ced4da 100%); \n}\n\n@keyframes fade-scale-in {\n from {\n opacity: 0;\n transform: translateY(10px) scale(0.98);\n }\n to {\n opacity: 1;\n transform: translateY(0) scale(1);\n }\n}\n\n/* Updated card entry animation */\n@keyframes pop-in {\n 0% {\n opacity: 0;\n transform: translateY(15px) scale(0.95);\n }\n 70% {\n opacity: 1;\n transform: translateY(-2px) scale(1.02);\n }\n 100% {\n opacity: 1;\n transform: translateY(0) scale(1);\n }\n}\n\n/* Animation for subtle background gradient movement */\n@keyframes animated-gradient {\n 0% {\n background-position: 0% 50%;\n }\n 50% {\n background-position: 100% 50%;\n }\n 100% {\n background-position: 0% 50%;\n }\n}\n\n/* Animation for flash effect on apply */\n@keyframes flash-border-glow {\n 0% {\n /* Start slightly intensified */\n border-top-color: #ff5b4a !important;\n box-shadow: 0 10px 30px rgba(0, 0, 0, 0.07), \n inset 0 1px 2px rgba(0, 0, 0, 0.01), \n 0 0 25px rgba(255, 91, 74, 0.5);\n }\n 50% {\n /* Peak intensity */\n border-top-color: #ff4733 !important;\n box-shadow: 0 10px 30px rgba(0, 0, 0, 0.08), \n inset 0 1px 2px rgba(0, 0, 0, 0.01), \n 0 0 35px rgba(255, 71, 51, 0.7);\n }\n 100% {\n /* Return to default state appearance */\n border-top-color: #ff6f61 !important;\n box-shadow: 0 10px 30px rgba(0, 0, 0, 0.07), \n inset 0 1px 2px rgba(0, 0, 0, 0.01), \n 0 0 10px rgba(255, 111, 97, 0.15); \n }\n}\n\n/* Existing animation for haiku lines */\n@keyframes fade-slide-in {\n from {\n opacity: 0;\n transform: translateX(-15px);\n }\n to {\n opacity: 1;\n transform: translateX(0);\n }\n}\n\n.animated-fade-in { \n /* Use the new pop-in animation */\n animation: pop-in 0.6s ease-out forwards;\n}\n\n.haiku-card {\n /* Subtle animated gradient background */\n background: linear-gradient(120deg, #ffffff 0%, #fdfdfd 50%, #ffffff 100%);\n background-size: 200% 200%; \n animation: animated-gradient 10s ease infinite; \n\n /* === Explicit Border Override Attempt === */\n /* 1. Set the default grey border for all sides */\n border: 1px solid #dee2e6; \n \n /* 2. Explicitly override the top border immediately after */\n border-top: 10px solid #ff6f61 !important; /* Orange top - Added !important */\n /* === End Explicit Border Override Attempt === */\n \n padding: 2.5rem 3rem; \n border-radius: 20px; \n \n /* Default glow intensity */\n box-shadow: 0 10px 30px rgba(0, 0, 0, 0.07), \n inset 0 1px 2px rgba(0, 0, 0, 0.01), \n 0 0 15px rgba(255, 111, 97, 0.25); \n text-align: left;\n max-width: 745px; \n margin: 3rem auto;\n min-width: 600px;\n \n /* Transition */\n transition: transform 0.35s ease, box-shadow 0.35s ease, border-top-width 0.35s ease, border-top-color 0.35s ease;\n}\n\n.haiku-card:hover {\n transform: translateY(-8px) scale(1.03); \n /* Enhanced shadow + Glow */\n box-shadow: 0 15px 35px rgba(0, 0, 0, 0.1), \n inset 0 1px 2px rgba(0, 0, 0, 0.01), \n 0 0 25px rgba(255, 91, 74, 0.5); \n /* Modify only top border properties */\n border-top-width: 14px !important; /* Added !important */\n border-top-color: #ff5b4a !important; /* Added !important */\n}\n\n.haiku-card .flex {\n margin-bottom: 1.5rem;\n}\n\n.haiku-card .flex.haiku-line { /* Target the lines specifically */\n margin-bottom: 1.5rem;\n opacity: 0; /* Start hidden for animation */\n animation: fade-slide-in 0.5s ease-out forwards;\n /* animation-delay is set inline in page.tsx */\n}\n\n/* Remove previous explicit color overrides - rely on Tailwind */\n/* .haiku-card p.text-4xl {\n color: #212529; \n}\n\n.haiku-card p.text-base {\n color: #495057; \n} */\n\n.haiku-card.applied-flash {\n /* Apply the flash animation once */\n /* Note: animation itself has !important on border-top-color */\n animation: flash-border-glow 0.6s ease-out forwards; \n}\n\n/* Styling for images within the main haiku card */\n.haiku-card-image {\n width: 9.5rem; /* Increased size (approx w-48) */\n height: 9.5rem; /* Increased size (approx h-48) */\n object-fit: cover;\n border-radius: 1.5rem; /* rounded-xl */\n border: 1px solid #e5e7eb;\n /* Enhanced shadow with subtle orange hint */\n box-shadow: 0 8px 15px rgba(0, 0, 0, 0.1),\n 0 3px 6px rgba(0, 0, 0, 0.08),\n 0 0 10px rgba(255, 111, 97, 0.2);\n /* Inherit animation delay from inline style */\n animation-name: fadeIn;\n animation-duration: 0.5s;\n animation-fill-mode: both;\n}\n\n/* Styling for images within the suggestion card */\n.suggestion-card-image {\n width: 6.5rem; /* Increased slightly (w-20) */\n height: 6.5rem; /* Increased slightly (h-20) */\n object-fit: cover;\n border-radius: 1rem; /* Equivalent to rounded-md */\n border: 1px solid #d1d5db; /* Equivalent to border (using Tailwind gray-300) */\n margin-top: 0.5rem;\n /* Added shadow for suggestion images */\n box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1),\n 0 2px 4px rgba(0, 0, 0, 0.06);\n transition: all 0.2s ease-in-out; /* Added for smooth deselection */\n}\n\n/* Styling for the focused suggestion card image */\n.suggestion-card-image-focus {\n width: 6.5rem; \n height: 6.5rem; \n object-fit: cover;\n border-radius: 1rem; \n margin-top: 0.5rem;\n /* Highlight styles */\n border: 2px solid #ff6f61; /* Thicker, themed border */\n box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1), /* Base shadow for depth */\n 0 0 12px rgba(255, 111, 97, 0.6); /* Orange glow */\n transform: scale(1.05); /* Slightly scale up */\n transition: all 0.2s ease-in-out; /* Smooth transition for focus */\n}\n\n/* Styling for the suggestion card container in the sidebar */\n.suggestion-card {\n border: 1px solid #dee2e6; /* Same default border as haiku-card */\n border-top: 10px solid #ff6f61; /* Same orange top border */\n border-radius: 0.375rem; /* Default rounded-md */\n /* Note: background-color is set by Tailwind bg-gray-100 */\n /* Other styles like padding, margin, flex are handled by Tailwind */\n}\n\n.suggestion-image-container {\n display: flex;\n gap: 1rem;\n justify-content: space-between;\n width: 100%;\n height: 6.5rem;\n}\n\n",
"path": "standard_tool_based_generative_ui/style.css",
"language": "css",
"type": "file"
},
{
"name": "README.mdx",
"content": "# 🪶 Tool-Based Generative UI Haiku Creator\n\n## What This Demo Shows\n\nThis demo showcases CopilotKit's **tool-based generative UI** capabilities:\n1. **Frontend Rendering of Tool Calls**: Backend tool calls are automatically rendered in the UI\n2. **Dynamic UI Generation**: The UI updates in real-time as the agent generates content\n3. **Elegant Content Presentation**: Complex structured data (haikus) are beautifully displayed\n\n## How to Interact\n\nChat with your Copilot and ask for haikus about different topics:\n- \"Create a haiku about nature\"\n- \"Write a haiku about technology\"\n- \"Generate a haiku about the changing seasons\"\n- \"Make a humorous haiku about programming\"\n\nEach request will trigger the agent to generate a haiku and display it in a visually appealing card format in the UI.\n\n## ✨ Tool-Based Generative UI in Action\n\n**What's happening technically:**\n- The agent processes your request and determines it should create a haiku\n- It calls a backend tool that returns structured haiku data\n- CopilotKit automatically renders this tool call in the frontend\n- The rendering is handled by the registered tool component in your React app\n- No manual state management is required to display the results\n\n**What you'll see in this demo:**\n- As you request a haiku, a beautifully formatted card appears in the UI\n- The haiku follows the traditional 5-7-5 syllable structure\n- Each haiku is presented with consistent styling\n- Multiple haikus can be generated in sequence\n- The UI adapts to display each new piece of content\n\nThis pattern of tool-based generative UI can be extended to create any kind of dynamic content - from data visualizations to interactive components, all driven by your Copilot's tool calls! ",
"path": "standard_tool_based_generative_ui/README.mdx",
"language": "markdown",
"type": "file"
}
]
},
"standard_shared_state": {
"files": [
{
"name": "page.tsx",
"content": "\"use client\";\nimport { CopilotKit, useCoAgent, useCopilotAction, useCopilotChat } from \"@copilotkit/react-core\";\nimport { CopilotKitCSSProperties, CopilotSidebar, useCopilotChatSuggestions } from \"@copilotkit/react-ui\";\nimport { useState, useEffect, useRef } from \"react\";\nimport { Role, TextMessage } from \"@copilotkit/runtime-client-gql\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport { initialPrompt, chatSuggestions } from \"@/lib/prompts\";\nenum SkillLevel {\n BEGINNER = \"Beginner\",\n INTERMEDIATE = \"Intermediate\",\n ADVANCED = \"Advanced\",\n}\n\nenum CookingTime {\n FiveMin = \"5 min\",\n FifteenMin = \"15 min\",\n ThirtyMin = \"30 min\",\n FortyFiveMin = \"45 min\",\n SixtyPlusMin = \"60+ min\",\n}\n\nconst cookingTimeValues = [\n { label: CookingTime.FiveMin, value: 0 },\n { label: CookingTime.FifteenMin, value: 1 },\n { label: CookingTime.ThirtyMin, value: 2 },\n { label: CookingTime.FortyFiveMin, value: 3 },\n { label: CookingTime.SixtyPlusMin, value: 4 },\n];\n\nconst dietaryOptions = [\n \"Vegetarian\",\n \"Nut-free\",\n \"Dairy-free\",\n \"Gluten-free\",\n \"Vegan\",\n \"Low-carb\"\n];\n\nexport default function SharedState() {\n return (\n\n <CopilotKit\n runtimeUrl=\"/api/copilotkit?standard=true\"\n showDevConsole={false}\n >\n <div\n className=\"app-container\"\n style={{\n backgroundImage: \"url('./shared_state_background.png')\",\n backgroundAttachment: \"fixed\",\n backgroundSize: \"cover\",\n position: \"relative\",\n }}\n >\n <div\n style={{\n position: \"absolute\",\n top: 0,\n left: 0,\n right: 0,\n bottom: 0,\n backgroundColor: \"rgba(0, 0, 0, 0.4)\",\n backdropFilter: \"blur(5px)\",\n zIndex: 0,\n }}\n />\n <Recipe />\n {/* </div> */}\n <CopilotSidebar\n defaultOpen={true}\n labels={{\n title: \"AI Recipe Assistant\",\n initial: initialPrompt.sharedState,\n }}\n clickOutsideToClose={false}\n />\n </div>\n </CopilotKit>\n );\n}\n\ninterface Ingredient {\n icon: string;\n name: string;\n amount: string;\n}\n\ninterface Recipe {\n title: string;\n skill_level: SkillLevel;\n cooking_time: CookingTime;\n dietary_preferences: string[];\n ingredients: Ingredient[];\n instructions: string[];\n}\n\ninterface RecipeAgentState {\n recipe: Recipe;\n}\n\nconst INITIAL_STATE: RecipeAgentState = {\n recipe: {\n title: \"Make Your Recipe\",\n skill_level: SkillLevel.INTERMEDIATE,\n cooking_time: CookingTime.FortyFiveMin,\n dietary_preferences: [],\n ingredients: [\n { icon: \"🥕\", name: \"Carrots\", amount: \"3 large, grated\" },\n { icon: \"🌾\", name: \"All-Purpose Flour\", amount: \"2 cups\" },\n ],\n instructions: [\n \"Preheat oven to 350°F (175°C)\",\n ],\n },\n};\n\nfunction Recipe() {\n const { state: agentState, setState: setAgentState } =\n useCoAgent<RecipeAgentState>({\n name: \"shared_state\",\n initialState: INITIAL_STATE,\n });\n\n useCopilotChatSuggestions({\n instructions: chatSuggestions.sharedState,\n })\n\n useCopilotAction({\n name: \"generate_recipe\",\n description: `Generate a recipe based on the user's input based on the ingredients and instructions, proceed with the recipe to finish it. The existing ingredients and instructions are provided to you as context: ${JSON.stringify(agentState)}. If you have just created or modified the recipe, just answer in one sentence what you did. dont describe the recipe, just say what you did`,\n parameters : [\n {\n name : \"recipe\",\n type : \"object\",\n attributes : [\n {\n name : \"title\",\n type : \"string\",\n description : \"The title of the recipe\"\n },\n {\n name : \"skill_level\",\n type : \"string\",\n description : \"The skill level of the recipe\",\n enum : Object.values(SkillLevel)\n },\n {\n name : \"cooking_time\",\n type : \"string\",\n description : \"The cooking time of the recipe\",\n enum : Object.values(CookingTime)\n },\n {\n name : \"dietary_preferences\",\n type : \"string[]\",\n enum : dietaryOptions\n },\n {\n name : \"ingredients\",\n type : \"object[]\",\n attributes : [\n {\n name : \"icon\",\n type : \"string\",\n description : \"The icon of the ingredient\"\n },\n {\n name : \"name\",\n type : \"string\",\n description : \"The name of the ingredient\"\n },\n {\n name : \"amount\",\n type : \"string\",\n description : \"The amount of the ingredient\"\n }\n ]\n },\n {\n name : \"instructions\",\n type : \"string[]\",\n description : \"The instructions of the recipe\"\n }\n ]\n }\n ],\n render : ({args}) =>{\n useEffect(() => {\n console.log(args, \"args.recipe\")\n updateRecipe(args?.recipe || {})\n }, [args.recipe])\n return <></>\n }\n })\n\n const [recipe, setRecipe] = useState(INITIAL_STATE.recipe);\n const { appendMessage, isLoading } = useCopilotChat();\n const [editingInstructionIndex, setEditingInstructionIndex] = useState<number | null>(null);\n\n const updateRecipe = (partialRecipe: Partial<Recipe>) => {\n setAgentState({\n ...agentState,\n recipe: {\n ...recipe,\n ...partialRecipe,\n },\n });\n setRecipe({\n ...recipe,\n ...partialRecipe,\n });\n };\n\n const newRecipeState = { ...recipe };\n const newChangedKeys = [];\n const changedKeysRef = useRef<string[]>([]);\n\n for (const key in recipe) {\n if (\n agentState &&\n agentState.recipe &&\n (agentState.recipe as any)[key] !== undefined &&\n (agentState.recipe as any)[key] !== null\n ) {\n let agentValue = (agentState.recipe as any)[key];\n const recipeValue = (recipe as any)[key];\n\n // Check if agentValue is a string and replace \\n with actual newlines\n if (typeof agentValue === \"string\") {\n agentValue = agentValue.replace(/\\\\n/g, \"\\n\");\n }\n\n if (JSON.stringify(agentValue) !== JSON.stringify(recipeValue)) {\n (newRecipeState as any)[key] = agentValue;\n newChangedKeys.push(key);\n }\n }\n }\n\n if (newChangedKeys.length > 0) {\n changedKeysRef.current = newChangedKeys;\n } else if (!isLoading) {\n changedKeysRef.current = [];\n }\n\n useEffect(() => {\n setRecipe(newRecipeState);\n }, [JSON.stringify(newRecipeState)]);\n\n const handleTitleChange = (event: React.ChangeEvent<HTMLInputElement>) => {\n updateRecipe({\n title: event.target.value,\n });\n };\n\n const handleSkillLevelChange = (\n event: React.ChangeEvent<HTMLSelectElement>\n ) => {\n updateRecipe({\n skill_level: event.target.value as SkillLevel,\n });\n };\n\n const handleDietaryChange = (preference: string, checked: boolean) => {\n if (checked) {\n updateRecipe({\n dietary_preferences: [...recipe.dietary_preferences, preference],\n });\n } else {\n updateRecipe({\n dietary_preferences: recipe.dietary_preferences.filter(\n (p) => p !== preference\n ),\n });\n }\n };\n\n const handleCookingTimeChange = (\n event: React.ChangeEvent<HTMLSelectElement>\n ) => {\n updateRecipe({\n cooking_time: cookingTimeValues[Number(event.target.value)].label,\n });\n };\n\n\n const addIngredient = () => {\n // Pick a random food emoji from our valid list\n updateRecipe({\n ingredients: [...recipe.ingredients, { icon: \"🍴\", name: \"\", amount: \"\" }],\n });\n };\n\n const updateIngredient = (index: number, field: keyof Ingredient, value: string) => {\n const updatedIngredients = [...recipe.ingredients];\n updatedIngredients[index] = {\n ...updatedIngredients[index],\n [field]: value,\n };\n updateRecipe({ ingredients: updatedIngredients });\n };\n\n const removeIngredient = (index: number) => {\n const updatedIngredients = [...recipe.ingredients];\n updatedIngredients.splice(index, 1);\n updateRecipe({ ingredients: updatedIngredients });\n };\n\n const addInstruction = () => {\n const newIndex = recipe.instructions.length;\n updateRecipe({\n instructions: [...recipe.instructions, \"\"],\n });\n // Set the new instruction as the editing one\n setEditingInstructionIndex(newIndex);\n\n // Focus the new instruction after render\n setTimeout(() => {\n const textareas = document.querySelectorAll('.instructions-container textarea');\n const newTextarea = textareas[textareas.length - 1] as HTMLTextAreaElement;\n if (newTextarea) {\n newTextarea.focus();\n }\n }, 50);\n };\n\n const updateInstruction = (index: number, value: string) => {\n const updatedInstructions = [...recipe.instructions];\n updatedInstructions[index] = value;\n updateRecipe({ instructions: updatedInstructions });\n };\n\n const removeInstruction = (index: number) => {\n const updatedInstructions = [...recipe.instructions];\n updatedInstructions.splice(index, 1);\n updateRecipe({ instructions: updatedInstructions });\n };\n\n // Simplified icon handler that defaults to a fork/knife for any problematic icons\n const getProperIcon = (icon: string | undefined): string => {\n // If icon is undefined return the default\n if (!icon) {\n return \"🍴\";\n }\n\n return icon;\n };\n\n return (\n <form className=\"recipe-card\">\n {/* Recipe Title */}\n <div className=\"recipe-header\">\n <input\n type=\"text\"\n value={recipe.title || ''}\n onChange={handleTitleChange}\n className=\"recipe-title-input\"\n />\n\n <div className=\"recipe-meta\">\n <div className=\"meta-item\">\n <span className=\"meta-icon\">🕒</span>\n <select\n className=\"meta-select\"\n value={cookingTimeValues.find(t => t.label === recipe.cooking_time)?.value || 3}\n onChange={handleCookingTimeChange}\n style={{\n backgroundImage: 'url(\"data:image/svg+xml;charset=UTF-8,%3csvg xmlns=\\'http://www.w3.org/2000/svg\\' viewBox=\\'0 0 24 24\\' fill=\\'none\\' stroke=\\'%23555\\' stroke-width=\\'2\\' stroke-linecap=\\'round\\' stroke-linejoin=\\'round\\'%3e%3cpolyline points=\\'6 9 12 15 18 9\\'%3e%3c/polyline%3e%3c/svg%3e\")',\n backgroundRepeat: 'no-repeat',\n backgroundPosition: 'right 0px center',\n backgroundSize: '12px',\n appearance: 'none',\n WebkitAppearance: 'none'\n }}\n >\n {cookingTimeValues.map((time) => (\n <option key={time.value} value={time.value}>\n {time.label}\n </option>\n ))}\n </select>\n </div>\n\n <div className=\"meta-item\">\n <span className=\"meta-icon\">🏆</span>\n <select\n className=\"meta-select\"\n value={recipe.skill_level}\n onChange={handleSkillLevelChange}\n style={{\n backgroundImage: 'url(\"data:image/svg+xml;charset=UTF-8,%3csvg xmlns=\\'http://www.w3.org/2000/svg\\' viewBox=\\'0 0 24 24\\' fill=\\'none\\' stroke=\\'%23555\\' stroke-width=\\'2\\' stroke-linecap=\\'round\\' stroke-linejoin=\\'round\\'%3e%3cpolyline points=\\'6 9 12 15 18 9\\'%3e%3c/polyline%3e%3c/svg%3e\")',\n backgroundRepeat: 'no-repeat',\n backgroundPosition: 'right 0px center',\n backgroundSize: '12px',\n appearance: 'none',\n WebkitAppearance: 'none'\n }}\n >\n {Object.values(SkillLevel).map((level) => (\n <option key={level} value={level}>\n {level}\n </option>\n ))}\n </select>\n </div>\n </div>\n </div>\n\n {/* Dietary Preferences */}\n <div className=\"section-container relative\">\n {changedKeysRef.current.includes(\"dietary_preferences\") && <Ping />}\n <h2 className=\"section-title\">Dietary Preferences</h2>\n <div className=\"dietary-options\">\n {dietaryOptions.map((option) => (\n <label key={option} className=\"dietary-option\">\n <input\n type=\"checkbox\"\n checked={recipe.dietary_preferences.includes(option)}\n onChange={(e: React.ChangeEvent<HTMLInputElement>) => handleDietaryChange(option, e.target.checked)}\n />\n <span>{option}</span>\n </label>\n ))}\n </div>\n </div>\n\n {/* Ingredients */}\n <div className=\"section-container relative\">\n {changedKeysRef.current.includes(\"ingredients\") && <Ping />}\n <div className=\"section-header\">\n <h2 className=\"section-title\">Ingredients</h2>\n <button\n type=\"button\"\n className=\"add-button\"\n onClick={addIngredient}\n >\n + Add Ingredient\n </button>\n </div>\n <div className=\"ingredients-container\">\n {recipe.ingredients.map((ingredient, index) => (\n <div key={index} className=\"ingredient-card\">\n <div className=\"ingredient-icon\">{getProperIcon(ingredient.icon)}</div>\n <div className=\"ingredient-content\">\n <input\n type=\"text\"\n value={ingredient.name || ''}\n onChange={(e) => updateIngredient(index, \"name\", e.target.value)}\n placeholder=\"Ingredient name\"\n className=\"ingredient-name-input\"\n />\n <input\n type=\"text\"\n value={ingredient.amount || ''}\n onChange={(e) => updateIngredient(index, \"amount\", e.target.value)}\n placeholder=\"Amount\"\n className=\"ingredient-amount-input\"\n />\n </div>\n <button\n type=\"button\"\n className=\"remove-button\"\n onClick={() => removeIngredient(index)}\n aria-label=\"Remove ingredient\"\n >\n ×\n </button>\n </div>\n ))}\n </div>\n </div>\n\n {/* Instructions */}\n <div className=\"section-container relative\">\n {changedKeysRef.current.includes(\"instructions\") && <Ping />}\n <div className=\"section-header\">\n <h2 className=\"section-title\">Instructions</h2>\n <button\n type=\"button\"\n className=\"add-step-button\"\n onClick={addInstruction}\n >\n + Add Step\n </button>\n </div>\n <div className=\"instructions-container\">\n {recipe.instructions.map((instruction, index) => (\n <div key={index} className=\"instruction-item\">\n {/* Number Circle */}\n <div className=\"instruction-number\">\n {index + 1}\n </div>\n\n {/* Vertical Line */}\n {index < recipe.instructions.length - 1 && (\n <div className=\"instruction-line\" />\n )}\n\n {/* Instruction Content */}\n <div\n className={`instruction-content ${editingInstructionIndex === index\n ? 'instruction-content-editing'\n : 'instruction-content-default'\n }`}\n onClick={() => setEditingInstructionIndex(index)}\n >\n <textarea\n className=\"instruction-textarea\"\n value={instruction || ''}\n onChange={(e) => updateInstruction(index, e.target.value)}\n placeholder={!instruction ? \"Enter cooking instruction...\" : \"\"}\n onFocus={() => setEditingInstructionIndex(index)}\n onBlur={(e) => {\n // Only blur if clicking outside this instruction\n if (!e.relatedTarget || !e.currentTarget.contains(e.relatedTarget as Node)) {\n setEditingInstructionIndex(null);\n }\n }}\n />\n\n {/* Delete Button (only visible on hover) */}\n <button\n type=\"button\"\n className={`instruction-delete-btn ${editingInstructionIndex === index\n ? 'instruction-delete-btn-editing'\n : 'instruction-delete-btn-default'\n } remove-button`}\n onClick={(e) => {\n e.stopPropagation(); // Prevent triggering parent onClick\n removeInstruction(index);\n }}\n aria-label=\"Remove instruction\"\n >\n ×\n </button>\n </div>\n </div>\n ))}\n </div>\n </div>\n\n {/* Improve with AI Button */}\n <div className=\"action-container\">\n <button\n className={isLoading ? \"improve-button loading\" : \"improve-button\"}\n type=\"button\"\n onClick={() => {\n if (!isLoading) {\n appendMessage(\n new TextMessage({\n content: \"Improve the recipe\",\n role: Role.User,\n })\n );\n }\n }}\n disabled={isLoading}\n >\n {isLoading ? \"Please Wait...\" : \"Improve with AI\"}\n </button>\n </div>\n </form>\n );\n}\n\nfunction Ping() {\n return (\n <span className=\"ping-animation\">\n <span className=\"ping-circle\"></span>\n <span className=\"ping-dot\"></span>\n </span>\n );\n}",
"path": "standard_shared_state/page.tsx",
"language": "typescript",
"type": "file"
},
{
"name": "style.css",
"content": ".copilotKitWindow {\n box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);\n}\n\n.copilotKitHeader {\n border-top-left-radius: 5px !important;\n background-color: #fff;\n color: #000;\n border-bottom: 0px;\n}\n\n/* Recipe App Styles */\n.app-container {\n min-height: 100vh;\n width: 100%;\n display: flex;\n align-items: center;\n justify-content: center;\n background-size: cover;\n background-position: center;\n background-repeat: no-repeat;\n background-attachment: fixed;\n position: relative;\n overflow: auto;\n}\n\n.recipe-card {\n background-color: rgba(255, 255, 255, 0.97);\n border-radius: 16px;\n box-shadow: 0 15px 30px rgba(0, 0, 0, 0.25), 0 5px 15px rgba(0, 0, 0, 0.15);\n width: 100%;\n max-width: 750px;\n margin: 20px auto;\n padding: 14px 32px;\n position: relative;\n z-index: 1;\n backdrop-filter: blur(5px);\n border: 1px solid rgba(255, 255, 255, 0.3);\n transition: transform 0.2s ease, box-shadow 0.2s ease;\n animation: fadeIn 0.5s ease-out forwards;\n box-sizing: border-box;\n overflow: hidden;\n}\n\n.recipe-card:hover {\n transform: translateY(-5px);\n box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3), 0 10px 20px rgba(0, 0, 0, 0.2);\n}\n\n/* Recipe Header */\n.recipe-header {\n margin-bottom: 24px;\n}\n\n.recipe-title-input {\n width: 100%;\n font-size: 24px;\n font-weight: bold;\n border: none;\n outline: none;\n padding: 8px 0;\n margin-bottom: 0px;\n}\n\n.recipe-meta {\n display: flex;\n align-items: center;\n gap: 20px;\n margin-top: 5px;\n margin-bottom: 14px;\n}\n\n.meta-item {\n display: flex;\n align-items: center;\n gap: 8px;\n color: #555;\n}\n\n.meta-icon {\n font-size: 20px;\n color: #777;\n}\n\n.meta-text {\n font-size: 15px;\n}\n\n/* Recipe Meta Selects */\n.meta-item select {\n border: none;\n background: transparent;\n font-size: 15px;\n color: #555;\n cursor: pointer;\n outline: none;\n padding-right: 18px;\n transition: color 0.2s, transform 0.1s;\n font-weight: 500;\n}\n\n.meta-item select:hover, \n.meta-item select:focus {\n color: #FF5722;\n}\n\n.meta-item select:active {\n transform: scale(0.98);\n}\n\n.meta-item select option {\n color: #333;\n background-color: white;\n font-weight: normal;\n padding: 8px;\n}\n\n/* Section Container */\n.section-container {\n margin-bottom: 20px;\n position: relative;\n width: 100%;\n}\n\n.section-title {\n font-size: 20px;\n font-weight: 700;\n margin-bottom: 20px;\n color: #333;\n position: relative;\n display: inline-block;\n}\n\n.section-title:after {\n content: \"\";\n position: absolute;\n bottom: -8px;\n left: 0;\n width: 40px;\n height: 3px;\n background-color: #ff7043;\n border-radius: 3px;\n}\n\n/* Dietary Preferences */\n.dietary-options {\n display: flex;\n flex-wrap: wrap;\n gap: 10px 16px;\n margin-bottom: 16px;\n width: 100%;\n}\n\n.dietary-option {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n cursor: pointer;\n margin-bottom: 4px;\n}\n\n.dietary-option input {\n cursor: pointer;\n}\n\n/* Ingredients */\n.ingredients-container {\n display: flex;\n flex-wrap: wrap;\n gap: 10px;\n margin-bottom: 15px;\n width: 100%;\n box-sizing: border-box;\n}\n\n.ingredient-card {\n display: flex;\n align-items: center;\n background-color: rgba(255, 255, 255, 0.9);\n border-radius: 12px;\n padding: 12px;\n margin-bottom: 10px;\n box-shadow: 0 4px 10px rgba(0, 0, 0, 0.08);\n position: relative;\n transition: all 0.2s ease;\n border: 1px solid rgba(240, 240, 240, 0.8);\n width: calc(33.333% - 7px);\n box-sizing: border-box;\n}\n\n.ingredient-card:hover {\n transform: translateY(-2px);\n box-shadow: 0 6px 15px rgba(0, 0, 0, 0.12);\n}\n\n.ingredient-card .remove-button {\n position: absolute;\n right: 10px;\n top: 10px;\n background: none;\n border: none;\n color: #ccc;\n font-size: 16px;\n cursor: pointer;\n display: none;\n padding: 0;\n width: 24px;\n height: 24px;\n line-height: 1;\n}\n\n.ingredient-card:hover .remove-button {\n display: block;\n}\n\n.ingredient-icon {\n font-size: 24px;\n margin-right: 12px;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 40px;\n height: 40px;\n background-color: #f7f7f7;\n border-radius: 50%;\n flex-shrink: 0;\n}\n\n.ingredient-content {\n flex: 1;\n display: flex;\n flex-direction: column;\n gap: 3px;\n min-width: 0;\n}\n\n.ingredient-name-input, \n.ingredient-amount-input {\n border: none;\n background: transparent;\n outline: none;\n width: 100%;\n padding: 0;\n text-overflow: ellipsis;\n overflow: hidden;\n white-space: nowrap;\n}\n\n.ingredient-name-input {\n font-weight: 500;\n font-size: 14px;\n}\n\n.ingredient-amount-input {\n font-size: 13px;\n color: #666;\n}\n\n.ingredient-name-input::placeholder,\n.ingredient-amount-input::placeholder {\n color: #aaa;\n}\n\n.remove-button {\n background: none;\n border: none;\n color: #999;\n font-size: 20px;\n cursor: pointer;\n padding: 0;\n width: 28px;\n height: 28px;\n display: flex;\n align-items: center;\n justify-content: center;\n margin-left: 10px;\n}\n\n.remove-button:hover {\n color: #FF5722;\n}\n\n/* Instructions */\n.instructions-container {\n display: flex;\n flex-direction: column;\n gap: 6px;\n position: relative;\n margin-bottom: 12px;\n width: 100%;\n}\n\n.instruction-item {\n position: relative;\n display: flex;\n width: 100%;\n box-sizing: border-box;\n margin-bottom: 8px;\n align-items: flex-start;\n}\n\n.instruction-number {\n display: flex;\n align-items: center;\n justify-content: center;\n min-width: 26px;\n height: 26px;\n background-color: #ff7043;\n color: white;\n border-radius: 50%;\n font-weight: 600;\n flex-shrink: 0;\n box-shadow: 0 2px 4px rgba(255, 112, 67, 0.3);\n z-index: 1;\n font-size: 13px;\n margin-top: 2px;\n}\n\n.instruction-line {\n position: absolute;\n left: 13px; /* Half of the number circle width */\n top: 22px;\n bottom: -18px;\n width: 2px;\n background: linear-gradient(to bottom, #ff7043 60%, rgba(255, 112, 67, 0.4));\n z-index: 0;\n}\n\n.instruction-content {\n background-color: white;\n border-radius: 10px;\n padding: 10px 14px;\n margin-left: 12px;\n flex-grow: 1;\n transition: all 0.2s ease;\n box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);\n border: 1px solid rgba(240, 240, 240, 0.8);\n position: relative;\n width: calc(100% - 38px);\n box-sizing: border-box;\n display: flex;\n align-items: center;\n}\n\n.instruction-content-editing {\n background-color: #fff9f6;\n box-shadow: 0 6px 16px rgba(0, 0, 0, 0.12), 0 0 0 2px rgba(255, 112, 67, 0.2);\n}\n\n.instruction-content:hover {\n transform: translateY(-2px);\n box-shadow: 0 6px 16px rgba(0, 0, 0, 0.12);\n}\n\n.instruction-textarea {\n width: 100%;\n background: transparent;\n border: none;\n resize: vertical;\n font-family: inherit;\n font-size: 14px;\n line-height: 1.4;\n min-height: 20px;\n outline: none;\n padding: 0;\n margin: 0;\n}\n\n.instruction-delete-btn {\n position: absolute;\n background: none;\n border: none;\n color: #ccc;\n font-size: 16px;\n cursor: pointer;\n display: none;\n padding: 0;\n width: 20px;\n height: 20px;\n line-height: 1;\n top: 50%;\n transform: translateY(-50%);\n right: 8px;\n}\n\n.instruction-content:hover .instruction-delete-btn {\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n/* Action Button */\n.action-container {\n display: flex;\n justify-content: center;\n margin-top: 40px;\n padding-bottom: 20px;\n position: relative;\n}\n\n.improve-button {\n background-color: #ff7043;\n border: none;\n color: white;\n border-radius: 30px;\n font-size: 18px;\n font-weight: 600;\n padding: 14px 28px;\n cursor: pointer;\n transition: all 0.3s ease;\n box-shadow: 0 4px 15px rgba(255, 112, 67, 0.4);\n display: flex;\n align-items: center;\n justify-content: center;\n text-align: center;\n position: relative;\n min-width: 180px;\n}\n\n.improve-button:hover {\n background-color: #ff5722;\n transform: translateY(-2px);\n box-shadow: 0 8px 20px rgba(255, 112, 67, 0.5);\n}\n\n.improve-button.loading {\n background-color: #ff7043;\n opacity: 0.8;\n cursor: not-allowed;\n padding-left: 42px; /* Reduced padding to bring text closer to icon */\n padding-right: 22px; /* Balance the button */\n justify-content: flex-start; /* Left align text for better alignment with icon */\n}\n\n.improve-button.loading:after {\n content: \"\"; /* Add space between icon and text */\n display: inline-block;\n width: 8px; /* Width of the space */\n}\n\n.improve-button:before {\n content: \"\";\n background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83'/%3E%3C/svg%3E\");\n width: 20px; /* Slightly smaller icon */\n height: 20px;\n background-repeat: no-repeat;\n background-size: contain;\n position: absolute;\n left: 16px; /* Slightly adjusted */\n top: 50%;\n transform: translateY(-50%);\n display: none;\n}\n\n.improve-button.loading:before {\n display: block;\n animation: spin 1.5s linear infinite;\n}\n\n@keyframes spin {\n 0% { transform: translateY(-50%) rotate(0deg); }\n 100% { transform: translateY(-50%) rotate(360deg); }\n}\n\n/* Ping Animation */\n.ping-animation {\n position: absolute;\n display: flex;\n width: 12px;\n height: 12px;\n top: 0;\n right: 0;\n}\n\n.ping-circle {\n position: absolute;\n display: inline-flex;\n width: 100%;\n height: 100%;\n border-radius: 50%;\n background-color: #38BDF8;\n opacity: 0.75;\n animation: ping 1.5s cubic-bezier(0, 0, 0.2, 1) infinite;\n}\n\n.ping-dot {\n position: relative;\n display: inline-flex;\n width: 12px;\n height: 12px;\n border-radius: 50%;\n background-color: #0EA5E9;\n}\n\n@keyframes ping {\n 75%, 100% {\n transform: scale(2);\n opacity: 0;\n }\n}\n\n/* Instruction hover effects */\n.instruction-item:hover .instruction-delete-btn {\n display: flex !important;\n}\n\n/* Add some subtle animations */\n@keyframes fadeIn {\n from { opacity: 0; transform: translateY(20px); }\n to { opacity: 1; transform: translateY(0); }\n}\n\n/* Better center alignment for the recipe card */\n.recipe-card-container {\n display: flex;\n justify-content: center;\n width: 100%;\n position: relative;\n z-index: 1;\n margin: 0 auto;\n box-sizing: border-box;\n}\n\n/* Add Buttons */\n.add-button {\n background-color: transparent;\n color: #FF5722;\n border: 1px dashed #FF5722;\n border-radius: 8px;\n padding: 10px 16px;\n cursor: pointer;\n font-weight: 500;\n display: inline-block;\n font-size: 14px;\n margin-bottom: 0;\n}\n\n.add-step-button {\n background-color: transparent;\n color: #FF5722;\n border: 1px dashed #FF5722;\n border-radius: 6px;\n padding: 6px 12px;\n cursor: pointer;\n font-weight: 500;\n font-size: 13px;\n}\n\n/* Section Headers */\n.section-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 12px;\n}",
"path": "standard_shared_state/style.css",
"language": "css",
"type": "file"
},
{
"name": "README.mdx",
"content": "# 🍳 Shared State Recipe Creator\n\n## What This Demo Shows\n\nThis demo showcases CopilotKit's **shared state** functionality - a powerful feature that enables bidirectional data flow between:\n1. **Frontend → Agent**: UI controls update the agent's context in real-time\n2. **Agent → Frontend**: The Copilot's recipe creations instantly update the UI components\n\nIt's like having a cooking buddy who not only listens to what you want but also updates your recipe card as you chat - no refresh needed! ✨\n\n## How to Interact\n\nMix and match any of these parameters (or none at all - it's up to you!):\n- **Skill Level**: Beginner to expert 👨🍳\n- **Cooking Time**: Quick meals or slow cooking ⏱️\n- **Special Preferences**: Dietary needs, flavor profiles, health goals 🥗\n- **Ingredients**: Items you want to include 🧅🥩🍄\n- **Instructions**: Any specific steps\n\nThen chat with your Copilot chef with prompts like:\n- \"I'm a beginner cook. Can you make me a quick dinner?\"\n- \"I need something spicy with chicken that takes under 30 minutes!\"\n\n## ✨ Shared State Magic in Action\n\n**What's happening technically:**\n- The UI and Copilot agent share the same state object (**Agent State = UI State**)\n- Changes from either side automatically update the other\n- Neither side needs to manually request updates from the other\n\n**What you'll see in this demo:**\n- Set cooking time to 20 minutes in the UI and watch the Copilot immediately respect your time constraint\n- Add ingredients through the UI and see them appear in your recipe\n- When the Copilot suggests new ingredients, watch them automatically appear in the UI ingredients list\n- Change your skill level and see how the Copilot adapts its instructions in real-time\n\nThis synchronized state creates a seamless experience where the agent always has your current preferences, and any updates to the recipe are instantly reflected in both places.\n\nThis shared state pattern can be applied to any application where you want your UI and Copilot to work together in perfect harmony! ",
"path": "standard_shared_state/README.mdx",
"language": "markdown",
"type": "file"
}
]
},
"standard_predictive_state_updates": {
"files": [
{
"name": "page.tsx",
"content": "\"use client\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\n\nimport MarkdownIt from \"markdown-it\";\n\nimport { diffWords } from \"diff\";\nimport { useEditor, EditorContent } from \"@tiptap/react\";\nimport StarterKit from \"@tiptap/starter-kit\";\nimport { useEffect, useState } from \"react\";\nimport {\n CopilotKit,\n useCoAgent,\n useCopilotAction,\n useCopilotChat,\n} from \"@copilotkit/react-core\";\nimport { CopilotSidebar, useCopilotChatSuggestions } from \"@copilotkit/react-ui\";\nimport { chatSuggestions, initialPrompt, instructions } from \"@/lib/prompts\";\nconst extensions = [StarterKit];\n\nexport default function PredictiveStateUpdates() {\n\n return (\n <CopilotKit\n runtimeUrl=\"/api/copilotkit?standard=true\"\n showDevConsole = {false}\n >\n <div\n className=\"min-h-screen w-full\"\n style={\n {\n // \"--copilot-kit-primary-color\": \"#222\",\n // \"--copilot-kit-separator-color\": \"#CCC\",\n } as React.CSSProperties\n }\n >\n <CopilotSidebar\n defaultOpen={true}\n labels={{\n title: \"AI Document Editor\",\n initial: initialPrompt.predictiveStateUpdates,\n }}\n instructions={instructions.predictiveStateUpdates}\n clickOutsideToClose={false}\n >\n <DocumentEditor />\n </CopilotSidebar>\n </div>\n </CopilotKit>\n );\n}\n\ninterface AgentState {\n document: string;\n}\n\nconst DocumentEditor = () => {\n const editor = useEditor({\n extensions,\n immediatelyRender: false,\n editorProps: {\n attributes: { class: \"min-h-screen p-10\" },\n },\n });\n const [placeholderVisible, setPlaceholderVisible] = useState(false);\n const [currentDocument, setCurrentDocument] = useState(\"\");\n\n useCopilotAction({\n name: \"write_document\",\n description: `Write a document. Use markdown formatting to format the document.\n It's good to format the document extensively so it's easy to read.\n You can use all kinds of markdown.\n However, do not use italic or strike-through formatting, it's reserved for another purpose.\n You MUST write the full document, even when changing only a few words.\n When making edits to the document, try to make them minimal - do not change every word.\n When you are done writing the document, provide a summary of the changes you made.\n Keep stories SHORT! If user rejects the changes, Send messages like \"Would you like to re-generate the document?\"`,\n parameters: [\n {\n type: \"string\",\n name: \"document\",\n description: \"The document to write\"\n }\n ],\n renderAndWaitForResponse: ({ args, respond, status }) => {\n console.log(args, respond, status)\n return <ConfirmChanges\n editor={editor}\n currentDocument={currentDocument}\n setCurrentDocument={setCurrentDocument}\n args={args}\n respond={respond}\n status={status}\n onReject={() => {\n if (currentDocument != \"\") {\n editor?.commands.setContent(fromMarkdown(currentDocument));\n }\n else {\n editor?.commands.setContent(\"\");\n }\n }}\n onConfirm={() => {\n editor?.commands.setContent(fromMarkdown(args.document || \"\"));\n }}\n />\n }\n })\n\n useCopilotChatSuggestions({\n instructions: chatSuggestions.predictiveStateUpdates,\n })\n return (\n <div className=\"relative min-h-screen w-full\">\n {placeholderVisible && (\n <div className=\"absolute top-6 left-6 m-4 pointer-events-none text-gray-400\">\n Write whatever you want here in Markdown format...\n </div>\n )}\n <EditorContent editor={editor} />\n </div>\n );\n};\n\n\ninterface ConfirmChangesProps {\n args: any;\n respond: any;\n status: any;\n onReject: () => void;\n onConfirm: () => void;\n editor: any;\n currentDocument: string;\n setCurrentDocument: (document: string) => void;\n}\n\nfunction ConfirmChanges({ args, respond, status, onReject, onConfirm, editor, currentDocument, setCurrentDocument }: ConfirmChangesProps) {\n useEffect(() => {\n console.log(args?.document, \"statusstatusstatusstatus\");\n if (currentDocument == \"\") {\n editor?.commands.setContent(fromMarkdown(args?.document || \"\"));\n }\n else {\n let diff = diffPartialText(currentDocument, args?.document || \"\");\n editor?.commands.setContent(fromMarkdown(diff));\n }\n }, [args?.document])\n\n const [accepted, setAccepted] = useState<boolean | null>(null);\n if (status != 'inProgress') {\n return (\n <div className=\"bg-white p-6 rounded shadow-lg border border-gray-200 mt-5 mb-5\">\n <h2 className=\"text-lg font-bold mb-4\">Confirm Changes</h2>\n <p className=\"mb-6\">Do you want to accept the changes?</p>\n {accepted === null && (\n <div className=\"flex justify-end space-x-4\">\n <button\n className={`bg-gray-200 text-black py-2 px-4 rounded disabled:opacity-50 ${status === \"executing\" ? \"cursor-pointer\" : \"cursor-default\"\n }`}\n disabled={status !== \"executing\"}\n onClick={() => {\n debugger\n if (respond) {\n setCurrentDocument(currentDocument);\n setAccepted(false);\n onReject();\n respond(\"Changes rejected\");\n }\n }}\n >\n Reject\n </button>\n <button\n className={`bg-black text-white py-2 px-4 rounded disabled:opacity-50 ${status === \"executing\" ? \"cursor-pointer\" : \"cursor-default\"\n }`}\n disabled={status !== \"executing\"}\n onClick={() => {\n debugger\n if (respond) {\n setCurrentDocument(args?.document || \"\");\n setAccepted(true);\n onConfirm();\n respond(\"Changes accepted\");\n }\n }}\n >\n Confirm\n </button>\n </div>\n )}\n {accepted !== null && (\n <div className=\"flex justify-end\">\n <div className=\"mt-4 bg-gray-200 text-black py-2 px-4 rounded inline-block\">\n {accepted ? \"✓ Accepted\" : \"✗ Rejected\"}\n </div>\n </div>\n )}\n </div>\n );\n }\n else {\n return null;\n }\n}\n\nfunction fromMarkdown(text: string) {\n const md = new MarkdownIt({\n typographer: true,\n html: true,\n });\n\n return md.render(text);\n}\n\nfunction diffPartialText(\n oldText: string,\n newText: string,\n isComplete: boolean = false\n) {\n let oldTextToCompare = oldText;\n if (oldText.length > newText.length && !isComplete) {\n // make oldText shorter\n oldTextToCompare = oldText.slice(0, newText.length);\n }\n\n const changes = diffWords(oldTextToCompare, newText);\n\n let result = \"\";\n changes.forEach((part) => {\n if (part.added) {\n result += `<em>${part.value}</em>`;\n } else if (part.removed) {\n result += `<s>${part.value}</s>`;\n } else {\n result += part.value;\n }\n });\n\n if (oldText.length > newText.length && !isComplete) {\n result += oldText.slice(newText.length);\n }\n\n return result;\n}\n\nfunction isAlpha(text: string) {\n return /[a-zA-Z\\u00C0-\\u017F]/.test(text.trim());\n}\n",
"path": "standard_predictive_state_updates/page.tsx",
"language": "typescript",
"type": "file"
},
{
"name": "style.css",
"content": "/* Basic editor styles */\n.tiptap-container {\n height: 100vh; /* Full viewport height */\n width: 100vw; /* Full viewport width */\n display: flex;\n flex-direction: column;\n}\n\n.tiptap {\n flex: 1; /* Take up remaining space */\n overflow: auto; /* Allow scrolling if content overflows */\n}\n\n.tiptap :first-child {\n margin-top: 0;\n}\n\n/* List styles */\n.tiptap ul,\n.tiptap ol {\n padding: 0 1rem;\n margin: 1.25rem 1rem 1.25rem 0.4rem;\n}\n\n.tiptap ul li p,\n.tiptap ol li p {\n margin-top: 0.25em;\n margin-bottom: 0.25em;\n}\n\n/* Heading styles */\n.tiptap h1,\n.tiptap h2,\n.tiptap h3,\n.tiptap h4,\n.tiptap h5,\n.tiptap h6 {\n line-height: 1.1;\n margin-top: 2.5rem;\n text-wrap: pretty;\n font-weight: bold;\n}\n\n.tiptap h1,\n.tiptap h2,\n.tiptap h3,\n.tiptap h4,\n.tiptap h5,\n.tiptap h6 {\n margin-top: 3.5rem;\n margin-bottom: 1.5rem;\n}\n\n.tiptap p {\n margin-bottom: 1rem;\n}\n\n.tiptap h1 {\n font-size: 1.4rem;\n}\n\n.tiptap h2 {\n font-size: 1.2rem;\n}\n\n.tiptap h3 {\n font-size: 1.1rem;\n}\n\n.tiptap h4,\n.tiptap h5,\n.tiptap h6 {\n font-size: 1rem;\n}\n\n/* Code and preformatted text styles */\n.tiptap code {\n background-color: var(--purple-light);\n border-radius: 0.4rem;\n color: var(--black);\n font-size: 0.85rem;\n padding: 0.25em 0.3em;\n}\n\n.tiptap pre {\n background: var(--black);\n border-radius: 0.5rem;\n color: var(--white);\n font-family: \"JetBrainsMono\", monospace;\n margin: 1.5rem 0;\n padding: 0.75rem 1rem;\n}\n\n.tiptap pre code {\n background: none;\n color: inherit;\n font-size: 0.8rem;\n padding: 0;\n}\n\n.tiptap blockquote {\n border-left: 3px solid var(--gray-3);\n margin: 1.5rem 0;\n padding-left: 1rem;\n}\n\n.tiptap hr {\n border: none;\n border-top: 1px solid var(--gray-2);\n margin: 2rem 0;\n}\n\n.tiptap s {\n background-color: #f9818150;\n padding: 2px;\n font-weight: bold;\n color: rgba(0, 0, 0, 0.7);\n}\n\n.tiptap em {\n background-color: #b2f2bb;\n padding: 2px;\n font-weight: bold;\n font-style: normal;\n}\n\n.copilotKitWindow {\n box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);\n}\n\n.copilotKitHeader {\n border-top-left-radius: 5px !important;\n background-color: #fff;\n color: #000;\n border-bottom: 0px;\n}\n\n.copilot-content {\n margin-right: 0;\n}\n\n/* When sidebar is open */\n.copilot-sidebar-visible .copilot-content {\n margin-right: 400px; /* Adjust this value based on your sidebar width */\n}\n\n/* Add smooth transition */\n.copilot-content {\n transition: margin-right 0.3s ease-in-out;\n}\n",
"path": "standard_predictive_state_updates/style.css",
"language": "css",
"type": "file"
},
{
"name": "README.mdx",
"content": "# 📝 Predictive State Updates Document Editor\n\n## What This Demo Shows\n\nThis demo showcases CopilotKit's **predictive state updates** for real-time document collaboration:\n1. **Live Document Editing**: Watch as your Copilot makes changes to a document in real-time\n2. **Diff Visualization**: See exactly what's being changed as it happens\n3. **Streaming Updates**: Changes are displayed character-by-character as the Copilot works\n\n## How to Interact\n\nTry these interactions with the collaborative document editor:\n- \"Fix the grammar and typos in this document\"\n- \"Make this text more professional\"\n- \"Add a section about [topic]\"\n- \"Summarize this content in bullet points\"\n- \"Change the tone to be more casual\"\n\nWatch as the Copilot processes your request and edits the document in real-time right before your eyes.\n\n## ✨ Predictive State Updates in Action\n\n**What's happening technically:**\n- The document state is shared between your UI and the Copilot\n- As the Copilot generates content, changes are streamed to the UI\n- Each modification is visualized with additions and deletions\n- The UI renders these changes progressively, without waiting for completion\n- All edits are tracked and displayed in a visually intuitive way\n\n**What you'll see in this demo:**\n- Text changes are highlighted in different colors (green for additions, red for deletions)\n- The document updates character-by-character, creating a typing-like effect\n- You can see the Copilot's thought process as it refines the content\n- The final document seamlessly incorporates all changes\n- The experience feels collaborative, as if someone is editing alongside you\n\nThis pattern of real-time collaborative editing with diff visualization is perfect for document editors, code review tools, content creation platforms, or any application where users benefit from seeing exactly how content is being transformed! ",
"path": "standard_predictive_state_updates/README.mdx",
"language": "markdown",
"type": "file"
}
]
}
}