Implementasi Frontend Utama

Serial Bagian 7: Menghubungkan antarmuka pengguna dengan API yang sudah diamankan dan dilimitasi kuotanya.

24. UI Main Application

Kita akan membangun halaman utama (`index.html`) di mana pengguna memasukkan API Key mereka dan berinteraksi dengan AI.

File: /www/wwwroot/ao.baktimakmur.com/index.html
<!DOCTYPE html>
<html lang="id">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>AI Text Generator Pro</title>
    <link rel="stylesheet" href="/static/css/style.css">
    <style>
        .api-config {
            background: #f8fafc;
            border: 1px dashed var(--border);
            padding: 15px;
            border-radius: 8px;
            margin-bottom: 20px;
            display: flex;
            gap: 10px;
            align-items: center;
        }
        .result-box {
            background: #fff;
            border: 1px solid var(--border);
            padding: 20px;
            border-radius: var(--radius);
            min-height: 100px;
            margin-top: 20px;
            white-space: pre-wrap;
        }
        .error-message {
            color: #ef4444;
            background: #fef2f2;
            padding: 10px;
            border-radius: 6px;
            margin-top: 10px;
            display: none;
            border-left: 4px solid #ef4444;
        }
        .quota-info {
            font-size: 0.85rem;
            color: var(--text-muted);
            margin-top: 5px;
        }
    </style>
</head>
<body>
    <div class="container">
        <header>
            <h1>AI Generator</h1>
            <p>SaaS Text Production</p>
        </header>

        <!-- Konfigurasi API Key -->
        <div class="api-config">
            <div style="flex:1;">
                <label>API Key Anda:</label>
                <input type="text" id="apiKey" placeholder="prod-sk-..." style="width:100%;">
            </div>
            <a href="/dashboard.html" style="font-size:0.8rem;">Lihat Dashboard</a>
        </div>

        <!-- Form Input -->
        <section class="card">
            <h2>Buat Konten</h2>
            <div class="form-group">
                <label>Prompt / Instruksi</label>
                <textarea id="promptInput" rows="5" placeholder="Contoh: Tulis deskripsi produk sepatu lari..."></textarea>
            </div>
            <button id="generateBtn" class="btn-primary">Generate Text</button>
        </section>

        <!-- Hasil & Error -->
        <section>
            <div id="errorMessage" class="error-message"></div>
            <div class="result-box" id="resultArea">
                Hasil akan muncul di sini...
            </div>
            <div id="quotaInfo" class="quota-info"></div>
        </section>

    </div>

    <script src="/static/js/app.js"></script>
</body>
</html>

25. Integration Logic (JavaScript)

File `app.js` inilah yang berkomunikasi dengan endpoint `/api/v1/chat/generate`. Perhatikan header `X-Api-Key` yang wajib dikirim.

File: /www/wwwroot/ao.baktimakmur.com/static/js/app.js
const API_ENDPOINT = '/api/v1/chat/generate';

const generateBtn = document.getElementById('generateBtn');
const promptInput = document.getElementById('promptInput');
const apiKeyInput = document.getElementById('apiKey');
const resultArea = document.getElementById('resultArea');
const errorMessage = document.getElementById('errorMessage');
const quotaInfo = document.getElementById('quotaInfo');

// Fungsi Utama Generate
generateBtn.addEventListener('click', async () => {
    const prompt = promptInput.value.trim();
    const apiKey = apiKeyInput.value.trim();

    // Validasi Input
    if (!prompt) {
        showError("Mohon isi prompt terlebih dahulu.");
        return;
    }
    if (!apiKey) {
        showError("Mohon masukkan API Key Anda.");
        return;
    }

    // Reset UI State
    setLoading(true);
    clearError();

    try {
        const response = await fetch(API_ENDPOINT, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/x-www-form-urlencoded', // Sesuai endpoint yang pakai Form data
                'X-Api-Key': apiKey // Header kunci dari Part 6
            },
            body: `prompt=${encodeURIComponent(prompt)}`
        });

        const data = await response.json();

        if (response.ok) {
            // SUKSES
            resultArea.textContent = data.text;
            
            // Tampilkan info kuota sisa (dari response Part 6)
            if (data.quota_remaining !== undefined) {
                quotaInfo.textContent = `Sisa Kuota: ${data.quota_remaining}`;
            }
        } else {
            // ERROR DARI SERVER
            showError(data.detail || "Terjadi kesalahan tak terduga.");
        }

    } catch (error) {
        showError("Gagal terhubung ke server. Cek koneksi internet.");
        console.error(error);
    } finally {
        setLoading(false);
    }
});

// Helper Functions
function setLoading(isLoading) {
    if (isLoading) {
        generateBtn.disabled = true;
        generateBtn.textContent = "Menggenerate...";
        resultArea.textContent = "Memproses...";
        resultArea.style.opacity = "0.6";
    } else {
        generateBtn.disabled = false;
        generateBtn.textContent = "Generate Text";
        resultArea.style.opacity = "1";
    }
}

function showError(msg) {
    errorMessage.textContent = msg;
    errorMessage.style.display = 'block';
    resultArea.textContent = ""; // Kosongkan hasil jika error
}

function clearError() {
    errorMessage.style.display = 'none';
}

// Simpan API Key di LocalStorage agar tidak perlu ketik ulang
window.onload = () => {
    const savedKey = localStorage.getItem('saas_api_key');
    if (savedKey) apiKeyInput.value = savedKey;
};

apiKeyInput.addEventListener('change', (e) => {
    localStorage.setItem('saas_api_key', e.target.value);
});

26. Error Handling UX (Quota Habis)

Sekarang sistem sudah lengkap. Jika user menghabiskan kuota, backend (Part 6) akan melempar Error 429. Frontend (Part 7) akan menangkapnya dan menampilkan pesan.

Flow Pengujian Lengkap

  1. Buka index.html.
  2. Tempelkan API Key (yang didapat dari Admin Panel Part 5).
  3. Klik "Generate". Harusnya berhasil.
  4. Ulangi hingga kuota mencapai limit (misal kuota sisa 0).
  5. Klik "Generate" lagi.
  6. Hasil: UI akan menampilkan kotak merah: "Quota Habis (Limit: 100). Silakan upgrade."

Saat ini SaaS Anda sudah memiliki siklus hidup lengkap: Infrastruktur -> Backend -> Admin/Quota -> Frontend App.

Serial Selanjutnya (Opsional)

Jika melanjutkan ke Bagian 8, kita bisa membahas:

  • Deployment Docker: Meng-containerkan aplikasi agar mudah di-deploy ke VPS.
  • Payment Gateway: Mengintegrasikan Midtrans/Stripe agar user bisa top-up kuota otomatis.
  • Refinement AI: Menambahkan context history agar AI "ingat" percakapan sebelumnya.