Files
patchy631--ai-engineering-hub/open-agent-builder/components/app/(home)/sections/workflow-builder/OutputSchemaPanel.tsx
T
2026-07-13 12:37:47 +08:00

161 lines
6.0 KiB
TypeScript

"use client";
import { motion } from "framer-motion";
import { useState, useEffect } from "react";
interface OutputField {
name: string;
type: "string" | "number" | "boolean" | "object" | "array";
description?: string;
}
interface OutputSchemaPanelProps {
nodeId: string;
currentSchema: OutputField[];
onUpdate: (schema: OutputField[]) => void;
}
export default function OutputSchemaPanel({ nodeId, currentSchema, onUpdate }: OutputSchemaPanelProps) {
const [schema, setSchema] = useState<OutputField[]>(currentSchema || []);
useEffect(() => {
const timeoutId = setTimeout(() => {
onUpdate(schema);
}, 500);
return () => clearTimeout(timeoutId);
}, [schema, onUpdate]);
const addField = () => {
setSchema([
...schema,
{
name: `field${schema.length + 1}`,
type: "string",
},
]);
};
const updateField = (index: number, updates: Partial<OutputField>) => {
setSchema(
schema.map((field, i) => (i === index ? { ...field, ...updates } : field))
);
};
const removeField = (index: number) => {
setSchema(schema.filter((_, i) => i !== index));
};
const generateTypeScriptInterface = () => {
if (schema.length === 0) return 'interface Output {\n // No fields defined\n}';
const fields = schema.map(f => {
const typeStr = f.type === 'array' ? 'any[]' : f.type;
const desc = f.description ? ` // ${f.description}\n` : '';
return `${desc} ${f.name}: ${typeStr};`;
}).join('\n');
return `interface ${nodeId.replace(/-/g, '_')}_Output {\n${fields}\n}`;
};
return (
<div className="space-y-16">
<div className="flex items-center justify-between">
<h3 className="text-label-small text-black-alpha-48">
Output Schema
</h3>
<button
onClick={addField}
className="px-10 py-6 bg-background-base hover:bg-black-alpha-4 border border-border-faint rounded-6 text-body-small text-accent-black transition-colors flex items-center gap-6"
>
<svg className="w-12 h-12" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4v16m8-8H4" />
</svg>
Add Field
</button>
</div>
{schema.length === 0 ? (
<div className="p-16 bg-background-base rounded-8 border border-border-faint text-center">
<p className="text-body-small text-black-alpha-48">
Define the shape of this node's output
</p>
</div>
) : (
<div className="space-y-12">
{schema.map((field, index) => (
<motion.div
key={index}
initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }}
className="p-12 bg-background-base rounded-8 border border-border-faint space-y-8"
>
<div className="flex items-start gap-8">
<div className="flex-1 grid grid-cols-2 gap-8">
{/* Field Name */}
<input
type="text"
value={field.name}
onChange={(e) => updateField(index, { name: e.target.value })}
placeholder="fieldName"
className="px-10 py-6 bg-white border border-border-faint rounded-6 text-body-small text-accent-black font-mono focus:outline-none focus:border-heat-100 transition-colors"
/>
{/* Field Type */}
<select
value={field.type}
onChange={(e) => updateField(index, { type: e.target.value as any })}
className="px-10 py-6 bg-white border border-border-faint rounded-6 text-body-small text-accent-black focus:outline-none focus:border-heat-100 transition-colors"
>
<option value="string">String</option>
<option value="number">Number</option>
<option value="boolean">Boolean</option>
<option value="object">Object</option>
<option value="array">Array</option>
</select>
</div>
{/* Remove Button */}
<button
onClick={() => removeField(index)}
className="w-24 h-24 rounded-4 hover:bg-black-alpha-4 transition-colors flex items-center justify-center group"
>
<svg className="w-12 h-12 text-black-alpha-48 group-hover:text-accent-black" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
{/* Field Description */}
<input
type="text"
value={field.description || ''}
onChange={(e) => updateField(index, { description: e.target.value })}
placeholder="Field description (optional)"
className="w-full px-10 py-6 bg-white border border-border-faint rounded-6 text-body-small text-black-alpha-48 focus:outline-none focus:border-heat-100 transition-colors"
/>
</motion.div>
))}
</div>
)}
{/* TypeScript Preview */}
{schema.length > 0 && (
<div>
<label className="block text-label-small text-black-alpha-48 mb-8">
TypeScript Interface
</label>
<div className="p-12 bg-gray-900 rounded-8 border border-border-faint">
<pre className="text-body-small text-heat-100 font-mono whitespace-pre-wrap">
{generateTypeScriptInterface()}
</pre>
</div>
<p className="text-body-small text-black-alpha-48 mt-8">
Access fields: <code className="font-mono text-heat-100">state.variables.{nodeId}.fieldName</code>
</p>
</div>
)}
</div>
);
}