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
JavaScript
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