mirror of https://github.com/kortix-ai/suna.git
fix: workflow text-area input fix
This commit is contained in:
parent
519c1da09c
commit
8a9644fcef
|
@ -1,6 +1,6 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { memo, useState } from "react";
|
import { memo, useState, useEffect, useCallback } from "react";
|
||||||
import { Handle, Position, NodeProps } from "@xyflow/react";
|
import { Handle, Position, NodeProps } from "@xyflow/react";
|
||||||
import { Play, Settings, Clock, Webhook, User, ChevronDown, ChevronUp, Brain } from "lucide-react";
|
import { Play, Settings, Clock, Webhook, User, ChevronDown, ChevronUp, Brain } from "lucide-react";
|
||||||
import { CardContent, CardHeader } from "@/components/ui/card";
|
import { CardContent, CardHeader } from "@/components/ui/card";
|
||||||
|
@ -35,6 +35,7 @@ const InputNode = memo(({ data, selected, id }: NodeProps) => {
|
||||||
const [isConfigOpen, setIsConfigOpen] = useState(false);
|
const [isConfigOpen, setIsConfigOpen] = useState(false);
|
||||||
const [isWebhookDialogOpen, setIsWebhookDialogOpen] = useState(false);
|
const [isWebhookDialogOpen, setIsWebhookDialogOpen] = useState(false);
|
||||||
const [isScheduleDialogOpen, setIsScheduleDialogOpen] = useState(false);
|
const [isScheduleDialogOpen, setIsScheduleDialogOpen] = useState(false);
|
||||||
|
const [localPrompt, setLocalPrompt] = useState(nodeData.prompt || '');
|
||||||
const { updateNodeData, workflowId } = useWorkflow();
|
const { updateNodeData, workflowId } = useWorkflow();
|
||||||
|
|
||||||
// Use the model selection hook
|
// Use the model selection hook
|
||||||
|
@ -50,6 +51,30 @@ const InputNode = memo(({ data, selected, id }: NodeProps) => {
|
||||||
// Initialize model if not set
|
// Initialize model if not set
|
||||||
const currentModel = nodeData.model || selectedModel;
|
const currentModel = nodeData.model || selectedModel;
|
||||||
|
|
||||||
|
// Update local prompt when nodeData changes
|
||||||
|
useEffect(() => {
|
||||||
|
setLocalPrompt(nodeData.prompt || '');
|
||||||
|
}, [nodeData.prompt]);
|
||||||
|
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
const debouncedUpdatePrompt = useCallback(
|
||||||
|
(() => {
|
||||||
|
let timeoutId: NodeJS.Timeout;
|
||||||
|
return (value: string) => {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
timeoutId = setTimeout(() => {
|
||||||
|
updateNodeData(id, { prompt: value });
|
||||||
|
}, 300);
|
||||||
|
};
|
||||||
|
})(),
|
||||||
|
[id, updateNodeData]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handlePromptChange = (value: string) => {
|
||||||
|
setLocalPrompt(value);
|
||||||
|
debouncedUpdatePrompt(value);
|
||||||
|
};
|
||||||
|
|
||||||
const handleModelChange = (modelId: string) => {
|
const handleModelChange = (modelId: string) => {
|
||||||
updateNodeData(id, { model: modelId });
|
updateNodeData(id, { model: modelId });
|
||||||
};
|
};
|
||||||
|
@ -182,8 +207,8 @@ const InputNode = memo(({ data, selected, id }: NodeProps) => {
|
||||||
<Textarea
|
<Textarea
|
||||||
id={`prompt-${id}`}
|
id={`prompt-${id}`}
|
||||||
placeholder="Describe what this workflow should accomplish..."
|
placeholder="Describe what this workflow should accomplish..."
|
||||||
value={nodeData.prompt || ''}
|
value={localPrompt}
|
||||||
onChange={(e) => updateNodeData(id, { prompt: e.target.value })}
|
onChange={(e) => handlePromptChange(e.target.value)}
|
||||||
className="min-h-[80px] text-sm"
|
className="min-h-[80px] text-sm"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { memo, useState, useEffect } from "react";
|
import { memo, useState, useEffect, useCallback } from "react";
|
||||||
import { Handle, Position, NodeProps } from "@xyflow/react";
|
import { Handle, Position, NodeProps } from "@xyflow/react";
|
||||||
import { Card, CardContent, CardHeader } from "@/components/ui/card";
|
import { Card, CardContent, CardHeader } from "@/components/ui/card";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
|
@ -39,6 +39,7 @@ const MCPNode = memo(({ data, selected, id }: NodeProps) => {
|
||||||
const nodeData = data as unknown as MCPNodeData;
|
const nodeData = data as unknown as MCPNodeData;
|
||||||
const [showConfigDialog, setShowConfigDialog] = useState(false);
|
const [showConfigDialog, setShowConfigDialog] = useState(false);
|
||||||
const [isConfigOpen, setIsConfigOpen] = useState(false);
|
const [isConfigOpen, setIsConfigOpen] = useState(false);
|
||||||
|
const [localInstructions, setLocalInstructions] = useState(nodeData.instructions || '');
|
||||||
const { updateNodeData } = useWorkflow();
|
const { updateNodeData } = useWorkflow();
|
||||||
|
|
||||||
const { data: credentialProfiles } = useCredentialProfilesForMcp(
|
const { data: credentialProfiles } = useCredentialProfilesForMcp(
|
||||||
|
@ -54,6 +55,28 @@ const MCPNode = memo(({ data, selected, id }: NodeProps) => {
|
||||||
nodeData.mcpType === "smithery" && !!nodeData.qualifiedName
|
nodeData.mcpType === "smithery" && !!nodeData.qualifiedName
|
||||||
);
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setLocalInstructions(nodeData.instructions || '');
|
||||||
|
}, [nodeData.instructions]);
|
||||||
|
|
||||||
|
const debouncedUpdateInstructions = useCallback(
|
||||||
|
(() => {
|
||||||
|
let timeoutId: NodeJS.Timeout;
|
||||||
|
return (value: string) => {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
timeoutId = setTimeout(() => {
|
||||||
|
updateNodeData(id, { instructions: value });
|
||||||
|
}, 300);
|
||||||
|
};
|
||||||
|
})(),
|
||||||
|
[id, updateNodeData]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleInstructionsChange = (value: string) => {
|
||||||
|
setLocalInstructions(value);
|
||||||
|
debouncedUpdateInstructions(value);
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (nodeData.mcpType === "smithery" && nodeData.qualifiedName && credentialProfiles && !nodeData.isConfigured) {
|
if (nodeData.mcpType === "smithery" && nodeData.qualifiedName && credentialProfiles && !nodeData.isConfigured) {
|
||||||
const hasProfiles = credentialProfiles.length > 0;
|
const hasProfiles = credentialProfiles.length > 0;
|
||||||
|
@ -215,13 +238,13 @@ const MCPNode = memo(({ data, selected, id }: NodeProps) => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{nodeData.instructions && !isConfigOpen && (
|
{localInstructions && !isConfigOpen && (
|
||||||
<div>
|
<div>
|
||||||
<Label className="text-xs font-medium text-muted-foreground">Instructions</Label>
|
<Label className="text-xs font-medium text-muted-foreground">Instructions</Label>
|
||||||
<div className="border-primary/20 text-xs text-muted-foreground bg-primary/10 p-2 rounded-lg border mt-1">
|
<div className="border-primary/20 text-xs text-muted-foreground bg-primary/10 p-2 rounded-lg border mt-1">
|
||||||
{nodeData.instructions.length > 50
|
{localInstructions.length > 50
|
||||||
? `${nodeData.instructions.substring(0, 50)}...`
|
? `${localInstructions.substring(0, 50)}...`
|
||||||
: nodeData.instructions}
|
: localInstructions}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
@ -246,8 +269,8 @@ const MCPNode = memo(({ data, selected, id }: NodeProps) => {
|
||||||
<Textarea
|
<Textarea
|
||||||
id={`instructions-${id}`}
|
id={`instructions-${id}`}
|
||||||
placeholder="Provide specific instructions for how this MCP server should be used in the workflow..."
|
placeholder="Provide specific instructions for how this MCP server should be used in the workflow..."
|
||||||
value={nodeData.instructions || ''}
|
value={localInstructions}
|
||||||
onChange={(e) => updateNodeData(id, { instructions: e.target.value })}
|
onChange={(e) => handleInstructionsChange(e.target.value)}
|
||||||
className="border-primary/20 min-h-[80px] text-sm"
|
className="border-primary/20 min-h-[80px] text-sm"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { memo, useState } from "react";
|
import { memo, useState, useEffect, useCallback } from "react";
|
||||||
import { Handle, Position, NodeProps } from "@xyflow/react";
|
import { Handle, Position, NodeProps } from "@xyflow/react";
|
||||||
import { Card, CardContent, CardHeader } from "@/components/ui/card";
|
import { Card, CardContent, CardHeader } from "@/components/ui/card";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
|
@ -36,8 +36,32 @@ interface ToolConnectionNodeData {
|
||||||
const ToolConnectionNode = memo(({ data, selected, id }: NodeProps) => {
|
const ToolConnectionNode = memo(({ data, selected, id }: NodeProps) => {
|
||||||
const nodeData = data as unknown as ToolConnectionNodeData;
|
const nodeData = data as unknown as ToolConnectionNodeData;
|
||||||
const [isConfigOpen, setIsConfigOpen] = useState(false);
|
const [isConfigOpen, setIsConfigOpen] = useState(false);
|
||||||
|
const [localInstructions, setLocalInstructions] = useState(nodeData.instructions || '');
|
||||||
const { updateNodeData } = useWorkflow();
|
const { updateNodeData } = useWorkflow();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setLocalInstructions(nodeData.instructions || '');
|
||||||
|
}, [nodeData.instructions]);
|
||||||
|
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
const debouncedUpdateInstructions = useCallback(
|
||||||
|
(() => {
|
||||||
|
let timeoutId: NodeJS.Timeout;
|
||||||
|
return (value: string) => {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
timeoutId = setTimeout(() => {
|
||||||
|
updateNodeData?.(id!, { instructions: value });
|
||||||
|
}, 300);
|
||||||
|
};
|
||||||
|
})(),
|
||||||
|
[id, updateNodeData]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleInstructionsChange = (value: string) => {
|
||||||
|
setLocalInstructions(value);
|
||||||
|
debouncedUpdateInstructions(value);
|
||||||
|
};
|
||||||
|
|
||||||
const getToolConfig = () => {
|
const getToolConfig = () => {
|
||||||
const toolId = nodeData.nodeId || nodeData.toolType;
|
const toolId = nodeData.nodeId || nodeData.toolType;
|
||||||
|
|
||||||
|
@ -145,13 +169,13 @@ const ToolConnectionNode = memo(({ data, selected, id }: NodeProps) => {
|
||||||
<span className="text-xs text-muted-foreground">Ready</span>
|
<span className="text-xs text-muted-foreground">Ready</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{nodeData.instructions && !isConfigOpen && (
|
{localInstructions && !isConfigOpen && (
|
||||||
<div>
|
<div>
|
||||||
<Label className="text-xs font-medium text-muted-foreground">Instructions</Label>
|
<Label className="text-xs font-medium text-muted-foreground">Instructions</Label>
|
||||||
<div className="border-primary/20 text-xs text-muted-foreground bg-primary/10 p-2 rounded-lg border mt-1">
|
<div className="border-primary/20 text-xs text-muted-foreground bg-primary/10 p-2 rounded-lg border mt-1">
|
||||||
{nodeData.instructions.length > 50
|
{localInstructions.length > 50
|
||||||
? `${nodeData.instructions.substring(0, 50)}...`
|
? `${localInstructions.substring(0, 50)}...`
|
||||||
: nodeData.instructions}
|
: localInstructions}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
@ -175,8 +199,8 @@ const ToolConnectionNode = memo(({ data, selected, id }: NodeProps) => {
|
||||||
<Textarea
|
<Textarea
|
||||||
id={`instructions-${id}`}
|
id={`instructions-${id}`}
|
||||||
placeholder="Provide specific instructions for how this tool should be used in the workflow..."
|
placeholder="Provide specific instructions for how this tool should be used in the workflow..."
|
||||||
value={nodeData.instructions || ''}
|
value={localInstructions}
|
||||||
onChange={(e) => updateNodeData?.(id!, { instructions: e.target.value })}
|
onChange={(e) => handleInstructionsChange(e.target.value)}
|
||||||
className="border-primary/20 min-h-[80px] text-sm"
|
className="border-primary/20 min-h-[80px] text-sm"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Reference in New Issue