feat: add app and database modules

This commit is contained in:
2026-05-21 16:05:11 +07:00
parent 37b7e783f5
commit fad70d096b
212 changed files with 23901 additions and 0 deletions
+65
View File
@@ -0,0 +1,65 @@
<?php
/**
* ============================================================
*
* @project biiproject
*
* @author Andika Debi Putra
*
* @email andikadebiputra@gmail.com
*
* @website https://biiproject.com
*
* @copyright Copyright (c) 2026 Andika Debi Putra
* @license Proprietary - All Rights Reserved
*
* @version 1.0.0
*
* @created 2026-05-01
* ============================================================
*/
namespace App\Services\AI;
class AiAssistantService
{
public function __construct(
protected AiService $aiService
) {}
/**
* Answer questions about the system.
*/
public function answer(string $question): string
{
if (! get_setting('ai_enabled', false)) {
return 'AI Assistant is currently disabled.';
}
$systemPrompt = "You are the BIIProject Intelligent Assistant.
You help administrators manage the system.
The system is built with Laravel, PostgreSQL, and Redis.
Key features: RBAC, System Monitoring, AI Log Analysis, Backup/Restore.
Guidelines:
- Be professional, helpful, and concise.
- If asked about technical details, provide accurate Laravel-based advice.
- If you don't know the specific configuration of this instance, advise checking 'System Config'.
- Security is priority. Never advise actions that compromise security.";
try {
$result = $this->aiService->provider()->generate($question, [
'system_instruction' => $systemPrompt,
]);
if (isset($result['success']) && $result['success']) {
return $result['response'];
}
return 'Sorry, I encountered an error: '.($result['error'] ?? 'Unknown error');
} catch (\Exception $e) {
return 'Error: '.$e->getMessage();
}
}
}
+18
View File
@@ -0,0 +1,18 @@
<?php
namespace App\Services\AI;
interface AiProviderInterface
{
/**
* Generate response from the AI provider.
*
* @return array [success => bool, response => string, usage => array, error => string]
*/
public function generate(string $prompt, array $options = []): array;
/**
* Get the provider identifier.
*/
public function getIdentifier(): string;
}
+132
View File
@@ -0,0 +1,132 @@
<?php
/**
* ============================================================
*
* @project biiproject
*
* @author Andika Debi Putra
*
* @email andikadebiputra@gmail.com
*
* @website https://biiproject.com
*
* @copyright Copyright (c) 2026 Andika Debi Putra
* @license Proprietary - All Rights Reserved
*
* @version 1.0.0
*
* @created 2026-05-01
* ============================================================
*
* Unauthorized copying, modification, distribution, or use
* of this file is strictly prohibited without prior written
* permission from the author.
* ============================================================
*/
namespace App\Services\AI;
use App\Services\SystemConfig\SystemConfigService;
class AiService
{
protected $systemConfig;
protected $providers = [];
public function __construct(SystemConfigService $systemConfig)
{
$this->systemConfig = $systemConfig;
}
/**
* Get a provider instance.
*/
public function provider(?string $identifier = null): AiProviderInterface
{
$identifier = $identifier ?: $this->systemConfig->get('ai_provider', 'gpt');
if (isset($this->providers[$identifier])) {
return $this->providers[$identifier];
}
$config = $this->getProviderConfig($identifier);
$instance = match ($identifier) {
'gpt' => new GptProvider($config),
'gemini' => new GeminiProvider($config),
'claude' => new ClaudeProvider($config),
'deepseek' => new OpenAiCompatibleProvider('deepseek', 'https://api.deepseek.com/chat/completions', $config),
'grok' => new OpenAiCompatibleProvider('grok', 'https://api.x.ai/v1/chat/completions', $config),
'mistral' => new OpenAiCompatibleProvider('mistral', 'https://api.mistral.ai/v1/chat/completions', $config),
'openrouter' => new OpenAiCompatibleProvider('openrouter', 'https://openrouter.ai/api/v1/chat/completions', $config),
'ollama' => new OllamaProvider($config),
default => throw new \Exception("Unsupported AI provider: {$identifier}"),
};
return $this->providers[$identifier] = $instance;
}
/**
* Get configuration for a specific provider from system settings.
*/
protected function getProviderConfig(string $identifier): array
{
return [
'key' => $this->systemConfig->get("ai_{$identifier}_key"),
'base_url' => $this->systemConfig->get("ai_{$identifier}_base_url"),
'default_model' => $this->systemConfig->get('ai_default_model'),
'instruction' => $this->systemConfig->get('ai_system_instruction'),
'temperature' => $this->systemConfig->get('ai_temperature', 0.7),
'max_tokens' => $this->systemConfig->get('ai_max_tokens', 2000),
];
}
/**
* Static method to get supported models for each provider.
*/
public static function getSupportedModels(): array
{
return [
'gpt' => [
['id' => 'gpt-4o', 'name' => 'GPT-4o (Newest)'],
['id' => 'gpt-4-turbo', 'name' => 'GPT-4 Turbo'],
['id' => 'gpt-3.5-turbo', 'name' => 'GPT-3.5 Turbo'],
],
'gemini' => [
['id' => 'gemini-1.5-pro', 'name' => 'Gemini 1.5 Pro'],
['id' => 'gemini-1.5-flash', 'name' => 'Gemini 1.5 Flash'],
['id' => 'gemini-1.0-pro', 'name' => 'Gemini 1.0 Pro'],
],
'claude' => [
['id' => 'claude-3-5-sonnet-20240620', 'name' => 'Claude 3.5 Sonnet'],
['id' => 'claude-3-opus-20240229', 'name' => 'Claude 3 Opus'],
['id' => 'claude-3-sonnet-20240229', 'name' => 'Claude 3 Sonnet'],
['id' => 'claude-3-haiku-20240307', 'name' => 'Claude 3 Haiku'],
],
'deepseek' => [
['id' => 'deepseek-chat', 'name' => 'DeepSeek Chat'],
['id' => 'deepseek-coder', 'name' => 'DeepSeek Coder'],
],
'grok' => [
['id' => 'grok-1', 'name' => 'Grok-1'],
],
'mistral' => [
['id' => 'mistral-large-latest', 'name' => 'Mistral Large'],
['id' => 'mistral-medium-latest', 'name' => 'Mistral Medium'],
['id' => 'mistral-small-latest', 'name' => 'Mistral Small'],
],
'openrouter' => [
['id' => 'google/gemini-pro-1.5', 'name' => 'Gemini Pro 1.5'],
['id' => 'anthropic/claude-3.5-sonnet', 'name' => 'Claude 3.5 Sonnet'],
['id' => 'meta-llama/llama-3-70b-instruct', 'name' => 'Llama 3 70B'],
],
'ollama' => [
['id' => 'llama3', 'name' => 'Llama 3'],
['id' => 'mistral', 'name' => 'Mistral'],
['id' => 'phi3', 'name' => 'Phi-3'],
],
];
}
}
+50
View File
@@ -0,0 +1,50 @@
<?php
namespace App\Services\AI;
use App\Models\AI\AiUsageLog;
use Illuminate\Support\Facades\Auth;
abstract class BaseAiProvider implements AiProviderInterface
{
protected $config;
public function __construct(array $config = [])
{
$this->config = $config;
}
/**
* Log the AI usage to the database.
*/
protected function logUsage(array $data): void
{
try {
AiUsageLog::create([
'user_id' => Auth::id(),
'provider' => $this->getIdentifier(),
'model' => $data['model'] ?? 'unknown',
'prompt' => $data['prompt'] ?? null,
'response' => $data['response'] ?? null,
'prompt_tokens' => $data['usage']['prompt_tokens'] ?? 0,
'completion_tokens' => $data['usage']['completion_tokens'] ?? 0,
'total_tokens' => $data['usage']['total_tokens'] ?? 0,
'estimated_cost' => $this->calculateCost($data),
'status' => $data['status'] ?? 'success',
'error_message' => $data['error'] ?? null,
'metadata' => $data['metadata'] ?? null,
]);
} catch (\Exception $e) {
\Log::error('Failed to log AI usage: '.$e->getMessage());
}
}
/**
* Abstract cost calculation, to be implemented by each provider.
*/
protected function calculateCost(array $data): float
{
// Default implementation, can be overridden
return 0.0;
}
}
+84
View File
@@ -0,0 +1,84 @@
<?php
namespace App\Services\AI;
use Illuminate\Support\Facades\Http;
class ClaudeProvider extends BaseAiProvider
{
public function getIdentifier(): string
{
return 'claude';
}
public function generate(string $prompt, array $options = []): array
{
$key = $options['key'] ?? $this->config['key'] ?? null;
if (! $key) {
return ['success' => false, 'error' => 'API Key not configured.'];
}
$model = $options['model'] ?? $this->config['default_model'] ?? 'claude-3-5-sonnet-20240620';
$instruction = $options['instruction'] ?? $this->config['instruction'] ?? '';
$startTime = microtime(true);
try {
$res = Http::timeout(60)->withHeaders([
'x-api-key' => $key,
'anthropic-version' => '2023-06-01',
'content-type' => 'application/json',
])->post('https://api.anthropic.com/v1/messages', [
'model' => $model,
'max_tokens' => (int) ($options['max_tokens'] ?? $this->config['max_tokens'] ?? 2000),
'system' => $instruction,
'messages' => [
['role' => 'user', 'content' => $prompt],
],
'temperature' => (float) ($options['temperature'] ?? $this->config['temperature'] ?? 0.7),
]);
if ($res->failed()) {
$error = $res->json()['error']['message'] ?? 'Claude Error';
$this->logUsage([
'model' => $model,
'prompt' => $prompt,
'status' => 'failed',
'error' => $error,
]);
return ['success' => false, 'error' => $error];
}
$data = $res->json();
$response = $data['content'][0]['text'];
$usage = [
'prompt_tokens' => $data['usage']['input_tokens'] ?? 0,
'completion_tokens' => $data['usage']['output_tokens'] ?? 0,
'total_tokens' => ($data['usage']['input_tokens'] ?? 0) + ($data['usage']['output_tokens'] ?? 0),
];
$result = [
'success' => true,
'response' => $response,
'usage' => $usage,
'model' => $model,
'latency' => microtime(true) - $startTime,
];
$this->logUsage([
'model' => $model,
'prompt' => $prompt,
'response' => $response,
'usage' => $usage,
'status' => 'success',
'metadata' => ['latency' => $result['latency']],
]);
return $result;
} catch (\Exception $e) {
return ['success' => false, 'error' => $e->getMessage()];
}
}
}
+82
View File
@@ -0,0 +1,82 @@
<?php
namespace App\Services\AI;
use Illuminate\Support\Facades\Http;
class GeminiProvider extends BaseAiProvider
{
public function getIdentifier(): string
{
return 'gemini';
}
public function generate(string $prompt, array $options = []): array
{
$key = $options['key'] ?? $this->config['key'] ?? null;
if (! $key) {
return ['success' => false, 'error' => 'API Key not configured.'];
}
$model = $options['model'] ?? $this->config['default_model'] ?? 'gemini-1.5-flash';
$instruction = $options['instruction'] ?? $this->config['instruction'] ?? '';
$startTime = microtime(true);
try {
$res = Http::timeout(60)->post("https://generativelanguage.googleapis.com/v1beta/models/{$model}:generateContent?key={$key}", [
'contents' => [
['parts' => [['text' => $instruction."\n\n".$prompt]]],
],
'generationConfig' => [
'temperature' => (float) ($options['temperature'] ?? $this->config['temperature'] ?? 0.7),
'maxOutputTokens' => (int) ($options['max_tokens'] ?? $this->config['max_tokens'] ?? 2000),
],
]);
if ($res->failed()) {
$error = $res->json()['error']['message'] ?? 'Gemini Error';
$this->logUsage([
'model' => $model,
'prompt' => $prompt,
'status' => 'failed',
'error' => $error,
]);
return ['success' => false, 'error' => $error];
}
$data = $res->json();
$response = $data['candidates'][0]['content']['parts'][0]['text'] ?? 'No response';
// Gemini doesn't always return token count in the same way, simplified for now
$usage = [
'prompt_tokens' => 0,
'completion_tokens' => 0,
'total_tokens' => 0,
];
$result = [
'success' => true,
'response' => $response,
'usage' => $usage,
'model' => $model,
'latency' => microtime(true) - $startTime,
];
$this->logUsage([
'model' => $model,
'prompt' => $prompt,
'response' => $response,
'usage' => $usage,
'status' => 'success',
'metadata' => ['latency' => $result['latency']],
]);
return $result;
} catch (\Exception $e) {
return ['success' => false, 'error' => $e->getMessage()];
}
}
}
+96
View File
@@ -0,0 +1,96 @@
<?php
namespace App\Services\AI;
use Illuminate\Support\Facades\Http;
class GptProvider extends BaseAiProvider
{
public function getIdentifier(): string
{
return 'gpt';
}
public function generate(string $prompt, array $options = []): array
{
$key = $options['key'] ?? $this->config['key'] ?? null;
if (! $key) {
return ['success' => false, 'error' => 'API Key not configured.'];
}
$model = $options['model'] ?? $this->config['default_model'] ?? 'gpt-4o';
$instruction = $options['instruction'] ?? $this->config['instruction'] ?? '';
$startTime = microtime(true);
try {
$res = Http::withToken($key)
->timeout(60)
->post('https://api.openai.com/v1/chat/completions', [
'model' => $model,
'messages' => [
['role' => 'system', 'content' => $instruction],
['role' => 'user', 'content' => $prompt],
],
'temperature' => (float) ($options['temperature'] ?? $this->config['temperature'] ?? 0.7),
'max_tokens' => (int) ($options['max_tokens'] ?? $this->config['max_tokens'] ?? 2000),
]);
if ($res->failed()) {
$error = $res->json()['error']['message'] ?? 'OpenAI Error';
$this->logUsage([
'model' => $model,
'prompt' => $prompt,
'status' => 'failed',
'error' => $error,
]);
return ['success' => false, 'error' => $error];
}
$data = $res->json();
$response = $data['choices'][0]['message']['content'];
$usage = $data['usage'] ?? [];
$result = [
'success' => true,
'response' => $response,
'usage' => $usage,
'model' => $model,
'latency' => microtime(true) - $startTime,
];
$this->logUsage([
'model' => $model,
'prompt' => $prompt,
'response' => $response,
'usage' => $usage,
'status' => 'success',
'metadata' => ['latency' => $result['latency']],
]);
return $result;
} catch (\Exception $e) {
return ['success' => false, 'error' => $e->getMessage()];
}
}
protected function calculateCost(array $data): float
{
$model = $data['model'] ?? '';
$promptTokens = $data['usage']['prompt_tokens'] ?? 0;
$completionTokens = $data['usage']['completion_tokens'] ?? 0;
// Simplified estimation
$rates = [
'gpt-4o' => ['prompt' => 0.005 / 1000, 'completion' => 0.015 / 1000],
'gpt-4-turbo' => ['prompt' => 0.01 / 1000, 'completion' => 0.03 / 1000],
'gpt-3.5-turbo' => ['prompt' => 0.0005 / 1000, 'completion' => 0.0015 / 1000],
];
$rate = $rates[$model] ?? $rates['gpt-4o'];
return ($promptTokens * $rate['prompt']) + ($completionTokens * $rate['completion']);
}
}
+86
View File
@@ -0,0 +1,86 @@
<?php
/**
* ============================================================
*
* @project biiproject
*
* @author Andika Debi Putra
*
* @email andikadebiputra@gmail.com
*
* @website https://biiproject.com
*
* @copyright Copyright (c) 2026 Andika Debi Putra
* @license Proprietary - All Rights Reserved
*
* @version 1.0.0
*
* @created 2026-05-01
* ============================================================
*/
namespace App\Services\AI;
use Illuminate\Support\Facades\Cache;
use Spatie\Activitylog\Models\Activity;
class LogAnalysisService
{
public function __construct(
protected AiService $aiService
) {}
/**
* Analyze recent activity logs using AI.
*/
public function analyzeRecentLogs(int $limit = 50): string
{
if (! get_setting('ai_enabled', false)) {
return 'AI Service is currently disabled in system settings.';
}
$logs = Activity::with('causer')
->latest()
->take($limit)
->get()
->map(function ($log) {
return [
'time' => $log->created_at->toDateTimeString(),
'user' => $log->causer ? $log->causer->name : 'System',
'action' => $log->description,
'subject' => $log->subject_type ? class_basename($log->subject_type) : 'N/A',
];
})
->toArray();
if (empty($logs)) {
return 'No activity logs found to analyze.';
}
$prompt = 'As a Security and Operational Auditor, analyze the following system activity logs and provide:
1. A brief summary of recent operations.
2. Security insights (detect any suspicious patterns or potential privilege abuse).
3. Operational health status.
4. Recommendations (if any).
FORMAT: Use Markdown with bold headers. Be concise and professional.
LOGS DATA:
'.json_encode($logs, JSON_PRETTY_PRINT);
try {
return Cache::remember('ai_log_analysis_result', 3600, function () use ($prompt) {
$result = $this->aiService->provider()->generate($prompt);
if (isset($result['success']) && $result['success']) {
return $result['response'] ?? 'AI failed to generate analysis.';
}
return 'AI Provider Error: '.($result['error'] ?? 'Unknown error');
});
} catch (\Exception $e) {
return 'Error during AI analysis: '.$e->getMessage();
}
}
}
+77
View File
@@ -0,0 +1,77 @@
<?php
namespace App\Services\AI;
use Illuminate\Support\Facades\Http;
class OllamaProvider extends BaseAiProvider
{
public function getIdentifier(): string
{
return 'ollama';
}
public function generate(string $prompt, array $options = []): array
{
$baseUrl = $options['base_url'] ?? $this->config['base_url'] ?? 'http://localhost:11434';
$model = $options['model'] ?? $this->config['default_model'] ?? 'llama3';
$instruction = $options['instruction'] ?? $this->config['instruction'] ?? '';
$startTime = microtime(true);
try {
$res = Http::timeout(120)->post("{$baseUrl}/api/generate", [
'model' => $model,
'prompt' => $instruction."\n\n".$prompt,
'stream' => false,
'options' => [
'temperature' => (float) ($options['temperature'] ?? $this->config['temperature'] ?? 0.7),
'num_predict' => (int) ($options['max_tokens'] ?? $this->config['max_tokens'] ?? 2000),
],
]);
if ($res->failed()) {
$error = 'Ollama Error: Make sure the server is running and the model is pulled.';
$this->logUsage([
'model' => $model,
'prompt' => $prompt,
'status' => 'failed',
'error' => $error,
]);
return ['success' => false, 'error' => $error];
}
$data = $res->json();
$response = $data['response'];
$usage = [
'prompt_tokens' => $data['prompt_eval_count'] ?? 0,
'completion_tokens' => $data['eval_count'] ?? 0,
'total_tokens' => ($data['prompt_eval_count'] ?? 0) + ($data['eval_count'] ?? 0),
];
$result = [
'success' => true,
'response' => $response,
'usage' => $usage,
'model' => $model,
'latency' => microtime(true) - $startTime,
];
$this->logUsage([
'model' => $model,
'prompt' => $prompt,
'response' => $response,
'usage' => $usage,
'status' => 'success',
'metadata' => ['latency' => $result['latency']],
]);
return $result;
} catch (\Exception $e) {
return ['success' => false, 'error' => $e->getMessage()];
}
}
}
@@ -0,0 +1,92 @@
<?php
namespace App\Services\AI;
use Illuminate\Support\Facades\Http;
class OpenAiCompatibleProvider extends BaseAiProvider
{
protected $identifier;
protected $baseUrl;
public function __construct(string $identifier, string $baseUrl, array $config = [])
{
parent::__construct($config);
$this->identifier = $identifier;
$this->baseUrl = $baseUrl;
}
public function getIdentifier(): string
{
return $this->identifier;
}
public function generate(string $prompt, array $options = []): array
{
$key = $options['key'] ?? $this->config['key'] ?? null;
if (! $key) {
return ['success' => false, 'error' => ucfirst($this->identifier).' API Key not configured.'];
}
$model = $options['model'] ?? $this->config['default_model'] ?? null;
$instruction = $options['instruction'] ?? $this->config['instruction'] ?? '';
$startTime = microtime(true);
try {
$res = Http::timeout(60)->withToken($key)
->post($this->baseUrl, [
'model' => $model,
'messages' => [
['role' => 'system', 'content' => $instruction],
['role' => 'user', 'content' => $prompt],
],
'temperature' => (float) ($options['temperature'] ?? $this->config['temperature'] ?? 0.7),
'max_tokens' => (int) ($options['max_tokens'] ?? $this->config['max_tokens'] ?? 2000),
]);
if ($res->failed()) {
$error = $res->json()['error']['message'] ?? $res->json()['message'] ?? ucfirst($this->identifier).' Error';
$this->logUsage([
'model' => $model,
'prompt' => $prompt,
'status' => 'failed',
'error' => $error,
]);
return ['success' => false, 'error' => $error];
}
$data = $res->json();
$response = $data['choices'][0]['message']['content'];
$usage = $data['usage'] ?? [
'prompt_tokens' => 0,
'completion_tokens' => 0,
'total_tokens' => 0,
];
$result = [
'success' => true,
'response' => $response,
'usage' => $usage,
'model' => $model,
'latency' => microtime(true) - $startTime,
];
$this->logUsage([
'model' => $model,
'prompt' => $prompt,
'response' => $response,
'usage' => $usage,
'status' => 'success',
'metadata' => ['latency' => $result['latency']],
]);
return $result;
} catch (\Exception $e) {
return ['success' => false, 'error' => $e->getMessage()];
}
}
}
@@ -0,0 +1,93 @@
<?php
/**
* ============================================================
*
* @project biiproject
*
* @author Andika Debi Putra
*
* @email andikadebiputra@gmail.com
*
* @website https://biiproject.com
*
* @copyright Copyright (c) 2026 Andika Debi Putra
* @license Proprietary - All Rights Reserved
*
* @version 1.0.0
*
* @created 2026-05-01
* ============================================================
*/
namespace App\Services\AI;
use App\Services\SystemConfig\SystemConfigService;
use Illuminate\Support\Facades\Cache;
class SecurityHardeningService
{
public function __construct(
protected AiService $aiService,
protected SystemConfigService $configService
) {}
/**
* Audit system security settings and get AI recommendations.
*/
public function auditSecurity(): array
{
if (! get_setting('ai_enabled', false)) {
return ['error' => 'AI Service disabled.'];
}
// Collect relevant security settings
$settings = [
'force_https' => get_setting('force_https'),
'hsts_enabled' => get_setting('hsts_enabled'),
'two_factor_auth' => get_setting('two_factor_auth'),
'password_min_length' => get_setting('password_min_length'),
'login_max_attempts' => get_setting('login_max_attempts'),
'session_lifetime' => get_setting('session_lifetime'),
'ip_whitelist_admin' => ! empty(get_setting('ip_whitelist_admin')),
'backup_db_encrypt' => get_setting('backup_db_encrypt'),
'maintenance_mode' => get_setting('maintenance_mode_enabled'),
'environment' => app()->environment(),
'debug_mode' => config('app.debug'),
];
$prompt = 'As a Cyber Security Expert, audit the following Laravel system security configurations and provide:
1. A Security Score (0-100).
2. Critical Vulnerabilities (if any).
3. Hardening Recommendations.
4. A JSON object summary at the end.
CONFIGURATIONS:
'.json_encode($settings, JSON_PRETTY_PRINT);
try {
return Cache::remember('security_audit_result', 86400, function () use ($prompt) {
$result = $this->aiService->provider()->generate($prompt);
if (isset($result['success']) && $result['success']) {
return [
'analysis' => $result['response'],
'score' => $this->extractScore($result['response']),
'timestamp' => now()->toDateTimeString(),
];
}
return ['error' => $result['error'] ?? 'Unknown error'];
});
} catch (\Exception $e) {
return ['error' => $e->getMessage()];
}
}
private function extractScore(string $text): int
{
preg_match('/Score:?\s*(\d+)/i', $text, $matches);
return isset($matches[1]) ? (int) $matches[1] : 70;
}
}