summaryrefslogtreecommitdiff
path: root/webapp/components/session-configuration-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/session-configuration-panel.tsx
Initial commit
Diffstat (limited to 'webapp/components/session-configuration-panel.tsx')
-rw-r--r--webapp/components/session-configuration-panel.tsx291
1 files changed, 291 insertions, 0 deletions
diff --git a/webapp/components/session-configuration-panel.tsx b/webapp/components/session-configuration-panel.tsx
new file mode 100644
index 0000000..85c9fc1
--- /dev/null
+++ b/webapp/components/session-configuration-panel.tsx
@@ -0,0 +1,291 @@
+import React, { useState, useEffect } from "react";
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
+import { ScrollArea } from "@/components/ui/scroll-area";
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@/components/ui/select";
+import { Textarea } from "@/components/ui/textarea";
+import { Button } from "@/components/ui/button";
+import { Plus, Edit, Trash, Check, AlertCircle } from "lucide-react";
+import { toolTemplates } from "@/lib/tool-templates";
+import { ToolConfigurationDialog } from "./tool-configuration-dialog";
+import { BackendTag } from "./backend-tag";
+import { useBackendTools } from "@/lib/use-backend-tools";
+
+interface SessionConfigurationPanelProps {
+ callStatus: string;
+ onSave: (config: any) => void;
+}
+
+const SessionConfigurationPanel: React.FC<SessionConfigurationPanelProps> = ({
+ callStatus,
+ onSave,
+}) => {
+ const [instructions, setInstructions] = useState(
+ "You are a helpful assistant in a phone call."
+ );
+ const [voice, setVoice] = useState("ash");
+ const [tools, setTools] = useState<string[]>([]);
+ const [editingIndex, setEditingIndex] = useState<number | null>(null);
+ const [editingSchemaStr, setEditingSchemaStr] = useState("");
+ const [isJsonValid, setIsJsonValid] = useState(true);
+ const [openDialog, setOpenDialog] = useState(false);
+ const [selectedTemplate, setSelectedTemplate] = useState("");
+ const [saveStatus, setSaveStatus] = useState<
+ "idle" | "saving" | "saved" | "error"
+ >("idle");
+ const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);
+
+ // Custom hook to fetch backend tools every 3 seconds
+ const backendTools = useBackendTools("http://localhost:8081/tools", 3000);
+
+ // Track changes to determine if there are unsaved modifications
+ useEffect(() => {
+ setHasUnsavedChanges(true);
+ }, [instructions, voice, tools]);
+
+ // Reset save status after a delay when saved
+ useEffect(() => {
+ if (saveStatus === "saved") {
+ const timer = setTimeout(() => {
+ setSaveStatus("idle");
+ }, 3000);
+ return () => clearTimeout(timer);
+ }
+ }, [saveStatus]);
+
+ const handleSave = async () => {
+ setSaveStatus("saving");
+ try {
+ await onSave({
+ instructions,
+ voice,
+ tools: tools.map((tool) => JSON.parse(tool)),
+ });
+ setSaveStatus("saved");
+ setHasUnsavedChanges(false);
+ } catch (error) {
+ setSaveStatus("error");
+ }
+ };
+
+ const handleAddTool = () => {
+ setEditingIndex(null);
+ setEditingSchemaStr("");
+ setSelectedTemplate("");
+ setIsJsonValid(true);
+ setOpenDialog(true);
+ };
+
+ const handleEditTool = (index: number) => {
+ setEditingIndex(index);
+ setEditingSchemaStr(tools[index] || "");
+ setSelectedTemplate("");
+ setIsJsonValid(true);
+ setOpenDialog(true);
+ };
+
+ const handleDeleteTool = (index: number) => {
+ const newTools = [...tools];
+ newTools.splice(index, 1);
+ setTools(newTools);
+ };
+
+ const handleDialogSave = () => {
+ try {
+ JSON.parse(editingSchemaStr);
+ } catch {
+ return;
+ }
+ const newTools = [...tools];
+ if (editingIndex === null) {
+ newTools.push(editingSchemaStr);
+ } else {
+ newTools[editingIndex] = editingSchemaStr;
+ }
+ setTools(newTools);
+ setOpenDialog(false);
+ };
+
+ const handleTemplateChange = (val: string) => {
+ setSelectedTemplate(val);
+
+ // Determine if the selected template is from local or backend
+ let templateObj =
+ toolTemplates.find((t) => t.name === val) ||
+ backendTools.find((t: any) => t.name === val);
+
+ if (templateObj) {
+ setEditingSchemaStr(JSON.stringify(templateObj, null, 2));
+ setIsJsonValid(true);
+ }
+ };
+
+ const onSchemaChange = (value: string) => {
+ setEditingSchemaStr(value);
+ try {
+ JSON.parse(value);
+ setIsJsonValid(true);
+ } catch {
+ setIsJsonValid(false);
+ }
+ };
+
+ const getToolNameFromSchema = (schema: string): string => {
+ try {
+ const parsed = JSON.parse(schema);
+ return parsed?.name || "Untitled Tool";
+ } catch {
+ return "Invalid JSON";
+ }
+ };
+
+ const isBackendTool = (name: string): boolean => {
+ return backendTools.some((t: any) => t.name === name);
+ };
+
+ return (
+ <Card className="flex flex-col h-full w-full mx-auto">
+ <CardHeader className="pb-0 px-4 sm:px-6">
+ <div className="flex items-center justify-between">
+ <CardTitle className="text-base font-semibold">
+ Session Configuration
+ </CardTitle>
+ <div className="flex items-center gap-2">
+ {saveStatus === "error" ? (
+ <span className="text-xs text-red-500 flex items-center gap-1">
+ <AlertCircle className="h-3 w-3" />
+ Save failed
+ </span>
+ ) : hasUnsavedChanges ? (
+ <span className="text-xs text-muted-foreground">Not saved</span>
+ ) : (
+ <span className="text-xs text-muted-foreground flex items-center gap-1">
+ <Check className="h-3 w-3" />
+ Saved
+ </span>
+ )}
+ </div>
+ </div>
+ </CardHeader>
+ <CardContent className="flex-1 p-3 sm:p-5">
+ <ScrollArea className="h-full">
+ <div className="space-y-4 sm:space-y-6 m-1">
+ <div className="space-y-2">
+ <label className="text-sm font-medium leading-none">
+ Instructions
+ </label>
+ <Textarea
+ placeholder="Enter instructions"
+ className="min-h-[100px] resize-none"
+ value={instructions}
+ onChange={(e) => setInstructions(e.target.value)}
+ />
+ </div>
+
+ <div className="space-y-2">
+ <label className="text-sm font-medium leading-none">Voice</label>
+ <Select value={voice} onValueChange={setVoice}>
+ <SelectTrigger className="w-full">
+ <SelectValue placeholder="Select voice" />
+ </SelectTrigger>
+ <SelectContent>
+ {["ash", "ballad", "coral", "sage", "verse"].map((v) => (
+ <SelectItem key={v} value={v}>
+ {v}
+ </SelectItem>
+ ))}
+ </SelectContent>
+ </Select>
+ </div>
+
+ <div className="space-y-2">
+ <label className="text-sm font-medium leading-none">Tools</label>
+ <div className="space-y-2">
+ {tools.map((tool, index) => {
+ const name = getToolNameFromSchema(tool);
+ const backend = isBackendTool(name);
+ return (
+ <div
+ key={index}
+ className="flex items-center justify-between rounded-md border p-2 sm:p-3 gap-2"
+ >
+ <span className="text-sm truncate flex-1 min-w-0 flex items-center">
+ {name}
+ {backend && <BackendTag />}
+ </span>
+ <div className="flex gap-1 flex-shrink-0">
+ <Button
+ variant="ghost"
+ size="icon"
+ onClick={() => handleEditTool(index)}
+ className="h-8 w-8"
+ >
+ <Edit className="h-4 w-4" />
+ </Button>
+ <Button
+ variant="ghost"
+ size="icon"
+ onClick={() => handleDeleteTool(index)}
+ className="h-8 w-8"
+ >
+ <Trash className="h-4 w-4" />
+ </Button>
+ </div>
+ </div>
+ );
+ })}
+ <Button
+ variant="outline"
+ className="w-full"
+ onClick={handleAddTool}
+ >
+ <Plus className="h-4 w-4 mr-2" />
+ Add Tool
+ </Button>
+ </div>
+ </div>
+
+ <Button
+ className="w-full mt-4"
+ onClick={handleSave}
+ disabled={saveStatus === "saving" || !hasUnsavedChanges}
+ >
+ {saveStatus === "saving" ? (
+ "Saving..."
+ ) : saveStatus === "saved" ? (
+ <span className="flex items-center">
+ Saved Successfully
+ <Check className="ml-2 h-4 w-4" />
+ </span>
+ ) : saveStatus === "error" ? (
+ "Error Saving"
+ ) : (
+ "Save Configuration"
+ )}
+ </Button>
+ </div>
+ </ScrollArea>
+ </CardContent>
+
+ <ToolConfigurationDialog
+ open={openDialog}
+ onOpenChange={setOpenDialog}
+ editingIndex={editingIndex}
+ selectedTemplate={selectedTemplate}
+ editingSchemaStr={editingSchemaStr}
+ isJsonValid={isJsonValid}
+ onTemplateChange={handleTemplateChange}
+ onSchemaChange={onSchemaChange}
+ onSave={handleDialogSave}
+ backendTools={backendTools}
+ />
+ </Card>
+ );
+};
+
+export default SessionConfigurationPanel;