Spaces:
Sleeping
Sleeping
| "use client"; | |
| import { | |
| ToolNodeData, | |
| UINode, | |
| WorkflowToolKey, | |
| } from "lib/ai/workflow/workflow.interface"; | |
| import { memo, useEffect, useMemo } from "react"; | |
| import { InfoIcon, VariableIcon, WrenchIcon } from "lucide-react"; | |
| import { useEdges, useNodes, useReactFlow } from "@xyflow/react"; | |
| import { appStore } from "@/app/store"; | |
| import { WorkflowToolSelect } from "../workflow-tool-select"; | |
| import { isString, toAny } from "lib/utils"; | |
| import { Separator } from "ui/separator"; | |
| import { SelectModel } from "@/components/select-model"; | |
| import { OutputSchemaMentionInput } from "../output-schema-mention-input"; | |
| import { useWorkflowStore } from "@/app/store/workflow.store"; | |
| import { MCPIcon } from "ui/mcp-icon"; | |
| import { useTranslations } from "next-intl"; | |
| import { Tooltip, TooltipContent, TooltipTrigger } from "ui/tooltip"; | |
| import { useMcpList } from "@/hooks/queries/use-mcp-list"; | |
| import { | |
| exaSearchSchema, | |
| exaSearchTool, | |
| exaContentsSchema, | |
| exaContentsTool, | |
| } from "lib/ai/tools/web/web-search"; | |
| import { DefaultToolName } from "lib/ai/tools"; | |
| export const ToolNodeDataConfig = memo(function ({ | |
| data, | |
| }: { | |
| data: ToolNodeData; | |
| }) { | |
| const t = useTranslations(); | |
| const { updateNodeData } = useReactFlow(); | |
| const nodes = useNodes() as UINode[]; | |
| const edges = useEdges(); | |
| const editable = useWorkflowStore((state) => { | |
| return ( | |
| state.processIds.length === 0 && | |
| state.hasEditAccess && | |
| !state.workflow?.isPublished | |
| ); | |
| }); | |
| const { data: mcpList } = useMcpList(); | |
| const toolList = useMemo<WorkflowToolKey[]>(() => { | |
| const mcpTools: WorkflowToolKey[] = (mcpList || []).flatMap((mcp) => { | |
| return mcp.toolInfo.map((tool) => { | |
| return { | |
| type: "mcp-tool", | |
| serverId: mcp.id, | |
| serverName: mcp.name, | |
| id: tool.name, | |
| description: tool.description, | |
| parameterSchema: tool.inputSchema, | |
| }; | |
| }); | |
| }); | |
| const defaultTools: WorkflowToolKey[] = [ | |
| { | |
| type: "app-tool", | |
| id: DefaultToolName.WebSearch, | |
| description: exaSearchTool.description!, | |
| parameterSchema: exaSearchSchema, | |
| }, | |
| { | |
| type: "app-tool", | |
| id: DefaultToolName.WebContent, | |
| description: exaContentsTool.description!, | |
| parameterSchema: exaContentsSchema, | |
| }, | |
| ]; | |
| return [...mcpTools, ...defaultTools]; | |
| }, [mcpList]); | |
| useEffect(() => { | |
| if (!data.model) { | |
| updateNodeData(data.id, { | |
| model: appStore.getState().chatModel!, | |
| }); | |
| } | |
| }, []); | |
| return ( | |
| <div className="flex flex-col gap-2 text-sm px-4"> | |
| <p className="text-sm font-semibold">{t("Common.tool")}</p> | |
| <WorkflowToolSelect | |
| tools={toolList} | |
| onChange={(tool) => { | |
| updateNodeData(data.id, { tool }); | |
| }} | |
| tool={data.tool} | |
| /> | |
| <p className="text-sm font-semibold my-2"> | |
| {t("Workflow.descriptionAndSchema")} | |
| </p> | |
| {data.tool?.description || | |
| Object.keys(data.tool?.parameterSchema?.properties || {}).length > 0 ? ( | |
| <div className="text-xs p-2 bg-background border rounded-md"> | |
| <p>{data.tool?.description}</p> | |
| {Object.keys(data.tool?.parameterSchema?.properties || {}).length > | |
| 0 && ( | |
| <div className="flex items-center flex-wrap gap-1 mt-2"> | |
| {Object.keys(data.tool?.parameterSchema?.properties || {}).map( | |
| (key) => { | |
| const isRequired = | |
| data.tool?.parameterSchema?.required?.includes(key); | |
| return ( | |
| <div | |
| key={key} | |
| className="mb-0.5 flex items-center text-xs px-1.5 py-0.5 bg-secondary rounded-md" | |
| > | |
| <VariableIcon className="size-3.5 text-blue-500" /> | |
| {isRequired && ( | |
| <span className="text-destructive">*</span> | |
| )} | |
| <span className="font-semibold">{key}</span> | |
| <span className="text-muted-foreground ml-2"> | |
| {isString(data.tool?.parameterSchema?.properties?.[key]) | |
| ? data.tool?.parameterSchema?.properties?.[key] | |
| : toAny(data.tool?.parameterSchema?.properties?.[key]) | |
| ?.type || "unknown"} | |
| </span> | |
| </div> | |
| ); | |
| }, | |
| )} | |
| </div> | |
| )} | |
| </div> | |
| ) : ( | |
| <div className="text-xs text-muted-foreground text-center py-2 border rounded-md"> | |
| {t("Workflow.noDescriptionAndSchema")} | |
| </div> | |
| )} | |
| <Separator className="my-4" /> | |
| <div className="flex items-center gap-2"> | |
| <p className="text-sm font-semibold my-2">Message</p> | |
| <SelectModel | |
| currentModel={data.model} | |
| onSelect={(model) => { | |
| updateNodeData(data.id, { | |
| model, | |
| }); | |
| }} | |
| /> | |
| <Tooltip> | |
| <TooltipTrigger asChild> | |
| <div className="p-1 hover:bg-secondary rounded cursor-pointer"> | |
| <InfoIcon className="size-3" /> | |
| </div> | |
| </TooltipTrigger> | |
| <TooltipContent className="p-4 whitespace-pre-wrap"> | |
| {t("Workflow.toolDescription")} | |
| </TooltipContent> | |
| </Tooltip> | |
| </div> | |
| <div className="w-full bg-secondary rounded-md p-2 min-h-20"> | |
| <OutputSchemaMentionInput | |
| currentNodeId={data.id} | |
| nodes={nodes} | |
| edges={edges} | |
| content={data.message} | |
| editable={editable} | |
| onChange={(content) => { | |
| updateNodeData(data.id, { | |
| message: content, | |
| }); | |
| }} | |
| /> | |
| </div> | |
| </div> | |
| ); | |
| }); | |
| ToolNodeDataConfig.displayName = "ToolNodeDataConfig"; | |
| export const ToolNodeStack = memo(function ({ data }: { data: ToolNodeData }) { | |
| const t = useTranslations(); | |
| const selectedToolLabel = useMemo(() => { | |
| if (!data.tool) | |
| return ( | |
| <> | |
| <WrenchIcon className="size-3" /> | |
| <span className="text-muted-foreground"> | |
| {t("Common.selectTool")} | |
| </span> | |
| </> | |
| ); | |
| if (data.tool.type == "mcp-tool") { | |
| return ( | |
| <> | |
| <MCPIcon className="size-3" /> | |
| <span className="font-bold">{data.tool.serverName}</span> | |
| <div className="bg-primary text-primary-foreground px-2 rounded-md truncate"> | |
| {data.tool.id} | |
| </div> | |
| </> | |
| ); | |
| } | |
| return ( | |
| <> | |
| <WrenchIcon className="size-3" /> | |
| <span className="font-semibold truncate">{data.tool.id}</span> | |
| </> | |
| ); | |
| }, [data.tool]); | |
| return ( | |
| <div className="flex flex-col gap-1 px-4 mt-4"> | |
| {!data.tool ? ( | |
| <div className="text-xs text-muted-foreground text-center py-2 border rounded-md"> | |
| {t("Common.noResults")} | |
| </div> | |
| ) : ( | |
| <div className="border bg-input text-[10px] rounded px-2 py-1 flex items-center gap-1"> | |
| {selectedToolLabel} | |
| </div> | |
| )} | |
| </div> | |
| ); | |
| }); | |
| ToolNodeStack.displayName = "ToolNodeStack"; | |