File size: 16,629 Bytes
15f1331
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
import gradio as gr
import torch
import numpy as np
from diffusers import DiffusionPipeline
import random
import time
import os
from datetime import datetime, timedelta
import csv
import pandas as pd
import threading

# --- BAGIAN LOGGING PENGUNJUNG (TIDAK ADA PERUBAHAN) ---
# Logika ini sudah sangat akurat untuk menangkap setiap pengunjung.
# Tidak ada perubahan yang diperlukan di sini.
VISITOR_LOG_FILE = "visitor_log.csv"
file_lock = threading.Lock()

def initialize_log_file():
    if not os.path.exists(VISITOR_LOG_FILE):
        with file_lock:
            if not os.path.exists(VISITOR_LOG_FILE):
                with open(VISITOR_LOG_FILE, mode='w', newline='', encoding='utf-8') as f:
                    writer = csv.writer(f)
                    writer.writerow(["Timestamp", "IP Address", "User Agent"])
                print(f"βœ… File log '{VISITOR_LOG_FILE}' berhasil dibuat.")

initialize_log_file()

def log_visitor(request: gr.Request):
    timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    ip_address = request.client.host if request else "N/A"
    user_agent = request.headers.get("user-agent", "Unknown") if request else "N/A"
    with file_lock:
        with open(VISITOR_LOG_FILE, mode='a', newline='', encoding='utf-8') as f:
            writer = csv.writer(f)
            writer.writerow([timestamp, ip_address, user_agent])
    print(f"βœ… Pengunjung baru tercatat: IP {ip_address}")


# --- FUNGSI MONITOR (TIDAK ADA PERUBAHAN) ---
# Logika ini sudah mengambil *jumlah* total pengunjung dengan akurat dari file log.
# Ini sudah sesuai dengan permintaan pertama Anda.
def update_visitor_monitor(time_filter: str):
    try:
        with file_lock:
            if not os.path.exists(VISITOR_LOG_FILE) or os.path.getsize(VISITOR_LOG_FILE) == 0:
                # Menampilkan "0" jika file log kosong
                return "## πŸ“ˆ 0", pd.DataFrame({"Timestamp": [], "Total Pengunjung": []})
            df = pd.read_csv(VISITOR_LOG_FILE)
            if df.empty:
                return "## πŸ“ˆ 0", pd.DataFrame({"Timestamp": [], "Total Pengunjung": []})

        df['Timestamp'] = pd.to_datetime(df['Timestamp'], errors='coerce')
        df.dropna(subset=['Timestamp'], inplace=True)

        # INI BAGIAN UTAMA PERMINTAAN 1: Menghitung jumlah total baris (pengunjung)
        total_overall_visitors = len(df)
        total_visitors_formatted = f"## πŸ“ˆ {total_overall_visitors:,}" # Format dengan Markdown

        df['Total Pengunjung'] = np.arange(1, len(df) + 1)
        
        now = pd.to_datetime('now').tz_localize(None)
        if time_filter == "1 Minggu Terakhir":
            df_plot = df[df['Timestamp'] >= now - timedelta(weeks=1)]
        elif time_filter == "2 Minggu Terakhir":
            df_plot = df[df['Timestamp'] >= now - timedelta(weeks=2)]
        elif time_filter == "3 Bulan Terakhir":
            df_plot = df[df['Timestamp'] >= now - timedelta(days=90)]
        else:
            df_plot = df

        if df_plot.empty:
             return total_visitors_formatted, pd.DataFrame({"Timestamp": [], "Total Pengunjung": []})
        
        return total_visitors_formatted, df_plot
    except Exception as e:
        error_message = f"Error saat memperbarui monitor: {e}"
        print(f"❌ {error_message}")
        return f"## ⚠️ Error", pd.DataFrame({"Error": [error_message]})

# --- BAGIAN CHATBOT GEMINI (TIDAK ADA PERUBAHAN LOGIKA) ---
try:
    import google.generativeai as genai
    print("βœ… Library 'google-generativeai' berhasil diimpor.")
except ImportError:
    print("❌ Peringatan: Library 'google-generativeai' tidak ditemukan di environment.")
    print("   Pastikan 'google-generativeai' ada di file requirements.txt Anda.")
    genai = None

class GeminiChat:
    def __init__(self):
        self.api_keys = []
        self.is_configured = False
        if not genai:
            print("❌ Konfigurasi Gemini dibatalkan karena library tidak tersedia.")
            return
        i = 1
        while True:
            key = os.getenv(f"GEMINI_API_KEY_{i}")
            if key:
                self.api_keys.append(key)
                i += 1
            else:
                break
        if self.api_keys:
            print(f"βœ… Berhasil memuat {len(self.api_keys)} API Key dari 'Secrets'. Sistem rotasi aktif.")
            self.is_configured = True
        else:
            print("❌ PERINGATAN: Tidak ada API Key yang ditemukan dengan format 'GEMINI_API_KEY_1', 'GEMINI_API_KEY_2', dst.")
            print("   Mohon atur di menu Settings -> Repository secrets di halaman Space Anda.")
    def chat(self, message, history):
        if not self.is_configured:
            return "Maaf, chatbot tidak terkonfigurasi. Admin perlu mengatur API Key di 'Secrets' Hugging Face."
        try:
            selected_key = random.choice(self.api_keys)
            genai.configure(api_key=selected_key)
            model = genai.GenerativeModel('gemini-2.5-flash') # Menggunakan gemini-pro untuk kompatibilitas lebih luas
            chat_session = model.start_chat(history=[])
            response = chat_session.send_message(message)
            return response.text
        except Exception as e:
            print(f"❌ Terjadi error pada salah satu API Key: {e}")
            return f"Terjadi kesalahan saat menghubungi API Gemini. Ini bisa jadi karena limit tercapai pada salah satu kunci. Silakan coba kirim pesan Anda sekali lagi."

gemini_bot = GeminiChat()

# --- BAGIAN IMAGE GENERATOR (TIDAK ADA PERUBAHAN LOGIKA INTI) ---
device = "cuda" if torch.cuda.is_available() else "cpu"
print(f"➑️ Menggunakan device untuk generator gambar: {device.upper()}")
pipe = DiffusionPipeline.from_pretrained("stabilityai/sdxl-turbo", torch_dtype=torch.float16 if device == "cuda" else torch.float32, variant="fp16" if device == "cuda" else None, use_safetensors=True)
if torch.cuda.is_available(): pipe.enable_xformers_memory_efficient_attention()
pipe = pipe.to(device)
    
# LOGIKA UTAMA PEMBUATAN GAMBAR (TIDAK DIUBAH SAMA SEKALI)
def generate_images(prompt, negative_prompt, steps, seed, num_images):
    if seed == -1: seed = random.randint(0, 2**32 - 1)
    generator = torch.manual_seed(seed)
    images = pipe(prompt=prompt, negative_prompt=negative_prompt, generator=generator, num_inference_steps=steps, guidance_scale=0.0, num_images_per_prompt=num_images).images
    return images, seed

# --- MODIFIKASI WRAPPER UNTUK EFEK LOADING ---
# Fungsi wrapper ini dimodifikasi untuk mengontrol UI (menampilkan & menyembunyikan loader)
# tanpa mengubah fungsi `generate_images` di atas.
def genie_wrapper(prompt, negative_prompt, steps, seed, num_images):
    # Langkah 1: Sembunyikan galeri, tampilkan loader, nonaktifkan tombol
    yield gr.update(visible=False), gr.update(visible=True), gr.update(interactive=False), gr.update(visible=False)
    
    start_time = time.time()
    # Memanggil fungsi logika inti yang tidak diubah
    images, used_seed = generate_images(prompt, negative_prompt, steps, int(seed), int(num_images))
    end_time = time.time()
    
    generation_time = end_time - start_time
    info_text = f"Seed yang digunakan: {used_seed}\nWaktu generasi: {generation_time:.2f} detik"
    
    # Langkah 2: Tampilkan hasil di galeri, sembunyikan loader, aktifkan kembali tombol
    yield gr.update(value=images, visible=True), gr.update(visible=False), gr.update(interactive=True), gr.update(value=info_text, visible=True)

# Fungsi dummy untuk report (tidak ada perubahan)
def submit_report(name, email, message):
    print(f"Laporan Diterima:\nNama: {name}\nEmail: {email}\nPesan: {message}")
    return gr.update(value="βœ… Terima kasih! Laporan Anda telah kami terima.", visible=True)

# --- BAGIAN BARU: CSS & HTML untuk Tampilan & Loading ---
# Permintaan 2 & 3: Menambahkan CSS untuk memperluas layout dan membuat animasi loading
custom_css = """
/* Memperluas container utama agar website lebih lebar */
.gradio-container { max-width: 95% !important; }

/* CSS untuk footer */
.footer { text-align: center; margin: 2rem auto; color: #999; }

/* CSS untuk animasi loading canggih */
.loader-container {
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    height: 550px; /* Sesuaikan dengan tinggi galeri */
    text-align: center;
    background-color: rgba(0, 0, 0, 0.1);
    border-radius: 12px;
}
.spinner {
    border: 8px solid #444;
    border-top: 8px solid #3498db; /* Warna biru untuk efek putaran */
    border-radius: 50%;
    width: 60px;
    height: 60px;
    animation: spin 1s linear infinite;
    margin-bottom: 20px;
}
@keyframes spin {
    0% { transform: rotate(0deg); }
    100% { transform: rotate(360deg); }
}
.loading-text { font-size: 1.2em; color: #ccc; }
.timer { font-size: 1.5em; font-weight: bold; color: #eee; margin-top: 10px; }
"""

# HTML untuk komponen loading, termasuk Javascript untuk timer live
loading_html = """
<div class="loader-container">
    <div class="spinner"></div>
    <p class="loading-text">AI sedang melukis mahakarya Anda... 🎨</p>
    <p class="timer"><span id="timer-seconds">0.0</span> detik</p>
    <script>
        // Skrip ini akan berjalan saat komponen HTML ini ditampilkan
        let startTime = Date.now();
        function updateTimer() {
            let elapsed = (Date.now() - startTime) / 1000;
            let timerElement = document.getElementById('timer-seconds');
            if (timerElement) {
                timerElement.textContent = elapsed.toFixed(1);
                // Terus update selama loader masih terlihat
                if (document.querySelector('.loader-container')) {
                   requestAnimationFrame(updateTimer);
                }
            }
        }
        requestAnimationFrame(updateTimer); // Mulai timer
    </script>
</div>
"""

# --- BAGIAN UTAMA TAMPILAN WEBSITE (UI DENGAN GRADIO) ---
with gr.Blocks(
    # Permintaan 3: Menggunakan tema Soft (dark mode) & CSS kustom
    theme=gr.themes.Soft(primary_hue=gr.themes.colors.blue, secondary_hue=gr.themes.colors.slate), 
    css=custom_css
) as demo:
    gr.Markdown("# πŸš€ RenXploit's Creative AI Suite 🌌\nSebuah platform lengkap untuk kreativitas Anda, ditenagai oleh AI.")
    
    with gr.Tabs():
        with gr.TabItem("🎨 Image Generator", id=0):
            # Permintaan 3: Menggunakan gr.Row dan gr.Column untuk tata letak yang lebih profesional
            with gr.Row(variant='compact'):
                with gr.Column(scale=1):
                    # Menggunakan gr.Box untuk mengelompokkan elemen kontrol
                    with gr.Box():
                        gr.Markdown("### πŸ“ Masukkan Perintah Anda")
                        prompt_input = gr.Textbox(label="Prompt", placeholder="Contoh: Cinematic photo, seekor rubah merah...", lines=3)
                        negative_prompt_input = gr.Textbox(label="Prompt Negatif", placeholder="Contoh: blurry, low quality...", lines=2)
                        num_images_slider = gr.Slider(minimum=1, maximum=8, value=2, step=1, label="Jumlah Gambar")
                        generate_btn = gr.Button("✨ Hasilkan Gambar!", variant="primary", scale=2)
                        with gr.Accordion("βš™οΈ Opsi Lanjutan", open=False):
                            steps_slider = gr.Slider(minimum=1, maximum=5, value=2, step=1, label="Langkah Iterasi (Kualitas)")
                            with gr.Row():
                                seed_input = gr.Number(label="Seed", value=-1, precision=0)
                                random_seed_btn = gr.Button("🎲 Acak")
                with gr.Column(scale=2):
                    with gr.Box():
                        gr.Markdown("### πŸ–ΌοΈ Hasil Generasi")
                        # Permintaan 2: Menambahkan komponen HTML untuk loader, awalnya disembunyikan
                        loader_component = gr.HTML(loading_html, visible=False)
                        
                        output_gallery = gr.Gallery(label="Hasil Gambar", show_label=False, elem_id="gallery", columns=2, object_fit="contain", height="auto")
                        info_box = gr.Textbox(label="Informasi Generasi", visible=False, interactive=False)
        
        with gr.TabItem("πŸ’‘ Panduan Prompting", id=1):
            gr.Markdown("## Cara Menjadi \"Art Director\" yang Hebat untuk AI\n...")
        
        with gr.TabItem("πŸ’¬ Chat with AI", id=2):
            gr.Markdown("### πŸ€– Asisten AI Flood/RenXploit\nTanyakan apa saja!")
            if not gemini_bot.is_configured:
                gr.Warning("Fitur Chatbot dinonaktifkan. API Key Gemini tidak terkonfigurasi. Silakan periksa Logs untuk detailnya.")
            if gemini_bot.is_configured:
                gr.ChatInterface(
                    gemini_bot.chat,
                    chatbot=gr.Chatbot(height=500, label="Asisten AI", show_label=False, avatar_images=("./user.png", "./bot.png"), value=[[None, "Halo! Saya adalah asisten AI dari RenXploit. Ada yang bisa saya bantu?"]]),
                    title=None
                )
        
        # PERMINTAAN 1: Menampilkan Jumlah Pengunjung
        # Komponen `visitor_count_display` ini akan menampilkan JUMLAH pengunjung
        # yang diambil oleh fungsi `update_visitor_monitor`.
        with gr.TabItem("πŸ“ˆ Monitor Pengunjung", id=5):
            gr.Markdown("### πŸ“Š Live Visitor Monitor\nPantau jumlah total pengunjung website Anda secara real-time.")
            with gr.Row():
                with gr.Column(scale=3):
                    # INI ADALAH KOMPONEN YANG MENAMPILKAN JUMLAH TOTAL PENGUNJUNG
                    visitor_count_display = gr.Markdown("## πŸ“ˆ Memuat data...")
                with gr.Column(scale=2):
                    time_filter_radio = gr.Radio(
                        ["Semua Waktu", "1 Minggu Terakhir", "2 Minggu Terakhir", "3 Bulan Terakhir"],
                        label="Tampilkan data untuk",
                        value="Semua Waktu"
                    )
                    refresh_btn = gr.Button("πŸ”„ Segarkan Manual")
            
            visitor_plot = gr.LinePlot(
                x="Timestamp",
                y="Total Pengunjung",
                title="Grafik Pertumbuhan Pengunjung",
                tooltip=['Timestamp', 'Total Pengunjung'],
                height=500,
                interactive=True
            )
        
        with gr.TabItem("πŸ“– Blog & Updates", id=3):
            gr.Markdown("### Perkembangan Terbaru dari RenXploit's AI Suite\nUpdate terbaru tanggal 30/10/2025 | tampilan UI & Chatbot & Visitor.")
        
        with gr.TabItem("ℹ️ About & Support", id=4):
            gr.Markdown("### Tentang Proyek dan Dukungan")
            with gr.Accordion("Tentang RenXploit's Creative AI Suite", open=True):
                gr.Markdown("**Bagian ini belum saya kerjakan dengan selesai** jadi jika anda ingin komplain atau ada hal apapun itu bisa report ke saya lewat website portofolio saya dengan url: https://ngoprek.xyz")
            with gr.Accordion("Laporkan Masalah atau Beri Masukan"):
                report_name = gr.Textbox(label="Nama Anda")
                report_email = gr.Textbox(label="Email Anda")
                report_message = gr.Textbox(label="Pesan Anda", lines=5)
                report_btn = gr.Button("Kirim Laporan", variant="primary")
                report_status = gr.Markdown(visible=False)
                
    gr.Markdown("<div class='footer'><p>Dibuat dengan ❀️ oleh <b>RenXploit</b>.</p></div>", elem_classes="footer")

    random_seed_btn.click(lambda: -1, outputs=seed_input)
    # Menghubungkan tombol generate ke wrapper baru dengan output yang disesuaikan
    generate_btn.click(
        fn=genie_wrapper, 
        inputs=[prompt_input, negative_prompt_input, steps_slider, seed_input, num_images_slider], 
        outputs=[output_gallery, loader_component, generate_btn, info_box]
    )
    report_btn.click(fn=submit_report, inputs=[report_name, report_email, report_message], outputs=[report_status])

    # Event Handlers untuk Monitor Pengunjung (Tidak ada perubahan logika)
    demo.load(log_visitor, inputs=None, outputs=None)
    demo.load(
        fn=update_visitor_monitor,
        inputs=[time_filter_radio],
        outputs=[visitor_count_display, visitor_plot]
    )
    refresh_btn.click(
        fn=update_visitor_monitor,
        inputs=[time_filter_radio],
        outputs=[visitor_count_display, visitor_plot]
    )
    time_filter_radio.change(
        fn=update_visitor_monitor,
        inputs=[time_filter_radio],
        outputs=[visitor_count_display, visitor_plot]
    )

demo.launch()