Files
diy-layout-creator/diylc/docs/plans/ai-select-with.md
T
Bane Stojkovic eea7d45cc9 ai notes
2026-11-06 10:51:34 +01:00

5.8 KiB

AI Select-With Companions

Declaring on a component which other component types must reach the AI together with it, so that the AI server stops hard-coding component knowledge.

1. Goal

When the AI's component selector picks a type that is never used alone, the types it needs have to be in the filtered catalog too, or the edit model cannot name them: given an Enclosure and told to drill holes, but without Drill Hole in its catalog, it invented chassis.DrillHole instead of electromechanical.DrillHole. A SELECTION @AiNote asks the selector to pick the companions, but a note is advice to a cheap, low-effort call and can be missed. The guarantee has to be deterministic, and it belongs with the component, not in the server.

2. Current state

  • @AiNotes on ChassisPanel (Enclosure), BlankBoard and DrillHole tell the selector and the edit model when to use Drill Hole and the cutouts and how to place them (see ai-component-notes.md for notes in general).
  • The guarantee lives in diylc-ai-server/v1/ai/classes/CircuitContextBuilder.php as constants and two if blocks after the selection is merged:
    • ENCLOSURE_CLASS (chassis.ChassisPanel) adds ENCLOSURE_HOLE_CLASSES: DrillHole, RectangularCutout, EllipticalCutout, PolygonalCutout;
    • BLANK_BOARD_CLASS (boards.BlankBoard) adds DRILL_HOLE_CLASS.
  • WIRE_CLASSES sits beside them but is a different kind of rule: wires follow from the construction type, not from a component, so it stays in PHP.
  • The server reads catalog_full.json only after the selection, to filter it.

3. Design decisions

# Decision Rationale
D1 A new single (not repeatable) annotation in diylc-core/.../core/annotations, @AiSelectWith(Class<? extends IDIYComponent<?>>[] value), beside @AiNote Class literals keep a rename or move compile-checked; core already knows components only through IDIYComponent, so the boundary holds.
D2 Collected from the class and every superclass, merged without duplicates, like collectNotes A base class can declare companions for all its subclasses, the same way it carries notes.
D3 Published only in catalog_full.json, as "selectWith": ["electromechanical.DrillHole", ...] in catalogClassName form, and left out when empty The server expands from the full catalog; the selector does not need it, and the index stays small.
D4 One level: the server adds the companions of the selected types, not the companions of those Nothing needs more today, and a single pass cannot loop. Revisit only if a chain appears.
D5 The @AiNotes stay selectWith only guarantees a type is present; the notes say why and how to use it.
D6 Blank Board lists Drill Hole even though mounting holes are optional One ~1.1K catalog entry the model may ignore is cheaper than a missing class name.
D7 The server skips a companion that is not in its catalog A catalog and server out of step must not break a request.

4. Steps

  1. Annotation. AiSelectWith.java in org.diylc.core.annotations, GPL header, @Retention(RUNTIME), @Target(TYPE), Javadoc with @author as on AiNote.
  2. Components (diylc-library):
    • ChassisPanel: @AiSelectWith({DrillHole.class, RectangularCutout.class, EllipticalCutout.class, PolygonalCutout.class});
    • BlankBoard: @AiSelectWith(DrillHole.class).
  3. Generator. In ComponentCatalogGenerator.buildFullEntry, after the notes, put selectWith from a collectSelectWith(Class<?>) that walks the hierarchy like collectNotes and maps each class through the same conversion as catalogClassName.
  4. Tests in diylc-library/.../plugins/chatbot/service/ComponentCatalogGeneratorTest:
    • the generated Enclosure entry lists the four hole types and Blank Board lists Drill Hole;
    • every selectWith entry of every component is itself a className in the catalog;
    • collectSelectWith merges a superclass's companions, as the existing collectNotes tests do.
  5. Regenerate catalog_full.json / catalog_index.json (run ComponentCatalogGenerator from diylc-swing, .run/ComponentCatalogGenerator.run.xml), check version matches the latest update.xml release, and copy both into diylc-ai-server/v1/ai/.
  6. Server. In CircuitContextBuilder::buildForEditIntent:
    • load catalog_full.json before the selection is merged instead of after;
    • after $selectedComponents is assembled (selection, existing classes, wires, Blank Board for PCB), add the selectWith of every selected type that is in the catalog, then array_unique;
    • delete ENCLOSURE_CLASS, BLANK_BOARD_CLASS, DRILL_HOLE_CLASS, ENCLOSURE_HOLE_CLASSES and the two if blocks; keep WIRE_CLASSES.
    • Order matters: the Blank Board added for PCB must be in the list before the expansion runs.
  7. Docs. diylc-ai-server/docs/promptChatbot_flow.md (filtered catalog paragraph) says the companions come from selectWith; ai-component-notes.md §1 mentions @AiSelectWith next to Scope.SELECTION.

Do step 6 only after the regenerated catalog is on the server: a catalog without selectWith and a server without the constants would lose the guarantee.

5. Verify

  • mvn -q -pl diylc-library -am test and mvn -q -pl diylc-swing -am compile.
  • php -l v1/ai/classes/CircuitContextBuilder.php.
  • A request that selects only the Enclosure ("put an LM386 amp in an enclosure") reaches the edit model with Drill Hole and the three cutouts in its catalog; a PCB request carries Drill Hole.

6. Open

  • Whether other types deserve companions. Candidates worth checking against real requests: a board-mounted pot or jack with its hookup wire is already covered by WIRE_CLASSES, so look for pairs the selector actually drops before adding any.