Recruitment Chat & Chatbot System =================================== The recruitment chat system provides interactive chatbot functionality for guiding users through job search and application processes. The system supports two main deployment modes and uses a tree-structured conversation flow. Architecture Overview --------------------- The recruitment chat system consists of several key components: **Backend (Haskell/Yesod)** - ``FloHam.Cms.Model.RecruitmentChat``: Core data model - ``FloHam.Cms.Model.RecruitmentChat.ChatBot``: Tree-structured conversation configuration - ``Handler.Api.RecruitmentChat``: CRUD API for individual chats - ``Handler.Api.RecruitmentChatCollection``: Collection management API - ``Render.Component.RecruitmentChat``: Server-side rendering component **Frontend (Elm)** - ``RecruitmentChat.elm``: Main widget container and visibility management - ``View.ChatBot.elm``: Core conversation engine and node rendering - ``Data.ChatBot.elm``: Tree navigation and data structures Deployment Modes ----------------- The system supports two distinct deployment modes controlled by the ``currentNodesOnly`` flag: **Embedded Component Mode** (``currentNodesOnly = True``) Used when the chat is embedded as a "KeuzeCompass" component within page content: - Shows only the current conversation level (immediate children/options) - No conversation history display - No progressive disclosure delays - Compact presentation suitable for inline page content - Back button available in job application forms for navigation **Live Chat Widget Mode** (``currentNodesOnly = False``) Used for the live chat widget in the bottom-right corner of pages: - Shows full conversation history leading to current position - Progressive disclosure with timed delays based on content length - Chat bubble interface with show/hide functionality - Introduction message with auto-hide after 10 seconds - Mobile-responsive with body scroll control Conversation Flow Structure --------------------------- Conversations are modeled as tree structures where each node represents content or interaction: **Node Types** - **Text**: Display message to user - **Option**: Clickable button that advances conversation - **Link**: External or internal link with configurable target (_blank or _self) - **VacancyOverview**: Filtered job listings with search and location functionality - **SalaryCalculator**: Interactive salary calculation with age/hours inputs - **Image/Video**: Media content display - **EmbeddedChatBot**: Reference to another recruitment chat (enables reusable components) - **Root**: Starting point of conversation tree **Tree Navigation** The system uses Elm's Tree.Zipper for efficient navigation: - ``current``: Returns immediate children of current position - ``currentAndBefore``: Returns conversation history plus current children - ``before``: Returns nodes that came before current position **Example Flow**:: Root ├── Text "How can we help you today?" ├── Option "Find a Job" │ ├── Text "Great! Let me show you available positions" │ └── VacancyOverview [filters: location, department] └── Option "Salary Information" ├── SalaryCalculator (age + hours input) └── Text "Based on your input: €{calculated_salary} per month" Dynamic Content Integration --------------------------- **Job Search Integration** - VacancyOverview nodes fetch live job data via API - Location autocomplete with TomTom integration - Configurable filters (department, location, contract type) - Pagination with "view more" functionality - Direct application flow with dynamic form loading **Form Integration** - Job application forms loaded dynamically via FormV2Schema - Form fields configured server-side based on domain settings - Privacy policy integration - Validation and submission handling **State Persistence** - Conversation state stored in localStorage with unique keys per chat - Structure hash validation for detecting chat configuration changes - User inputs (salary calculator values) preserved across sessions - Application form state maintained during navigation API Endpoints ------------- Collection Endpoints ~~~~~~~~~~~~~~~~~~~~~ ``GET /api/{domainId}/recruitment-chat`` List all recruitment chats for the specified domain. **Response**: Array of recruitment chat objects with metadata ``POST /api/{domainId}/recruitment-chat`` Create a new recruitment chat with optional duplication from existing chat. **Body**: Chat configuration object **Features**: Supports duplicating existing chats via ``duplicateFrom`` parameter Individual Chat Endpoints ~~~~~~~~~~~~~~~~~~~~~~~~~~ ``GET /api/{domainId}/recruitment-chat/{id}`` Retrieve complete configuration for a specific recruitment chat. **Response**: Full chat object including conversation tree structure ``PATCH /api/{domainId}/recruitment-chat/{id}`` Update recruitment chat configuration (supports partial updates). **Body**: Object containing fields to update **Features**: Automatic timestamp tracking, field validation ``DELETE /api/{domainId}/recruitment-chat/{id}`` Delete recruitment chat after comprehensive validation. **Validation**: Checks page usage and embedded chat references **Response**: 202 Accepted on successful deletion Field Updates ~~~~~~~~~~~~~ The PATCH endpoint supports partial updates for these fields: ``label`` **Type**: String **Description**: Unique identifier within domain **Validation**: Must be unique across all chats in the domain ``title`` **Type**: String **Description**: Display title for chat widget **Usage**: Shown in chat header and CMS interface ``introduction`` **Type**: String **Description**: Welcome message text **Usage**: Displayed before chat conversation begins ``chatBot`` **Type**: ChatBot Object **Description**: Complete conversation tree structure **Features**: Full tree replacement with embedded chat resolution ``botAvatar`` **Type**: Optional Image **Description**: Avatar image for bot representation **Format**: ImageSource object (upload ID or URL) Validation Rules ~~~~~~~~~~~~~~~~ **Domain Isolation** - All operations scoped to the provided domain - Cross-domain references are prohibited **Label Uniqueness** - Labels must be unique within each domain - Duplicate label creation/updates will be rejected **Deletion Protection** - Chats cannot be deleted if referenced by any pages - Chats cannot be deleted if embedded in other chats - Comprehensive usage validation before deletion **Automatic Tracking** - All updates include automatic ``updated`` timestamp - Original ``inserted`` timestamp preserved - Full audit trail maintained Server-Side Rendering ---------------------- The ``Render.Component.RecruitmentChat`` module prepares data for Elm initialization: **Data Aggregation** - Domain context and language settings - Available recruitment chats for embedding - Form schema for job applications - Upload URL mappings for media content - Local storage keys for state persistence **Configuration Generation** The renderer creates a JavaScript configuration object containing: .. code-block:: javascript { recruitmentChat: {...}, // Main chat configuration recruitmentChats: [...], // Available embedded chats localStorageState: {...}, // Restored conversation state showRecruitmentChat: boolean, // Initial visibility showChat: number, // Auto-show delay in seconds domainId: string, // Current domain langCode: string, // Language preference schemaFields: [...], // Dynamic form configuration currentNodesOnly: boolean, // Deployment mode flag uploads: {...} // Media URL mappings } Embedded Chat System -------------------- The recruitment chat system supports embedding one chat within another via ``EmbeddedChatBot`` nodes. This enables creating reusable conversation components that can be shared across multiple chats. **How Embedding Works** When a conversation tree contains an ``EmbeddedChatBot`` node: 1. **Reference Resolution**: The node contains an ``EmbeddedRecruitmentChatId`` that references another chat 2. **Tree Replacement**: During initialization, the embedded node is replaced with the actual content from the referenced chat 3. **Recursive Processing**: If the embedded chat itself contains embedded references, they are resolved recursively (with depth limits) 4. **ID Regeneration**: All embedded nodes receive new unique IDs to prevent conflicts **Embedded Chat ID System** .. code-block:: haskell newtype EmbeddedRecruitmentChatId = EmbeddedRecruitmentChatId Int64 -- In Node definition: EmbeddedChatBot (Maybe EmbeddedRecruitmentChatId) - Uses separate ID type to prevent cyclic dependencies - ``Nothing`` represents an unlinked embedded chat placeholder - ``Just id`` references a specific recruitment chat to embed **Processing Flow** 1. **Discovery**: ``embeddedRecruitmentChatIds`` scans tree for embedded references 2. **Resolution**: ``replaceEmbeddedChatBot`` replaces placeholder nodes with actual chat content 3. **ID Updates**: ``updateNewIds`` assigns fresh IDs to prevent conflicts 4. **Depth Limiting**: Recursive depth is limited to prevent infinite embedding loops **Example Structure**:: Main Chat Tree: ├── Text "Welcome! What brings you here?" ├── Option "Job Search" │ └── EmbeddedChatBot (Just recruitmentChatId_123) └── Option "Company Info" └── Text "About our company..." After Embedding Resolution: ├── Text "Welcome! What brings you here?" ├── Option "Job Search" │ ├── Text "What type of position interests you?" (from chat 123) │ ├── Option "Engineering" (from chat 123) │ └── Option "Marketing" (from chat 123) └── Option "Company Info" └── Text "About our company..." **Cyclic Dependency Prevention** - Separate ``EmbeddedRecruitmentChatId`` type prevents direct cyclic references in Haskell types - Runtime depth limits prevent infinite recursion during tree processing - Validation prevents embedding a chat within itself (direct or indirect) Node ID Generation System -------------------------- The system uses a sophisticated ID generation approach that has evolved to support both legacy and modern requirements: **ID Types** .. code-block:: elm type NodeId = NodeId Int -- Legacy sequential IDs | UuidNodeId Uuid -- Modern deterministic UUIDs | NodeIdUnset -- Placeholder for new nodes **Legacy Sequential IDs** - Simple integer-based IDs assigned sequentially - Used for backward compatibility with existing conversations - Generated by ``updateNewIds`` function during tree processing **Modern UUID System** - Deterministic UUIDs based on tree path and content hash - Generated by ``generateUuidId`` function using node position and content - Ensures same content at same position always gets same ID - Provides better stability when tree structure changes **ID Generation Process** 1. **Content Hashing**: Each node type generates a unique content hash: .. code-block:: elm contentHash = case node of ChatBot.Text t -> t ChatBot.Option t -> t ChatBot.Link settings -> settings.url ++ settings.title ChatBot.VacancyOverview _ -> "vacancy_overview" ChatBot.SalaryCalculator _ -> "salary_calc" ChatBot.EmbeddedChatBot _ -> "embedded_chat" 2. **Path Integration**: Tree path (e.g., ``[0, 2, 1]``) combined with content hash 3. **UUID Creation**: Deterministic UUID generated from hash using custom algorithm 4. **Collision Handling**: Fallback to sequential IDs if UUID generation fails **Mixed ID Support** - Frontend handles both legacy and UUID-based IDs transparently - Custom equality function ``nodeIdEquals`` enables mixed comparisons - Gradual migration from sequential to UUID-based system **State Persistence** - Conversation state stored using stable UUIDs when available - Hash validation detects when chat structure changes significantly - Automatic state reset when incompatible structure changes detected Progressive Disclosure ---------------------- In live chat mode, nodes appear with calculated delays to create natural conversation pacing: **Delay Calculation** - **Text nodes**: 32ms per character, clamped between 1250-2000ms - **Option buttons**: 16ms per character, clamped between 750-2000ms - **Interactive elements**: Fixed 600ms delay - **Root node**: No delay (conversation starter) **Queue Management** - Nodes are queued for display in tree traversal order - Only one node marked as "in_progress" at a time - Queue bypassed in embedded component mode for immediate display Security & Validation ---------------------- **Domain Isolation** - All operations scoped to provided domain - Cross-domain access prevented at API level **Referential Integrity** - Deletion blocked if chat used by pages (``PageRecruitmentChat`` field) - Deletion blocked if chat embedded in other chats (``EmbeddedChatBot`` nodes) - EmbeddedChatBot nodes validate target existence during rendering **Embedded Chat Validation** The system performs comprehensive validation before allowing chat deletion: .. code-block:: haskell -- Check for page usage isUsedByPage :: RecruitmentChatId -> DB Bool -- Check for embedding in other chats isUsedByOtherChatBot :: RecruitmentChatId -> DB Bool 1. **Page Usage Check**: Scans all pages for ``pageRecruitmentChat`` field references 2. **Embedding Check**: Examines all chat trees for ``EmbeddedChatBot`` nodes containing the target ID 3. **Validation Errors**: Returns specific error messages (``RecruitmentChatIsUsedByPage`` or ``RecruitmentChatIsUsedByOtherChat``) **Input Validation** - Label uniqueness enforced within domains - Required fields validated - Malformed tree structures rejected **Authentication** - Session tokens passed for API requests - Form submissions include CSRF protection - File uploads require appropriate permissions Troubleshooting --------------- **Common Issues** *Chat not displaying* - Check ``showRecruitmentChat`` flag in page configuration - Verify chat exists and belongs to correct domain - Check browser console for JavaScript errors *Conversation state lost* - localStorage may be cleared or corrupt - Chat structure hash mismatch triggers reset - Check browser localStorage for ``chat_state_`` keys *Forms not loading* - Verify FormV2Schema configuration - Check vacancy template has formIdV2 set - Ensure form exists and is accessible *Media not displaying* - Check upload URL mappings in configuration - Verify image/video files exist and are accessible - Check ImageSource configuration in chat nodes *Embedded chats not loading* - Verify the referenced ``EmbeddedRecruitmentChatId`` exists - Check that embedded chat belongs to the same domain - Ensure no circular references (chat A embeds chat B which embeds chat A) - Monitor for recursive depth limits being reached - Check browser console for ``embeddedRecruitmentChatIds`` errors *Chat deletion failing* - Check if chat is used by any pages (``pageRecruitmentChat`` field) - Scan other chats for ``EmbeddedChatBot`` nodes referencing this chat - Review validation error messages for specific usage details - Use API to get comprehensive usage report before deletion **Performance Considerations** - Large conversation trees may impact initial load time - VacancyOverview nodes trigger API calls on display - Consider limiting embedded chat depth to prevent cycles - Monitor localStorage usage for conversation state - Embedded chat resolution happens during initialization, not runtime - ID regeneration for embedded nodes may cause temporary inconsistencies Implementation Details ---------------------- **Key Functions and Modules** Backend (Haskell): - ``FloHam.Cms.Model.RecruitmentChat.ChatBot.hs``: Core node definitions and embedding types - ``Handler.Api.RecruitmentChat.hs``: CRUD operations and validation logic - ``Render.Component.RecruitmentChat.hs``: Server-side rendering and data preparation Frontend (Elm): - ``Data.ChatBot.elm``: Tree navigation, ID generation, and embedded chat resolution - ``View.ChatBot.elm``: Node rendering and conversation management - ``RecruitmentChat.elm``: Widget container and initialization **Critical Processing Functions** .. code-block:: elm -- Embedded chat resolution fromChatBotWithEmbedded : ChatBots -> ChatBot -> Tree NodeWithId replaceEmbeddedChatBot : Int -> ChatBots -> Tree NodeWithId -> Tree NodeWithId embeddedRecruitmentChatIds : Tree NodeWithId -> List (NodeId, RecruitmentChatId) -- ID management generateUuidId : List Int -> ChatBot.Node -> NodeId updateNewIds : Tree NodeWithId -> Tree NodeWithId nodeIdEquals : NodeId -> NodeId -> Bool **Data Flow Summary** 1. **Server**: Render component aggregates chat data and available embedded chats 2. **Client**: Elm app initializes with embedded chat resolution 3. **Resolution**: ``replaceEmbeddedChatBot`` replaces placeholder nodes with actual content 4. **ID Assignment**: New UUIDs generated for all embedded content 5. **Navigation**: Tree.Zipper manages conversation state and progression 6. **Persistence**: Stable IDs enable consistent localStorage state management