import * as React from "react" import { buttonVariants } from "@/components/ui/button" import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue, } from "@/components/ui/select" import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle, SheetTrigger, } from "@/components/ui/sheet" import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, } from "@/components/ui/form" import { Button } from "@/components/ui/button" import { cn } from "@/lib/utils" import { useAppSelector, useGraphs, } from "@/common/hooks" import { AddonDef, Graph, Destination, GraphEditor, ProtocolLabel as GraphConnProtocol, ProtocolLabel } from "@/common/graph" import { toast } from "sonner" import { BoxesIcon, ChevronRightIcon, LoaderCircleIcon, SettingsIcon, Trash2Icon, WrenchIcon } from "lucide-react" import { useForm } from "react-hook-form" import { zodResolver } from "@hookform/resolvers/zod" import { z } from "zod" import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuPortal, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger } from "../ui/dropdown" import { isLLM } from "@/common" import { compatibleTools, ModuleRegistry, ModuleTypeLabels, moduleRegistry, toolModuleRegistry } from "@/common/moduleConfig" export function RemoteModuleCfgSheet() { const addonModules = useAppSelector((state) => state.global.addonModules); const { getGraphNodeAddonByName, selectedGraph, update: updateGraph, installedAndRegisteredModulesMap, installedAndRegisteredToolModules } = useGraphs(); const metadata = React.useMemo(() => { const dynamicMetadata: Record = {}; if (selectedGraph) { Object.keys(installedAndRegisteredModulesMap).forEach((key) => { const moduleTypeKey = key as ModuleRegistry.NonToolModuleType; // Check if the current graph has a node whose name contains the ModuleType const hasMatchingNode = selectedGraph.nodes.some((node) => node.name.includes(moduleTypeKey) ); if (hasMatchingNode) { dynamicMetadata[moduleTypeKey] = { type: "string", options: installedAndRegisteredModulesMap[moduleTypeKey].map((module) => ({ value: module.name, label: module.label, })), }; } }); } return dynamicMetadata; }, [installedAndRegisteredModulesMap, selectedGraph]); const initialData = React.useMemo(() => { const dynamicInitialData: Record = {}; if (selectedGraph) { Object.keys(installedAndRegisteredModulesMap).forEach((key) => { const moduleTypeKey = key as ModuleRegistry.ModuleType; // Check if the current graph has a node whose name contains the ModuleType const hasMatchingNode = selectedGraph.nodes.some((node) => node.name.includes(moduleTypeKey) ); if (hasMatchingNode) { dynamicInitialData[moduleTypeKey] = getGraphNodeAddonByName(moduleTypeKey)?.addon; } }); } return dynamicInitialData; }, [installedAndRegisteredModulesMap, selectedGraph, getGraphNodeAddonByName]); return ( Module Picker You can adjust STT/TTS/LLM/LLMv2v extension modules here, the values will be written into property.json file when you save.
{ try { // Clone the selectedGraph to avoid mutating the original graph const selectedGraphCopy: Graph = JSON.parse(JSON.stringify(selectedGraph)); const nodes = selectedGraphCopy.nodes; let needUpdate = false; let enableRTCVideoSubscribe = false; // Retrieve the agora_rtc node const agoraRtcNode = GraphEditor.findNode(selectedGraphCopy, "agora_rtc"); if (!agoraRtcNode) { toast.error("agora_rtc node not found in the graph"); return; } // Update graph nodes with selected modules Object.entries(data).forEach(([key, value]) => { const node = nodes.find((n) => n.name === key); if (node && value && node.addon !== value) { node.addon = value; node.property = addonModules.find((module) => module.name === value)?.defaultProperty; needUpdate = true; } }); const reasoningNodesWithVisualSupport = GraphEditor.findNodeByPredicate(selectedGraphCopy, (node) => { const module = moduleRegistry[node.addon] if (!module) { return false } if (module.type === ModuleRegistry.ModuleType.LLM) { const llmModule = module as ModuleRegistry.LLMModule return isLLM(node.name) && llmModule.options.inputModalities.includes(ModuleRegistry.Modalities.Video) } if (module.type === ModuleRegistry.ModuleType.V2V) { const v2vModule = module as ModuleRegistry.V2VModule return isLLM(node.name) && v2vModule.options.inputModalities.includes(ModuleRegistry.Modalities.Video) } return false }) if (reasoningNodesWithVisualSupport) { GraphEditor.addOrUpdateConnection( selectedGraphCopy, `${agoraRtcNode.name}`, `${reasoningNodesWithVisualSupport.name}`, ProtocolLabel.VIDEO_FRAME, "video_frame" ); enableRTCVideoSubscribe = true; } // Identify removed tools and process them const currentToolsInGraph = nodes .filter((node) => installedAndRegisteredToolModules.map((module) => module.name).includes(node.addon)) .map((node) => node.addon); const removedTools = currentToolsInGraph.filter((tool) => !tools.includes(tool)); removedTools.forEach((tool) => { GraphEditor.removeNodeAndConnections(selectedGraphCopy, tool); needUpdate = true; }); // Process tool modules if (tools.length > 0) { if (!enableRTCVideoSubscribe) { enableRTCVideoSubscribe = tools.some((tool) => tool.includes("vision")) } tools.forEach((tool) => { if (!currentToolsInGraph.includes(tool)) { const toolModule = addonModules.find((module) => module.name === tool); if (!toolModule) { toast.error(`Module ${tool} not found`); return; } const toolNode = GraphEditor.addNode(selectedGraphCopy, tool, tool, "default", toolModule.defaultProperty) // Create or update connections const llmNode = GraphEditor.findNodeByPredicate(selectedGraphCopy, (node) => isLLM(node.name)); if (llmNode) { GraphEditor.linkTool(selectedGraphCopy, llmNode, toolNode, toolModuleRegistry[tool]); } } }); needUpdate = true; } GraphEditor.enableRTCVideoSubscribe(selectedGraphCopy, enableRTCVideoSubscribe); // Perform the update if changes are detected if (needUpdate) { await updateGraph(selectedGraphCopy.id, selectedGraphCopy); toast.success("Modules updated", { description: `Graph: ${selectedGraphCopy.id}`, }); } } catch (e: any) { toast.error(`Failed to update modules: ${e}`); } }} />
); } const GraphModuleCfgForm = ({ initialData, metadata, onUpdate, }: { initialData: Record; metadata: Record; onUpdate: (data: Record, tools: string[]) => void; }) => { const formSchema = z.record(z.string(), z.string().nullable()); const form = useForm>({ resolver: zodResolver(formSchema), defaultValues: initialData, }); const { selectedGraph, installedAndRegisteredToolModules } = useGraphs(); const { watch } = form; // Watch for changes in "llm" and "v2v" fields const llmValue = watch("llm"); const v2vValue = watch("v2v"); const toolModules = React.useMemo(() => { // Step 1: Get installed and registered tool modules const allToolModules = installedAndRegisteredToolModules || []; // Step 2: Determine the active module based on form values const activeModule = llmValue || v2vValue; // Step 3: Get compatible tools for the active module if (activeModule) { const compatibleToolNames = compatibleTools[activeModule] || []; return allToolModules.filter((module) => compatibleToolNames.includes(module.name)); } // If no LLM or V2V module is selected, return all tool modules return []; }, [installedAndRegisteredToolModules, selectedGraph, llmValue, v2vValue]); const onSubmit = (data: z.infer) => { onUpdate(data, selectedTools); }; const [selectedTools, setSelectedTools] = React.useState([]); // Synchronize selectedTools with selectedGraph and toolModules React.useEffect(() => { const toolNames = toolModules.map((module) => module.name); const graphToolAddons = selectedGraph?.nodes .filter((node) => toolNames.includes(node.addon)) .map((node) => node.addon) || []; setSelectedTools(graphToolAddons); }, [toolModules, selectedGraph]); // Desired field order const fieldOrder: ModuleRegistry.NonToolModuleType[] = [ ModuleRegistry.ModuleType.STT, ModuleRegistry.ModuleType.LLM, ModuleRegistry.ModuleType.V2V, ModuleRegistry.ModuleType.TTS, ]; return (
{fieldOrder.map( (key) => metadata[key] && ( // Check if the field exists in metadata
(
{ModuleTypeLabels[key]}
{isLLM(key) && ( } className="flex justify-between"> Add Tools {toolModules.length > 0 ? toolModules.map((module) => ( { if (!selectedTools.includes(module.name)) { setSelectedTools((prev) => [ ...prev, module.name, ]); } }}> {module.name} )) : ( No compatible tools )} )}
)} /> {isLLM(key) && selectedTools.length > 0 && (
{selectedTools.map((tool) => (
{tool}
{ setSelectedTools((prev) => prev.filter((t) => t !== tool)) }} // Delete action >
))}
)}
) )}
); };