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