feat: form-input library with schema parsing and endpoint
This commit is contained in:
parent
80d81b8022
commit
46ecab00db
13 changed files with 2775 additions and 2 deletions
12
backend/components/kirche/__init__.py
Normal file
12
backend/components/kirche/__init__.py
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
"""Kirche form-input components (Langflow category ``kirche``).
|
||||
|
||||
GUI-editable by design: plain :class:`lfx.custom.Component` subclasses with
|
||||
``lfx.io`` inputs only (stdlib + ``lfx``), so both the Langflow authoring UI
|
||||
and the LFX runtime can load them. The backend parses the exported node
|
||||
template into a web form (``app/flows/input_schema.py``); the frontend
|
||||
renders that schema with RJSF.
|
||||
"""
|
||||
|
||||
from .form_inputs import CustomInputForm
|
||||
|
||||
__all__ = ["CustomInputForm"]
|
||||
161
backend/components/kirche/form_inputs.py
Normal file
161
backend/components/kirche/form_inputs.py
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
"""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 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 = [
|
||||
# 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 = [
|
||||
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 Exception:
|
||||
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))
|
||||
Loading…
Add table
Add a link
Reference in a new issue