Files
ai-agent/backend/schemas/forms.py
Gabriel Vidal 7cad02cd70 feat(forms): field groups + an automatic "Anything else?" catch-all
valid_fields keeps a trimmed `group` name on any field (the viewer's
page) and ends every spec with an optional `anything_else` textarea
flagged `catchAll` — reusing a trailing optional "anything else…"
textarea instead of doubling it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 14:48:16 +02:00

69 lines
2.2 KiB
Python

"""Forms: rich structured questions answered in the PWA (``forms.py``)."""
from __future__ import annotations
from typing import Any, Literal, Optional
from .base import Schema
__all__ = [
"FormFieldOption",
"FormField",
"FormRecord",
"FormsResponse",
]
class FormFieldOption(Schema):
value: str
label: str
class FormField(Schema):
"""One field of a form spec (see forms.FIELD_TYPES)."""
key: str
label: str
type: Literal["text", "textarea", "number", "select", "multiselect",
"radio", "checkbox", "slider", "file", "date",
"markdown", "html"]
required: bool
text: Optional[str] = None # markdown: the block's content
html: Optional[str] = None # html: raw markup of the block
sandbox: Optional[bool] = None # html: render in a sandboxed iframe
height: Optional[int] = None # html: fixed iframe height (px)
placeholder: Optional[str] = None
help: Optional[str] = None
accept: Optional[str] = None # file: input accept filter
options: Optional[list[FormFieldOption]] = None
other: Optional[bool] = None # option fields: an "Other…" free-text choice
min: Optional[float] = None
max: Optional[float] = None
step: Optional[float] = None
multiple: Optional[bool] = None # file: allow several
default: Optional[Any] = None
group: Optional[str] = None # the viewer page the field shows on
catchAll: Optional[bool] = None # the trailing "Anything else?" field
class FormRecord(Schema):
"""A form with its lifecycle state; ``answers`` keyed by field key."""
id: str
title: str
description: str
type: str
url: str
sessionId: str
at: str
fields: list[FormField]
status: Literal["pending", "submitted", "cancelled"]
answers: Optional[dict[str, Any]] = None
submittedAt: Optional[str] = None
cancelledAt: Optional[str] = None
# Transcript id for `sessionId` (joined at list time) — the /conversation/…
# link back to the conversation the form belongs to.
conversationId: Optional[str] = None
class FormsResponse(Schema):
forms: list[FormRecord]
count: Optional[int] = None