Manage display of "Run as Task" checkbox on Tools Tab
* In ToolsTab.tsx
- define ExtendedTool interface for augmenting with execution, meta, icons
- in hasMeta function,
- cast tool as ExtendedTool
- add getTaskSupport function
- returns forbidden if input is not a tool
- returns the setting of execution.taskSupport if set and valid
- returns optional otherwise
- in useEffect map callback
- set runAsTask to true if getTaskSupport returns "required", else false
- in IconDisplay instances cast tool as ExtendedTool instead of as WithIcons
- hide "Run as Task" checkbox if getTaskSupport returns "forbidden" for the selected tool
- disable the "Run as Task" checkbox if getTaskSupport returns "required"
* In ToolsTab.test.tsx
- added unit test
- "should show/hide/disable run-as-task checkbox based on taskSupport"
- tests checkbox checked and disabled states for "forbidden", "required", and "optional" states
This commit is contained in:
parent
ce092a39e7
commit
96b1b586f9
2 changed files with 109 additions and 21 deletions
|
|
@ -50,11 +50,40 @@ import {
|
||||||
hasValidMetaName,
|
hasValidMetaName,
|
||||||
hasValidMetaPrefix,
|
hasValidMetaPrefix,
|
||||||
isReservedMetaKey,
|
isReservedMetaKey,
|
||||||
} from "@/utils/metaUtils";
|
} from "../utils/metaUtils";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extended Tool type that includes optional fields used by the inspector.
|
||||||
|
*/
|
||||||
|
interface ExtendedTool extends Tool, WithIcons {
|
||||||
|
_meta?: Record<string, unknown>;
|
||||||
|
execution?: {
|
||||||
|
taskSupport?: "forbidden" | "required" | "optional";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Type guard to safely detect the optional _meta field without using `any`
|
// Type guard to safely detect the optional _meta field without using `any`
|
||||||
const hasMeta = (tool: Tool): tool is Tool & { _meta: unknown } =>
|
const hasMeta = (
|
||||||
typeof (tool as { _meta?: unknown })._meta !== "undefined";
|
tool: Tool,
|
||||||
|
): tool is ExtendedTool & { _meta: Record<string, unknown> } =>
|
||||||
|
typeof (tool as ExtendedTool)._meta !== "undefined";
|
||||||
|
|
||||||
|
// Type guard to detect execution.taskSupport
|
||||||
|
const getTaskSupport = (
|
||||||
|
tool: Tool | null,
|
||||||
|
): "forbidden" | "required" | "optional" => {
|
||||||
|
if (!tool) return "forbidden";
|
||||||
|
const extendedTool = tool as ExtendedTool;
|
||||||
|
const taskSupport = extendedTool.execution?.taskSupport;
|
||||||
|
if (
|
||||||
|
taskSupport === "forbidden" ||
|
||||||
|
taskSupport === "required" ||
|
||||||
|
taskSupport === "optional"
|
||||||
|
) {
|
||||||
|
return taskSupport;
|
||||||
|
}
|
||||||
|
return "optional";
|
||||||
|
};
|
||||||
|
|
||||||
const ToolsTab = ({
|
const ToolsTab = ({
|
||||||
tools,
|
tools,
|
||||||
|
|
@ -131,7 +160,8 @@ const ToolsTab = ({
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
setParams(Object.fromEntries(params));
|
setParams(Object.fromEntries(params));
|
||||||
setRunAsTask(false);
|
const taskSupport = getTaskSupport(selectedTool);
|
||||||
|
setRunAsTask(taskSupport === "required");
|
||||||
|
|
||||||
// Reset validation errors when switching tools
|
// Reset validation errors when switching tools
|
||||||
setHasValidationErrors(false);
|
setHasValidationErrors(false);
|
||||||
|
|
@ -170,7 +200,7 @@ const ToolsTab = ({
|
||||||
renderItem={(tool) => (
|
renderItem={(tool) => (
|
||||||
<div className="flex items-start w-full gap-2">
|
<div className="flex items-start w-full gap-2">
|
||||||
<div className="flex-shrink-0 mt-1">
|
<div className="flex-shrink-0 mt-1">
|
||||||
<IconDisplay icons={(tool as WithIcons).icons} size="sm" />
|
<IconDisplay icons={(tool as ExtendedTool).icons} size="sm" />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col flex-1 min-w-0">
|
<div className="flex flex-col flex-1 min-w-0">
|
||||||
<span className="truncate">{tool.name}</span>
|
<span className="truncate">{tool.name}</span>
|
||||||
|
|
@ -191,7 +221,7 @@ const ToolsTab = ({
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{selectedTool && (
|
{selectedTool && (
|
||||||
<IconDisplay
|
<IconDisplay
|
||||||
icons={(selectedTool as WithIcons).icons}
|
icons={(selectedTool as ExtendedTool).icons}
|
||||||
size="md"
|
size="md"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
@ -659,21 +689,24 @@ const ToolsTab = ({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="flex items-center space-x-2">
|
{getTaskSupport(selectedTool) !== "forbidden" && (
|
||||||
<Checkbox
|
<div className="flex items-center space-x-2">
|
||||||
id="run-as-task"
|
<Checkbox
|
||||||
checked={runAsTask}
|
id="run-as-task"
|
||||||
onCheckedChange={(checked: boolean) =>
|
checked={runAsTask}
|
||||||
setRunAsTask(checked)
|
onCheckedChange={(checked: boolean) =>
|
||||||
}
|
setRunAsTask(checked)
|
||||||
/>
|
}
|
||||||
<Label
|
disabled={getTaskSupport(selectedTool) === "required"}
|
||||||
htmlFor="run-as-task"
|
/>
|
||||||
className="text-sm font-medium text-gray-700 dark:text-gray-300 cursor-pointer"
|
<Label
|
||||||
>
|
htmlFor="run-as-task"
|
||||||
Run as task
|
className="text-sm font-medium text-gray-700 dark:text-gray-300 cursor-pointer"
|
||||||
</Label>
|
>
|
||||||
</div>
|
Run as task
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<Button
|
<Button
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
// Validate JSON inputs before calling tool
|
// Validate JSON inputs before calling tool
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,13 @@ import {
|
||||||
RESERVED_NAMESPACE_MESSAGE,
|
RESERVED_NAMESPACE_MESSAGE,
|
||||||
} from "../../utils/metaUtils";
|
} from "../../utils/metaUtils";
|
||||||
|
|
||||||
|
interface ExtendedTool extends Tool {
|
||||||
|
_meta?: Record<string, unknown>;
|
||||||
|
execution?: {
|
||||||
|
taskSupport?: "forbidden" | "required" | "optional";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
describe("ToolsTab", () => {
|
describe("ToolsTab", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
// Clear the output schema cache before each test
|
// Clear the output schema cache before each test
|
||||||
|
|
@ -107,6 +114,54 @@ describe("ToolsTab", () => {
|
||||||
expect(newInput.value).toBe("");
|
expect(newInput.value).toBe("");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should show/hide/disable run-as-task checkbox based on taskSupport", async () => {
|
||||||
|
const forbiddenTool: ExtendedTool = {
|
||||||
|
...mockTools[0],
|
||||||
|
name: "forbiddenTool",
|
||||||
|
execution: { taskSupport: "forbidden" },
|
||||||
|
};
|
||||||
|
const requiredTool: ExtendedTool = {
|
||||||
|
...mockTools[0],
|
||||||
|
name: "requiredTool",
|
||||||
|
execution: { taskSupport: "required" },
|
||||||
|
};
|
||||||
|
const optionalTool: ExtendedTool = {
|
||||||
|
...mockTools[0],
|
||||||
|
name: "optionalTool",
|
||||||
|
execution: { taskSupport: "optional" },
|
||||||
|
};
|
||||||
|
|
||||||
|
const { rerender } = renderToolsTab({
|
||||||
|
selectedTool: forbiddenTool,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(screen.queryByLabelText(/run as task/i)).not.toBeInTheDocument();
|
||||||
|
|
||||||
|
rerender(
|
||||||
|
<Tabs defaultValue="tools">
|
||||||
|
<ToolsTab {...defaultProps} selectedTool={optionalTool} />
|
||||||
|
</Tabs>,
|
||||||
|
);
|
||||||
|
const optionalCheckbox = screen.getByLabelText(
|
||||||
|
/run as task/i,
|
||||||
|
) as HTMLInputElement;
|
||||||
|
expect(optionalCheckbox).toBeInTheDocument();
|
||||||
|
expect(optionalCheckbox.getAttribute("aria-checked")).toBe("false");
|
||||||
|
expect(optionalCheckbox).not.toBeDisabled();
|
||||||
|
|
||||||
|
rerender(
|
||||||
|
<Tabs defaultValue="tools">
|
||||||
|
<ToolsTab {...defaultProps} selectedTool={requiredTool} />
|
||||||
|
</Tabs>,
|
||||||
|
);
|
||||||
|
const requiredCheckbox = screen.getByLabelText(
|
||||||
|
/run as task/i,
|
||||||
|
) as HTMLInputElement;
|
||||||
|
expect(requiredCheckbox).toBeInTheDocument();
|
||||||
|
expect(requiredCheckbox.getAttribute("aria-checked")).toBe("true");
|
||||||
|
expect(requiredCheckbox).toBeDisabled();
|
||||||
|
});
|
||||||
|
|
||||||
it("should handle integer type inputs", async () => {
|
it("should handle integer type inputs", async () => {
|
||||||
renderToolsTab({
|
renderToolsTab({
|
||||||
selectedTool: mockTools[1], // Use the tool with integer type
|
selectedTool: mockTools[1], // Use the tool with integer type
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue