From 2701b4c94c6793d400aa3d8b43839af10ab28e34 Mon Sep 17 00:00:00 2001 From: Nikhil-Doye Date: Tue, 21 Oct 2025 23:47:27 -0400 Subject: [PATCH] Add help system and field configuration components - Introduced a new help system with styled help icons and panels for user guidance. - Created FieldWithHelp component to integrate help text and examples for various input fields. - Added comprehensive field configurations for different node types, enhancing user experience and clarity. - Implemented tests for dual-label system to ensure proper configuration and validation of node types and fields. - Updated NodeConfiguration and NodeLibrary components to utilize new help features and improve accessibility. --- src/__tests__/dualLabelSystem.test.ts | 175 +++++++++++++ src/components/FieldWithHelp.tsx | 206 +++++++++++++++ src/components/NodeConfiguration.tsx | 179 ++++--------- src/components/NodeLibrary.tsx | 328 +++++++++++------------ src/config/fieldConfigs.ts | 363 ++++++++++++++++++++++++++ src/config/nodeTypeConfigs.ts | 204 +++++++++++++++ src/index.css | 41 +++ 7 files changed, 1201 insertions(+), 295 deletions(-) create mode 100644 src/__tests__/dualLabelSystem.test.ts create mode 100644 src/components/FieldWithHelp.tsx create mode 100644 src/config/fieldConfigs.ts create mode 100644 src/config/nodeTypeConfigs.ts diff --git a/src/__tests__/dualLabelSystem.test.ts b/src/__tests__/dualLabelSystem.test.ts new file mode 100644 index 0000000..34c078e --- /dev/null +++ b/src/__tests__/dualLabelSystem.test.ts @@ -0,0 +1,175 @@ +import { getNodeTypeConfig, nodeTypeConfigs } from "../config/nodeTypeConfigs"; +import { + getFieldConfig, + fieldConfigs, + getFieldsForNodeType, +} from "../config/fieldConfigs"; +import { NodeType } from "../types"; + +describe("Dual-Label System", () => { + describe("Node Type Configurations", () => { + test("should have configurations for all node types", () => { + const nodeTypes: NodeType[] = [ + "dataInput", + "webScraping", + "llmTask", + "embeddingGenerator", + "similaritySearch", + "structuredOutput", + "dataOutput", + "databaseQuery", + "databaseInsert", + "databaseUpdate", + "databaseDelete", + ]; + + nodeTypes.forEach((nodeType) => { + const config = getNodeTypeConfig(nodeType); + expect(config).toBeDefined(); + expect(config.userFriendlyName).toBeTruthy(); + expect(config.technicalName).toBeTruthy(); + expect(config.description).toBeTruthy(); + expect(config.helpText).toBeTruthy(); + expect(config.commonUseCases).toBeInstanceOf(Array); + expect(config.commonUseCases.length).toBeGreaterThan(0); + expect(config.icon).toBeTruthy(); + expect(config.category).toBeTruthy(); + }); + }); + + test("should have user-friendly names that are different from technical names", () => { + Object.entries(nodeTypeConfigs).forEach(([nodeType, config]) => { + expect(config.userFriendlyName).not.toBe(config.technicalName); + expect(config.userFriendlyName).toBeTruthy(); + expect(config.technicalName).toBeTruthy(); + }); + }); + + test("should have meaningful help text", () => { + Object.entries(nodeTypeConfigs).forEach(([nodeType, config]) => { + expect(config.helpText.length).toBeGreaterThan(10); + expect(config.helpText).toBeTruthy(); + }); + }); + }); + + describe("Field Configurations", () => { + const testFields = [ + "dataType", + "prompt", + "url", + "model", + "temperature", + "maxTokens", + "label", + "formats", + "maxLength", + "query", + "table", + "format", + "filename", + ]; + + test("should have configurations for common fields", () => { + testFields.forEach((fieldName) => { + const config = getFieldConfig(fieldName); + expect(config).toBeDefined(); + expect(config.userFriendlyName).toBeTruthy(); + expect(config.technicalName).toBeTruthy(); + expect(config.description).toBeTruthy(); + expect(config.helpText).toBeTruthy(); + expect(config.examples).toBeInstanceOf(Array); + expect(config.examples.length).toBeGreaterThan(0); + }); + }); + + test("should have user-friendly field names", () => { + testFields.forEach((fieldName) => { + const config = getFieldConfig(fieldName); + if (config) { + expect(config.userFriendlyName).not.toBe(config.technicalName); + expect(config.userFriendlyName).toMatch(/^[A-Z]/); // Should start with capital letter + } + }); + }); + + test("should have helpful examples for each field", () => { + testFields.forEach((fieldName) => { + const config = getFieldConfig(fieldName); + if (config) { + expect(config.examples.length).toBeGreaterThan(0); + config.examples.forEach((example) => { + expect(example).toBeTruthy(); + expect(example.length).toBeGreaterThan(0); + }); + } + }); + }); + }); + + describe("Field Mappings", () => { + const testNodeTypes: NodeType[] = [ + "dataInput", + "llmTask", + "webScraping", + "databaseQuery", + ]; + + test("should return fields for each node type", () => { + testNodeTypes.forEach((nodeType) => { + const fields = getFieldsForNodeType(nodeType); + expect(fields).toBeInstanceOf(Array); + expect(fields.length).toBeGreaterThan(0); + }); + }); + + test("should have configurations for all mapped fields", () => { + testNodeTypes.forEach((nodeType) => { + const fields = getFieldsForNodeType(nodeType); + fields.forEach((fieldName) => { + const fieldConfig = getFieldConfig(fieldName); + expect(fieldConfig).toBeDefined(); + expect(fieldConfig.userFriendlyName).toBeTruthy(); + }); + }); + }); + }); + + describe("Content Quality", () => { + test("should have non-technical language in user-friendly names", () => { + Object.entries(nodeTypeConfigs).forEach(([nodeType, config]) => { + // Check that user-friendly names don't contain technical jargon + const technicalTerms = [ + "API", + "JSON", + "HTTP", + "SQL", + "LLM", + "URL", + "CSV", + ]; + technicalTerms.forEach((term) => { + expect(config.userFriendlyName).not.toContain(term); + }); + }); + }); + + test("should have clear, actionable help text", () => { + Object.entries(nodeTypeConfigs).forEach(([nodeType, config]) => { + expect(config.helpText).toMatch(/[.!?]$/); // Should end with punctuation + expect(config.helpText).not.toContain("undefined"); + expect(config.helpText).not.toContain("null"); + }); + }); + + test("should have practical common use cases", () => { + Object.entries(nodeTypeConfigs).forEach(([nodeType, config]) => { + config.commonUseCases.forEach((useCase) => { + expect(useCase).toBeTruthy(); + expect(useCase.length).toBeGreaterThan(10); + expect(useCase).toMatch(/^[A-Z]/); // Should start with capital letter + }); + }); + }); + }); +}); diff --git a/src/components/FieldWithHelp.tsx b/src/components/FieldWithHelp.tsx new file mode 100644 index 0000000..cf52635 --- /dev/null +++ b/src/components/FieldWithHelp.tsx @@ -0,0 +1,206 @@ +import React, { useState } from "react"; +import { HelpCircle } from "lucide-react"; +import { getFieldConfig } from "../config/fieldConfigs"; +import { Input } from "./ui/input"; +import { Textarea } from "./ui/textarea"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "./ui/select"; + +interface FieldWithHelpProps { + fieldName: string; + value: any; + onChange: (value: any) => void; + type: string; + options?: string[]; + multiple?: boolean; + placeholder?: string; + min?: number; + max?: number; + step?: number; + className?: string; +} + +export const FieldWithHelp: React.FC = ({ + fieldName, + value, + onChange, + type, + options = [], + multiple = false, + placeholder, + min, + max, + step, + className = "", +}) => { + const [showHelp, setShowHelp] = useState(false); + const fieldConfig = getFieldConfig(fieldName); + + if (!fieldConfig) { + // Fallback for unknown fields + return ( +
+ + onChange(e.target.value)} + className="field-input" + placeholder={placeholder} + /> +
+ ); + } + + const renderInput = () => { + switch (type) { + case "textarea": + return ( +