import { LLMNodeData, UINode } from "lib/ai/workflow/workflow.interface"; import { SelectModel } from "../../select-model"; import { Button } from "ui/button"; import { InfoIcon, MessageCirclePlusIcon, TrashIcon, VariableIcon, } from "lucide-react"; import { Select, SelectTrigger, SelectContent, SelectItem } from "ui/select"; import { OutputSchemaMentionInput } from "../output-schema-mention-input"; import { Label } from "ui/label"; import { memo, useCallback, useEffect, useMemo, useState } from "react"; import { appStore } from "@/app/store"; import { Edge, useEdges, useNodes, useReactFlow } from "@xyflow/react"; import { useWorkflowStore } from "@/app/store/workflow.store"; import { useTranslations } from "next-intl"; import { Tooltip, TooltipContent, TooltipTrigger } from "ui/tooltip"; import { Separator } from "ui/separator"; import { Switch } from "ui/switch"; import { OutputSchemaEditor } from "../output-schema-editor"; import { defaultLLMNodeOutputSchema } from "lib/ai/workflow/create-ui-node"; import { ObjectJsonSchema7 } from "app-types/util"; import { toAny } from "lib/utils"; import { notify } from "lib/notify"; export const LLMNodeDataConfig = memo(function ({ data, }: { data: LLMNodeData; }) { const { updateNodeData } = useReactFlow(); const [structuredOutputOpen, setStructuredOutputOpen] = useState(false); const t = useTranslations(); const editable = useWorkflowStore((state) => { return ( state.processIds.length === 0 && state.hasEditAccess && !state.workflow?.isPublished ); }); const nodes = useNodes() as UINode[]; const edges = useEdges() as Edge[]; const model = useMemo(() => { return data.model || appStore.getState().chatModel!; }, [data.model]); const updateMessage = useCallback( (index: number, message: Partial) => { updateNodeData(data.id, (node) => { const prev = node.data as LLMNodeData; return { messages: prev.messages.map((m, i) => { if (i !== index) return m; return { ...m, ...message }; }), }; }); }, [data.id], ); const removeMessage = useCallback( (index: number) => { updateNodeData(data.id, (node) => { const prev = node.data as LLMNodeData; return { messages: prev.messages.filter((_, i) => i !== index), }; }); }, [data.id], ); const addMessage = useCallback(() => { updateNodeData(data.id, (node) => { const prev = node.data as LLMNodeData; return { messages: [...prev.messages, { role: "user" }], }; }); }, [data.id]); useEffect(() => { if (!data.model) { updateNodeData(data.id, { model: appStore.getState().chatModel!, }); } }, []); const isStructuredOutput = useMemo(() => { return data.outputSchema.properties?.answer?.type != "string"; }, [data.outputSchema]); return (
{ updateNodeData(data.id, { model, }); }} />
{ if (isStructuredOutput) { const ok = await notify.confirm({ description: t("Workflow.structuredOutputSwitchConfirm"), okText: t("Workflow.structuredOutputSwitchConfirmOk"), cancelText: t( "Workflow.structuredOutputSwitchConfirmCancel", ), }); if (!ok) return updateNodeData(data.id, { outputSchema: structuredClone( defaultLLMNodeOutputSchema, ), }); } setStructuredOutputOpen(true); }} checked={isStructuredOutput} />
{t("Workflow.structuredOutputDescription")}
{Object.keys(data.outputSchema.properties).flatMap((key) => { if ( key === "answer" && data.outputSchema.properties[key].type === "object" ) { return Object.keys( data.outputSchema.properties[key].properties ?? {}, ).map((property) => { return (
{`${key}.${property}`} { toAny( data.outputSchema.properties[key].properties![property], )?.type }
); }); } return [
{key} {data.outputSchema.properties[key].type}
, ]; })}
{t("Workflow.messagesDescription")}
{data.messages.map((message, index) => { return (
{ updateMessage(index, { content, }); }} />
); })}
{ updateNodeData(data.id, { outputSchema: { ...data.outputSchema, properties: { ...data.outputSchema.properties, answer: schema, }, }, }); }} >
); }); LLMNodeDataConfig.displayName = "LLMNodeDataConfig"; export const LLMNodeDataStack = memo(function ({ data, }: { data: LLMNodeData }) { if (!data.model) return null; const isTextResponse = data.outputSchema.properties?.answer?.type === "string"; return (
{data.model.model} {isTextResponse ? "text" : "object"}
); }); LLMNodeDataStack.displayName = "LLMNodeDataStack";