|
|
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 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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}") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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: |
|
|
|
|
|
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) |
|
|
|
|
|
|
|
|
total_overall_visitors = len(df) |
|
|
total_visitors_formatted = f"## π {total_overall_visitors:,}" |
|
|
|
|
|
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]}) |
|
|
|
|
|
|
|
|
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') |
|
|
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() |
|
|
|
|
|
|
|
|
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) |
|
|
|
|
|
|
|
|
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 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def genie_wrapper(prompt, negative_prompt, steps, seed, num_images): |
|
|
|
|
|
yield gr.update(visible=False), gr.update(visible=True), gr.update(interactive=False), gr.update(visible=False) |
|
|
|
|
|
start_time = time.time() |
|
|
|
|
|
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" |
|
|
|
|
|
|
|
|
yield gr.update(value=images, visible=True), gr.update(visible=False), gr.update(interactive=True), gr.update(value=info_text, visible=True) |
|
|
|
|
|
|
|
|
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) |
|
|
|
|
|
|
|
|
|
|
|
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; } |
|
|
""" |
|
|
|
|
|
|
|
|
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> |
|
|
""" |
|
|
|
|
|
|
|
|
with gr.Blocks( |
|
|
|
|
|
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): |
|
|
|
|
|
with gr.Row(variant='compact'): |
|
|
with gr.Column(scale=1): |
|
|
|
|
|
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") |
|
|
|
|
|
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 |
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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): |
|
|
|
|
|
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) |
|
|
|
|
|
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]) |
|
|
|
|
|
|
|
|
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() |