Autoreply Module Implementation Plan
Overview
Membangun module Autoreply untuk nomor Dedicated (WABA milik client) dengan fitur:

Balasan otomatis berbasis AI (via OpenRouter)
Balasan otomatis berbasis Button (Quick Reply)
Mode Hybrid (Button → AI → Agent handover)
Compliance dengan 24 jam service window Meta
Phase 1: Database & Models
Migrations
[NEW] create_autoreply_settings_table.php
Schema::create('autoreply_settings', function (Blueprint $table) {
    $table->id();
    $table->foreignId('client_id')->constrained()->onDelete('cascade');
    $table->foreignId('waba_pool_id')->constrained()->onDelete('cascade');
    $table->enum('mode', ['off', 'ai', 'button', 'hybrid'])->default('off');
    $table->boolean('ai_enabled')->default(false);
    $table->boolean('button_enabled')->default(false);
    $table->json('active_hours')->nullable(); // {start: "08:00", end: "22:00", days: [1,2,3,4,5]}
    $table->foreignId('fallback_template_id')->nullable()->constrained('templates');
    $table->boolean('is_active')->default(true);
    $table->timestamps();
    
    $table->unique(['client_id', 'waba_pool_id']);
});
[NEW] create_autoreply_ai_configs_table.php
Schema::create('autoreply_ai_configs', function (Blueprint $table) {
    $table->id();
    $table->foreignId('autoreply_setting_id')->constrained()->onDelete('cascade');
    $table->text('system_prompt');
    $table->string('model')->default('meta-llama/llama-3.1-8b-instruct');
    $table->decimal('temperature', 2, 1)->default(0.7);
    $table->integer('max_tokens')->default(150);
    $table->enum('language_mode', ['auto', 'id', 'en'])->default('auto');
    $table->decimal('confidence_threshold', 3, 2)->default(0.70);
    $table->json('forbidden_topics')->nullable(); // ["harga", "legal", "keuangan"]
    $table->timestamps();
});
[NEW] create_autoreply_buttons_table.php
Schema::create('autoreply_buttons', function (Blueprint $table) {
    $table->id();
    $table->foreignId('autoreply_setting_id')->constrained()->onDelete('cascade');
    $table->string('trigger_type')->default('first_message'); // first_message, keyword, always
    $table->string('trigger_keywords')->nullable(); // comma separated
    $table->string('header_text')->nullable();
    $table->text('body_text');
    $table->string('footer_text')->nullable();
    $table->json('buttons'); // [{id, title, action_type, action_value}]
    $table->integer('order_index')->default(0);
    $table->boolean('is_active')->default(true);
    $table->timestamps();
});
[NEW] create_autoreply_logs_table.php
Schema::create('autoreply_logs', function (Blueprint $table) {
    $table->id();
    $table->foreignId('chat_assignment_id')->constrained();
    $table->foreignId('waba_inbox_message_id')->nullable()->constrained();
    $table->text('message_in');
    $table->text('message_out')->nullable();
    $table->enum('reply_type', ['ai', 'button', 'template', 'handover', 'skipped']);
    $table->decimal('confidence_score', 4, 3)->nullable();
    $table->json('ai_response_meta')->nullable(); // {model, tokens_used, latency_ms}
    $table->string('error_message')->nullable();
    $table->timestamps();
    
    $table->index(['chat_assignment_id', 'created_at']);
});
Models
[NEW] AutoreplySetting.php
Relationships: 
client
, 
wabaPool
, aiConfig, buttons, fallbackTemplate, logs
Scopes: 
active()
, forClient($id), forWaba($id)
Methods: isEnabled(), getActiveMode(), isWithinActiveHours()
[NEW] AutoreplyAiConfig.php
Relationship: autoreplySetting
Methods: getSystemPrompt(), getModelConfig()
[NEW] AutoreplyButton.php
Relationship: autoreplySetting
Methods: toWhatsAppFormat(), matchesTrigger($message)
[NEW] AutoreplyLog.php
Relationships: chatAssignment, 
inboxMessage
Scopes: forAssignment($id), byType($type), recent()
Phase 2: Core Services
[NEW] AutoreplyService.php
Orchestrator utama untuk autoreply logic.

class AutoreplyService
{
    public function __construct(
        private OpenRouterService $ai,
        private ButtonReplyService $buttonService,
        private WhatsAppApiService $whatsapp
    ) {}
    /**
     * Process incoming message for autoreply
     */
    public function processIncoming(
        WabaInboxMessage $message,
        ChatAssignment $assignment
    ): ?AutoreplyLog;
    /**
     * Check if autoreply should handle this message
     */
    public function shouldAutoReply(
        WabaPool $wabaPool,
        ChatAssignment $assignment
    ): bool;
    /**
     * Execute autoreply based on mode
     */
    private function executeReply(
        AutoreplySetting $setting,
        WabaInboxMessage $message,
        ChatAssignment $assignment
    ): AutoreplyLog;
}
[NEW] OpenRouterService.php
AI integration via OpenRouter API.

class OpenRouterService
{
    public function generateReply(
        string $userMessage,
        AutoreplyAiConfig $config,
        array $conversationContext = []
    ): array; // {reply, confidence, tokens_used, model}
    private function buildPrompt(AutoreplyAiConfig $config, string $message): array;
    
    private function callApi(array $messages, array $config): array;
}
[NEW] ButtonReplyService.php
Handle interactive button messages.

class ButtonReplyService
{
    public function sendButtonMessage(
        AutoreplyButton $buttonConfig,
        ChatAssignment $assignment
    ): array;
    public function handleButtonResponse(
        string $buttonId,
        ChatAssignment $assignment
    ): void;
    public function buildWhatsAppPayload(AutoreplyButton $button): array;
}
Phase 3: Webhook Integration
[MODIFY] 
WabaWebhookParserService.php
Add autoreply trigger after message is saved:

$savedMessage = WabaInboxMessage::create($parsedMessage);
 $messagesCount++;
+// Trigger autoreply if applicable
+$this->triggerAutoreply($savedMessage, $assignment, $wabaPool);
 // Dispatch job to process media
 if ($savedMessage->hasMedia()) {
Add new method:

private function triggerAutoreply(
    WabaInboxMessage $message,
    ChatAssignment $assignment,
    WabaPool $wabaPool
): void {
    // Dispatch to queue to not block webhook
    ProcessAutoreplyJob::dispatch($message->id, $assignment->id)
        ->onQueue('autoreply');
}
[NEW] ProcessAutoreplyJob.php
Queue job for autoreply processing:

class ProcessAutoreplyJob implements ShouldQueue
{
    public function __construct(
        public int $messageId,
        public int $assignmentId
    ) {}
    public function handle(AutoreplyService $autoreply): void
    {
        $message = WabaInboxMessage::find($this->messageId);
        $assignment = ChatAssignment::find($this->assignmentId);
        
        if ($message && $assignment) {
            $autoreply->processIncoming($message, $assignment);
        }
    }
}
Phase 4: Client UI
Routes (add to web.php)
Route::prefix('client')->middleware(['auth', 'client'])->group(function () {
    // Autoreply
    Route::prefix('autoreply')->group(function () {
        Route::get('/', [AutoreplyController::class, 'index'])->name('client.autoreply.index');
        Route::get('/settings/{wabaPool}', [AutoreplyController::class, 'settings'])->name('client.autoreply.settings');
        Route::post('/settings/{wabaPool}', [AutoreplyController::class, 'updateSettings']);
        Route::get('/ai-config/{wabaPool}', [AutoreplyController::class, 'aiConfig'])->name('client.autoreply.ai');
        Route::post('/ai-config/{wabaPool}', [AutoreplyController::class, 'updateAiConfig']);
        Route::get('/buttons/{wabaPool}', [AutoreplyController::class, 'buttons'])->name('client.autoreply.buttons');
        Route::post('/buttons/{wabaPool}', [AutoreplyController::class, 'saveButtons']);
        Route::get('/logs/{wabaPool}', [AutoreplyController::class, 'logs'])->name('client.autoreply.logs');
        Route::post('/test', [AutoreplyController::class, 'testReply'])->name('client.autoreply.test');
    });
});
[NEW] AutoreplyController.php
class AutoreplyController extends Controller
{
    public function index();           // List dedicated WABAs with autoreply status
    public function settings($waba);   // Main settings (mode, active hours)
    public function aiConfig($waba);   // AI configuration (prompt, model)
    public function buttons($waba);    // Button builder UI
    public function logs($waba);       // Autoreply logs viewer
    public function testReply();       // Test AI/Button response
}
Views Structure
resources/views/pages/client/autoreply/
├── index.blade.php          # List WABAs with autoreply toggle
├── settings.blade.php       # Mode, active hours, fallback template
├── ai-config.blade.php      # AI prompt editor, model settings
├── buttons.blade.php        # Drag-drop button builder
└── logs.blade.php           # Logs DataTable with filters
Phase 5: Environment & Config
[MODIFY] .env.example
# OpenRouter AI
OPENROUTER_API_KEY=
OPENROUTER_BASE_URL=https://openrouter.ai/api/v1
OPENROUTER_DEFAULT_MODEL=meta-llama/llama-3.1-8b-instruct
OPENROUTER_TIMEOUT=30
[NEW] config/autoreply.php
return [
    'openrouter' => [
        'api_key' => env('OPENROUTER_API_KEY'),
        'base_url' => env('OPENROUTER_BASE_URL', 'https://openrouter.ai/api/v1'),
        'default_model' => env('OPENROUTER_DEFAULT_MODEL', 'meta-llama/llama-3.1-8b-instruct'),
        'timeout' => env('OPENROUTER_TIMEOUT', 30),
    ],
    'defaults' => [
        'max_tokens' => 150,
        'temperature' => 0.7,
        'confidence_threshold' => 0.70,
    ],
    'safety' => [
        'forbidden_patterns' => [
            '/harga\s*(pasti|fix)/i',
            '/garansi\s*uang/i',
            '/transfer\s*ke\s*rekening/i',
        ],
    ],
];
Verification Plan
Unit Tests
 AutoreplyServiceTest - Test mode switching, service window check
 OpenRouterServiceTest - Test API call mocking
 ButtonReplyServiceTest - Test WhatsApp payload building
Manual Testing
AI Reply Test

Enable AI mode → Send message dari WhatsApp → Verify AI response
Button Reply Test

Configure buttons → Send "Halo" → Verify button message appears
Hybrid Flow Test

First message → Button menu
Click button → Follow action
Type free text → AI responds
AI low confidence → Handover to agent
Service Window Test

Let 24h pass → Verify fallback template used
File Summary
Type	Path	Description
Migration	database/migrations/xxx_create_autoreply_settings_table.php	Main settings
Migration	database/migrations/xxx_create_autoreply_ai_configs_table.php	AI config
Migration	database/migrations/xxx_create_autoreply_buttons_table.php	Button config
Migration	database/migrations/xxx_create_autoreply_logs_table.php	Logs
Model	app/Models/AutoreplySetting.php	Settings model
Model	app/Models/AutoreplyAiConfig.php	AI config model
Model	app/Models/AutoreplyButton.php	Button model
Model	app/Models/AutoreplyLog.php	Log model
Service	app/Services/AutoreplyService.php	Orchestrator
Service	
app/Services/OpenRouterService.php
AI integration
Service	app/Services/ButtonReplyService.php	Button handling
Job	app/Jobs/ProcessAutoreplyJob.php	Queue worker
Controller	app/Http/Controllers/Client/AutoreplyController.php	UI controller
Config	config/autoreply.php	Configuration
Views	resources/views/pages/client/autoreply/*.blade.php	UI views
