HEX
Server: LiteSpeed
System: Linux s1049.use1.mysecurecloudhost.com 4.18.0-477.27.2.lve.el8.x86_64 #1 SMP Wed Oct 11 12:32:56 UTC 2023 x86_64
User: xedaptot (3356)
PHP: 8.3.31
Disabled: NONE
Upload Files
File: /home/xedaptot/hi.naniguide.com/app/Http/Controllers/Admin/AiToolController.php
<?php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use App\Models\AiGeneration;
use App\Models\Setting;
use App\Services\AI\AiUsageService;
use App\Services\AI\GeminiClient;
use App\Services\AI\OpenAIClient;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;

class AiToolController extends Controller
{
    public function __construct(
        protected OpenAIClient $openai,
        protected GeminiClient $gemini,
        protected AiUsageService $aiUsage
    ) {
    }

    public function index()
    {
        return view('admin.ai-tools.index');
    }

    public function dashboard(Request $request)
    {
        $admin = auth('admin')->user();
        if (!$admin) {
            abort(403);
        }

        $range = (string) $request->query('range', '7d');
        $now = now();

        $start = null;
        $end = null;

        if ($range === '30d') {
            $start = $now->copy()->subDays(29)->startOfDay();
            $end = $now->copy()->endOfDay();
        } elseif ($range === 'custom') {
            $from = $request->query('from');
            $to = $request->query('to');

            $start = $from ? Carbon::parse($from)->startOfDay() : $now->copy()->subDays(6)->startOfDay();
            $end = $to ? Carbon::parse($to)->endOfDay() : $now->copy()->endOfDay();
        } else {
            $range = '7d';
            $start = $now->copy()->subDays(6)->startOfDay();
            $end = $now->copy()->endOfDay();
        }

        if ($start->greaterThan($end)) {
            [$start, $end] = [$end->copy()->startOfDay(), $start->copy()->endOfDay()];
            $range = 'custom';
        }

        $byProviderModel = AiGeneration::query()
            ->where('used_admin_keys', true)
            ->where('success', true)
            ->whereBetween('created_at', [$start, $end])
            ->selectRaw("provider, COALESCE(model, '') as model, SUM(COALESCE(tokens_used, 0)) as tokens")
            ->groupBy('provider', DB::raw("COALESCE(model, '')"))
            ->get();

        $providers = [
            'chatgpt' => 'ChatGPT',
            'gemini' => 'Gemini',
            'claude' => 'Claude',
        ];

        $providerStats = [];
        $providerDailyStats = [];
        $breakdownRows = [];

        $totalTokensUsed = 0;
        $totalCostCents = 0;
        $totalLimit = 0;
        $hasUnlimited = false;

        $totalTokensUsedToday = 0;
        $totalDailyLimit = 0;
        $hasUnlimitedDaily = false;

        foreach ($providers as $providerKey => $providerLabel) {
            $limit = (int) $this->aiUsage->getAdminMonthlyLimit($providerKey);

            $dailyLimit = (int) $this->aiUsage->getAdminDailyLimit($providerKey);
            $usedToday = (int) $this->aiUsage->getAdminTokensUsedToday($providerKey);
            $dailyPercent = null;
            if ($dailyLimit > 0) {
                $dailyPercent = min(100, ($usedToday / $dailyLimit) * 100);
            }

            $used = (int) $byProviderModel->where('provider', $providerKey)->sum('tokens');

            $estimatedCostCents = 0;
            $rowsForProvider = $byProviderModel->where('provider', $providerKey)->values();
            foreach ($rowsForProvider as $row) {
                $model = is_string($row->model ?? null) ? (string) $row->model : '';
                $tokens = is_numeric($row->tokens ?? null) ? (int) $row->tokens : 0;
                $costPer1m = (int) $this->aiUsage->getAdminCostPer1mCentsForModel($providerKey, $model !== '' ? $model : null);
                $estimatedCostCents += (int) round(($tokens / 1000000) * $costPer1m);
            }

            $costPer1mCents = (int) $this->aiUsage->getAdminCostPer1mCents($providerKey);

            $percent = null;
            if ($limit > 0) {
                $percent = $limit > 0 ? min(100, ($used / $limit) * 100) : null;
            }

            $providerStats[$providerKey] = [
                'label' => $providerLabel,
                'tokens_used' => $used,
                'token_limit' => $limit,
                'percent' => $percent,
                'cost_per_1m_cents' => $costPer1mCents,
                'estimated_cost_cents' => $estimatedCostCents,
            ];

            $providerDailyStats[$providerKey] = [
                'label' => $providerLabel,
                'tokens_used' => $usedToday,
                'token_limit' => $dailyLimit,
                'percent' => $dailyPercent,
            ];

            $totalTokensUsed += $used;
            $totalCostCents += $estimatedCostCents;

            if ($limit > 0) {
                $totalLimit += $limit;
            } else {
                $hasUnlimited = true;
            }

            $totalTokensUsedToday += $usedToday;
            if ($dailyLimit > 0) {
                $totalDailyLimit += $dailyLimit;
            } else {
                $hasUnlimitedDaily = true;
            }

            $rows = $rowsForProvider;
            foreach ($rows as $row) {
                $model = is_string($row->model ?? null) ? (string) $row->model : '';
                $tokens = is_numeric($row->tokens ?? null) ? (int) $row->tokens : 0;

                $modelCostPer1mCents = (int) $this->aiUsage->getAdminCostPer1mCentsForModel($providerKey, $model !== '' ? $model : null);

                $breakdownRows[] = [
                    'provider' => $providerKey,
                    'provider_label' => $providerLabel,
                    'model' => $model !== '' ? $model : 'Default',
                    'tokens' => $tokens,
                    'cost_per_1m_cents' => $modelCostPer1mCents,
                    'estimated_cost_cents' => (int) round(($tokens / 1000000) * $modelCostPer1mCents),
                ];
            }
        }

        usort($breakdownRows, function ($a, $b) {
            $p = strcmp((string) ($a['provider'] ?? ''), (string) ($b['provider'] ?? ''));
            if ($p !== 0) {
                return $p;
            }

            return ((int) ($b['tokens'] ?? 0)) <=> ((int) ($a['tokens'] ?? 0));
        });

        $totalPercent = null;
        if (!$hasUnlimited && $totalLimit > 0) {
            $totalPercent = min(100, ($totalTokensUsed / $totalLimit) * 100);
        }

        $totalDailyPercent = null;
        if (!$hasUnlimitedDaily && $totalDailyLimit > 0) {
            $totalDailyPercent = min(100, ($totalTokensUsedToday / $totalDailyLimit) * 100);
        }

        $rangeLabel = 'Last 7 Days';
        if ($range === '30d') {
            $rangeLabel = 'Last 30 Days';
        } elseif ($range === 'custom') {
            $rangeLabel = 'Custom Range';
        }

        return view('admin.ai-tools.dashboard', [
            'range' => $range,
            'startDate' => $start,
            'endDate' => $end,
            'rangeLabel' => $rangeLabel,
            'providerStats' => $providerStats,
            'providerDailyStats' => $providerDailyStats,
            'breakdownRows' => $breakdownRows,
            'totalTokensUsed' => $totalTokensUsed,
            'totalLimit' => $hasUnlimited ? null : $totalLimit,
            'totalPercent' => $totalPercent,
            'totalCostCents' => $totalCostCents,
            'totalTokensUsedToday' => $totalTokensUsedToday,
            'totalDailyLimit' => $hasUnlimitedDaily ? null : $totalDailyLimit,
            'totalDailyPercent' => $totalDailyPercent,
        ]);
    }

    public function emailTextGenerator()
    {
        return view('admin.ai-tools.email-text-generator');
    }

    public function generateEmailText(Request $request)
    {
        $admin = auth('admin')->user();
        if (!$admin) {
            abort(403);
        }

        $validated = $request->validate([
            'provider' => ['required', 'in:chatgpt,gemini,claude'],
            'model' => ['nullable', 'string', 'max:100'],
            'email_type' => ['required', 'string', 'max:255'],
            'tone' => ['required', 'string', 'max:255'],
            'audience' => ['required', 'string', 'max:255'],
            'audience_custom' => ['nullable', 'string', 'max:255'],
            'length' => ['required', 'string', 'max:255'],
            'word_count' => ['nullable', 'integer', 'min:10', 'max:5000'],
            'objective' => ['required', 'string', 'max:255'],
            'subject_idea' => ['nullable', 'string', 'max:255'],
            'context' => ['nullable', 'string', 'max:5000'],
            'key_points' => ['nullable', 'string', 'max:5000'],
            'cta' => ['nullable', 'string', 'max:500'],
            'offer_details' => ['nullable', 'string', 'max:2000'],
        ]);

        $audience = $validated['audience'];
        if ($audience === 'custom' && is_string($validated['audience_custom'] ?? null) && trim((string) $validated['audience_custom']) !== '') {
            $audience = trim((string) $validated['audience_custom']);
        }

        $length = $validated['length'];
        $wordCount = null;
        if ($length === 'custom' && is_numeric($validated['word_count'] ?? null)) {
            $wordCount = (int) $validated['word_count'];
        }

        $subjectIdea = is_string($validated['subject_idea'] ?? null) ? trim((string) $validated['subject_idea']) : '';
        $context = is_string($validated['context'] ?? null) ? trim((string) $validated['context']) : '';
        $keyPoints = is_string($validated['key_points'] ?? null) ? trim((string) $validated['key_points']) : '';
        $cta = is_string($validated['cta'] ?? null) ? trim((string) $validated['cta']) : '';
        $offerDetails = is_string($validated['offer_details'] ?? null) ? trim((string) $validated['offer_details']) : '';

        $promptParts = [];
        $promptParts[] = 'Write an email.';
        $promptParts[] = 'Output plain text only.';
        $promptParts[] = 'Include a subject line and the email body.';
        $promptParts[] = 'Email type: ' . $validated['email_type'] . '.';
        $promptParts[] = 'Tone: ' . $validated['tone'] . '.';
        $promptParts[] = 'Target audience: ' . $audience . '.';
        $promptParts[] = 'Objective: ' . $validated['objective'] . '.';

        if ($wordCount !== null) {
            $promptParts[] = 'Length: approximately ' . $wordCount . ' words.';
        } else {
            $promptParts[] = 'Length: ' . $length . '.';
        }

        if ($subjectIdea !== '') {
            $promptParts[] = 'Subject idea: ' . $subjectIdea . '.';
        }
        if ($context !== '') {
            $promptParts[] = 'Context: ' . $context;
        }
        if ($keyPoints !== '') {
            $promptParts[] = 'Key points to include: ' . $keyPoints;
        }
        if ($cta !== '') {
            $promptParts[] = 'CTA: ' . $cta;
        }
        if ($offerDetails !== '') {
            $promptParts[] = 'Product/Offer details: ' . $offerDetails;
        }

        $prompt = implode("\n", $promptParts);

        $log = AiGeneration::create([
            'customer_id' => null,
            'admin_user_id' => $admin->id,
            'tool' => 'email_text_generator',
            'provider' => $validated['provider'],
            'model' => is_string($validated['model'] ?? null) && trim((string) $validated['model']) !== '' ? trim((string) $validated['model']) : null,
            'used_admin_keys' => true,
            'prompt' => $prompt,
            'input' => $validated,
            'success' => false,
            'output' => null,
            'tokens_used' => null,
            'error_message' => null,
        ]);

        if ($validated['provider'] === 'claude') {
            $log->success = false;
            $log->error_message = 'Claude is not supported yet.';
            $log->save();

            if ($request->expectsJson()) {
                return response()->json([
                    'success' => false,
                    'message' => $log->error_message,
                    'generation_id' => $log->id,
                ], 501);
            }

            return redirect()
                ->back()
                ->withInput()
                ->with('error', $log->error_message);
        }

        $estimatedTokens = $this->aiUsage->estimateTokens($prompt, 1200);
        if (!$this->aiUsage->canUseAdminTokens($validated['provider'], $estimatedTokens)) {
            $log->success = false;
            $log->error_message = 'Admin AI monthly token limit reached. Please try again later or increase the limit in settings.';
            $log->save();

            if ($request->expectsJson()) {
                return response()->json([
                    'success' => false,
                    'message' => $log->error_message,
                    'generation_id' => $log->id,
                ], 429);
            }

            return redirect()
                ->back()
                ->withInput()
                ->with('error', $log->error_message);
        }

        $apiKey = null;
        if ($validated['provider'] === 'chatgpt') {
            $apiKey = Setting::get('openai_api_key');
        } else {
            $apiKey = Setting::get('gemini_api_key');
        }

        if (!is_string($apiKey) || trim($apiKey) === '') {
            $log->success = false;
            $log->error_message = 'Admin AI API key is not configured yet.';
            $log->save();

            if ($request->expectsJson()) {
                return response()->json([
                    'success' => false,
                    'message' => $log->error_message,
                    'generation_id' => $log->id,
                ], 422);
            }

            return redirect()
                ->back()
                ->withInput()
                ->with('error', $log->error_message);
        }

        $model = is_string($validated['model'] ?? null) && trim((string) $validated['model']) !== '' ? trim((string) $validated['model']) : null;

        $result = $validated['provider'] === 'chatgpt'
            ? $this->openai->generateText($apiKey, $prompt, 1200, $model)
            : $this->gemini->generateText($apiKey, $prompt, 1200, $model);

        $log->tokens_used = is_numeric($result['tokens'] ?? null) ? (int) $result['tokens'] : null;

        if (is_array($result) && ($result['success'] ?? false) === true) {
            $log->success = true;
            $log->output = (string) ($result['text'] ?? '');
            $log->error_message = null;
            $log->save();

            if ($request->expectsJson()) {
                return response()->json([
                    'success' => true,
                    'text' => (string) ($result['text'] ?? ''),
                    'tokens' => is_numeric($result['tokens'] ?? null) ? (int) $result['tokens'] : null,
                    'used_admin_keys' => true,
                    'generation_id' => $log->id,
                ]);
            }

            return redirect()
                ->back()
                ->withInput()
                ->with('success', 'Email text generated.')
                ->with('generated_text', (string) ($result['text'] ?? ''));
        }

        $log->success = false;
        $log->output = null;
        $log->error_message = is_string($result['message'] ?? null) ? (string) $result['message'] : 'AI generation failed.';
        $log->save();

        if ($request->expectsJson()) {
            return response()->json([
                'success' => false,
                'message' => $log->error_message,
                'generation_id' => $log->id,
            ], 502);
        }

        return redirect()
            ->back()
            ->withInput()
            ->with('error', $log->error_message);
    }
}