feat: refactor NodeConfigPanel and WorkflowNodeCard for improved readability and structure
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { useEffect, useMemo, useState } from "react"
|
import { useMemo, useState } from "react"
|
||||||
import { Trash2Icon } from "lucide-react"
|
import { Trash2Icon } from "lucide-react"
|
||||||
|
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
@@ -61,24 +61,6 @@ export function NodeConfigPanel({
|
|||||||
onChange: (nodeId: string, data: AIWorkflowDefinition["nodes"][number]["data"]) => void
|
onChange: (nodeId: string, data: AIWorkflowDefinition["nodes"][number]["data"]) => void
|
||||||
onDelete?: (nodeId: string) => void
|
onDelete?: (nodeId: string) => void
|
||||||
}) {
|
}) {
|
||||||
const [configText, setConfigText] = useState("{}")
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setConfigText(JSON.stringify(node?.data?.config ?? {}, null, 2))
|
|
||||||
}, [node?.id, node?.data?.config])
|
|
||||||
|
|
||||||
const configError = useMemo(() => {
|
|
||||||
if (!node) {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const parsed = JSON.parse(configText || "{}")
|
|
||||||
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? "" : "配置必须是 JSON 对象"
|
|
||||||
} catch {
|
|
||||||
return "JSON 格式错误"
|
|
||||||
}
|
|
||||||
}, [configText, node])
|
|
||||||
|
|
||||||
if (!node) {
|
if (!node) {
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full items-center justify-center p-6 text-sm text-muted-foreground">
|
<div className="flex h-full items-center justify-center p-6 text-sm text-muted-foreground">
|
||||||
@@ -148,6 +130,16 @@ export function NodeConfigPanel({
|
|||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
<div className="min-h-0 flex-1 space-y-5 overflow-y-auto p-4">
|
<div className="min-h-0 flex-1 space-y-5 overflow-y-auto p-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor={`node-type-${node.id}`}>节点类型</Label>
|
||||||
|
<Input
|
||||||
|
id={`node-type-${node.id}`}
|
||||||
|
value={node.type}
|
||||||
|
readOnly
|
||||||
|
className="font-mono text-xs text-muted-foreground"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor={`node-title-${node.id}`}>标题</Label>
|
<Label htmlFor={`node-title-${node.id}`}>标题</Label>
|
||||||
<Input
|
<Input
|
||||||
@@ -204,34 +196,63 @@ export function NodeConfigPanel({
|
|||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{showConfigJSON ? (
|
{showConfigJSON ? (
|
||||||
<div className="space-y-2">
|
<NodeConfigJSONField
|
||||||
<Label htmlFor={`node-config-${node.id}`}>配置 JSON</Label>
|
key={`${node.id}:${JSON.stringify(node.data?.config ?? {})}`}
|
||||||
<Textarea
|
nodeId={node.id}
|
||||||
id={`node-config-${node.id}`}
|
config={node.data?.config ?? {}}
|
||||||
value={configText}
|
onChange={(nextConfig) => updateData({ config: nextConfig })}
|
||||||
className="min-h-36 font-mono text-xs"
|
/>
|
||||||
spellCheck={false}
|
|
||||||
onChange={(event) => {
|
|
||||||
const next = event.target.value
|
|
||||||
setConfigText(next)
|
|
||||||
try {
|
|
||||||
const parsed = JSON.parse(next || "{}")
|
|
||||||
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
||||||
updateData({ config: parsed as Record<string, unknown> })
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
// The textarea keeps the draft while the user fixes invalid JSON.
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{configError ? <div className="text-xs text-destructive">{configError}</div> : null}
|
|
||||||
</div>
|
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function NodeConfigJSONField({
|
||||||
|
nodeId,
|
||||||
|
config,
|
||||||
|
onChange,
|
||||||
|
}: {
|
||||||
|
nodeId: string
|
||||||
|
config: Record<string, unknown>
|
||||||
|
onChange: (config: Record<string, unknown>) => void
|
||||||
|
}) {
|
||||||
|
const [configText, setConfigText] = useState(() => JSON.stringify(config, null, 2))
|
||||||
|
const configError = useMemo(() => {
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(configText || "{}")
|
||||||
|
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? "" : "配置必须是 JSON 对象"
|
||||||
|
} catch {
|
||||||
|
return "JSON 格式错误"
|
||||||
|
}
|
||||||
|
}, [configText])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor={`node-config-${nodeId}`}>配置 JSON</Label>
|
||||||
|
<Textarea
|
||||||
|
id={`node-config-${nodeId}`}
|
||||||
|
value={configText}
|
||||||
|
className="min-h-36 font-mono text-xs"
|
||||||
|
spellCheck={false}
|
||||||
|
onChange={(event) => {
|
||||||
|
const next = event.target.value
|
||||||
|
setConfigText(next)
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(next || "{}")
|
||||||
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
||||||
|
onChange(parsed as Record<string, unknown>)
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// The textarea keeps the draft while the user fixes invalid JSON.
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{configError ? <div className="text-xs text-destructive">{configError}</div> : null}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export function ConditionBranchConfigPanel({
|
export function ConditionBranchConfigPanel({
|
||||||
node,
|
node,
|
||||||
nodes,
|
nodes,
|
||||||
|
|||||||
@@ -39,11 +39,6 @@ export function WorkflowNodeCard({
|
|||||||
<div className="truncate text-sm font-semibold leading-5 text-foreground">
|
<div className="truncate text-sm font-semibold leading-5 text-foreground">
|
||||||
{title}
|
{title}
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-0.5 flex items-center gap-1.5">
|
|
||||||
<span className="min-w-0 truncate text-[11px] leading-4 text-muted-foreground">
|
|
||||||
{nodeType}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{spec?.description ? (
|
{spec?.description ? (
|
||||||
|
|||||||
Reference in New Issue
Block a user