Skip to content
Open
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
57 changes: 24 additions & 33 deletions packages/visualizer/src/component/prompt-input/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,13 +97,22 @@ export const PromptInput: React.FC<PromptInputProps> = ({
[history, selectedType],
);

const actionMap = useMemo(() => {
const map = new Map<string, DeviceAction<any>>();
if (actionSpace) {
for (const action of actionSpace) {
if (action.name) map.set(action.name, action);
if (action.interfaceAlias) map.set(action.interfaceAlias, action);
Comment on lines +104 to +105
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preserve first-match precedence when indexing actionSpace

Building actionMap with unconditional map.set(...) makes duplicate name/interfaceAlias keys resolve to the last action, while the rest of the execution path still resolves by first match (executeAction in packages/playground/src/common.ts uses actionSpace.find(...)). This introduces a behavioral mismatch: for action spaces that include duplicates (e.g., default actions plus same-alias custom actions), the prompt UI/validation now reads one action schema but runtime dispatch executes a different action, which can reject parameters or run the wrong behavior.

Useful? React with 👍 / 👎.

}
}
return map;
}, [actionSpace]);

// Check if current method needs structured parameters (dynamic based on actionSpace)
const needsStructuredParams = useMemo(() => {
if (actionSpace) {
// Use actionSpace to determine if method needs structured params
const action = actionSpace.find(
(a) => a.interfaceAlias === selectedType || a.name === selectedType,
);
const action = actionMap.get(selectedType);

if (!action?.paramSchema) return false;

Expand All @@ -119,15 +128,13 @@ export const PromptInput: React.FC<PromptInputProps> = ({
return true;
}
return false;
}, [selectedType, actionSpace]);
}, [selectedType, actionMap, actionSpace]);

// Check if current method needs any input (either prompt or parameters)
const needsAnyInput = useMemo(() => {
if (actionSpace && actionSpace.length > 0) {
// Use actionSpace to determine if method needs any input
const action = actionSpace.find(
(a) => a.interfaceAlias === selectedType || a.name === selectedType,
);
const action = actionMap.get(selectedType);

// If action exists in actionSpace, check if it has required parameters
if (action) {
Expand Down Expand Up @@ -179,9 +186,7 @@ export const PromptInput: React.FC<PromptInputProps> = ({

if (actionSpace) {
// Use actionSpace to determine if method supports deep locate
const action = actionSpace.find(
(a) => a.interfaceAlias === selectedType || a.name === selectedType,
);
const action = actionMap.get(selectedType);

if (action?.paramSchema && isZodObjectSchema(action.paramSchema)) {
const schema = action.paramSchema as ZodObjectSchema;
Expand Down Expand Up @@ -279,9 +284,7 @@ export const PromptInput: React.FC<PromptInputProps> = ({
if (!needsStructuredParams || !actionSpace) {
return {};
}
const action = actionSpace.find(
(a) => a.interfaceAlias === selectedType || a.name === selectedType,
);
const action = actionMap.get(selectedType);

if (action?.paramSchema && isZodObjectSchema(action.paramSchema)) {
const defaultParams: FormParams = {};
Expand Down Expand Up @@ -444,17 +447,15 @@ export const PromptInput: React.FC<PromptInputProps> = ({
if (!needsStructuredParams || !actionSpace) {
return false;
}
const action = actionSpace.find(
(a) => a.interfaceAlias === selectedType || a.name === selectedType,
);
const action = actionMap.get(selectedType);

if (action?.paramSchema && isZodObjectSchema(action.paramSchema)) {
const schema = action.paramSchema as ZodObjectSchema;
const shape: Record<string, ZodType> = schema.shape || {};
return Object.keys(shape).length === 1;
}
return false;
}, [selectedType, needsStructuredParams, actionSpace]);
}, [selectedType, needsStructuredParams, actionMap, actionSpace]);

// Calculate if run button should be enabled
const isRunButtonEnabled = useMemo(
Expand All @@ -463,15 +464,15 @@ export const PromptInput: React.FC<PromptInputProps> = ({
runButtonEnabled,
!!needsStructuredParams,
params,
actionSpace,
actionMap,
selectedType,
promptValue,
),
[
runButtonEnabled,
needsStructuredParams,
selectedType,
actionSpace,
actionMap,
promptValue,
params,
],
Expand All @@ -488,9 +489,7 @@ export const PromptInput: React.FC<PromptInputProps> = ({
// For structured params, create a display string for history - dynamically
let historyPrompt = '';
if (needsStructuredParams && values.params && actionSpace) {
const action = actionSpace.find(
(a) => a.interfaceAlias === selectedType || a.name === selectedType,
);
const action = actionMap.get(selectedType);

if (action?.paramSchema && isZodObjectSchema(action.paramSchema)) {
// Separate locate field from other fields for legacy format compatibility
Expand Down Expand Up @@ -617,9 +616,7 @@ export const PromptInput: React.FC<PromptInputProps> = ({

// Try to get action from actionSpace first
if (actionSpace) {
const action = actionSpace.find(
(a) => a.interfaceAlias === selectedType || a.name === selectedType,
);
const action = actionMap.get(selectedType);

if (action?.paramSchema && isZodObjectSchema(action.paramSchema)) {
const schema = action.paramSchema as ZodObjectSchema;
Expand Down Expand Up @@ -651,10 +648,7 @@ export const PromptInput: React.FC<PromptInputProps> = ({

// Try to get from action's paramSchema directly
if (actionSpace) {
const action = actionSpace.find(
(a) =>
a.interfaceAlias === selectedType || a.name === selectedType,
);
const action = actionMap.get(selectedType);
if (
action?.paramSchema &&
typeof action.paramSchema === 'object' &&
Expand Down Expand Up @@ -736,10 +730,7 @@ export const PromptInput: React.FC<PromptInputProps> = ({

// Try to get from action's paramSchema directly
if (actionSpace) {
const action = actionSpace.find(
(a) =>
a.interfaceAlias === selectedType || a.name === selectedType,
);
const action = actionMap.get(selectedType);
if (
action?.paramSchema &&
typeof action.paramSchema === 'object' &&
Expand Down
19 changes: 7 additions & 12 deletions packages/visualizer/src/utils/playground-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,23 +89,20 @@ export const isRunButtonEnabled = (
runButtonEnabled: boolean,
needsStructuredParams: boolean,
params: any,
actionSpace: any[] | undefined,
actionMap: Map<string, any> | undefined,
selectedType: string,
promptValue: string,
) => {
if (!runButtonEnabled) {
return false;
}

const action = actionMap?.get(selectedType);

// Check if this method needs any input
const needsAnyInput = (() => {
if (actionSpace) {
// Use actionSpace to determine if method needs any input
const action = actionSpace.find(
(a) => a.interfaceAlias === selectedType || a.name === selectedType,
);

// If action exists in actionSpace, check if it has paramSchema with actual fields
if (actionMap) {
// If action exists in actionMap, check if it has paramSchema with actual fields
if (action) {
if (!action.paramSchema) return false;

Expand All @@ -125,7 +122,7 @@ export const isRunButtonEnabled = (
return true;
}

// If not found in actionSpace, assume most methods need input
// If not found in actionMap, assume most methods need input
return true;
}

Expand All @@ -140,9 +137,7 @@ export const isRunButtonEnabled = (

if (needsStructuredParams) {
const currentParams = params || {};
const action = actionSpace?.find(
(a) => a.interfaceAlias === selectedType || a.name === selectedType,
);

if (action?.paramSchema && isZodObjectSchema(action.paramSchema)) {
// Check if all required fields are filled
const schema = action.paramSchema as unknown as ZodObjectSchema;
Expand Down
Loading