addType('id', 'integer'); $this->addType('temperature', 'float'); $this->addType('isActive', 'bool'); $this->addType('isPublic', 'bool'); $this->addType('ragEnabled', 'bool'); $this->addType('submittedAt', 'integer'); $this->addType('approvedAt', 'integer'); $this->addType('createdAt', 'integer'); $this->addType('updatedAt', 'integer'); } public function jsonSerialize(): array { return [ 'id' => $this->id, 'user_id' => $this->userId, 'bot_name' => $this->botName, 'mention_name' => $this->mentionName, 'system_prompt' => $this->systemPrompt, 'model' => $this->model, 'temperature' => $this->temperature, 'is_active' => $this->isActive, 'is_public' => $this->isPublic, 'visibility' => $this->visibility, 'allowed_groups' => $this->normalizeAllowedGroups(), 'allowed_teams' => $this->normalizeAllowedTeams(), 'rag_enabled' => (bool)$this->ragEnabled, 'description' => $this->description, 'approval_reason' => $this->approvalReason, 'bot_capabilities' => $this->botCapabilities, 'rag_source_description' => $this->ragSourceDescription, 'testing_description' => $this->testingDescription, 'rejection_reason' => $this->rejectionReason, 'testing_enabled_by' => $this->testingEnabledBy, 'approval_status' => $this->approvalStatus ?? 'approved', 'submitted_at' => $this->submittedAt, 'approved_by' => $this->approvedBy, 'approved_at' => $this->approvedAt, 'pending_changes' => $this->getPendingChangesArray(), 'has_pending_changes' => $this->hasPendingChanges(), 'onboarding_questions' => $this->getOnboardingQuestionsArray(), 'has_onboarding_questions' => $this->hasOnboardingQuestions(), 'created_at' => $this->createdAt, 'updated_at' => $this->updatedAt, ]; } /** * Get pending changes as decoded array * @return array|null */ public function getPendingChangesArray(): ?array { if ($this->pendingChanges === null || $this->pendingChanges === '') { return null; } $decoded = json_decode($this->pendingChanges, true); return is_array($decoded) ? $decoded : null; } /** * Set pending changes from array * @param array|null $changes */ public function setPendingChangesArray(?array $changes): void { if ($changes === null || count($changes) === 0) { $this->pendingChanges = null; } else { $this->pendingChanges = json_encode($changes); } $this->markFieldUpdated('pendingChanges'); } /** * Check if bot has pending changes awaiting approval */ public function hasPendingChanges(): bool { return $this->pendingChanges !== null && $this->pendingChanges !== ''; } /** * Clear pending changes (on approve or reject) */ public function clearPendingChanges(): void { $this->pendingChanges = null; $this->markFieldUpdated('pendingChanges'); } /** * Apply pending changes to actual fields (called on approval) */ public function applyPendingChanges(): void { $changes = $this->getPendingChangesArray(); if ($changes === null) { return; } if (isset($changes['bot_name'])) { $this->setBotName($changes['bot_name']); } if (isset($changes['system_prompt'])) { $this->setSystemPrompt($changes['system_prompt']); } if (isset($changes['description'])) { $this->setDescription($changes['description']); } if (isset($changes['model'])) { $this->setModel($changes['model']); } if (array_key_exists('temperature', $changes)) { $this->setTemperature(is_numeric($changes['temperature']) ? (float)$changes['temperature'] : null); } if (isset($changes['visibility'])) { $this->setVisibility($changes['visibility']); } if (isset($changes['is_public'])) { $this->setIsPublic($changes['is_public']); } if (isset($changes['allowed_groups'])) { $this->setAllowedGroups($changes['allowed_groups']); } if (isset($changes['allowed_teams'])) { $this->setAllowedTeams($changes['allowed_teams']); } if (isset($changes['rag_enabled'])) { $this->setRagEnabled($changes['rag_enabled']); } if (array_key_exists('onboarding_questions', $changes)) { $this->setOnboardingQuestionsArray($changes['onboarding_questions']); } $this->clearPendingChanges(); } /** * @return array */ private function normalizeAllowedGroups(): array { $raw = $this->allowedGroups; if ($raw === null || $raw === '') { return []; } $decoded = json_decode($raw, true); return is_array($decoded) ? array_values(array_map('strval', $decoded)) : []; } /** * @return array */ private function normalizeAllowedTeams(): array { $raw = $this->allowedTeams; if ($raw === null || $raw === '') { return []; } $decoded = json_decode($raw, true); return is_array($decoded) ? array_values(array_map('strval', $decoded)) : []; } /** * Get onboarding questions as decoded array * * Structure: { * "start": "q1", * "questions": [ * {"id": "q1", "text": "Question?", "answers": [ * {"id": "a", "text": "Answer A", "next": "q2a"}, * {"id": "b", "text": "Answer B", "next": null} * ]} * ] * } * * @return OnboardingQuestionTree|null */ public function getOnboardingQuestionsArray(): ?array { if ($this->onboardingQuestions === null || $this->onboardingQuestions === '') { return null; } $decoded = json_decode($this->onboardingQuestions, true); if (!is_array($decoded)) { return null; } // Validate structure if (!isset($decoded['start']) || !isset($decoded['questions']) || !is_array($decoded['questions'])) { return null; } return $decoded; } /** * Set onboarding questions from array * * @param OnboardingQuestionTree|null $questions */ public function setOnboardingQuestionsArray(?array $questions): void { if ($questions === null || !isset($questions['questions']) || count($questions['questions']) === 0) { $this->onboardingQuestions = null; } else { $this->onboardingQuestions = json_encode($questions); } $this->markFieldUpdated('onboardingQuestions'); } /** * Check if bot has onboarding questions configured */ public function hasOnboardingQuestions(): bool { $questions = $this->getOnboardingQuestionsArray(); return $questions !== null && count($questions['questions'] ?? []) > 0; } /** * Get a specific question by ID from the question tree * * @param string $questionId * @return OnboardingQuestion|null */ public function getOnboardingQuestion(string $questionId): ?array { $questions = $this->getOnboardingQuestionsArray(); if ($questions === null) { return null; } foreach ($questions['questions'] as $question) { if ($question['id'] === $questionId) { return $question; } } return null; } /** * Get the starting question for onboarding * * @return OnboardingQuestion|null */ public function getStartingOnboardingQuestion(): ?array { $questions = $this->getOnboardingQuestionsArray(); if ($questions === null) { return null; } return $this->getOnboardingQuestion($questions['start']); } }