showcase/shell-docs/src/content/docs/integrations/crewai-flows/generative-ui/state-rendering.mdx
<video src="https://cdn.copilotkit.ai/docs/copilotkit/images/coagents/agentic-generative-ui.mp4" className="rounded-lg shadow-xl" loop playsInline controls autoPlay muted /> <Callout> This video demonstrates the implementation section applied to our coagents starter project. </Callout>
All CrewAI Flow agents are stateful. This means that as your agent progresses through nodes, a state object is passed between them perserving the overall state of a session. CopilotKit allows you to render this state in your application with custom UI components, which we call Agentic Generative UI.
Rendering the state of your agent in the UI is useful when you want to provide the user with feedback about the overall state of a session. A great example of this is a situation where a user and an agent are working together to solve a problem. The agent can store a draft in its state which is then rendered in the UI.
This guide uses the [CoAgents starter repo](https://github.com/CopilotKit/CopilotKit/tree/main/examples/coagents-starter-crewai-flows) as its starting point.
For the sake of this guide, let's say our state looks like this in our agent.
<Tabs groupId="language_crewai-flows_agent" items={['Python']} default="Python" persist>
<Tab value="Python">
```python title="agent.py"
# ...
from copilotkit.crewai import CopilotKitState # extends MessagesState
# ...
# This is the state of the agent.
# It inherits from the CopilotKitState properties from CopilotKit.
class AgentState(CopilotKitState):
searches: list[dict]
```
</Tab>
</Tabs>
<Tabs groupId="language_crewai-flows_agent" items={['Python']} default="Python" persist>
<Tab value="Python">
```python title="agent.py"
from crewai.flow.flow import start
from litellm import completion
from copilotkit.crewai import copilotkit_stream, CopilotKitState, copilotkit_emit_state
from typing import TypedDict
class Searches(TypedDict):
query: str
done: bool
class AgentState(CopilotKitState):
searches: list[Searches] = [] # [!code highlight]
@start
async def chat(self):
self.state.searches = [
{"query": "Initial research", "done": False},
{"query": "Retrieving sources", "done": False},
{"query": "Forming an answer", "done": False},
]
await copilotkit_emit_state(self.state)
# Simulate state updates # [!code highlight:4]
for search in self.state.searches:
await asyncio.sleep(1)
search["done"] = True
await copilotkit_emit_state(self.state)
# Run the model to generate a response
response = await copilotkit_stream(
completion(
model="openai/gpt-5.4",
messages=[
{"role": "system", "content": "You are a helpful assistant."},
*self.state.get("messages", [])
],
stream=True
)
)
```
</Tab>
</Tabs>
```tsx title="app/page.tsx"
// ...
// ...
// Define the state of the agent, should match the state of the agent in your Flow.
type AgentState = {
searches: {
query: string;
done: boolean;
}[];
};
function YourMainContent() {
// ...
// [!code highlight:13]
// styles omitted for brevity
useAgent({
agentId: "sample_agent",
render: ({ state }) => (
<div>
{state.searches?.map((search, index) => (
<div key={index}>
{search.done ? "✅" : "❌"} {search.query}{search.done ? "" : "..."}
</div>
))}
</div>
),
});
// ...
return <div>...</div>;
}
```
```tsx title="app/page.tsx"
// ...
// Define the state of the agent, should match the state of the agent in your Flow.
type AgentState = {
searches: {
query: string;
done: boolean;
}[];
};
function YourMainContent() {
// ...
// [!code highlight:3]
const { agent } = useAgent({
agentId: "sample_agent",
})
// ...
return (
<div>
<div className="flex flex-col gap-2 mt-4">
{agent.state?.searches?.map((search, index) => (
<div key={index} className="flex flex-row">
{search.done ? "✅" : "❌"} {search.query}
</div>
))}
</div>
</div>
)
}
```
You've now created a component that will render the agent's state in the chat.