ReplacePlaceHolders($message); $this->FormOptions = $formOptions; $this->History = new HistoryManager($_history); // Single API call via ContextActionAll $contextActionAll = new ContextActionAll($this->GetCaller(), $this->GetForm()); $contextActionAll->PromptName = $this->PromptName; $actions = $contextActionAll->Generate($message, $this->GetForm(), $this->History, $files); $this->History->RecordHistoryLog($message, 'user'); return [ "history" => $this->History->GetHistory(), "data" => [ "message" => "Here you have it, looks good?", "actions" => $actions ] ]; } /** * Build the prompt text only, without calling the API. * Used for external chat mode. */ public function BuildPromptOnly($message = '', $_history = null, $formOptions = null) { $message = $this->ReplacePlaceHolders($message); $this->FormOptions = $formOptions; $this->History = new HistoryManager($_history); $contextActionAll = new ContextActionAll(null, $this->GetForm()); $contextActionAll->PromptName = $this->PromptName; $prompt = $contextActionAll->BuildPrompt($message, $this->GetForm(), $this->History); // Combine system + user messages into a single text for the user to copy $promptText = ''; foreach ($prompt as $msg) { if (isset($msg->role) && $msg->role === 'system') { $promptText .= $msg->content . "\n\n"; } else if (isset($msg->role) && $msg->role === 'user') { $promptText .= "User request: " . $msg->content; } } // Add JSON-only instruction for external chat $promptText = "IMPORTANT: Return ONLY the raw JSON object. Do not add any additional text, explanations, markdown formatting, or code blocks before or after the JSON. Your entire response must be valid JSON and nothing else.\n\n" . $promptText; $this->History->RecordHistoryLog($message, 'user'); return [ "history" => $this->History->GetHistory(), "data" => [ "promptText" => $promptText ] ]; } /** * Process an external AI response (pasted JSON). * Used for external chat mode. */ public function ProcessExternalResponse($responseJson, $message = '', $_history = null, $formOptions = null) { $message = $this->ReplacePlaceHolders($message); $this->FormOptions = $formOptions; $this->History = new HistoryManager($_history); $contextActionAll = new ContextActionAll(null, $this->GetForm()); $contextActionAll->PromptName = $this->PromptName; // Initialize Form and History on the context action so ProcessResponse has access $contextActionAll->Form = $this->GetForm(); $contextActionAll->History = $this->History; $actions = $contextActionAll->ProcessResponse($responseJson); $this->History->RecordHistoryLog($message, 'user'); return [ "history" => $this->History->GetHistory(), "data" => [ "message" => "Here you have it, looks good?", "actions" => $actions ] ]; } public function GetAIFieldUtilities() { if ($this->_aIFieldUtilities == null) $this->_aIFieldUtilities = new AIFieldUtilities($this->GetForm()); return $this->_aIFieldUtilities; } private function IsValidContextItem($contextItem) { if (!isset($contextItem->intent_type) || !in_array($contextItem->intent_type, ['condition', 'formula', 'set_field_setting'])) return false; if (!isset($contextItem->target_field) || !preg_match('/^\d+(_[a-zA-Z0-9]+)?$/', $contextItem->target_field)) return false; if (!isset($contextItem->intent_text) || empty($contextItem->intent_text) || trim($contextItem->intent_text) == "") return false; if ($this->GetForm()->GetFieldById($contextItem->target_field, true, false, 'FieldTemplate') === null) return false; if (isset($contextItem->fields) && is_array($contextItem->fields)) foreach ($contextItem->fields as $field) { $fieldId = $field; $fieldSection = ''; if (preg_match('/^(\d+)_([a-zA-Z0-9]+)$/', $field, $matches)) { $fieldId = $matches[1]; $fieldSection = $matches[2]; } if (!is_numeric($fieldId)) return false; $field = $this->GetForm()->GetFieldById($fieldId, true, false, 'FieldTemplate'); if ($field == null) return false; if (!empty($fieldSection)) { $composed = $field->GetSubSections(); if ( !ArrayUtils::Some($composed, function ($current) use ($fieldSection) { return $current->PathId == $fieldSection; }) ) return false; } } else $contextItem->fields = []; return true; } public function GetForm() { if ($this->_form == null) { $this->_form = new FormBuilder(AllInOneForms()->GetLoader(), (new BuilderOptionsDTO())->Merge($this->FormOptions)); $this->_form->Initialize(); } return $this->_form; } private function GetContextActions($message) { $userMessages = $this->History->GetAllMessagesOfRole('user'); $messageHistory = ""; foreach ($userMessages as $userMessage) { if ($messageHistory != "") $messageHistory .= "\n\n"; $messageHistory .= $userMessage->content; } $prompt = file_get_contents(AllInOneForms()->GetLoader()->DIR . '/Managers/AIManagers/AIGenerators/AIContextualFormDesigner/AIContextualGetContext.txt'); if (trim($messageHistory) == "") $messageHistory = "No conversation history"; $prompt = str_replace("{{conversationHistory}}", $messageHistory, $prompt); $prompt = [ $this->CreateMessage('system', $prompt), $this->CreateMessage('user', $message) ]; $response = $this->GetCaller()->Chat($prompt); /* $response= ' { "intents" : [ { "intent_type" : "condition", "target_field" : "8", "intent_text" : "hide [field 8] when [field 13] is not empty", "fields" : [ "13" ] } ] } ';*/ $data = json_decode($response); if ($data == false) throw new FriendlyException('The ai responded nonsenses, please try again, it might help if you rephrase your message'); return $data; } private function ReplacePlaceHolders($message) { // Replace placeholders in the format [option fieldid_optionid] with "optionid" of [field fieldid] $pattern = '/\[option\s+([^_]+)_([^\]]+)\]/i'; if (preg_match_all($pattern, $message, $matches, PREG_SET_ORDER)) { foreach ($matches as $match) { $fullMatch = $match[0]; $fieldId = $match[1]; $optionId = $match[2]; $replacement = "option \"$optionId\" of [Field $fieldId]"; $message = str_replace($fullMatch, $replacement, $message); } } return $message; } }