=== NEW === - studio/ — MCPEngine Studio scaffold (Next.js monorepo, build plan) - docs/FACTORY-V2.md — Factory v2 architecture doc - docs/CALENDLY_MCP_BUILD_SUMMARY.md — Calendly MCP build report === UPDATED SERVERS === - fieldedge: Added jobs-tools, UI build script, main entry update - lightspeed: Updated main + server entry points - squarespace: Added collection-browser + page-manager apps - toast: Added main + server entry points === INFRA === - infra/command-center/state.json — Updated pipeline state - infra/command-center/FACTORY-V2.md — Factory v2 operator playbook
153 lines
5.1 KiB
TypeScript
153 lines
5.1 KiB
TypeScript
'use client';
|
|
|
|
import React, { useState, useCallback } from 'react';
|
|
import { GripVertical, Trash2, ChevronDown, ChevronRight } from 'lucide-react';
|
|
|
|
import type { SchemaProperty } from '@mcpengine/ai-pipeline/types';
|
|
|
|
interface ParamEditorProps {
|
|
name: string;
|
|
property: SchemaProperty;
|
|
required: boolean;
|
|
onUpdate: (updates: Partial<SchemaProperty>) => void;
|
|
onToggleRequired: () => void;
|
|
onDelete: () => void;
|
|
}
|
|
|
|
const TYPE_OPTIONS = ['string', 'number', 'boolean', 'array', 'object'] as const;
|
|
|
|
export function ParamEditor({
|
|
name,
|
|
property,
|
|
required,
|
|
onUpdate,
|
|
onToggleRequired,
|
|
onDelete,
|
|
}: ParamEditorProps) {
|
|
const [expanded, setExpanded] = useState(false);
|
|
|
|
const toggleExpand = useCallback(() => setExpanded((prev) => !prev), []);
|
|
|
|
return (
|
|
<div className="bg-gray-800/60 border border-gray-700 rounded-lg overflow-hidden">
|
|
{/* Collapsed header row */}
|
|
<div className="flex items-center gap-2 px-2 py-2">
|
|
{/* Drag handle */}
|
|
<GripVertical className="w-3.5 h-3.5 text-gray-600 cursor-grab shrink-0" />
|
|
|
|
{/* Expand toggle */}
|
|
<button onClick={toggleExpand} className="shrink-0 text-gray-500 hover:text-gray-300">
|
|
{expanded ? (
|
|
<ChevronDown className="w-3.5 h-3.5" />
|
|
) : (
|
|
<ChevronRight className="w-3.5 h-3.5" />
|
|
)}
|
|
</button>
|
|
|
|
{/* Name */}
|
|
<span className="text-sm text-gray-200 font-mono flex-1 truncate">{name}</span>
|
|
|
|
{/* Type badge */}
|
|
<span className="text-[10px] bg-gray-700 text-gray-400 px-1.5 py-0.5 rounded shrink-0">
|
|
{property.type}
|
|
</span>
|
|
|
|
{/* Required badge */}
|
|
{required && (
|
|
<span className="text-[10px] bg-amber-500/20 text-amber-400 px-1.5 py-0.5 rounded shrink-0">
|
|
req
|
|
</span>
|
|
)}
|
|
|
|
{/* Delete */}
|
|
<button
|
|
onClick={onDelete}
|
|
className="shrink-0 p-1 text-gray-600 hover:text-red-400 transition-colors"
|
|
>
|
|
<Trash2 className="w-3 h-3" />
|
|
</button>
|
|
</div>
|
|
|
|
{/* Expanded fields */}
|
|
{expanded && (
|
|
<div className="px-3 pb-3 pt-1 space-y-2.5 border-t border-gray-700/50">
|
|
{/* Name input */}
|
|
<div>
|
|
<label className="block text-[10px] text-gray-500 mb-1">Name</label>
|
|
<input
|
|
type="text"
|
|
value={name}
|
|
readOnly
|
|
className="w-full bg-gray-800 border border-gray-700 rounded px-2 py-1.5 text-xs text-gray-300 font-mono outline-none"
|
|
/>
|
|
</div>
|
|
|
|
{/* Type select */}
|
|
<div>
|
|
<label className="block text-[10px] text-gray-500 mb-1">Type</label>
|
|
<select
|
|
value={property.type}
|
|
onChange={(e) => onUpdate({ type: e.target.value })}
|
|
className="w-full bg-gray-800 border border-gray-700 rounded px-2 py-1.5 text-xs text-gray-300 outline-none"
|
|
>
|
|
{TYPE_OPTIONS.map((t) => (
|
|
<option key={t} value={t}>
|
|
{t}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
|
|
{/* Required toggle */}
|
|
<div className="flex items-center justify-between">
|
|
<label className="text-[10px] text-gray-500">Required</label>
|
|
<button
|
|
onClick={onToggleRequired}
|
|
className={`
|
|
relative w-8 h-4.5 rounded-full transition-colors duration-200
|
|
${required ? 'bg-amber-500' : 'bg-gray-600'}
|
|
`}
|
|
>
|
|
<span
|
|
className={`
|
|
absolute top-0.5 left-0.5 w-3.5 h-3.5 bg-white rounded-full
|
|
transition-transform duration-200
|
|
${required ? 'translate-x-3.5' : 'translate-x-0'}
|
|
`}
|
|
/>
|
|
</button>
|
|
</div>
|
|
|
|
{/* Description */}
|
|
<div>
|
|
<label className="block text-[10px] text-gray-500 mb-1">Description</label>
|
|
<input
|
|
type="text"
|
|
value={property.description}
|
|
onChange={(e) => onUpdate({ description: e.target.value })}
|
|
placeholder="Describe this parameter..."
|
|
className="w-full bg-gray-800 border border-gray-700 rounded px-2 py-1.5 text-xs text-gray-300 outline-none focus:border-indigo-500 transition-colors"
|
|
/>
|
|
</div>
|
|
|
|
{/* Default value */}
|
|
<div>
|
|
<label className="block text-[10px] text-gray-500 mb-1">Default Value</label>
|
|
<input
|
|
type="text"
|
|
value={property.default != null ? String(property.default) : ''}
|
|
onChange={(e) =>
|
|
onUpdate({
|
|
default: e.target.value === '' ? undefined : e.target.value,
|
|
})
|
|
}
|
|
placeholder="Optional default..."
|
|
className="w-full bg-gray-800 border border-gray-700 rounded px-2 py-1.5 text-xs text-gray-300 outline-none focus:border-indigo-500 transition-colors"
|
|
/>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|