ai2 / app.py
floodd's picture
Create app.py
15f1331 verified
raw
history blame
16.6 kB
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()