Merge pull request #157 from kshern/feature/add-array-proptype
Add an array input field to the ToolsTab using DynamicJsonForm.
This commit is contained in:
@@ -215,23 +215,112 @@ const DynamicJsonForm = ({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const newValue = {
|
const updateArray = (
|
||||||
...(typeof value === "object" && value !== null && !Array.isArray(value)
|
array: JsonValue[],
|
||||||
? value
|
path: string[],
|
||||||
: {}),
|
value: JsonValue,
|
||||||
} as JsonObject;
|
): JsonValue[] => {
|
||||||
let current: JsonObject = newValue;
|
const [index, ...restPath] = path;
|
||||||
|
const arrayIndex = Number(index);
|
||||||
|
|
||||||
for (let i = 0; i < path.length - 1; i++) {
|
// Validate array index
|
||||||
const key = path[i];
|
if (isNaN(arrayIndex)) {
|
||||||
if (!(key in current)) {
|
console.error(`Invalid array index: ${index}`);
|
||||||
current[key] = {};
|
return array;
|
||||||
}
|
}
|
||||||
current = current[key] as JsonObject;
|
|
||||||
}
|
|
||||||
|
|
||||||
current[path[path.length - 1]] = fieldValue;
|
// Check array bounds
|
||||||
onChange(newValue);
|
if (arrayIndex < 0) {
|
||||||
|
console.error(`Array index out of bounds: ${arrayIndex} < 0`);
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newArray = [...array];
|
||||||
|
|
||||||
|
if (restPath.length === 0) {
|
||||||
|
newArray[arrayIndex] = value;
|
||||||
|
} else {
|
||||||
|
// Ensure index position exists
|
||||||
|
if (arrayIndex >= array.length) {
|
||||||
|
console.warn(`Extending array to index ${arrayIndex}`);
|
||||||
|
newArray.length = arrayIndex + 1;
|
||||||
|
newArray.fill(null, array.length, arrayIndex);
|
||||||
|
}
|
||||||
|
newArray[arrayIndex] = updateValue(
|
||||||
|
newArray[arrayIndex],
|
||||||
|
restPath,
|
||||||
|
value,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return newArray;
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateObject = (
|
||||||
|
obj: JsonObject,
|
||||||
|
path: string[],
|
||||||
|
value: JsonValue,
|
||||||
|
): JsonObject => {
|
||||||
|
const [key, ...restPath] = path;
|
||||||
|
|
||||||
|
// Validate object key
|
||||||
|
if (typeof key !== "string") {
|
||||||
|
console.error(`Invalid object key: ${key}`);
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newObj = { ...obj };
|
||||||
|
|
||||||
|
if (restPath.length === 0) {
|
||||||
|
newObj[key] = value;
|
||||||
|
} else {
|
||||||
|
// Ensure key exists
|
||||||
|
if (!(key in newObj)) {
|
||||||
|
console.warn(`Creating new key in object: ${key}`);
|
||||||
|
newObj[key] = {};
|
||||||
|
}
|
||||||
|
newObj[key] = updateValue(newObj[key], restPath, value);
|
||||||
|
}
|
||||||
|
return newObj;
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateValue = (
|
||||||
|
current: JsonValue,
|
||||||
|
path: string[],
|
||||||
|
value: JsonValue,
|
||||||
|
): JsonValue => {
|
||||||
|
if (path.length === 0) return value;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!current) {
|
||||||
|
current = !isNaN(Number(path[0])) ? [] : {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type checking
|
||||||
|
if (Array.isArray(current)) {
|
||||||
|
return updateArray(current, path, value);
|
||||||
|
} else if (typeof current === "object" && current !== null) {
|
||||||
|
return updateObject(current, path, value);
|
||||||
|
} else {
|
||||||
|
console.error(
|
||||||
|
`Cannot update path ${path.join(".")} in non-object/array value:`,
|
||||||
|
current,
|
||||||
|
);
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error updating value at path ${path.join(".")}:`, error);
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const newValue = updateValue(value, path, fieldValue);
|
||||||
|
onChange(newValue);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to update form value:", error);
|
||||||
|
// Keep the original value unchanged
|
||||||
|
onChange(value);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -17,12 +17,6 @@ import ListPane from "./ListPane";
|
|||||||
|
|
||||||
import { CompatibilityCallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
import { CompatibilityCallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
||||||
|
|
||||||
type SchemaProperty = {
|
|
||||||
type: string;
|
|
||||||
description?: string;
|
|
||||||
properties?: Record<string, SchemaProperty>;
|
|
||||||
};
|
|
||||||
|
|
||||||
const ToolsTab = ({
|
const ToolsTab = ({
|
||||||
tools,
|
tools,
|
||||||
listTools,
|
listTools,
|
||||||
@@ -168,7 +162,7 @@ const ToolsTab = ({
|
|||||||
</p>
|
</p>
|
||||||
{Object.entries(selectedTool.inputSchema.properties ?? []).map(
|
{Object.entries(selectedTool.inputSchema.properties ?? []).map(
|
||||||
([key, value]) => {
|
([key, value]) => {
|
||||||
const prop = value as SchemaProperty;
|
const prop = value as JsonSchemaType;
|
||||||
return (
|
return (
|
||||||
<div key={key}>
|
<div key={key}>
|
||||||
<Label
|
<Label
|
||||||
@@ -211,16 +205,15 @@ const ToolsTab = ({
|
|||||||
}
|
}
|
||||||
className="mt-1"
|
className="mt-1"
|
||||||
/>
|
/>
|
||||||
) : prop.type === "object" ? (
|
) : prop.type === "object" || prop.type === "array" ? (
|
||||||
<div className="mt-1">
|
<div className="mt-1">
|
||||||
<DynamicJsonForm
|
<DynamicJsonForm
|
||||||
schema={
|
schema={{
|
||||||
{
|
type: prop.type,
|
||||||
type: "object",
|
properties: prop.properties,
|
||||||
properties: prop.properties,
|
description: prop.description,
|
||||||
description: prop.description,
|
items: prop.items,
|
||||||
} as JsonSchemaType
|
}}
|
||||||
}
|
|
||||||
value={(params[key] as JsonValue) ?? {}}
|
value={(params[key] as JsonValue) ?? {}}
|
||||||
onChange={(newValue: JsonValue) => {
|
onChange={(newValue: JsonValue) => {
|
||||||
setParams({
|
setParams({
|
||||||
|
|||||||
Reference in New Issue
Block a user