FormOptions = $formOptions; $this->History = new HistoryManager($_history); $prompt = $this->BuildPromptMessages($message, $targetFieldId, $expectedReturnType); $response = $this->GetCaller()->Chat($prompt); return $this->ProcessResponse($response, $message); } /** * Build the prompt text only (for external chat mode). */ public function BuildPromptOnly($message = '', $_history = null, $formOptions = null, $targetFieldId = '', $expectedReturnType = 'any') { $this->FormOptions = $formOptions; $this->History = new HistoryManager($_history); $prompt = $this->BuildPromptMessages($message, $targetFieldId, $expectedReturnType); $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; } } $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). */ public function ProcessExternalResponse($responseJson, $message = '', $_history = null, $formOptions = null) { $this->FormOptions = $formOptions; $this->History = new HistoryManager($_history); return $this->ProcessResponse($responseJson, $message); } /** * Build the prompt messages array. */ private function BuildPromptMessages($message, $targetFieldId, $expectedReturnType) { // 1. Load prompt template $promptFile = AllInOneForms()->GetLoader()->DIR . '/Managers/AIManagers/AIGenerators/AIFormulaGenerator/FormulaGeneratorPrompt.txt'; $prompt = file_get_contents($promptFile); // 1b. Inject shared formula syntax $formulaSyntaxFile = AllInOneForms()->GetLoader()->DIR . '/Managers/AIManagers/AIGenerators/prompts/formula_syntax.txt'; $formulaSyntax = file_get_contents($formulaSyntaxFile); $prompt = str_replace('{{formulaSyntax}}', $formulaSyntax, $prompt); // 2. Build expected return type description $returnTypeText = $this->GetReturnTypeDescription($expectedReturnType); $prompt = str_replace('{{expectedReturnType}}', $returnTypeText, $prompt); // 3. Build target field info $targetFieldText = $this->BuildTargetFieldText($targetFieldId); $prompt = str_replace('{{targetField}}', $targetFieldText, $prompt); // 4. Extract referenced field IDs from the message and build their methods $referencedFieldIds = $this->ExtractFieldIds($message); $formulaMethods = $this->BuildFormulaMethods($targetFieldId, $referencedFieldIds); $prompt = str_replace('{{formulaMethods}}', $formulaMethods, $prompt); // 5. Build conversation history $conversationHistory = $this->BuildConversationHistory(); return [ $this->CreateMessage('system', $prompt), $this->CreateMessage('user', $conversationHistory . "\n\nCurrent request: " . $message) ]; } /** * Process the AI response into the format the frontend expects. */ private function ProcessResponse($response, $message) { $data = json_decode($response); if ($data == null || !isset($data->formula)) { throw new FriendlyException('The AI could not generate a valid formula. Please rephrase your request and try again.'); } $this->History->RecordHistoryLog($message, 'user'); return [ "history" => $this->History->GetHistory(), "data" => [ "message" => isset($data->explanation) ? $data->explanation : "Formula generated successfully.", "formula" => $data->formula ] ]; } /** * Translate the PreferredReturnTypeEnum value to a human-readable description. */ private function GetReturnTypeDescription($type) { switch ($type) { case 'price': return 'a number representing the price or cost'; case 'number': return 'a number'; case 'string': return 'a text string'; case 'boolean': return 'true or false (boolean)'; case 'raw': return 'a raw value (any type)'; case 'any': default: return 'any type of value (number, text, or boolean depending on context)'; } } /** * Build text describing the target field. */ private function BuildTargetFieldText($targetFieldId) { if (empty($targetFieldId)) { return 'No specific target field'; } $fieldParts = $this->GetAIFieldUtilities()->GetIdParts($targetFieldId); $field = $this->GetForm()->GetFieldById($fieldParts[0], true, false, 'FieldTemplate'); if ($field == null) { return 'Field ID: ' . $targetFieldId . ' (field not found)'; } $text = 'Field ID: ' . $targetFieldId . "\n"; $text .= 'Field Type: ' . $field->Options->Type . "\n"; $text .= 'Label: ' . (isset($field->Options->Label) ? $field->Options->Label : 'No label') . "\n"; return $text; } /** * Build the formula methods documentation for all relevant fields. */ private function BuildFormulaMethods($targetFieldId, $referencedFieldIds) { $formulaDescriptions = include(AllInOneForms()->GetLoader()->DIR . '/Managers/AIManagers/Descriptions/FormulaDescriptions.php'); $fieldTypeClassMap = include(AllInOneForms()->GetLoader()->DIR . '/Managers/AIManagers/AIGenerators/AIFormulaGenerator/FieldTypeClassMap.php'); // Build set of field IDs that need detailed methods (target + referenced) $detailedFieldIds = $referencedFieldIds; if (!empty($targetFieldId) && !in_array($targetFieldId, $detailedFieldIds)) { array_unshift($detailedFieldIds, $targetFieldId); } // List ALL form fields (brief) so the AI knows what's available $allFields = $this->GetForm()->GetFields(true); $text = ''; foreach ($allFields as $field) { $fieldId = $field->Options->Id; $fieldType = $field->Options->Type; $fieldLabel = isset($field->Options->Label) ? $field->Options->Label : 'No label'; $isTarget = ($fieldId == $targetFieldId); $targetTag = $isTarget ? ' ⭐ TARGET FIELD' : ''; $showMethods = in_array((string)$fieldId, array_map('strval', $detailedFieldIds)); $text .= "\n### [Field " . $fieldId . '] - "' . $fieldLabel . '" (Type: ' . $fieldType . ')' . $targetTag . "\n"; // Only show detailed methods for the target field and fields mentioned in the message if ($showMethods) { $classNames = isset($fieldTypeClassMap[$fieldType]) ? $fieldTypeClassMap[$fieldType] : ['FieldBase']; $methods = []; foreach ($classNames as $className) { if (isset($formulaDescriptions[$className])) { foreach ($formulaDescriptions[$className] as $methodName => $description) { if (!isset($methods[$methodName])) { $methods[$methodName] = $description; } } } } if (!empty($methods)) { $text .= "Available methods:\n"; foreach ($methods as $methodName => $description) { $text .= "- [Field " . $fieldId . "]." . $methodName . " — " . $description . "\n"; } } } } if (empty($text)) { $text = "No fields found in the form.\n"; } return $text; } /** * Extract field IDs from the message (matches [Field X] patterns). */ private function ExtractFieldIds($message) { $fieldIds = []; if (preg_match_all('/\[Field\s+(\d+(?:_[a-zA-Z0-9]+)?)\]/i', $message, $matches)) { foreach ($matches[1] as $fieldId) { if (!in_array($fieldId, $fieldIds)) { $fieldIds[] = $fieldId; } } } return $fieldIds; } /** * Build conversation history from previous user messages. */ private function BuildConversationHistory() { $userMessages = $this->History->GetAllMessagesOfRole('user'); $messageHistory = ""; foreach ($userMessages as $userMessage) { if ($messageHistory != "") { $messageHistory .= "\n\n"; } $messageHistory .= $userMessage->content; } if (trim($messageHistory) == "") { return ""; } return "--Previous conversation--\n" . $messageHistory . "\n\n"; } public function GetForm() { if ($this->_form == null) { $this->_form = new FormBuilder(AllInOneForms()->GetLoader(), (new BuilderOptionsDTO())->Merge($this->FormOptions)); $this->_form->Initialize(); } return $this->_form; } public function GetAIFieldUtilities() { if ($this->_aiFieldUtilities == null) $this->_aiFieldUtilities = new AIFieldUtilities($this->GetForm()); return $this->_aiFieldUtilities; } }