webapp/components/function-calls-panel.tsx (102 lines of code) (raw):

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;