History = new HistoryManager($_history); if ($this->History->LogIsEmpty()) { $prompt = [ $this->CreateMessage('system', $this->GetSystemPrompt($fields)) ]; } else { $prompt = $this->History->GetLog(); } $prompt[] = $this->CreateMessage('user', $message); $this->History->SetLog($prompt); $response = $this->GetCaller()->Chat($prompt); $this->History->RecordHistoryLog($response); $data = $this->DecodeResponse($response); if ($data == false || !isset($data->conditions) || !is_array($data->conditions)) throw new FriendlyException('The ai could not generate valid formulas/conditions, please try again, it might help if you rephrase your message'); return [ "history" => $this->History->GetHistory(), "data" => $data ]; } /** * Build the prompt text only, without calling the API. * Used for external chat mode. */ public function BuildPromptOnly($message = '', $_history = null, $fields = '') { $this->History = new HistoryManager($_history); if ($this->History->LogIsEmpty()) { $promptText = $this->GetSystemPrompt($fields) . "\n\n" . "User request: " . $message; } else { $promptText = ''; foreach ($this->History->GetLog() 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 . "\n\n"; } else if (isset($msg->role) && $msg->role === 'assistant') { $promptText .= "Assistant response: " . $msg->content . "\n\n"; } } $promptText .= "User request: " . $message; } $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) { $this->History = new HistoryManager($_history); $data = $this->DecodeResponse($responseJson); if ($data == false || !isset($data->conditions) || !is_array($data->conditions)) throw new FriendlyException('The pasted response is not valid JSON. Please make sure you copied the entire response from the AI chat.'); $this->History->RecordHistoryLog($responseJson, 'assistant'); return [ "history" => $this->History->GetHistory(), "data" => $data ]; } /** * Build the system prompt with the form's field list and the shared * formula syntax reference. */ private function GetSystemPrompt($fields) { $prompt = file_get_contents(AllInOneForms()->GetLoader()->DIR . '/Managers/AIManagers/AIGenerators/AIFormLogicGenerator/FormLogicGeneratorPrompt.txt'); $formulaSyntax = file_get_contents(AllInOneForms()->GetLoader()->DIR . '/Managers/AIManagers/AIGenerators/prompts/formula_syntax.txt'); $prompt = str_replace('{{formulaSyntax}}', $formulaSyntax, $prompt); $prompt = str_replace('{{fields}}', $this->BuildFieldsText($fields), $prompt); return $prompt; } /** * Format the fields JSON the frontend sends into a readable list with the * data type each field uses for comparisons. */ private function BuildFieldsText($fieldsJson) { $fields = json_decode($fieldsJson); if (!is_array($fields) || count($fields) == 0) { return 'No fields provided.'; } $descriptor = new AIFieldDescriptor(); $text = ''; foreach ($fields as $field) { $id = isset($field->id) ? $field->id : null; if ($id === null) continue; $label = isset($field->label) ? $field->label : 'No label'; $type = isset($field->type) ? $field->type : 'text'; $fieldDescription = $descriptor->GetField($type); $dataType = $fieldDescription != null && isset($fieldDescription['datatype']) ? $fieldDescription['datatype'] : 'text'; $text .= '- [Field ' . $id . '] "' . $label . '" (type: ' . $type . ', datatype: ' . $dataType; if (!empty($field->required)) $text .= ', required'; if (!empty($field->readOnly)) $text .= ', read-only result field'; $text .= ')'; if (isset($field->options) && is_array($field->options) && count($field->options) > 0) { $text .= ' options: "' . implode('", "', $field->options) . '"'; } $text .= "\n"; } return $text === '' ? 'No fields provided.' : $text; } /** * Decode the AI/pasted JSON, tolerating markdown fences and surrounding * text. Valid JSON is decoded as-is. */ private function DecodeResponse($response) { $data = json_decode($response); if ($data !== null) { return $data; } $text = trim((string) $response); $text = preg_replace('/```(?:json)?/i', '', $text); $text = trim($text); $first = strpos($text, '{'); $last = strrpos($text, '}'); if ($first !== false && $last !== false && $last > $first) { $text = substr($text, $first, $last - $first + 1); } $data = json_decode($text); if ($data !== null) { return $data; } // AI-generated formulas require double quotes for string literals (e.g. // GetText() == "15 years"); the model often forgets to escape them inside // the JSON, which breaks json_decode. Repair the quotes and try once more. return json_decode($this->RepairUnescapedQuotes($text)); } }