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 = """

AI sedang melukis mahakarya Anda... 🎨

0.0 detik

""" # --- 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("", 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()