Skip to content

Commit feada10

Browse files
committed
implement renderAndWait
1 parent 56d7e78 commit feada10

3 files changed

Lines changed: 158 additions & 7 deletions

File tree

CopilotKit/packages/react-core/src/hooks/use-chat.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,10 @@ export function useChat(options: UseChatOptions): UseChatHelpers {
288288
}
289289
// execute action
290290
try {
291+
// We update the message state before calling the handler so that the render
292+
// function can be called with `executing` state
293+
setMessages([...previousMessages, ...newMessages]);
294+
291295
const result = await onFunctionCall({
292296
messages: previousMessages,
293297
name: message.name,

CopilotKit/packages/react-core/src/hooks/use-copilot-action.ts

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { useRef, useEffect } from "react";
2-
import { FrontendAction } from "../types/frontend-action";
2+
import { FrontendAction, ActionRenderProps, ActionRenderPropsWait } from "../types/frontend-action";
33
import { useCopilotContext } from "../context/copilot-context";
44
import { Parameter, randomId } from "@copilotkit/shared";
55

@@ -18,6 +18,44 @@ export function useCopilotAction<const T extends Parameter[] | [] = []>(
1818
): void {
1919
const { setAction, removeAction, actions, chatComponentsCache } = useCopilotContext();
2020
const idRef = useRef<string>(randomId());
21+
const renderAndWaitRef = useRef<RenderAndWait | null>(null);
22+
23+
// clone the action to avoid mutating the original object
24+
action = { ...action };
25+
26+
// If the developer provides a renderAndWait function, we transform the action
27+
// to use a promise internally, so that we can treat it like a normal action.
28+
if (action.renderAndWait) {
29+
const renderAndWait = action.renderAndWait!;
30+
31+
// remove the renderAndWait function from the action
32+
action.renderAndWait = undefined;
33+
34+
// add a handler that will be called when the action is executed
35+
action.handler = (async () => {
36+
// we create a new promise when the handler is called
37+
let resolve: (result: any) => void;
38+
let reject: (error: any) => void;
39+
const promise = new Promise<any>((resolvePromise, rejectPromise) => {
40+
resolve = resolvePromise;
41+
reject = rejectPromise;
42+
});
43+
renderAndWaitRef.current = { promise, resolve: resolve!, reject: reject! };
44+
// then we await the promise (it will be resolved in the original renderAndWait function)
45+
return await promise;
46+
}) as any;
47+
48+
// add a render function that will be called when the action is rendered
49+
action.render = ((props: ActionRenderProps<any>): React.ReactElement => {
50+
const waitProps: ActionRenderPropsWait<any> = {
51+
status: props.status as any,
52+
args: props.args,
53+
result: props.result,
54+
handler: props.status === "executing" ? renderAndWaitRef.current!.resolve : undefined,
55+
};
56+
return renderAndWait(waitProps);
57+
}) as any;
58+
}
2159

2260
// If the developer doesn't provide dependencies, we assume they want to
2361
// update handler and render function when the action object changes.
@@ -62,6 +100,52 @@ export function useCopilotAction<const T extends Parameter[] | [] = []>(
62100
]);
63101
}
64102

103+
interface RenderAndWait {
104+
promise: Promise<any>;
105+
resolve: (result: any) => void;
106+
reject: (error: any) => void;
107+
}
108+
109+
function transformRenderAndWaitAction<T extends Parameter[] | []>(
110+
action: FrontendAction<T>,
111+
): FrontendAction<T> {
112+
console.log("transformRenderAndWaitAction", action);
113+
114+
let resolvePromise: (result: any) => void;
115+
const promise = new Promise<any>((resolve, reject) => {
116+
resolvePromise = function (result: any) {
117+
console.log("resolvePromise", result);
118+
resolve(result);
119+
};
120+
});
121+
let handler = async () => {
122+
try {
123+
console.log("awaiting promise");
124+
const result = await promise;
125+
console.log("result", result);
126+
return result;
127+
} catch (error) {
128+
console.error("Error in handler:", error);
129+
throw error;
130+
}
131+
};
132+
const render = (props: ActionRenderProps<any>): React.ReactElement => {
133+
const waitProps: ActionRenderPropsWait<any> = {
134+
status: props.status as any,
135+
args: props.args,
136+
result: props.result,
137+
handler: props.status === "executing" ? resolvePromise : undefined,
138+
};
139+
return action.renderAndWait!(waitProps);
140+
};
141+
return {
142+
...action,
143+
render: render as any,
144+
handler: handler as any,
145+
renderAndWait: undefined,
146+
};
147+
}
148+
65149
// Usage Example:
66150
// useCopilotAction({
67151
// name: "myAction",

CopilotKit/packages/react-core/src/types/frontend-action.ts

Lines changed: 69 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,48 @@ interface CompleteStateNoArgs<T extends Parameter[] | [] = []> {
3737
result: any;
3838
}
3939

40+
interface InProgressStateWait<T extends Parameter[] | [] = []> {
41+
status: "inProgress";
42+
args: Partial<MappedParameterTypes<T>>;
43+
handler: undefined;
44+
result: undefined;
45+
}
46+
47+
interface ExecutingStateWait<T extends Parameter[] | [] = []> {
48+
status: "executing";
49+
args: MappedParameterTypes<T>;
50+
handler: (result: any) => void;
51+
result: undefined;
52+
}
53+
54+
interface CompleteStateWait<T extends Parameter[] | [] = []> {
55+
status: "complete";
56+
args: MappedParameterTypes<T>;
57+
handler: undefined;
58+
result: any;
59+
}
60+
61+
interface InProgressStateNoArgsWait<T extends Parameter[] | [] = []> {
62+
status: "inProgress";
63+
args: Partial<MappedParameterTypes<T>>;
64+
handler: undefined;
65+
result: undefined;
66+
}
67+
68+
interface ExecutingStateNoArgsWait<T extends Parameter[] | [] = []> {
69+
status: "executing";
70+
args: MappedParameterTypes<T>;
71+
handler: (result: any) => void;
72+
result: undefined;
73+
}
74+
75+
interface CompleteStateNoArgsWait<T extends Parameter[] | [] = []> {
76+
status: "complete";
77+
args: MappedParameterTypes<T>;
78+
handler: undefined;
79+
result: any;
80+
}
81+
4082
export type ActionRenderProps<T extends Parameter[] | [] = []> =
4183
| CompleteState<T>
4284
| ExecutingState<T>
@@ -47,13 +89,34 @@ export type ActionRenderPropsNoArgs<T extends Parameter[] | [] = []> =
4789
| ExecutingStateNoArgs<T>
4890
| InProgressStateNoArgs<T>;
4991

92+
export type ActionRenderPropsWait<T extends Parameter[] | [] = []> =
93+
| CompleteStateWait<T>
94+
| ExecutingStateWait<T>
95+
| InProgressStateWait<T>;
96+
97+
export type ActionRenderPropsNoArgsWait<T extends Parameter[] | [] = []> =
98+
| CompleteStateNoArgsWait<T>
99+
| ExecutingStateNoArgsWait<T>
100+
| InProgressStateNoArgsWait<T>;
101+
50102
export type FrontendAction<T extends Parameter[] | [] = []> = Action<T> & {
51103
disabled?: boolean;
52-
render?:
53-
| string
54-
| (T extends []
55-
? (props: ActionRenderPropsNoArgs<T>) => string | React.ReactElement
56-
: (props: ActionRenderProps<T>) => string | React.ReactElement);
57-
};
104+
} & (
105+
| {
106+
render?:
107+
| string
108+
| (T extends []
109+
? (props: ActionRenderPropsNoArgs<T>) => string | React.ReactElement
110+
: (props: ActionRenderProps<T>) => string | React.ReactElement);
111+
renderAndWait?: never;
112+
}
113+
| {
114+
render?: never;
115+
renderAndWait: T extends []
116+
? (props: ActionRenderPropsNoArgsWait<T>) => React.ReactElement
117+
: (props: ActionRenderPropsWait<T>) => React.ReactElement;
118+
handler?: never;
119+
}
120+
);
58121

59122
export type RenderFunctionStatus = ActionRenderProps<any>["status"];

0 commit comments

Comments
 (0)