Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 21 additions & 2 deletions apps/web/src/components/flows/node-inspector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,28 @@ function ParamInput({
paramKey,
value,
onChange,
options,
}: {
paramKey: string;
value: unknown;
onChange: (val: unknown) => void;
options?: string[];
}) {
if (options && options.length > 0) {
return (
<select
value={String(value)}
onChange={(e) => onChange(e.target.value)}
className="rounded border border-border bg-background px-2 py-1 text-xs text-foreground outline-none focus:border-primary"
>
{options.map((opt) => (
<option key={opt} value={opt}>
{PARAM_VALUE_LABELS[opt] ?? opt}
</option>
))}
</select>
);
}
Comment on lines +50 to +64
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Stringifying undefined can yield "undefined" in the select value.

For optional params, value may briefly be undefined (e.g., during config updates), so String(value) becomes 'undefined', which won't match any option and can put the select in an odd state. Consider normalizing nullish values, e.g. value={value == null ? '' : String(value)}, and providing a corresponding placeholder option if appropriate.

Suggested change
if (options && options.length > 0) {
return (
<select
value={String(value)}
onChange={(e) => onChange(e.target.value)}
className="rounded border border-border bg-background px-2 py-1 text-xs text-foreground outline-none focus:border-primary"
>
{options.map((opt) => (
<option key={opt} value={opt}>
{PARAM_VALUE_LABELS[opt] ?? opt}
</option>
))}
</select>
);
}
if (options && options.length > 0) {
const hasEmptyOption = options.includes('');
const stringValue = value == null ? '' : String(value);
return (
<select
value={stringValue}
onChange={(e) => onChange(e.target.value)}
className="rounded border border-border bg-background px-2 py-1 text-xs text-foreground outline-none focus:border-primary"
>
{!hasEmptyOption && stringValue === '' && (
<option value="" disabled>
Select…
</option>
)}
{options.map((opt) => (
<option key={opt} value={opt}>
{PARAM_VALUE_LABELS[opt] ?? opt}
</option>
))}
</select>
);
}

if (typeof value === 'boolean') {
return (
<button
Expand Down Expand Up @@ -133,7 +150,7 @@ export function NodeInspector() {
{params ? (
<div className="flex flex-col gap-2">
{/* Required params — always shown */}
{requiredParams.map(({ key }) => (
{requiredParams.map(({ key, options }) => (
<label key={key} className="flex flex-col gap-0.5">
<span className="text-[10px] text-muted-foreground">
{PARAM_LABELS[key] ?? key}
Expand All @@ -142,6 +159,7 @@ export function NodeInspector() {
paramKey={key}
value={config[key] ?? registry.defaultConfig[key]}
onChange={(val) => updateNodeConfig(node.id, { [key]: val })}
options={options}
/>
</label>
))}
Expand All @@ -154,7 +172,7 @@ export function NodeInspector() {
선택적 파라미터
</span>
</div>
{optionalParams.map(({ key }) => {
{optionalParams.map(({ key, options }) => {
const isEnabled = key in config;
const defaultVal = registry.defaultConfig[key];
return (
Expand Down Expand Up @@ -183,6 +201,7 @@ export function NodeInspector() {
paramKey={key}
value={config[key]}
onChange={(val) => updateNodeConfig(node.id, { [key]: val })}
options={options}
/>
) : (
<span className="rounded border border-border/40 bg-muted/40 px-2 py-1 text-xs text-muted-foreground/40">
Expand Down
11 changes: 6 additions & 5 deletions packages/types/src/flow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export interface PortDefinition {
export interface ParamDefinition {
key: string;
required: boolean;
options?: string[];
}

export interface NodeTypeInfo {
Expand Down Expand Up @@ -75,7 +76,7 @@ export const NODE_TYPE_REGISTRY: Record<string, NodeTypeInfo> = {
defaultConfig: { period: 14, source: 'close' },
params: [
{ key: 'period', required: true },
{ key: 'source', required: false },
{ key: 'source', required: false, options: ['close', 'open', 'high', 'low'] },
],
},
macd: {
Expand Down Expand Up @@ -128,7 +129,7 @@ export const NODE_TYPE_REGISTRY: Record<string, NodeTypeInfo> = {
outputs: [{ name: 'result', type: 'boolean' }],
defaultConfig: { operator: '<', threshold: 30 },
params: [
{ key: 'operator', required: true },
{ key: 'operator', required: true, options: ['<', '>', '<=', '>=', '=='] },
{ key: 'threshold', required: true },
],
},
Expand All @@ -142,7 +143,7 @@ export const NODE_TYPE_REGISTRY: Record<string, NodeTypeInfo> = {
],
outputs: [{ name: 'result', type: 'boolean' }],
defaultConfig: { direction: 'above' },
params: [{ key: 'direction', required: true }],
params: [{ key: 'direction', required: true, options: ['above', 'below'] }],
},
'and-or': {
subtype: 'and-or',
Expand All @@ -154,7 +155,7 @@ export const NODE_TYPE_REGISTRY: Record<string, NodeTypeInfo> = {
],
outputs: [{ name: 'result', type: 'boolean' }],
defaultConfig: { operator: 'AND' },
params: [{ key: 'operator', required: true }],
params: [{ key: 'operator', required: true, options: ['AND', 'OR'] }],
},
'market-order': {
subtype: 'market-order',
Expand All @@ -164,7 +165,7 @@ export const NODE_TYPE_REGISTRY: Record<string, NodeTypeInfo> = {
outputs: [{ name: 'result', type: 'OrderResult' }],
defaultConfig: { side: 'buy', amount: '0.001' },
params: [
{ key: 'side', required: true },
{ key: 'side', required: true, options: ['buy', 'sell'] },
{ key: 'amount', required: true },
],
},
Expand Down
Loading