mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-04-08 03:00:28 -04:00
feat(builder): Rewrite node input UI (#7626)
- feat(builder): Rewrite & split up `NodeInputField`
- Create `NodeObjectInputTree`
- Create `NodeGenericInputField`
- Create `NodeKeyValueInput`
- Create `NodeArrayInput`
- Create `NodeStringInput`
- Create `NodeNumberInput`
- Create `NodeBooleanInput`
- Create `NodeFallbackInput`
- Create `ClickableInput` from `renderClickableInput(..)`
- Amend usage in `CustomNode`
- Remove deprecated/unused styling from `flow.css` and `customnode.css`
- Fix alignment between `NodeHandle` and `NodeInputField`
- Split up `BlockIOSchema` & rename to `BlockIOSubSchema`
- Create `BlockIOObjectSubSchema`
- Create `BlockIOKVSubSchema`
- Create `BlockIOArraySubSchema`
- Create `BlockIOStringSubSchema`
- Create `BlockIONumberSubSchema`
- Create `BlockIOBooleanSubSchema`
- Create `BlockIONullSubSchema`
- Install `Select` component from shad/cn
- refactor(builder): Move `NodeInputField.tsx` to `node-input.tsx`
This commit is contained in:
committed by
GitHub
parent
ec6bae0467
commit
3c2c3e57a0
@@ -16,6 +16,7 @@
|
||||
"@radix-ui/react-icons": "^1.3.0",
|
||||
"@radix-ui/react-label": "^2.1.0",
|
||||
"@radix-ui/react-popover": "^1.1.1",
|
||||
"@radix-ui/react-select": "^2.1.1",
|
||||
"@radix-ui/react-slot": "^1.1.0",
|
||||
"@radix-ui/react-switch": "^1.1.0",
|
||||
"@radix-ui/react-tooltip": "^1.1.2",
|
||||
|
||||
@@ -5,13 +5,12 @@ import './customnode.css';
|
||||
import InputModalComponent from './InputModalComponent';
|
||||
import OutputModalComponent from './OutputModalComponent';
|
||||
import { BlockIORootSchema, NodeExecutionResult } from '@/lib/autogpt-server-api/types';
|
||||
import { BlockSchema } from '@/lib/types';
|
||||
import { beautifyString, setNestedProperty } from '@/lib/utils';
|
||||
import { Switch } from "@/components/ui/switch"
|
||||
import NodeHandle from './NodeHandle';
|
||||
import NodeInputField from './NodeInputField';
|
||||
import { Copy, Trash2 } from 'lucide-react';
|
||||
import { history } from './history';
|
||||
import { NodeGenericInputField } from './node-input';
|
||||
|
||||
export type CustomNodeData = {
|
||||
blockType: string;
|
||||
@@ -22,8 +21,8 @@ export type CustomNodeData = {
|
||||
setHardcodedValues: (values: { [key: string]: any }) => void;
|
||||
connections: Array<{ source: string; sourceHandle: string; target: string; targetHandle: string }>;
|
||||
isOutputOpen: boolean;
|
||||
status?: string;
|
||||
output_data?: any;
|
||||
status?: NodeExecutionResult["status"];
|
||||
output_data?: NodeExecutionResult["output_data"];
|
||||
block_id: string;
|
||||
backend_id?: string;
|
||||
errors?: { [key: string]: string | null };
|
||||
@@ -119,7 +118,7 @@ const CustomNode: FC<NodeProps<CustomNodeData>> = ({ data, id }) => {
|
||||
|
||||
const getValue = (key: string) => {
|
||||
const keys = key.split('.');
|
||||
return keys.reduce((acc, k) => (acc && acc[k] !== undefined) ? acc[k] : '', data.hardcodedValues);
|
||||
return keys.reduce((acc, k) => acc && acc[k] ? acc[k] : undefined, data.hardcodedValues);
|
||||
};
|
||||
|
||||
const isHandleConnected = (key: string) => {
|
||||
@@ -248,28 +247,36 @@ const CustomNode: FC<NodeProps<CustomNodeData>> = ({ data, id }) => {
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="node-content">
|
||||
<div className="flex justify-between items-start gap-2">
|
||||
<div>
|
||||
{data.inputSchema &&
|
||||
Object.entries(data.inputSchema.properties).map(([key, schema]) => {
|
||||
const isRequired = data.inputSchema.required?.includes(key);
|
||||
Object.entries(data.inputSchema.properties).map(([propKey, propSchema]) => {
|
||||
const isRequired = data.inputSchema.required?.includes(propKey);
|
||||
return (isRequired || isAdvancedOpen) && (
|
||||
<div key={key} onMouseOver={() => { }}>
|
||||
<NodeHandle keyName={key} isConnected={isHandleConnected(key)} isRequired={isRequired} schema={schema} side="left" />
|
||||
{!isHandleConnected(key) &&
|
||||
<NodeInputField
|
||||
keyName={key}
|
||||
schema={schema}
|
||||
value={getValue(key)}
|
||||
handleInputClick={handleInputClick}
|
||||
<div key={propKey} onMouseOver={() => { }}>
|
||||
<NodeHandle
|
||||
keyName={propKey}
|
||||
isConnected={isHandleConnected(propKey)}
|
||||
schema={propSchema}
|
||||
side="left"
|
||||
/>
|
||||
{!isHandleConnected(propKey) &&
|
||||
<NodeGenericInputField
|
||||
className="mt-1 mb-2"
|
||||
propKey={propKey}
|
||||
propSchema={propSchema}
|
||||
currentValue={getValue(propKey)}
|
||||
handleInputChange={handleInputChange}
|
||||
errors={data.errors?.[key]}
|
||||
/>}
|
||||
handleInputClick={handleInputClick}
|
||||
errors={data.errors ?? {}}
|
||||
displayName={propSchema.title || beautifyString(propKey)}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex-none">
|
||||
{data.outputSchema && generateOutputHandles(data.outputSchema)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -296,11 +303,11 @@ const CustomNode: FC<NodeProps<CustomNodeData>> = ({ data, id }) => {
|
||||
</div>
|
||||
)}
|
||||
<div className="flex items-center mt-2.5">
|
||||
<Switch onCheckedChange={toggleOutput} className='custom-switch' />
|
||||
<Switch className='pl-[2px]' onCheckedChange={toggleOutput} />
|
||||
<span className='m-1 mr-4'>Output</span>
|
||||
{hasOptionalFields() && (
|
||||
<>
|
||||
<Switch onCheckedChange={toggleAdvancedSettings} className='custom-switch' />
|
||||
<Switch className='pl-[2px]' onCheckedChange={toggleAdvancedSettings} />
|
||||
<span className='m-1'>Advanced</span>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -15,7 +15,7 @@ import ReactFlow, {
|
||||
import 'reactflow/dist/style.css';
|
||||
import CustomNode, { CustomNodeData } from './CustomNode';
|
||||
import './flow.css';
|
||||
import AutoGPTServerAPI, { Block, BlockIOSchema, Graph, NodeExecutionResult, ObjectSchema } from '@/lib/autogpt-server-api';
|
||||
import AutoGPTServerAPI, { Block, BlockIOSubSchema, Graph, NodeExecutionResult } from '@/lib/autogpt-server-api';
|
||||
import { Button } from './ui/button';
|
||||
import { Input } from './ui/input';
|
||||
import { ChevronRight, ChevronLeft } from "lucide-react";
|
||||
@@ -392,7 +392,6 @@ const FlowEditor: React.FC<{
|
||||
type: 'custom',
|
||||
position: { x: node.metadata.position.x, y: node.metadata.position.y },
|
||||
data: {
|
||||
setIsAnyModalOpen: setIsAnyModalOpen,
|
||||
block_id: block.id,
|
||||
blockType: block.name,
|
||||
title: `${block.name} ${node.id}`,
|
||||
@@ -453,7 +452,7 @@ const FlowEditor: React.FC<{
|
||||
}
|
||||
|
||||
const getNestedData = (
|
||||
schema: BlockIOSchema, values: { [key: string]: any }
|
||||
schema: BlockIOSubSchema, values: { [key: string]: any }
|
||||
): { [key: string]: any } => {
|
||||
let inputData: { [key: string]: any } = {};
|
||||
|
||||
@@ -602,7 +601,7 @@ const FlowEditor: React.FC<{
|
||||
// Populate errors if validation fails
|
||||
validate.errors?.forEach((error) => {
|
||||
// Skip error if there's an edge connected
|
||||
const path = error.instancePath || error.schemaPath;
|
||||
const path = 'dataPath' in error ? error.dataPath as string : error.instancePath;
|
||||
const handle = path.split(/[\/.]/)[0];
|
||||
if (node.data.connections.some(conn => conn.target === node.id || conn.targetHandle === handle)) {
|
||||
return;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { BlockIOSchema } from "@/lib/autogpt-server-api/types";
|
||||
import { BlockIOSubSchema } from "@/lib/autogpt-server-api/types";
|
||||
import { beautifyString, getTypeBgColor, getTypeTextColor } from "@/lib/utils";
|
||||
import { FC } from "react";
|
||||
import { Handle, Position } from "reactflow";
|
||||
@@ -6,7 +6,7 @@ import SchemaTooltip from "./SchemaTooltip";
|
||||
|
||||
type HandleProps = {
|
||||
keyName: string,
|
||||
schema: BlockIOSchema,
|
||||
schema: BlockIOSubSchema,
|
||||
isConnected: boolean,
|
||||
isRequired?: boolean,
|
||||
side: 'left' | 'right'
|
||||
@@ -23,19 +23,19 @@ const NodeHandle: FC<HandleProps> = ({ keyName, schema, isConnected, isRequired,
|
||||
null: 'null',
|
||||
};
|
||||
|
||||
const typeClass = `text-sm ${getTypeTextColor(schema.type)} ${side === 'left' ? 'text-left' : 'text-right'}`;
|
||||
const typeClass = `text-sm ${getTypeTextColor(schema.type || 'any')} ${side === 'left' ? 'text-left' : 'text-right'}`;
|
||||
|
||||
const label = (
|
||||
<div className="flex flex-col flex-grow">
|
||||
<span className="text-m text-gray-900 -mb-1 green">
|
||||
{schema.title || beautifyString(keyName)}{isRequired ? '*' : ''}
|
||||
</span>
|
||||
<span className={typeClass}>{typeName[schema.type]}</span>
|
||||
<span className={typeClass}>{typeName[schema.type] || 'any'}</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
const dot = (
|
||||
<div className={`w-4 h-4 m-1 ${isConnected ? getTypeBgColor(schema.type) : 'bg-gray-600'} rounded-full transition-colors duration-100 group-hover:bg-gray-300`} />
|
||||
<div className={`w-4 h-4 m-1 ${isConnected ? getTypeBgColor(schema.type || 'any') : 'bg-gray-600'} rounded-full transition-colors duration-100 group-hover:bg-gray-300`} />
|
||||
);
|
||||
|
||||
if (side === 'left') {
|
||||
@@ -45,7 +45,7 @@ const NodeHandle: FC<HandleProps> = ({ keyName, schema, isConnected, isRequired,
|
||||
type="target"
|
||||
position={Position.Left}
|
||||
id={keyName}
|
||||
className='group -ml-[29px]'
|
||||
className='group -ml-[26px]'
|
||||
>
|
||||
<div className="pointer-events-none flex items-center">
|
||||
{dot}
|
||||
@@ -62,7 +62,7 @@ const NodeHandle: FC<HandleProps> = ({ keyName, schema, isConnected, isRequired,
|
||||
type="source"
|
||||
position={Position.Right}
|
||||
id={keyName}
|
||||
className='group -mr-[29px]'
|
||||
className='group -mr-[26px]'
|
||||
>
|
||||
<div className="pointer-events-none flex items-center">
|
||||
{label}
|
||||
|
||||
@@ -1,294 +0,0 @@
|
||||
import { Cross2Icon, PlusIcon } from "@radix-ui/react-icons";
|
||||
import { beautifyString } from "@/lib/utils";
|
||||
import { BlockIOSchema } from "@/lib/autogpt-server-api/types";
|
||||
import { FC, useState } from "react";
|
||||
import { Button } from "./ui/button";
|
||||
import { Input } from "./ui/input";
|
||||
|
||||
type BlockInputFieldProps = {
|
||||
keyName: string
|
||||
schema: BlockIOSchema
|
||||
parentKey?: string
|
||||
value: string | Array<string> | { [key: string]: string }
|
||||
handleInputClick: (key: string) => void
|
||||
handleInputChange: (key: string, value: any) => void
|
||||
errors?: { [key: string]: string } | string | null
|
||||
}
|
||||
|
||||
const NodeInputField: FC<BlockInputFieldProps> = ({
|
||||
keyName: key,
|
||||
schema,
|
||||
parentKey = '',
|
||||
value,
|
||||
handleInputClick,
|
||||
handleInputChange,
|
||||
errors
|
||||
}) => {
|
||||
const fullKey = parentKey ? `${parentKey}.${key}` : key;
|
||||
const error = typeof errors === 'string' ? errors : errors?.[key] ?? "";
|
||||
const displayKey = schema.title || beautifyString(key);
|
||||
|
||||
const [keyValuePairs, _setKeyValuePairs] = useState<{ key: string, value: string }[]>(
|
||||
"additionalProperties" in schema && value
|
||||
? Object.entries(value).map(([key, value]) => ({ key: key, value: value }))
|
||||
: []
|
||||
);
|
||||
|
||||
function setKeyValuePairs(newKVPairs: typeof keyValuePairs): void {
|
||||
_setKeyValuePairs(newKVPairs);
|
||||
handleInputChange(
|
||||
fullKey,
|
||||
newKVPairs.reduce((obj, { key, value }) => ({ ...obj, [key]: value }), {})
|
||||
);
|
||||
}
|
||||
|
||||
const renderClickableInput = (value: string | null = null, placeholder: string = "", secret: boolean = false) => {
|
||||
const className = `clickable-input ${error ? 'border-error' : ''}`;
|
||||
|
||||
return secret ? (
|
||||
<div className={className} onClick={() => handleInputClick(fullKey)}>
|
||||
{value ? <span>********</span> : <i className="text-gray-500">{placeholder}</i>}
|
||||
</div>
|
||||
) : (
|
||||
<div className={className} onClick={() => handleInputClick(fullKey)}>
|
||||
{value || <i className="text-gray-500">{placeholder}</i>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
if ("properties" in schema) {
|
||||
return (
|
||||
<div key={fullKey} className="object-input">
|
||||
<strong>{displayKey}:</strong>
|
||||
{Object.entries(schema.properties).map(([propKey, propSchema]) => (
|
||||
<div key={`${fullKey}.${propKey}`} className="nested-input">
|
||||
<NodeInputField
|
||||
keyName={propKey}
|
||||
schema={propSchema}
|
||||
parentKey={fullKey}
|
||||
value={(value as { [key: string]: string })[propKey]}
|
||||
handleInputClick={handleInputClick}
|
||||
handleInputChange={handleInputChange}
|
||||
errors={errors}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (schema.type === 'object' && schema.additionalProperties) {
|
||||
return (
|
||||
<div key={fullKey}>
|
||||
<div>
|
||||
{keyValuePairs.map(({ key, value }, index) => (
|
||||
<div key={index} className="flex items-center w-[325px] space-x-2 mb-2">
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Key"
|
||||
value={key}
|
||||
onChange={(e) => setKeyValuePairs(
|
||||
keyValuePairs.toSpliced(index, 1, {
|
||||
key: e.target.value, value: value
|
||||
})
|
||||
)}
|
||||
/>
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Value"
|
||||
value={value}
|
||||
onChange={(e) => setKeyValuePairs(
|
||||
keyValuePairs.toSpliced(index, 1, {
|
||||
key: key, value: e.target.value
|
||||
})
|
||||
)}
|
||||
/>
|
||||
<Button variant="ghost" className="px-2"
|
||||
onClick={() => setKeyValuePairs(keyValuePairs.toSpliced(index, 1))}
|
||||
>
|
||||
<Cross2Icon />
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
<Button className="w-full"
|
||||
onClick={() => setKeyValuePairs(
|
||||
keyValuePairs.concat({ key: "", value: "" })
|
||||
)}
|
||||
>
|
||||
<PlusIcon className="mr-2" /> Add Property
|
||||
</Button>
|
||||
</div>
|
||||
{error && <span className="error-message">{error}</span>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if ("anyOf" in schema) {
|
||||
const types = schema.anyOf.map(s => "type" in s ? s.type : undefined);
|
||||
if (types.includes('string') && types.includes('null')) {
|
||||
return (
|
||||
<div key={fullKey} className="input-container">
|
||||
{renderClickableInput(value as string, schema.placeholder || `Enter ${displayKey} (optional)`)}
|
||||
{error && <span className="error-message">{error}</span>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ("allOf" in schema) {
|
||||
return (
|
||||
<div key={fullKey} className="object-input">
|
||||
<strong>{displayKey}:</strong>
|
||||
{"properties" in schema.allOf[0] &&
|
||||
Object.entries(schema.allOf[0].properties).map(([propKey, propSchema]) => (
|
||||
<div key={`${fullKey}.${propKey}`} className="nested-input">
|
||||
<NodeInputField
|
||||
keyName={propKey}
|
||||
schema={propSchema}
|
||||
parentKey={fullKey}
|
||||
value={(value as { [key: string]: string })[propKey]}
|
||||
handleInputClick={handleInputClick}
|
||||
handleInputChange={handleInputChange}
|
||||
errors={errors}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if ("oneOf" in schema) {
|
||||
return (
|
||||
<div key={fullKey} className="object-input">
|
||||
<strong>{displayKey}:</strong>
|
||||
{"properties" in schema.oneOf[0] &&
|
||||
Object.entries(schema.oneOf[0].properties).map(([propKey, propSchema]) => (
|
||||
<div key={`${fullKey}.${propKey}`} className="nested-input">
|
||||
<NodeInputField
|
||||
keyName={propKey}
|
||||
schema={propSchema}
|
||||
parentKey={fullKey}
|
||||
value={(value as { [key: string]: string })[propKey]}
|
||||
handleInputClick={handleInputClick}
|
||||
handleInputChange={handleInputChange}
|
||||
errors={errors}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!("type" in schema)) {
|
||||
console.warn(`Schema for input ${key} does not specify a type:`, schema);
|
||||
return (
|
||||
<div key={fullKey} className="input-container">
|
||||
{renderClickableInput(value as string, schema.placeholder || `Enter ${beautifyString(displayKey)} (Complex)`)}
|
||||
{error && <span className="error-message">{error}</span>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
switch (schema.type) {
|
||||
case 'string':
|
||||
if (schema.enum) {
|
||||
return (
|
||||
<div key={fullKey} className="input-container">
|
||||
<select
|
||||
value={value as string || ''}
|
||||
onChange={(e) => handleInputChange(fullKey, e.target.value)}
|
||||
className="select-input"
|
||||
>
|
||||
<option value="">Select {displayKey}</option>
|
||||
{schema.enum.map((option: string) => (
|
||||
<option key={option} value={option}>
|
||||
{beautifyString(option)}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
{error && <span className="error-message">{error}</span>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (schema.secret) {
|
||||
return (
|
||||
<div key={fullKey} className="input-container">
|
||||
{renderClickableInput(value as string, schema.placeholder || `Enter ${displayKey}`, true)}
|
||||
{error && <span className="error-message">{error}</span>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div key={fullKey} className="input-container">
|
||||
{renderClickableInput(value as string, schema.placeholder || `Enter ${displayKey}`)}
|
||||
{error && <span className="error-message">{error}</span>}
|
||||
</div>
|
||||
);
|
||||
case 'boolean':
|
||||
return (
|
||||
<div key={fullKey} className="input-container">
|
||||
<select
|
||||
value={value === undefined ? '' : value.toString()}
|
||||
onChange={(e) => handleInputChange(fullKey, e.target.value === 'true')}
|
||||
className="select-input"
|
||||
>
|
||||
<option value="">Select {displayKey}</option>
|
||||
<option value="true">True</option>
|
||||
<option value="false">False</option>
|
||||
</select>
|
||||
{error && <span className="error-message">{error}</span>}
|
||||
</div>
|
||||
);
|
||||
case 'number':
|
||||
case 'integer':
|
||||
return (
|
||||
<div key={fullKey} className="input-container">
|
||||
<Input
|
||||
type="number"
|
||||
value={value as string || ''}
|
||||
onChange={(e) => handleInputChange(fullKey, parseFloat(e.target.value))}
|
||||
className={`number-input ${error ? 'border-error' : ''}`}
|
||||
/>
|
||||
{error && <span className="error-message">{error}</span>}
|
||||
</div>
|
||||
);
|
||||
case 'array':
|
||||
if (schema.items && schema.items.type === 'string') {
|
||||
const arrayValues = value as Array<string> || [];
|
||||
return (
|
||||
<div key={fullKey} className="input-container">
|
||||
{arrayValues.map((item: string, index: number) => (
|
||||
<div key={`${fullKey}.${index}`} className="array-item-container">
|
||||
<Input
|
||||
type="text"
|
||||
value={item}
|
||||
onChange={(e) => handleInputChange(`${fullKey}.${index}`, e.target.value)}
|
||||
className="array-item-input"
|
||||
/>
|
||||
<Button onClick={() => handleInputChange(`${fullKey}.${index}`, '')} className="array-item-remove">
|
||||
×
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
<Button onClick={() => handleInputChange(fullKey, [...arrayValues, ''])} className="array-item-add">
|
||||
Add Item
|
||||
</Button>
|
||||
{error && <span className="error-message ml-2">{error}</span>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
default:
|
||||
console.warn(`Schema for input ${key} specifies unknown type:`, schema);
|
||||
return (
|
||||
<div key={fullKey} className="input-container">
|
||||
{renderClickableInput(value as string, schema.placeholder || `Enter ${beautifyString(displayKey)} (Complex)`)}
|
||||
{error && <span className="error-message">{error}</span>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default NodeInputField;
|
||||
@@ -4,11 +4,11 @@ import {
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip"
|
||||
import { BlockIOSchema } from "@/lib/autogpt-server-api/types";
|
||||
import { BlockIOSubSchema } from "@/lib/autogpt-server-api/types";
|
||||
import { Info } from 'lucide-react';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
|
||||
const SchemaTooltip: React.FC<{ schema: BlockIOSchema }> = ({ schema }) => {
|
||||
const SchemaTooltip: React.FC<{ schema: BlockIOSubSchema }> = ({ schema }) => {
|
||||
if (!schema.description) return null;
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
.custom-node {
|
||||
padding: 15px;
|
||||
@apply p-3;
|
||||
border: 3px solid #4b5563;
|
||||
border-radius: 12px;
|
||||
background: #ffffff;
|
||||
@@ -89,10 +89,6 @@
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.input-container {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.clickable-input {
|
||||
padding: 5px;
|
||||
width: 325px;
|
||||
@@ -191,29 +187,9 @@
|
||||
|
||||
.error-message {
|
||||
color: #d9534f;
|
||||
font-size: 12px;
|
||||
font-size: 13px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.object-input {
|
||||
margin-left: 10px;
|
||||
border-left: 1px solid #000; /* Border for nested inputs */
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.nested-input {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.key-value-input {
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
align-items: center;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.key-value-input input {
|
||||
flex-grow: 1;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
/* Styles for node states */
|
||||
|
||||
@@ -126,24 +126,3 @@ input::placeholder, textarea::placeholder {
|
||||
width: 100%;
|
||||
height: 600px; /* Adjust this height as needed */
|
||||
}
|
||||
|
||||
.flow-wrapper {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.flow-controls {
|
||||
position: absolute;
|
||||
left: -80px;
|
||||
z-index: 1001;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.flow-controls.open {
|
||||
transform: translateX(350px);
|
||||
}
|
||||
|
||||
545
rnd/autogpt_builder/src/components/node-input.tsx
Normal file
545
rnd/autogpt_builder/src/components/node-input.tsx
Normal file
@@ -0,0 +1,545 @@
|
||||
import { Cross2Icon, Pencil2Icon, PlusIcon } from "@radix-ui/react-icons";
|
||||
import { beautifyString, cn } from "@/lib/utils";
|
||||
import {
|
||||
BlockIORootSchema,
|
||||
BlockIOSubSchema,
|
||||
BlockIOObjectSubSchema,
|
||||
BlockIOKVSubSchema,
|
||||
BlockIOArraySubSchema,
|
||||
BlockIOStringSubSchema,
|
||||
BlockIONumberSubSchema,
|
||||
BlockIOBooleanSubSchema,
|
||||
} from "@/lib/autogpt-server-api/types";
|
||||
import { FC, useState } from "react";
|
||||
import { Button } from "./ui/button";
|
||||
import { Switch } from "./ui/switch";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./ui/select";
|
||||
import { Input } from "./ui/input";
|
||||
import { Label } from "./ui/label";
|
||||
|
||||
type NodeObjectInputTreeProps = {
|
||||
selfKey?: string;
|
||||
schema: BlockIORootSchema | BlockIOObjectSubSchema;
|
||||
object?: { [key: string]: any };
|
||||
handleInputClick: (key: string) => void;
|
||||
handleInputChange: (key: string, value: any) => void;
|
||||
errors: { [key: string]: string | undefined };
|
||||
className?: string;
|
||||
displayName?: string;
|
||||
};
|
||||
|
||||
const NodeObjectInputTree: FC<NodeObjectInputTreeProps> = ({
|
||||
selfKey = "",
|
||||
schema,
|
||||
object,
|
||||
handleInputClick,
|
||||
handleInputChange,
|
||||
errors,
|
||||
className,
|
||||
displayName,
|
||||
}) => {
|
||||
object ??= ("default" in schema ? schema.default : null) ?? {};
|
||||
return (
|
||||
<div className={cn(className, 'flex-col')}>
|
||||
{displayName && <strong>{displayName}:</strong>}
|
||||
{Object.entries(schema.properties).map(([propKey, propSchema]) => {
|
||||
const childKey = selfKey ? `${selfKey}.${propKey}` : propKey;
|
||||
|
||||
return (
|
||||
<div className="flex flex-row space-y-2 w-full">
|
||||
<span className="mr-2 mt-3">{propKey}</span>
|
||||
<NodeGenericInputField
|
||||
key={propKey}
|
||||
propKey={childKey}
|
||||
propSchema={propSchema}
|
||||
currentValue={object[propKey]}
|
||||
errors={errors}
|
||||
handleInputChange={handleInputChange}
|
||||
handleInputClick={handleInputClick}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default NodeObjectInputTree;
|
||||
|
||||
export const NodeGenericInputField: FC<{
|
||||
propKey: string;
|
||||
propSchema: BlockIOSubSchema;
|
||||
currentValue?: any;
|
||||
errors: NodeObjectInputTreeProps["errors"];
|
||||
handleInputChange: NodeObjectInputTreeProps["handleInputChange"];
|
||||
handleInputClick: NodeObjectInputTreeProps["handleInputClick"];
|
||||
className?: string;
|
||||
displayName?: string;
|
||||
}> = ({
|
||||
propKey,
|
||||
propSchema,
|
||||
currentValue,
|
||||
errors,
|
||||
handleInputChange,
|
||||
handleInputClick,
|
||||
className,
|
||||
displayName,
|
||||
}) => {
|
||||
displayName ??= propSchema.title || beautifyString(propKey);
|
||||
|
||||
if ("allOf" in propSchema) {
|
||||
// If this happens, that is because Pydantic wraps $refs in an allOf if the
|
||||
// $ref has sibling schema properties (which isn't technically allowed),
|
||||
// so there will only be one item in allOf[].
|
||||
// AFAIK this should NEVER happen though, as $refs are resolved server-side.
|
||||
propSchema = propSchema.allOf[0];
|
||||
console.warn(`Unsupported 'allOf' in schema for '${propKey}'!`, propSchema);
|
||||
}
|
||||
|
||||
if ("properties" in propSchema) {
|
||||
return (
|
||||
<NodeObjectInputTree
|
||||
selfKey={propKey}
|
||||
schema={propSchema}
|
||||
object={currentValue}
|
||||
errors={errors}
|
||||
className={cn("border-l border-gray-500 pl-2", className)} // visual indent
|
||||
displayName={displayName}
|
||||
handleInputClick={handleInputClick}
|
||||
handleInputChange={handleInputChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if ("additionalProperties" in propSchema) {
|
||||
return (
|
||||
<NodeKeyValueInput
|
||||
selfKey={propKey}
|
||||
schema={propSchema}
|
||||
entries={currentValue}
|
||||
errors={errors}
|
||||
className={className}
|
||||
displayName={displayName}
|
||||
handleInputChange={handleInputChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if ("anyOf" in propSchema) { // optional items
|
||||
const types = propSchema.anyOf.map((s) => ("type" in s ? s.type : undefined));
|
||||
if (types.includes("string") && types.includes("null")) { // optional string
|
||||
return (
|
||||
<NodeStringInput
|
||||
selfKey={propKey}
|
||||
schema={{ ...propSchema, type: "string" } as BlockIOStringSubSchema}
|
||||
value={currentValue}
|
||||
error={errors[propKey]}
|
||||
className={className}
|
||||
displayName={displayName}
|
||||
handleInputChange={handleInputChange}
|
||||
handleInputClick={handleInputClick}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ("oneOf" in propSchema) {
|
||||
// At the time of writing, this isn't used in the backend -> no impl. needed
|
||||
console.error(`Unsupported 'oneOf' in schema for '${propKey}'!`, propSchema);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!("type" in propSchema)) {
|
||||
return (
|
||||
<NodeFallbackInput
|
||||
selfKey={propKey}
|
||||
schema={propSchema}
|
||||
value={currentValue}
|
||||
error={errors[propKey]}
|
||||
className={className}
|
||||
displayName={displayName}
|
||||
handleInputClick={handleInputClick}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
switch (propSchema.type) {
|
||||
case "string":
|
||||
return (
|
||||
<NodeStringInput
|
||||
selfKey={propKey}
|
||||
schema={propSchema}
|
||||
value={currentValue}
|
||||
error={errors[propKey]}
|
||||
className={className}
|
||||
displayName={displayName}
|
||||
handleInputChange={handleInputChange}
|
||||
handleInputClick={handleInputClick}
|
||||
/>
|
||||
);
|
||||
case "boolean":
|
||||
return (
|
||||
<NodeBooleanInput
|
||||
selfKey={propKey}
|
||||
schema={propSchema}
|
||||
value={currentValue}
|
||||
error={errors[propKey]}
|
||||
className={className}
|
||||
displayName={displayName}
|
||||
handleInputChange={handleInputChange}
|
||||
/>
|
||||
);
|
||||
case "number":
|
||||
case "integer":
|
||||
return (
|
||||
<NodeNumberInput
|
||||
selfKey={propKey}
|
||||
schema={propSchema}
|
||||
value={currentValue}
|
||||
error={errors[propKey]}
|
||||
className={className}
|
||||
displayName={displayName}
|
||||
handleInputChange={handleInputChange}
|
||||
/>
|
||||
);
|
||||
case "array":
|
||||
return (
|
||||
<NodeArrayInput
|
||||
selfKey={propKey}
|
||||
schema={propSchema}
|
||||
entries={currentValue}
|
||||
errors={errors}
|
||||
className={className}
|
||||
displayName={displayName}
|
||||
handleInputChange={handleInputChange}
|
||||
handleInputClick={handleInputClick}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
console.warn(`Schema for '${propKey}' specifies unknown type:`, propSchema);
|
||||
return (
|
||||
<NodeFallbackInput
|
||||
selfKey={propKey}
|
||||
schema={propSchema}
|
||||
value={currentValue}
|
||||
error={errors[propKey]}
|
||||
className={className}
|
||||
displayName={displayName}
|
||||
handleInputClick={handleInputClick}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const NodeKeyValueInput: FC<{
|
||||
selfKey: string;
|
||||
schema: BlockIOKVSubSchema;
|
||||
entries?: { [key: string]: string } | { [key: string]: number };
|
||||
errors: { [key: string]: string | undefined };
|
||||
handleInputChange: NodeObjectInputTreeProps["handleInputChange"];
|
||||
className?: string;
|
||||
displayName?: string;
|
||||
}> = ({ selfKey, entries, schema, handleInputChange, errors, className, displayName }) => {
|
||||
const [keyValuePairs, setKeyValuePairs] = useState<{
|
||||
key: string;
|
||||
value: string | number | null;
|
||||
}[]>(
|
||||
Object.entries(entries ?? schema.default ?? {})
|
||||
.map(([key, value]) => ({ key, value: value }))
|
||||
);
|
||||
|
||||
function updateKeyValuePairs(newPairs: typeof keyValuePairs) {
|
||||
setKeyValuePairs(newPairs);
|
||||
handleInputChange(
|
||||
selfKey,
|
||||
newPairs.reduce((obj, { key, value }) => ({ ...obj, [key]: value }), {})
|
||||
);
|
||||
};
|
||||
|
||||
function convertValueType(value: string): string | number | null {
|
||||
if (schema.additionalProperties.type == "string") return value;
|
||||
if (!value) return null;
|
||||
return Number(value)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
{displayName && <strong>{displayName}:</strong>}
|
||||
<div>
|
||||
{keyValuePairs.map(({ key, value }, index) => (
|
||||
<div key={index}>
|
||||
<div className="flex items-center space-x-2 mb-2">
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Key"
|
||||
value={key}
|
||||
onChange={(e) =>
|
||||
updateKeyValuePairs(
|
||||
keyValuePairs.toSpliced(index, 1, {
|
||||
key: e.target.value,
|
||||
value: value,
|
||||
})
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Value"
|
||||
value={value ?? ""}
|
||||
onChange={(e) =>
|
||||
updateKeyValuePairs(
|
||||
keyValuePairs.toSpliced(index, 1, {
|
||||
key: key,
|
||||
value: convertValueType(e.target.value),
|
||||
})
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="px-2"
|
||||
onClick={() => updateKeyValuePairs(keyValuePairs.toSpliced(index, 1))}
|
||||
>
|
||||
<Cross2Icon />
|
||||
</Button>
|
||||
</div>
|
||||
{errors[`${selfKey}.${key}`] &&
|
||||
<span className="error-message">{errors[`${selfKey}.${key}`]}</span>
|
||||
}
|
||||
</div>
|
||||
))}
|
||||
<Button
|
||||
className="w-full"
|
||||
onClick={() =>
|
||||
updateKeyValuePairs(keyValuePairs.concat({ key: "", value: "" }))
|
||||
}
|
||||
>
|
||||
<PlusIcon className="mr-2" /> Add Property
|
||||
</Button>
|
||||
</div>
|
||||
{errors[selfKey] && <span className="error-message">{errors[selfKey]}</span>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const NodeArrayInput: FC<{
|
||||
selfKey: string;
|
||||
schema: BlockIOArraySubSchema;
|
||||
entries?: string[];
|
||||
errors: { [key: string]: string | undefined };
|
||||
handleInputChange: NodeObjectInputTreeProps["handleInputChange"];
|
||||
handleInputClick: NodeObjectInputTreeProps["handleInputClick"];
|
||||
className?: string;
|
||||
displayName?: string;
|
||||
}> = ({
|
||||
selfKey,
|
||||
schema,
|
||||
entries,
|
||||
errors,
|
||||
handleInputChange,
|
||||
handleInputClick,
|
||||
className,
|
||||
displayName,
|
||||
}) => {
|
||||
entries ??= schema.default ?? [];
|
||||
return (
|
||||
<div className={`input-container ${className ?? ""}`}>
|
||||
{displayName && <strong>{displayName}</strong>}
|
||||
{entries.map((entry: string, index: number) => {
|
||||
const entryKey = `${selfKey}[${index}]`;
|
||||
return (
|
||||
<div key={entryKey}>
|
||||
<div className="flex items-center space-x-2 mb-2">
|
||||
{schema.items
|
||||
? <NodeGenericInputField
|
||||
propKey={entryKey}
|
||||
propSchema={schema.items}
|
||||
currentValue={entry}
|
||||
errors={errors}
|
||||
handleInputChange={handleInputChange}
|
||||
handleInputClick={handleInputClick}
|
||||
/>
|
||||
: <NodeFallbackInput
|
||||
selfKey={entryKey}
|
||||
schema={schema.items}
|
||||
value={entry}
|
||||
error={errors[entryKey]}
|
||||
displayName={displayName ?? "something"}
|
||||
handleInputClick={handleInputClick}
|
||||
/>
|
||||
}
|
||||
<Button
|
||||
variant="ghost" size="icon"
|
||||
onClick={() => handleInputChange(selfKey, entries.toSpliced(index, 1))}
|
||||
>
|
||||
<Cross2Icon />
|
||||
</Button>
|
||||
</div>
|
||||
{errors[entryKey] &&
|
||||
<span className="error-message">{errors[entryKey]}</span>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
<Button onClick={() => handleInputChange(selfKey, [...entries, ""])}>
|
||||
<PlusIcon className="mr-2" /> Add Item
|
||||
</Button>
|
||||
{errors[selfKey] && <span className="error-message">{errors[selfKey]}</span>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const NodeStringInput: FC<{
|
||||
selfKey: string;
|
||||
schema: BlockIOStringSubSchema;
|
||||
value?: string;
|
||||
error?: string;
|
||||
handleInputChange: NodeObjectInputTreeProps["handleInputChange"];
|
||||
handleInputClick: NodeObjectInputTreeProps["handleInputClick"];
|
||||
className?: string;
|
||||
displayName: string;
|
||||
}> = ({
|
||||
selfKey,
|
||||
schema,
|
||||
value,
|
||||
error,
|
||||
handleInputChange,
|
||||
handleInputClick,
|
||||
className,
|
||||
displayName,
|
||||
}) => {
|
||||
return (
|
||||
<div className={`input-container ${className ?? ""}`}>
|
||||
{schema.enum ? (
|
||||
<Select
|
||||
defaultValue={value}
|
||||
onValueChange={newValue => handleInputChange(selfKey, newValue)}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder={displayName || schema.placeholder || schema.title} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{schema.enum.map((option, index) => (
|
||||
<SelectItem key={index} value={option}>
|
||||
{beautifyString(option)}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
) : (
|
||||
<div className="relative">
|
||||
<Input
|
||||
type="text"
|
||||
id={selfKey}
|
||||
value={value}
|
||||
placeholder={
|
||||
schema?.placeholder || `Enter ${beautifyString(displayName)}`
|
||||
}
|
||||
onChange={e => handleInputChange(selfKey, e.target.value)}
|
||||
className="pr-8"
|
||||
/>
|
||||
<Button
|
||||
variant="ghost" size="icon"
|
||||
className="absolute inset-1 left-auto h-7 w-8 rounded-[0.25rem]"
|
||||
onClick={() => handleInputClick(selfKey)}
|
||||
title="Open a larger textbox input"
|
||||
>
|
||||
<Pencil2Icon className="m-0 p-0" />
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
{error && <span className="error-message">{error}</span>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const NodeNumberInput: FC<{
|
||||
selfKey: string;
|
||||
schema: BlockIONumberSubSchema;
|
||||
value?: number;
|
||||
error?: string;
|
||||
handleInputChange: NodeObjectInputTreeProps["handleInputChange"];
|
||||
className?: string;
|
||||
displayName?: string;
|
||||
}> = ({ selfKey, schema, value, error, handleInputChange, className, displayName }) => {
|
||||
value ??= schema.default;
|
||||
return (
|
||||
<div className={`input-container ${className ?? ""}`}>
|
||||
<div className="flex items-center justify-between space-x-3">
|
||||
{displayName && <Label htmlFor={selfKey}>{displayName}</Label>}
|
||||
<Input
|
||||
type="number"
|
||||
id={selfKey}
|
||||
value={value}
|
||||
onChange={(e) => handleInputChange(selfKey, parseFloat(e.target.value))}
|
||||
/>
|
||||
</div>
|
||||
{error && <span className="error-message">{error}</span>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const NodeBooleanInput: FC<{
|
||||
selfKey: string;
|
||||
schema: BlockIOBooleanSubSchema;
|
||||
value?: boolean;
|
||||
error?: string;
|
||||
handleInputChange: NodeObjectInputTreeProps["handleInputChange"];
|
||||
className?: string;
|
||||
displayName: string;
|
||||
}> = ({ selfKey, schema, value, error, handleInputChange, className, displayName }) => {
|
||||
value ??= schema.default ?? false;
|
||||
return (
|
||||
<div className={`input-container ${className ?? ""}`}>
|
||||
<div className="flex items-center">
|
||||
<Switch checked={value} onCheckedChange={v => handleInputChange(selfKey, v)} />
|
||||
<span className="ml-3">{displayName}</span>
|
||||
</div>
|
||||
{error && <span className="error-message">{error}</span>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const NodeFallbackInput: FC<{
|
||||
selfKey: string;
|
||||
schema?: BlockIOSubSchema;
|
||||
value: any;
|
||||
error?: string;
|
||||
handleInputClick: NodeObjectInputTreeProps["handleInputClick"];
|
||||
className?: string;
|
||||
displayName: string;
|
||||
}> = ({ selfKey, schema, value, error, handleInputClick, className, displayName }) => {
|
||||
return (
|
||||
<div className={`input-container ${className ?? ""}`}>
|
||||
<ClickableInput
|
||||
handleInputClick={() => handleInputClick(selfKey)}
|
||||
value={value}
|
||||
placeholder={
|
||||
schema?.placeholder || `Enter ${beautifyString(displayName)} (Complex)`
|
||||
}
|
||||
/>
|
||||
{error && <span className="error-message">{error}</span>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const ClickableInput: FC<{
|
||||
handleInputClick: () => void;
|
||||
value?: string;
|
||||
secret?: boolean;
|
||||
placeholder?: string;
|
||||
className?: string;
|
||||
}> = ({
|
||||
handleInputClick,
|
||||
value = "",
|
||||
placeholder = "",
|
||||
secret = false,
|
||||
className,
|
||||
}) => (
|
||||
<div className={`clickable-input ${className}`} onClick={handleInputClick}>
|
||||
{secret
|
||||
? <i className="text-gray-500">{value ? "********" : placeholder}</i>
|
||||
: value || <i className="text-gray-500">{placeholder}</i>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
164
rnd/autogpt_builder/src/components/ui/select.tsx
Normal file
164
rnd/autogpt_builder/src/components/ui/select.tsx
Normal file
@@ -0,0 +1,164 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import {
|
||||
CaretSortIcon,
|
||||
CheckIcon,
|
||||
ChevronDownIcon,
|
||||
ChevronUpIcon,
|
||||
} from "@radix-ui/react-icons"
|
||||
import * as SelectPrimitive from "@radix-ui/react-select"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const Select = SelectPrimitive.Root
|
||||
|
||||
const SelectGroup = SelectPrimitive.Group
|
||||
|
||||
const SelectValue = SelectPrimitive.Value
|
||||
|
||||
const SelectTrigger = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Trigger>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<SelectPrimitive.Trigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-neutral-200 bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-white placeholder:text-neutral-500 focus:outline-none focus:ring-1 focus:ring-neutral-950 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1 dark:border-neutral-800 dark:ring-offset-neutral-950 dark:placeholder:text-neutral-400 dark:focus:ring-neutral-300",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<SelectPrimitive.Icon asChild>
|
||||
<CaretSortIcon className="h-4 w-4 opacity-50" />
|
||||
</SelectPrimitive.Icon>
|
||||
</SelectPrimitive.Trigger>
|
||||
))
|
||||
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
|
||||
|
||||
const SelectScrollUpButton = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SelectPrimitive.ScrollUpButton
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex cursor-default items-center justify-center py-1",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<ChevronUpIcon />
|
||||
</SelectPrimitive.ScrollUpButton>
|
||||
))
|
||||
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
|
||||
|
||||
const SelectScrollDownButton = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SelectPrimitive.ScrollDownButton
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex cursor-default items-center justify-center py-1",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<ChevronDownIcon />
|
||||
</SelectPrimitive.ScrollDownButton>
|
||||
))
|
||||
SelectScrollDownButton.displayName =
|
||||
SelectPrimitive.ScrollDownButton.displayName
|
||||
|
||||
const SelectContent = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
|
||||
>(({ className, children, position = "popper", ...props }, ref) => (
|
||||
<SelectPrimitive.Portal>
|
||||
<SelectPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border border-neutral-200 bg-white text-neutral-950 shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 dark:border-neutral-800 dark:bg-neutral-950 dark:text-neutral-50",
|
||||
position === "popper" &&
|
||||
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
||||
className
|
||||
)}
|
||||
position={position}
|
||||
{...props}
|
||||
>
|
||||
<SelectScrollUpButton />
|
||||
<SelectPrimitive.Viewport
|
||||
className={cn(
|
||||
"p-1",
|
||||
position === "popper" &&
|
||||
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</SelectPrimitive.Viewport>
|
||||
<SelectScrollDownButton />
|
||||
</SelectPrimitive.Content>
|
||||
</SelectPrimitive.Portal>
|
||||
))
|
||||
SelectContent.displayName = SelectPrimitive.Content.displayName
|
||||
|
||||
const SelectLabel = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Label>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SelectPrimitive.Label
|
||||
ref={ref}
|
||||
className={cn("px-2 py-1.5 text-sm font-semibold", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
SelectLabel.displayName = SelectPrimitive.Label.displayName
|
||||
|
||||
const SelectItem = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Item>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<SelectPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-neutral-100 focus:text-neutral-900 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-neutral-800 dark:focus:text-neutral-50",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<span className="absolute right-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||
<SelectPrimitive.ItemIndicator>
|
||||
<CheckIcon className="h-4 w-4" />
|
||||
</SelectPrimitive.ItemIndicator>
|
||||
</span>
|
||||
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
||||
</SelectPrimitive.Item>
|
||||
))
|
||||
SelectItem.displayName = SelectPrimitive.Item.displayName
|
||||
|
||||
const SelectSeparator = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Separator>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SelectPrimitive.Separator
|
||||
ref={ref}
|
||||
className={cn("-mx-1 my-1 h-px bg-neutral-100 dark:bg-neutral-800", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
SelectSeparator.displayName = SelectPrimitive.Separator.displayName
|
||||
|
||||
export {
|
||||
Select,
|
||||
SelectGroup,
|
||||
SelectValue,
|
||||
SelectTrigger,
|
||||
SelectContent,
|
||||
SelectLabel,
|
||||
SelectItem,
|
||||
SelectSeparator,
|
||||
SelectScrollUpButton,
|
||||
SelectScrollDownButton,
|
||||
}
|
||||
@@ -9,51 +9,80 @@ export type Block = {
|
||||
|
||||
export type BlockIORootSchema = {
|
||||
type: "object";
|
||||
properties: { [key: string]: BlockIOSchema };
|
||||
properties: { [key: string]: BlockIOSubSchema };
|
||||
required?: string[];
|
||||
additionalProperties?: { type: string };
|
||||
}
|
||||
|
||||
export type BlockIOSchema = {
|
||||
export type BlockIOSubSchema =
|
||||
| BlockIOSimpleTypeSubSchema
|
||||
| BlockIOCombinedTypeSubSchema;
|
||||
|
||||
type BlockIOSimpleTypeSubSchema =
|
||||
| BlockIOObjectSubSchema
|
||||
| BlockIOKVSubSchema
|
||||
| BlockIOArraySubSchema
|
||||
| BlockIOStringSubSchema
|
||||
| BlockIONumberSubSchema
|
||||
| BlockIOBooleanSubSchema
|
||||
| BlockIONullSubSchema;
|
||||
|
||||
type BlockIOSubSchemaMeta = {
|
||||
title?: string;
|
||||
description?: string;
|
||||
placeholder?: string;
|
||||
} & (BlockIOSimpleTypeSchema | BlockIOCombinedTypeSchema);
|
||||
};
|
||||
|
||||
type BlockIOSimpleTypeSchema = {
|
||||
export type BlockIOObjectSubSchema = BlockIOSubSchemaMeta & {
|
||||
type: "object";
|
||||
properties: { [key: string]: BlockIOSchema };
|
||||
required?: string[];
|
||||
additionalProperties?: { type: string };
|
||||
} | {
|
||||
properties: { [key: string]: BlockIOSubSchema };
|
||||
default?: { [key: keyof BlockIOObjectSubSchema["properties"]]: any };
|
||||
required?: keyof BlockIOObjectSubSchema["properties"][];
|
||||
};
|
||||
|
||||
export type BlockIOKVSubSchema = BlockIOSubSchemaMeta & {
|
||||
type: "object";
|
||||
additionalProperties: { type: "string" | "number" | "integer" };
|
||||
default?: { [key: string]: string | number };
|
||||
};
|
||||
|
||||
export type BlockIOArraySubSchema = BlockIOSubSchemaMeta & {
|
||||
type: "array";
|
||||
items?: BlockIOSimpleTypeSchema;
|
||||
} | {
|
||||
items?: BlockIOSimpleTypeSubSchema;
|
||||
default?: Array<string>;
|
||||
};
|
||||
|
||||
export type BlockIOStringSubSchema = BlockIOSubSchemaMeta & {
|
||||
type: "string";
|
||||
enum?: string[];
|
||||
secret?: true;
|
||||
default?: string;
|
||||
} | {
|
||||
};
|
||||
|
||||
export type BlockIONumberSubSchema = BlockIOSubSchemaMeta & {
|
||||
type: "integer" | "number";
|
||||
default?: number;
|
||||
} | {
|
||||
};
|
||||
|
||||
export type BlockIOBooleanSubSchema = BlockIOSubSchemaMeta & {
|
||||
type: "boolean";
|
||||
default?: boolean;
|
||||
} | {
|
||||
};
|
||||
|
||||
export type BlockIONullSubSchema = BlockIOSubSchemaMeta & {
|
||||
type: "null";
|
||||
};
|
||||
|
||||
// At the time of writing, combined schemas only occur on the first nested level in a
|
||||
// block schema. It is typed this way to make the use of these objects less tedious.
|
||||
type BlockIOCombinedTypeSchema = {
|
||||
allOf: [BlockIOSimpleTypeSchema];
|
||||
type BlockIOCombinedTypeSubSchema = BlockIOSubSchemaMeta & ({
|
||||
allOf: [BlockIOSimpleTypeSubSchema];
|
||||
} | {
|
||||
anyOf: BlockIOSimpleTypeSchema[];
|
||||
anyOf: BlockIOSimpleTypeSubSchema[];
|
||||
default?: string | number | boolean | null;
|
||||
} | {
|
||||
oneOf: BlockIOSimpleTypeSchema[];
|
||||
oneOf: BlockIOSimpleTypeSubSchema[];
|
||||
default?: string | number | boolean | null;
|
||||
};
|
||||
});
|
||||
|
||||
/* Mirror of autogpt_server/data/graph.py:Node */
|
||||
export type Node = {
|
||||
|
||||
@@ -41,6 +41,7 @@ export function getTypeTextColor(type: string | null): string {
|
||||
array: 'text-indigo-500',
|
||||
null: 'text-gray-500',
|
||||
'': 'text-gray-500',
|
||||
any: 'text-gray-500',
|
||||
}[type] || 'text-gray-500';
|
||||
}
|
||||
|
||||
@@ -55,6 +56,7 @@ export function getTypeBgColor(type: string | null): string {
|
||||
array: 'bg-indigo-500',
|
||||
null: 'bg-gray-500',
|
||||
'': 'bg-gray-500',
|
||||
any: 'bg-gray-500',
|
||||
}[type] || 'bg-gray-500';
|
||||
}
|
||||
|
||||
@@ -68,6 +70,7 @@ export function getTypeColor(type: string | null): string {
|
||||
array: '#6366f1',
|
||||
null: '#6b7280',
|
||||
'': '#6b7280',
|
||||
any: '#6b7280',
|
||||
}[type] || '#6b7280';
|
||||
}
|
||||
|
||||
|
||||
@@ -224,6 +224,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33"
|
||||
integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==
|
||||
|
||||
"@radix-ui/number@1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/number/-/number-1.1.0.tgz#1e95610461a09cdf8bb05c152e76ca1278d5da46"
|
||||
integrity sha512-V3gRzhVNU1ldS5XhAPTom1fOIo4ccrjjJgmE+LI2h/WaFpHmx0MQApT+KZHnx8abG6Avtfcz4WoEciMnpFT3HQ==
|
||||
|
||||
"@radix-ui/primitive@1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/primitive/-/primitive-1.1.0.tgz#42ef83b3b56dccad5d703ae8c42919a68798bbe2"
|
||||
@@ -447,6 +452,33 @@
|
||||
"@radix-ui/react-use-callback-ref" "1.1.0"
|
||||
"@radix-ui/react-use-controllable-state" "1.1.0"
|
||||
|
||||
"@radix-ui/react-select@^2.1.1":
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-select/-/react-select-2.1.1.tgz#df05cb0b29d3deaef83b505917c4042e0e418a9f"
|
||||
integrity sha512-8iRDfyLtzxlprOo9IicnzvpsO1wNCkuwzzCM+Z5Rb5tNOpCdMvcc2AkzX0Fz+Tz9v6NJ5B/7EEgyZveo4FBRfQ==
|
||||
dependencies:
|
||||
"@radix-ui/number" "1.1.0"
|
||||
"@radix-ui/primitive" "1.1.0"
|
||||
"@radix-ui/react-collection" "1.1.0"
|
||||
"@radix-ui/react-compose-refs" "1.1.0"
|
||||
"@radix-ui/react-context" "1.1.0"
|
||||
"@radix-ui/react-direction" "1.1.0"
|
||||
"@radix-ui/react-dismissable-layer" "1.1.0"
|
||||
"@radix-ui/react-focus-guards" "1.1.0"
|
||||
"@radix-ui/react-focus-scope" "1.1.0"
|
||||
"@radix-ui/react-id" "1.1.0"
|
||||
"@radix-ui/react-popper" "1.2.0"
|
||||
"@radix-ui/react-portal" "1.1.1"
|
||||
"@radix-ui/react-primitive" "2.0.0"
|
||||
"@radix-ui/react-slot" "1.1.0"
|
||||
"@radix-ui/react-use-callback-ref" "1.1.0"
|
||||
"@radix-ui/react-use-controllable-state" "1.1.0"
|
||||
"@radix-ui/react-use-layout-effect" "1.1.0"
|
||||
"@radix-ui/react-use-previous" "1.1.0"
|
||||
"@radix-ui/react-visually-hidden" "1.1.0"
|
||||
aria-hidden "^1.1.1"
|
||||
react-remove-scroll "2.5.7"
|
||||
|
||||
"@radix-ui/react-slot@1.1.0", "@radix-ui/react-slot@^1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-slot/-/react-slot-1.1.0.tgz#7c5e48c36ef5496d97b08f1357bb26ed7c714b84"
|
||||
|
||||
Reference in New Issue
Block a user