Skip to content

Commit e1de032

Browse files
authored
fix: synchronously execute renderAndWaitForResponse (CopilotKit#1861)
Signed-off-by: Tyler Slaton <tyler@copilotkit.ai>
1 parent cf90f85 commit e1de032

8 files changed

Lines changed: 437 additions & 82 deletions

File tree

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
---
2+
"@copilotkit/react-core": patch
3+
"@copilotkit/react-ui": patch
4+
---
5+
6+
- fix: synchronously execute renderAndWaitForResponse
7+
8+
Previously, it was impossible to execute multiple human-in-the-loop (renderAndWaitForResponse)
9+
calls in a row. Ultimately this was due to an issue with how CopilotKit was rendering the updates
10+
when multiple renderAndWaitForResponse actions appeared on screen due to a reference based approach.
11+
12+
With this change, actions will be executed in a synchronous way appearing almost queue like. This
13+
works with any combination of action given much more freedom when asking for user input.
14+
15+
Signed-off-by: Tyler Slaton <tyler@copilotkit.ai>
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
"use client";
2+
3+
import { CopilotChat } from "@copilotkit/react-ui";
4+
import "./styles.css";
5+
import { CopilotKit, useCopilotAction, useCopilotChat } from "@copilotkit/react-core";
6+
import { useSearchParams } from "next/navigation";
7+
import { MessageRole, TextMessage } from "@copilotkit/runtime-client-gql";
8+
9+
const testMessages = [
10+
{
11+
name: "Multiple of the same action",
12+
message:
13+
"Get the weather 3 times all at once, you decide everything. Do not ask me for anything. At the end, tell me what the weather was between them.",
14+
},
15+
{
16+
name: "Multiple different actions",
17+
message:
18+
"Get the weather and the hotel all at once, you decide everything. Do not ask me for anything. At the end, tell me what the weather and hotel was.",
19+
},
20+
{
21+
name: "Multiple HITL actions and non-hitl actions",
22+
message:
23+
"Get the weather, hotel and flight all at once, you decide everything. Do not ask me for anything.",
24+
},
25+
{
26+
name: "Add a message",
27+
message: "Add a message via your tool. Do not ask me for anything.",
28+
},
29+
];
30+
31+
export default function PanelPage() {
32+
const searchParams = useSearchParams();
33+
const serviceAdapter = searchParams.get("serviceAdapter") || "openai";
34+
const runtimeUrl =
35+
searchParams.get("runtimeUrl") || `/api/copilotkit?serviceAdapter=${serviceAdapter}`;
36+
const publicApiKey = searchParams.get("publicApiKey");
37+
const copilotKitProps: Partial<React.ComponentProps<typeof CopilotKit>> = {
38+
runtimeUrl,
39+
publicApiKey: publicApiKey || undefined,
40+
};
41+
42+
return (
43+
<CopilotKit {...copilotKitProps}>
44+
<TravelPlanner />
45+
</CopilotKit>
46+
);
47+
}
48+
49+
function TravelPlanner() {
50+
const { appendMessage } = useCopilotChat();
51+
52+
// regular action
53+
useCopilotAction({
54+
name: "getFlight",
55+
followUp: false,
56+
render() {
57+
return <div>Flight</div>;
58+
},
59+
});
60+
61+
// backend action
62+
useCopilotAction({
63+
name: "getImageUrl",
64+
followUp: true,
65+
render() {
66+
return <div>Image</div>;
67+
},
68+
});
69+
70+
// hitl action 1
71+
useCopilotAction({
72+
name: "getWeather",
73+
renderAndWaitForResponse({ status, respond }) {
74+
return (
75+
<div className="flex flex-col gap-2 bg-blue-500/50 p-4 border border-blue-500 rounded-md w-1/2">
76+
<p>Weather</p>
77+
<p>Status: {status}</p>
78+
{status !== "complete" && (
79+
<button
80+
className="bg-blue-500 text-white p-2 rounded-md"
81+
onClick={() => respond?.("the weather is 70 degrees")}
82+
>
83+
Continue
84+
</button>
85+
)}
86+
</div>
87+
);
88+
},
89+
});
90+
91+
// hitl action 2
92+
useCopilotAction({
93+
name: "getHotel",
94+
renderAndWaitForResponse({ status, args, respond }) {
95+
return (
96+
<div className="flex flex-col gap-2 bg-blue-500/50 p-4 border border-blue-500 rounded-md w-1/2">
97+
<p>Hotel</p>
98+
<p>Status: {status}</p>
99+
{status !== "complete" && (
100+
<button
101+
className="bg-blue-500 text-white p-2 rounded-md"
102+
onClick={() => respond?.("Marriott")}
103+
>
104+
Continue
105+
</button>
106+
)}
107+
</div>
108+
);
109+
},
110+
});
111+
112+
// add a message with followUp false
113+
useCopilotAction({
114+
name: "addMessage",
115+
followUp: false,
116+
render() {
117+
return (
118+
<div className="flex flex-col gap-2 bg-blue-500/50 p-4 border border-blue-500 rounded-md w-1/2">
119+
<p>Adding a message...</p>
120+
</div>
121+
);
122+
},
123+
handler: async () => {
124+
appendMessage(
125+
new TextMessage({
126+
role: MessageRole.Assistant,
127+
content: "What is the weather in San Francisco?",
128+
}),
129+
{
130+
followUp: false,
131+
},
132+
);
133+
},
134+
});
135+
136+
return (
137+
<div className="w-screen h-screen flex items-center justify-center">
138+
<CopilotChat
139+
className="w-4/5 h-4/5 border p-4 rounded-xl border-gray-200"
140+
labels={{
141+
initial: "Hi you! 👋 Let's book your next vacation. Ask me anything.",
142+
}}
143+
instructions="You are a travel planner. You help the user plan their vacation. After presenting something, don't summarize, but keep the reply short."
144+
/>
145+
{/*
146+
----------------------------------------------------------------
147+
Buttons for triggering different cases
148+
----------------------------------------------------------------
149+
*/}
150+
<div className="flex flex-col gap-2 px-4">
151+
{testMessages.map((testMessage) => (
152+
<div key={testMessage.name}>
153+
<button
154+
className="bg-blue-500 text-white p-2 rounded-md"
155+
onClick={() =>
156+
appendMessage(
157+
new TextMessage({ role: MessageRole.User, content: testMessage.message }),
158+
{},
159+
)
160+
}
161+
>
162+
{testMessage.name}
163+
</button>
164+
</div>
165+
))}
166+
</div>
167+
</div>
168+
);
169+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
html,
2+
body {
3+
height: 100%;
4+
margin: 0;
5+
background-color: white;
6+
}

CopilotKit/examples/next-openai/src/lib/dynamic-service-adapter.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ async function getAzureOpenAIAdapter() {
4040

4141
async function getAnthropicAdapter() {
4242
const { AnthropicAdapter } = await import("@copilotkit/runtime");
43-
return new AnthropicAdapter();
43+
return new AnthropicAdapter({ model: "claude-3-7-sonnet-20250219" });
4444
}
4545

4646
async function getGeminiAdapter() {

0 commit comments

Comments
 (0)