UNPKG

circuit-bricks

Version:

A modular, Lego-style SVG circuit component system for React (ALPHA - Not for production use)

436 lines (430 loc) 17.3 kB
import { getAllComponents, getComponentSchema, validateCircuitState } from "./registry-D4rkLXEi.mjs"; //#region rolldown:runtime var __defProp = Object.defineProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; //#endregion //#region src/utils/circuitValidation.ts /** * Validate a circuit for common issues * * @param circuit - The circuit state to validate * @returns Array of validation issues */ function validateCircuit(circuit) { const validationResult = validateCircuitState(circuit); if (!validationResult.success) return [{ type: "error", message: `Invalid circuit structure: ${validationResult.error}` }]; const issues = []; issues.push(...validateWireConnections(circuit)); issues.push(...validateFloatingPorts(circuit)); issues.push(...validateShortCircuits(circuit)); return issues; } /** * Validate wire connections to ensure they reference valid components */ function validateWireConnections(circuit) { const issues = []; const componentIds = new Set(circuit.components.map((c) => c.id)); for (const wire of circuit.wires) { if (!componentIds.has(wire.from.componentId)) issues.push({ type: "error", message: `Wire connected to non-existent component ID: ${wire.from.componentId}`, wireId: wire.id }); if (!componentIds.has(wire.to.componentId)) issues.push({ type: "error", message: `Wire connected to non-existent component ID: ${wire.to.componentId}`, wireId: wire.id }); const sourceComponent = circuit.components.find((c) => c.id === wire.from.componentId); const destComponent = circuit.components.find((c) => c.id === wire.to.componentId); if (sourceComponent) { const schema = getComponentSchema(sourceComponent.type); if (schema && !schema.ports.some((p) => p.id === wire.from.portId)) issues.push({ type: "error", message: `Wire connected to non-existent port: ${wire.from.portId} on component ${sourceComponent.type}`, wireId: wire.id }); } if (destComponent) { const schema = getComponentSchema(destComponent.type); if (schema && !schema.ports.some((p) => p.id === wire.to.portId)) issues.push({ type: "error", message: `Wire connected to non-existent port: ${wire.to.portId} on component ${destComponent.type}`, wireId: wire.id }); } } return issues; } /** * Check for floating input/output ports that should be connected */ function validateFloatingPorts(circuit) { const issues = []; const connectedPorts = new Map(); for (const wire of circuit.wires) { if (!connectedPorts.has(wire.from.componentId)) connectedPorts.set(wire.from.componentId, new Set()); if (!connectedPorts.has(wire.to.componentId)) connectedPorts.set(wire.to.componentId, new Set()); connectedPorts.get(wire.from.componentId).add(wire.from.portId); connectedPorts.get(wire.to.componentId).add(wire.to.portId); } for (const component of circuit.components) { const schema = getComponentSchema(component.type); if (!schema) continue; const componentConnectedPorts = connectedPorts.get(component.id) || new Set(); for (const port of schema.ports) { if (port.type === "input" && !componentConnectedPorts.has(port.id)) issues.push({ type: "warning", message: `Floating input port: ${port.id} on ${schema.name} (${component.id})`, componentId: component.id }); if (port.type === "output" && !componentConnectedPorts.has(port.id)) issues.push({ type: "warning", message: `Floating output port: ${port.id} on ${schema.name} (${component.id})`, componentId: component.id }); } } return issues; } /** * Check for short circuits (multiple outputs connected together) */ function validateShortCircuits(circuit) { const issues = []; const connectionGraph = new Map(); for (const wire of circuit.wires) { const fromKey = `${wire.from.componentId}-${wire.from.portId}`; const toKey = `${wire.to.componentId}-${wire.to.portId}`; if (!connectionGraph.has(fromKey)) connectionGraph.set(fromKey, new Set()); if (!connectionGraph.has(toKey)) connectionGraph.set(toKey, new Set()); connectionGraph.get(fromKey).add(toKey); connectionGraph.get(toKey).add(fromKey); } const portTypes = new Map(); for (const component of circuit.components) { const schema = getComponentSchema(component.type); if (!schema) continue; for (const port of schema.ports) portTypes.set(`${component.id}-${port.id}`, port.type); } const visited = new Set(); const connectedComponents = []; for (const [port, connections] of connectionGraph.entries()) { if (visited.has(port)) continue; const connectedPorts = new Set(); const queue = [port]; visited.add(port); while (queue.length > 0) { const currentPort = queue.shift(); connectedPorts.add(currentPort); for (const connectedPort of connectionGraph.get(currentPort) || []) if (!visited.has(connectedPort)) { queue.push(connectedPort); visited.add(connectedPort); } } const outputPorts = [...connectedPorts].filter((p) => portTypes.get(p) === "output"); if (outputPorts.length > 1) issues.push({ type: "error", message: `Short circuit detected: ${outputPorts.length} output ports connected together` }); } return issues; } //#endregion //#region src/llm/componentDiscovery.ts /** * Convert a ComponentSchema to simplified ComponentInfo for LLM consumption */ function schemaToComponentInfo(schema) { return { id: schema.id, name: schema.name, category: schema.category, description: schema.description, ports: schema.ports.map((port) => ({ id: port.id, type: port.type, label: port.label })), properties: schema.properties.map((prop) => ({ key: prop.key, label: prop.label, type: prop.type, default: prop.default, unit: prop.unit, options: prop.options?.map((option) => ({ value: option.value, label: option.label })) })) }; } /** * List all available components in a format optimized for LLM consumption * * @returns Array of simplified component information */ function listAvailableComponents() { const allComponents = getAllComponents(); return allComponents.map(schemaToComponentInfo); } /** * Get detailed information about a specific component * * @param componentId - The ID of the component to get details for * @returns Detailed component information or null if not found */ function getComponentDetails(componentId) { const schema = getComponentSchema(componentId); if (!schema) return null; return schemaToComponentInfo(schema); } /** * Search components by name, description, or category * * @param query - Search query string * @returns Array of search results with relevance scores */ function searchComponents(query) { const allComponents = getAllComponents(); const searchTerm = query.toLowerCase(); const results = []; for (const schema of allComponents) { const matchedFields = []; let relevanceScore = 0; if (schema.name.toLowerCase().includes(searchTerm)) { matchedFields.push("name"); relevanceScore += 10; } if (schema.id.toLowerCase().includes(searchTerm)) { matchedFields.push("id"); relevanceScore += 8; } if (schema.category.toLowerCase().includes(searchTerm)) { matchedFields.push("category"); relevanceScore += 5; } if (schema.description.toLowerCase().includes(searchTerm)) { matchedFields.push("description"); relevanceScore += 3; } for (const prop of schema.properties) if (prop.label.toLowerCase().includes(searchTerm) || prop.key.toLowerCase().includes(searchTerm)) { matchedFields.push("properties"); relevanceScore += 1; break; } if (relevanceScore > 0) results.push({ component: schemaToComponentInfo(schema), relevanceScore, matchedFields }); } return results.sort((a, b) => b.relevanceScore - a.relevanceScore); } //#endregion //#region src/llm/validation.ts /** * Validate a circuit design with LLM-friendly output * * @param circuit - The circuit state to validate * @returns Validation result with clear messages */ function validateCircuitDesign(circuit) { const errors = []; const warnings = []; const suggestions = []; for (const component of circuit.components) { const schema = getComponentSchema(component.type); if (!schema) { errors.push(`Component type "${component.type}" (ID: ${component.id}) is not registered in the component registry.`); continue; } for (const prop of schema.properties) { const value = component.props[prop.key]; if (value === void 0 && prop.default === void 0) warnings.push(`Component "${component.id}" is missing required property "${prop.label}" (${prop.key}).`); if (value !== void 0) switch (prop.type) { case "number": if (typeof value !== "number") errors.push(`Property "${prop.label}" of component "${component.id}" must be a number, got ${typeof value}.`); else { if (prop.min !== void 0 && value < prop.min) errors.push(`Property "${prop.label}" of component "${component.id}" must be at least ${prop.min}, got ${value}.`); if (prop.max !== void 0 && value > prop.max) errors.push(`Property "${prop.label}" of component "${component.id}" must be at most ${prop.max}, got ${value}.`); } break; case "boolean": if (typeof value !== "boolean") errors.push(`Property "${prop.label}" of component "${component.id}" must be a boolean, got ${typeof value}.`); break; case "select": if (prop.options && !prop.options.some((opt) => opt.value === value)) { const validOptions = prop.options.map((opt) => opt.value).join(", "); errors.push(`Property "${prop.label}" of component "${component.id}" must be one of: ${validOptions}, got ${value}.`); } break; } } } for (const wire of circuit.wires) { const fromComponent = circuit.components.find((c) => c.id === wire.from.componentId); const toComponent = circuit.components.find((c) => c.id === wire.to.componentId); if (!fromComponent) { errors.push(`Wire "${wire.id}" references non-existent source component "${wire.from.componentId}".`); continue; } if (!toComponent) { errors.push(`Wire "${wire.id}" references non-existent destination component "${wire.to.componentId}".`); continue; } const fromSchema = getComponentSchema(fromComponent.type); const toSchema = getComponentSchema(toComponent.type); if (!fromSchema || !toSchema) continue; const fromPort = fromSchema.ports.find((p) => p.id === wire.from.portId); const toPort = toSchema.ports.find((p) => p.id === wire.to.portId); if (!fromPort) errors.push(`Wire "${wire.id}" references non-existent port "${wire.from.portId}" on component "${fromComponent.id}" (${fromSchema.name}).`); if (!toPort) errors.push(`Wire "${wire.id}" references non-existent port "${wire.to.portId}" on component "${toComponent.id}" (${toSchema.name}).`); if (fromPort && toPort) { if (fromPort.type === "input" && toPort.type === "input") warnings.push(`Wire "${wire.id}" connects two input ports, which may not be electrically valid.`); if (fromPort.type === "output" && toPort.type === "output") warnings.push(`Wire "${wire.id}" connects two output ports, which may cause conflicts.`); } } const powerSources = circuit.components.filter((comp) => { const schema = getComponentSchema(comp.type); return schema?.category === "power" || schema?.id === "battery" || schema?.id === "voltage-source"; }); const grounds = circuit.components.filter((comp) => { const schema = getComponentSchema(comp.type); return schema?.id === "ground"; }); if (powerSources.length === 0) warnings.push("Circuit has no power sources. Consider adding a battery or voltage source."); if (grounds.length === 0) warnings.push("Circuit has no ground connection. This may cause issues in real circuits."); if (powerSources.length > 1) warnings.push(`Circuit has ${powerSources.length} power sources. Ensure they are properly isolated or connected.`); const connectedComponents = new Set(); for (const wire of circuit.wires) { connectedComponents.add(wire.from.componentId); connectedComponents.add(wire.to.componentId); } for (const component of circuit.components) if (!connectedComponents.has(component.id)) warnings.push(`Component "${component.id}" is not connected to any other components.`); if (powerSources.length > 0 && grounds.length === 0) suggestions.push("Add a ground component to complete the circuit."); if (circuit.components.length > 0 && circuit.wires.length === 0) suggestions.push("Add wire connections between components to create a functional circuit."); const componentsByCategory = circuit.components.reduce((acc, comp) => { const schema = getComponentSchema(comp.type); if (schema) { if (!acc[schema.category]) acc[schema.category] = []; acc[schema.category].push(comp); } return acc; }, {}); const outputComponents = componentsByCategory["output"] || []; const passiveComponents = componentsByCategory["passive"] || []; const hasLEDLikeComponent = outputComponents.some((comp) => { const schema = getComponentSchema(comp.type); return schema?.name.toLowerCase().includes("led") || schema?.description.toLowerCase().includes("light") || schema?.description.toLowerCase().includes("diode"); }); const hasCurrentLimitingComponent = passiveComponents.some((comp) => { const schema = getComponentSchema(comp.type); return schema?.name.toLowerCase().includes("resistor") || schema?.description.toLowerCase().includes("resistance") || schema?.description.toLowerCase().includes("current limiting"); }); if (hasLEDLikeComponent && !hasCurrentLimitingComponent && powerSources.length > 0) suggestions.push("Consider adding a current-limiting component (like a resistor) in series with light-emitting components to prevent damage."); try { const existingValidation = validateCircuit(circuit); for (const issue of existingValidation) if (issue.type === "error") errors.push(issue.message); else if (issue.type === "warning") warnings.push(issue.message); } catch (error) { errors.push(`Circuit validation failed: ${error instanceof Error ? error.message : "Unknown error"}`); } return { isValid: errors.length === 0, errors, warnings, suggestions }; } /** * Validate a single component instance * * @param component - The component instance to validate * @returns Validation result */ function validateComponentInstance(component) { const errors = []; const warnings = []; const suggestions = []; const schema = getComponentSchema(component.type); if (!schema) { errors.push(`Component type "${component.type}" is not registered.`); return { isValid: false, errors, warnings, suggestions }; } for (const prop of schema.properties) { const value = component.props[prop.key]; if (value === void 0 && prop.default === void 0) warnings.push(`Missing property "${prop.label}" (${prop.key}).`); } if (typeof component.position.x !== "number" || typeof component.position.y !== "number") errors.push("Component position must have numeric x and y coordinates."); if (!component.id || typeof component.id !== "string") errors.push("Component must have a valid string ID."); return { isValid: errors.length === 0, errors, warnings, suggestions }; } /** * Validate a wire connection * * @param wire - The wire to validate * @param components - Array of components in the circuit * @returns Validation result */ function validateWireConnection(wire, components) { const errors = []; const warnings = []; const suggestions = []; const fromComponent = components.find((c) => c.id === wire.from.componentId); const toComponent = components.find((c) => c.id === wire.to.componentId); if (!fromComponent) errors.push(`Source component "${wire.from.componentId}" not found.`); if (!toComponent) errors.push(`Destination component "${wire.to.componentId}" not found.`); if (fromComponent && toComponent) { const fromSchema = getComponentSchema(fromComponent.type); const toSchema = getComponentSchema(toComponent.type); if (fromSchema && toSchema) { const fromPort = fromSchema.ports.find((p) => p.id === wire.from.portId); const toPort = toSchema.ports.find((p) => p.id === wire.to.portId); if (!fromPort) errors.push(`Port "${wire.from.portId}" not found on ${fromSchema.name}.`); if (!toPort) errors.push(`Port "${wire.to.portId}" not found on ${toSchema.name}.`); if (fromPort && toPort) { if (fromPort.type === "input" && toPort.type === "input") warnings.push("Connecting two input ports may not be electrically valid."); } } } return { isValid: errors.length === 0, errors, warnings, suggestions }; } //#endregion //#region src/llm/index.ts var llm_exports = {}; __export(llm_exports, { getComponentDetails: () => getComponentDetails, listAvailableComponents: () => listAvailableComponents, searchComponents: () => searchComponents, validateCircuitDesign: () => validateCircuitDesign, validateComponentInstance: () => validateComponentInstance, validateWireConnection: () => validateWireConnection }); //#endregion export { getComponentDetails, listAvailableComponents, llm_exports, searchComponents, validateCircuit, validateCircuitDesign, validateComponentInstance, validateWireConnection }; //# sourceMappingURL=llm-DXKM1NzQ.mjs.map