summaryrefslogtreecommitdiff
path: root/webapp/components/function-calls-panel.tsx
diff options
context:
space:
mode:
authorIlan Bigio <ilan@openai.com>2024-12-16 13:06:08 -0800
committerIlan Bigio <ilan@openai.com>2024-12-19 16:08:22 -0500
commit20009aed53d8864c9204d43a17895168a777d2cc (patch)
tree754dded819869bc34a8a2a02c66ea72dac1ccd24 /webapp/components/function-calls-panel.tsx
Initial commit
Diffstat (limited to 'webapp/components/function-calls-panel.tsx')
-rw-r--r--webapp/components/function-calls-panel.tsx118
1 files changed, 118 insertions, 0 deletions
diff --git a/webapp/components/function-calls-panel.tsx b/webapp/components/function-calls-panel.tsx
new file mode 100644
index 0000000..75be81f
--- /dev/null
+++ b/webapp/components/function-calls-panel.tsx
@@ -0,0 +1,118 @@
+import React, { useState } from "react";
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
+import { ScrollArea } from "@/components/ui/scroll-area";
+import { Input } from "@/components/ui/input";
+import { Badge } from "@/components/ui/badge";
+import { Button } from "@/components/ui/button";
+import { Item } from "@/components/types";
+
+type FunctionCallsPanelProps = {
+ items: Item[];
+ ws?: WebSocket | null; // pass down ws from parent
+};
+
+const FunctionCallsPanel: React.FC<FunctionCallsPanelProps> = ({
+ items,
+ ws,
+}) => {
+ const [responses, setResponses] = useState<Record<string, string>>({});
+
+ // Filter function_call items
+ const functionCalls = items.filter((it) => it.type === "function_call");
+
+ // For each function_call, check for a corresponding function_call_output
+ const functionCallsWithStatus = functionCalls.map((call) => {
+ const outputs = items.filter(
+ (it) => it.type === "function_call_output" && it.call_id === call.call_id
+ );
+ const outputItem = outputs[0];
+ const completed = call.status === "completed" || !!outputItem;
+ const response = outputItem ? outputItem.output : undefined;
+ return {
+ ...call,
+ completed,
+ response,
+ };
+ });
+
+ const handleChange = (call_id: string, value: string) => {
+ setResponses((prev) => ({ ...prev, [call_id]: value }));
+ };
+
+ const handleSubmit = (call: Item) => {
+ if (!ws || ws.readyState !== WebSocket.OPEN) return;
+ const call_id = call.call_id || "";
+ ws.send(
+ JSON.stringify({
+ type: "conversation.item.create",
+ item: {
+ type: "function_call_output",
+ call_id: call_id,
+ output: JSON.stringify(responses[call_id] || ""),
+ },
+ })
+ );
+ // Ask the model to continue after providing the tool response
+ ws.send(JSON.stringify({ type: "response.create" }));
+ };
+
+ return (
+ <Card className="flex flex-col h-full">
+ <CardHeader className="space-y-1.5 pb-0">
+ <CardTitle className="text-base font-semibold">
+ Function Calls
+ </CardTitle>
+ </CardHeader>
+ <CardContent className="flex-1 p-4">
+ <ScrollArea className="h-full">
+ <div className="space-y-4">
+ {functionCallsWithStatus.map((call) => (
+ <div
+ key={call.id}
+ className="rounded-lg border bg-card p-4 space-y-3"
+ >
+ <div className="flex items-center justify-between">
+ <h3 className="font-medium text-sm">{call.name}</h3>
+ <Badge variant={call.completed ? "default" : "secondary"}>
+ {call.completed ? "Completed" : "Pending"}
+ </Badge>
+ </div>
+
+ <div className="text-sm text-muted-foreground font-mono break-all">
+ {JSON.stringify(call.params)}
+ </div>
+
+ {!call.completed ? (
+ <div className="space-y-2">
+ <Input
+ placeholder="Enter response"
+ value={responses[call.call_id || ""] || ""}
+ onChange={(e) =>
+ handleChange(call.call_id || "", e.target.value)
+ }
+ />
+ <Button
+ variant="outline"
+ size="sm"
+ onClick={() => handleSubmit(call)}
+ disabled={!responses[call.call_id || ""]}
+ className="w-full"
+ >
+ Submit Response
+ </Button>
+ </div>
+ ) : (
+ <div className="text-sm rounded-md bg-muted p-3">
+ {JSON.stringify(JSON.parse(call.response || ""))}
+ </div>
+ )}
+ </div>
+ ))}
+ </div>
+ </ScrollArea>
+ </CardContent>
+ </Card>
+ );
+};
+
+export default FunctionCallsPanel;