"""Structured form-input component for kirche-einfach flows. A flow that starts with :class:`CustomInputForm` declares the fields of its web form. The API reads the exported node template (``template`` minus ``code``) and derives ``{json_schema, ui_schema}`` from the ``lfx.io`` input definitions below — one JSON-Schema property per field, keyed by ``name``. Field metadata maps as follows: * ``display_name`` → ``title``, ``info`` → ``description``, ``value`` → ``default``, ``options`` → ``enum`` * ``required`` → required array, ``advanced`` → ``ui:collapsed`` hint * ``_input_type`` → JSON type + widget (see ``app/flows/input_schema.py``) ``FileInput`` is a stub for now: it carries a string reference only, real upload comes later. """ from __future__ import annotations import json from typing import ClassVar from lfx.custom import Component from lfx.field_typing.range_spec import RangeSpec from lfx.io import ( BoolInput, DropdownInput, FileInput, FloatInput, IntInput, MessageTextInput, MultilineInput, MultiselectInput, Output, SecretStrInput, SliderInput, StrInput, ) from lfx.schema.message import Message def _text(value) -> str: if value is None: return "" if hasattr(value, "text"): return value.text or "" return str(value) class CustomInputForm(Component): display_name = "CustomInput Form" description = "Collects structured form values into a single JSON message." icon = "ClipboardList" inputs: ClassVar[list] = [ # Plumbing, not a form field: the API posts submitted web-form # values as the run envelope (same {"payload": {...}} shape the # other kirche components use); they arrive here through a ChatInput # entry node and override the defaults below. Hidden in the GUI and # skipped by the form parser (show=False). MessageTextInput( name="form_values", display_name="Form values (JSON envelope)", info="Submitted values as JSON; merged over the defaults", value="", show=False, ), StrInput( name="full_name", display_name="Full name", info="Your first and last name", value="", required=True, ), MultilineInput( name="notes", display_name="Notes", info="Anything else we should know", value="", ), SecretStrInput( name="password", display_name="Password", info="Optional access phrase", value="", ), IntInput( name="age", display_name="Age", info="Your age in years", value=18, ), FloatInput( name="amount", display_name="Amount", info="Amount in euros", value=0.0, ), BoolInput( name="newsletter", display_name="Newsletter", info="Send me the parish newsletter", value=False, ), DropdownInput( name="department", display_name="Department", info="Which team is this for", options=["Music", "Youth", "Seniors", "Admin"], value="Music", ), MultiselectInput( name="topics", display_name="Topics", info="What are you interested in", options=["Worship", "Outreach", "Courses"], value=[], ), SliderInput( name="satisfaction", display_name="Satisfaction", info="How satisfied are you (1-5)", value=3, range_spec=RangeSpec(min=1, max=5, step=1), ), FileInput( name="attachment", display_name="Attachment", info="String reference only for now (no upload yet)", file_types=["pdf", "png", "jpg"], ), ] outputs: ClassVar[list] = [ Output(display_name="Form Data", name="form_data", method="collect"), ] def collect(self) -> Message: data = { "full_name": _text(self.full_name), "notes": _text(self.notes), "password": _text(self.password), "age": self.age, "amount": self.amount, "newsletter": self.newsletter, "department": _text(self.department), "topics": self.topics, "satisfaction": self.satisfaction, "attachment": _text(self.attachment), } # Submitted values (via form_values) win over the field defaults. # Accepts the raw dict or the run envelope {"payload": {...}}. try: override = json.loads(_text(self.form_values) or "{}") except json.JSONDecodeError: override = {} if isinstance(override, dict): if isinstance(override.get("payload"), dict): override = override["payload"] for key in data: if key in override: data[key] = override[key] return Message(text=json.dumps(data, ensure_ascii=False))