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() |