import { NodeProps, NodeToolbar, Position } from "@xyflow/react"; import { ChevronDown, ChevronRight, Circle, ClipboardCheck, Edit, Trash2Icon } from "lucide-react"; import { memo, useEffect, useMemo, useState } from "react"; import { useWorkflow } from "@/app/workflow/[workflowId]/contexts/WorkflowContext"; import { FlowNodeData } from "@/components/flow/types"; import { LLMConfigSelector } from "@/components/LLMConfigSelector"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Switch } from "@/components/ui/switch"; import { Textarea } from "@/components/ui/textarea"; import { NodeContent } from "./common/NodeContent"; import { NodeEditDialog } from "./common/NodeEditDialog"; import { useNodeHandlers } from "./common/useNodeHandlers"; interface QANodeProps extends NodeProps { data: FlowNodeData; } export const QANode = memo(({ data, selected, id }: QANodeProps) => { const { open, setOpen, handleSaveNodeData, handleDeleteNode } = useNodeHandlers({ id }); const { saveWorkflow } = useWorkflow(); // Form state const [name, setName] = useState(data.name || "QA Analysis"); const [qaEnabled, setQaEnabled] = useState(data.qa_enabled ?? true); const [useWorkflowLlm, setUseWorkflowLlm] = useState(data.qa_use_workflow_llm ?? true); const [qaProvider, setQaProvider] = useState(data.qa_provider || "openai"); const [qaModel, setQaModel] = useState(data.qa_model || "gpt-4.1"); const [qaApiKey, setQaApiKey] = useState(data.qa_api_key || ""); const [qaSystemPrompt, setQaSystemPrompt] = useState(data.qa_system_prompt || ""); const [minCallDuration, setMinCallDuration] = useState(data.qa_min_call_duration ?? 15); const [qaVoicemailCalls, setQaVoicemailCalls] = useState(data.qa_voicemail_calls ?? false); const [qaSampleRate, setQaSampleRate] = useState(data.qa_sample_rate ?? 100); const isDirty = useMemo(() => { return ( name !== (data.name || "QA Analysis") || qaEnabled !== (data.qa_enabled ?? true) || useWorkflowLlm !== (data.qa_use_workflow_llm ?? true) || qaProvider !== (data.qa_provider || "openai") || qaModel !== (data.qa_model || "gpt-4.1") || qaApiKey !== (data.qa_api_key || "") || qaSystemPrompt !== (data.qa_system_prompt || "") || minCallDuration !== (data.qa_min_call_duration ?? 15) || qaVoicemailCalls !== (data.qa_voicemail_calls ?? false) || qaSampleRate !== (data.qa_sample_rate ?? 100) ); }, [name, qaEnabled, useWorkflowLlm, qaProvider, qaModel, qaApiKey, qaSystemPrompt, minCallDuration, qaVoicemailCalls, qaSampleRate, data]); const handleSave = async () => { handleSaveNodeData({ ...data, name, qa_enabled: qaEnabled, qa_use_workflow_llm: useWorkflowLlm, qa_provider: qaProvider, qa_model: qaModel, qa_api_key: qaApiKey, qa_system_prompt: qaSystemPrompt, qa_min_call_duration: minCallDuration, qa_voicemail_calls: qaVoicemailCalls, qa_sample_rate: qaSampleRate, }); setOpen(false); setTimeout(async () => { await saveWorkflow(); }, 100); }; const resetFormState = () => { setName(data.name || "QA Analysis"); setQaEnabled(data.qa_enabled ?? true); setUseWorkflowLlm(data.qa_use_workflow_llm ?? true); setQaProvider(data.qa_provider || "openai"); setQaModel(data.qa_model || "gpt-4.1"); setQaApiKey(data.qa_api_key || ""); setQaSystemPrompt(data.qa_system_prompt || ""); setMinCallDuration(data.qa_min_call_duration ?? 15); setQaVoicemailCalls(data.qa_voicemail_calls ?? false); setQaSampleRate(data.qa_sample_rate ?? 100); }; const handleOpenChange = (newOpen: boolean) => { if (newOpen) { resetFormState(); } setOpen(newOpen); }; useEffect(() => { if (open) { resetFormState(); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [data, open]); return ( <> } nodeType="qa" onDoubleClick={() => handleOpenChange(true)} nodeId={id} >
{data.qa_use_workflow_llm !== false ? "Workflow LLM" : `${data.qa_provider || "openai"}/${data.qa_model || "gpt-4.1"}`}
{data.qa_enabled !== false ? "Enabled" : "Disabled"}
{open && ( )} ); }); interface QANodeEditFormProps { name: string; setName: (value: string) => void; qaEnabled: boolean; setQaEnabled: (value: boolean) => void; useWorkflowLlm: boolean; setUseWorkflowLlm: (value: boolean) => void; qaProvider: string; setQaProvider: (value: string) => void; qaModel: string; setQaModel: (value: string) => void; qaApiKey: string; setQaApiKey: (value: string) => void; qaSystemPrompt: string; setQaSystemPrompt: (value: string) => void; minCallDuration: number; setMinCallDuration: (value: number) => void; qaVoicemailCalls: boolean; setQaVoicemailCalls: (value: boolean) => void; qaSampleRate: number; setQaSampleRate: (value: number) => void; } const QANodeEditForm = ({ name, setName, qaEnabled, setQaEnabled, useWorkflowLlm, setUseWorkflowLlm, qaProvider, setQaProvider, qaModel, setQaModel, qaApiKey, setQaApiKey, qaSystemPrompt, setQaSystemPrompt, minCallDuration, setMinCallDuration, qaVoicemailCalls, setQaVoicemailCalls, qaSampleRate, setQaSampleRate, }: QANodeEditFormProps) => { const [advancedOpen, setAdvancedOpen] = useState(false); return (
setName(e.target.value)} />
{!useWorkflowLlm && ( )}