feat: form-input library with schema parsing and endpoint

This commit is contained in:
Jonas Miederer 2026-09-12 07:36:05 +02:00
parent 80d81b8022
commit 46ecab00db
13 changed files with 2775 additions and 2 deletions

View file

@ -68,8 +68,7 @@ def builtin_node(
return node_dict
def custom_node(
code: str,
def custom_node( code: str,
node_id: str,
values: dict | None = None,
position: tuple[int, int] = (0, 0),
@ -87,6 +86,66 @@ def custom_node(
return node_dict
def kirche_node(
node_id: str,
values: dict | None = None,
position: tuple[int, int] = (0, 0),
) -> dict:
"""A node from our GUI-editable category lib (backend/components/kirche).
Built with lfx's own frontend-node serialization, exactly like
``Component.to_frontend_node()`` — minus its final ``Template.from_dict``
round-trip: lfx 1.12.x cannot re-instantiate ``MultiselectInput`` (it
emits ``list``, the constructor forbids extras; their own builtins fail
the same way). The template dict before that round-trip is complete and
faithful, so we use it directly and append the ``code`` field by hand
(mirroring the shape ``build_custom_component_template`` produces).
"""
sys.path.insert(0, str(REPO_ROOT / "backend" / "components"))
from kirche.form_inputs import CustomInputForm
from lfx.template.frontend_node.custom_components import (
ComponentFrontendNode,
)
comp_name = getattr(CustomInputForm, "name", None) or "CustomInputForm"
inst = CustomInputForm()
frontend_node = ComponentFrontendNode.from_inputs(
**inst.get_template_config(inst)
)
inst._map_parameters_on_frontend_node(frontend_node)
node = frontend_node.to_dict(keep_name=False)
node = inst._update_template(node)
inst._map_parameters_on_template(node["template"])
if not getattr(inst, "_code", None):
inst.set_class_code()
node["template"]["code"] = {
"advanced": True,
"api_editable": False,
"dynamic": True,
"fileTypes": [],
"info": "",
"list": False,
"load_from_db": False,
"multiline": True,
"name": "code",
"password": False,
"placeholder": "",
"required": True,
"show": True,
"title_case": False,
"type": "code",
"value": inst._code,
}
node_dict = {
"data": {"node": node, "type": comp_name, "id": node_id},
"id": node_id,
}
node_dict = _normalize(node_dict, node_id, comp_name, *position)
for field, value in (values or {}).items():
node_dict["data"]["node"]["template"][field]["value"] = value
return node_dict
# --------------------------------------------------------------------------
# edges
# --------------------------------------------------------------------------
@ -433,12 +492,43 @@ def build_approval_handoff() -> dict:
)
def build_intake_form() -> dict:
entry = builtin_node(
"lfx.components.input_output.chat",
"ChatInput",
"ChatInput-entry",
position=(0, 200),
)
form = kirche_node(
"CustomInputForm-intake",
position=(400, 200),
)
out = builtin_node(
"lfx.components.input_output.chat_output",
"ChatOutput",
"ChatOutput-collected",
position=(800, 200),
)
nodes = [entry, form, out]
edges = [
edge(entry, "message", form, "form_values"),
edge(form, "form_data", out, "input_value"),
]
return flow_doc(
"intake_form",
"Structured intake via CustomInputForm (parsed into a web form).",
nodes,
edges,
)
def main() -> None:
FIXTURE_DIR.mkdir(parents=True, exist_ok=True)
FLOW_DIR.mkdir(parents=True, exist_ok=True)
flows = {
"simple_rule.json": build_simple_rule(),
"approval_handoff.json": build_approval_handoff(),
"intake_form.json": build_intake_form(),
}
for filename, doc in flows.items():
payload = json.dumps(doc, indent=2, ensure_ascii=False) + "\n"