|
| 1 | +--- |
| 2 | +title: Response Renderer |
| 3 | +description: Display agent responses and collect user feedback |
| 4 | +icon: "lucide/Eye" |
| 5 | +--- |
| 6 | + |
| 7 | +import { Callout } from "fumadocs-ui/components/callout"; |
| 8 | + |
| 9 | +# Response Renderer |
| 10 | + |
| 11 | +The Response Renderer displays responses from an agent that may require user feedback, such as approving or rejecting a suggestion. It's especially useful for implementing human-in-the-loop workflows with CrewAI crews. |
| 12 | + |
| 13 | +<Callout type="info"> |
| 14 | + The Response Renderer is perfect for CrewAI crews that require user approval |
| 15 | + for critical decisions or need to collect user feedback. |
| 16 | +</Callout> |
| 17 | + |
| 18 | +## Installation |
| 19 | + |
| 20 | +This component is part of the `@copilotkit/react-ui` package: |
| 21 | + |
| 22 | +```bash |
| 23 | +npm install @copilotkit/react-ui |
| 24 | +``` |
| 25 | + |
| 26 | +Don't forget to import the styles: |
| 27 | + |
| 28 | +```tsx |
| 29 | +import "@copilotkit/react-ui/styles.css"; |
| 30 | +``` |
| 31 | + |
| 32 | +## Basic Usage |
| 33 | + |
| 34 | +Here's a simple example of how to use the DefaultResponseRenderer: |
| 35 | + |
| 36 | +```tsx |
| 37 | +import { DefaultResponseRenderer } from "@copilotkit/react-ui"; |
| 38 | +import "@copilotkit/react-ui/styles.css"; |
| 39 | + |
| 40 | +export function YourComponent() { |
| 41 | + return ( |
| 42 | + <DefaultResponseRenderer |
| 43 | + response={{ |
| 44 | + id: "response-1", |
| 45 | + content: "I've analyzed your data and found these insights...", |
| 46 | + }} |
| 47 | + status="inProgress" |
| 48 | + onRespond={(input) => console.log(`User responded: ${input}`)} |
| 49 | + /> |
| 50 | + ); |
| 51 | +} |
| 52 | +``` |
| 53 | + |
| 54 | +## Integration with CoAgents |
| 55 | + |
| 56 | +The DefaultResponseRenderer works seamlessly with the useCopilotAction hook to gather user feedback: |
| 57 | + |
| 58 | +```tsx |
| 59 | +import { useCopilotAction } from "@copilotkit/react-core"; |
| 60 | +import { DefaultResponseRenderer, ResponseStatus } from "@copilotkit/react-ui"; |
| 61 | + |
| 62 | +export function YourApp() { |
| 63 | + // Example: Crew is asking for user feedback on recommendations |
| 64 | + useCopilotAction({ |
| 65 | + name: "crew_requesting_feedback", |
| 66 | + renderAndWaitForResponse({ status, args, respond }) { |
| 67 | + const feedback = args as { id: string; task_output: string }; |
| 68 | + return ( |
| 69 | + <DefaultResponseRenderer |
| 70 | + response={{ |
| 71 | + id: feedback.id, |
| 72 | + content: feedback.task_output, |
| 73 | + metadata: feedback, |
| 74 | + }} |
| 75 | + onRespond={(input: string) => { |
| 76 | + respond?.(input); |
| 77 | + }} |
| 78 | + status={status as ResponseStatus} |
| 79 | + /> |
| 80 | + ); |
| 81 | + }, |
| 82 | + }); |
| 83 | + |
| 84 | + return ( |
| 85 | + // Your app content |
| 86 | + ); |
| 87 | +} |
| 88 | +``` |
| 89 | + |
| 90 | +## Customization |
| 91 | + |
| 92 | +You can customize the appearance and behavior of the component: |
| 93 | + |
| 94 | +```tsx |
| 95 | +<DefaultResponseRenderer |
| 96 | + response={{ |
| 97 | + id: "task-123", |
| 98 | + content: "Would you like to proceed with this recommendation?", |
| 99 | + }} |
| 100 | + status="inProgress" |
| 101 | + onRespond={handleResponse} |
| 102 | + labels={{ |
| 103 | + responseLabel: "AI Recommendation", |
| 104 | + approveLabel: "Yes, proceed", |
| 105 | + rejectLabel: "No, cancel", |
| 106 | + approvedMessage: "Proceeding with recommendation", |
| 107 | + rejectedMessage: "Recommendation cancelled", |
| 108 | + }} |
| 109 | + className="my-custom-response" |
| 110 | + contentClassName="my-custom-content" |
| 111 | + buttonClassName="my-custom-button" |
| 112 | +/> |
| 113 | +``` |
| 114 | + |
| 115 | +### Custom Content Renderer |
| 116 | + |
| 117 | +You can provide a custom content renderer to format the response content: |
| 118 | + |
| 119 | +```tsx |
| 120 | +import ReactMarkdown from "react-markdown"; |
| 121 | + |
| 122 | +<DefaultResponseRenderer |
| 123 | + response={{ |
| 124 | + id: "task-456", |
| 125 | + content: "# Important Decision\nThis requires your approval", |
| 126 | + }} |
| 127 | + status="inProgress" |
| 128 | + onRespond={handleResponse} |
| 129 | + ContentRenderer={({ content, className }) => ( |
| 130 | + <div className={className}> |
| 131 | + <ReactMarkdown>{content}</ReactMarkdown> |
| 132 | + </div> |
| 133 | + )} |
| 134 | +/>; |
| 135 | +``` |
| 136 | + |
| 137 | +### Custom Feedback Buttons |
| 138 | + |
| 139 | +You can provide custom button components: |
| 140 | + |
| 141 | +```tsx |
| 142 | +<DefaultResponseRenderer |
| 143 | + response={{ |
| 144 | + id: "task-789", |
| 145 | + content: "Please review these findings", |
| 146 | + }} |
| 147 | + status="inProgress" |
| 148 | + onRespond={handleResponse} |
| 149 | + FeedbackButton={({ label, onClick, className }) => ( |
| 150 | + <button |
| 151 | + onClick={onClick} |
| 152 | + className={`${className} ${ |
| 153 | + label === "Approve" ? "bg-green-500" : "bg-red-500" |
| 154 | + }`} |
| 155 | + > |
| 156 | + {label} |
| 157 | + </button> |
| 158 | + )} |
| 159 | +/> |
| 160 | +``` |
| 161 | + |
| 162 | +## Props Reference |
| 163 | + |
| 164 | +### `ResponseRendererProps` |
| 165 | + |
| 166 | +| Prop | Type | Description | Default | |
| 167 | +| ---------------------------- | ------------------------------------------------------------------ | -------------------------------------------------------- | ------------------------------------------ | |
| 168 | +| `response` | `Response` | The response data to render | - | |
| 169 | +| `status` | `ResponseStatus` | Current status: "inProgress", "complete", or "executing" | - | |
| 170 | +| `onRespond` | `(input: string) => void` | Function to call when a response is given | `undefined` | |
| 171 | +| `icons` | `{ expand?: React.ComponentType; collapse?: React.ComponentType }` | Custom icons for the component | Default icons | |
| 172 | +| `labels` | `ResponseRendererLabels` | Custom labels for the component | Default labels | |
| 173 | +| `ContentRenderer` | `React.ComponentType<ContentRendererProps>` | Custom component for rendering content | `DefaultContentRenderer` | |
| 174 | +| `FeedbackButton` | `React.ComponentType<FeedbackButtonProps>` | Custom component for rendering feedback buttons | `DefaultFeedbackButton` | |
| 175 | +| `CompletedFeedback` | `React.ComponentType<CompletedFeedbackProps>` | Custom component for rendering completed feedback | `DefaultCompletedFeedback` | |
| 176 | +| `className` | `string` | CSS class for the root element | `"copilotkit-response"` | |
| 177 | +| `contentClassName` | `string` | CSS class for the content element | `"copilotkit-response-content"` | |
| 178 | +| `actionsClassName` | `string` | CSS class for the actions container | `"copilotkit-response-actions"` | |
| 179 | +| `buttonClassName` | `string` | CSS class for feedback buttons | `"copilotkit-response-button"` | |
| 180 | +| `completedFeedbackClassName` | `string` | CSS class for completed feedback | `"copilotkit-response-completed-feedback"` | |
| 181 | + |
| 182 | +### `Response` Interface |
| 183 | + |
| 184 | +```typescript |
| 185 | +interface Response { |
| 186 | + id: string; |
| 187 | + content: string; |
| 188 | + metadata?: Record<string, any>; |
| 189 | +} |
| 190 | +``` |
| 191 | + |
| 192 | +### `ResponseRendererLabels` Interface |
| 193 | + |
| 194 | +```typescript |
| 195 | +interface ResponseRendererLabels { |
| 196 | + responseLabel?: string; |
| 197 | + approveLabel?: string; |
| 198 | + rejectLabel?: string; |
| 199 | + approvedMessage?: string; |
| 200 | + rejectedMessage?: string; |
| 201 | + feedbackSubmittedMessage?: string; |
| 202 | +} |
| 203 | +``` |
| 204 | + |
| 205 | +## Styling |
| 206 | + |
| 207 | +By default, CopilotKit components use minimal styling. You can customize the look and feel by either: |
| 208 | + |
| 209 | +1. Overriding the default CSS classes |
| 210 | +2. Providing your own class names via props |
| 211 | +3. Using the built-in CSS variables |
| 212 | + |
| 213 | +The component uses classes like `copilotkit-response`, `copilotkit-response-content`, and `copilotkit-response-button` that you can target for styling. |
| 214 | + |
| 215 | +## Examples |
| 216 | + |
| 217 | +### Human Approval Flow |
| 218 | + |
| 219 | +```tsx |
| 220 | +// Example: Crew is asking for user approval before taking an important action |
| 221 | +useCopilotAction({ |
| 222 | + name: "approve_restaurant_reservation", |
| 223 | + renderAndWaitForResponse({ status, args, respond }) { |
| 224 | + const reservation = args as { |
| 225 | + id: string; |
| 226 | + restaurant: string; |
| 227 | + time: string; |
| 228 | + guests: number; |
| 229 | + }; |
| 230 | + |
| 231 | + return ( |
| 232 | + <DefaultResponseRenderer |
| 233 | + response={{ |
| 234 | + id: reservation.id, |
| 235 | + content: `Would you like to make a reservation at ${reservation.restaurant} |
| 236 | + for ${reservation.guests} people at ${reservation.time}?`, |
| 237 | + metadata: reservation, |
| 238 | + }} |
| 239 | + onRespond={(input: string) => { |
| 240 | + if (input === "Approve") { |
| 241 | + // User approved the reservation |
| 242 | + respond?.("confirmed"); |
| 243 | + } else { |
| 244 | + // User rejected the reservation |
| 245 | + respond?.("cancelled"); |
| 246 | + } |
| 247 | + }} |
| 248 | + status={status as ResponseStatus} |
| 249 | + labels={{ |
| 250 | + approveLabel: "Confirm Reservation", |
| 251 | + rejectLabel: "Cancel Reservation", |
| 252 | + }} |
| 253 | + /> |
| 254 | + ); |
| 255 | + }, |
| 256 | +}); |
| 257 | +``` |
0 commit comments