floodd commited on
Commit
15f1331
Β·
verified Β·
1 Parent(s): 7228055

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +344 -0
app.py ADDED
@@ -0,0 +1,344 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import torch
3
+ import numpy as np
4
+ from diffusers import DiffusionPipeline
5
+ import random
6
+ import time
7
+ import os
8
+ from datetime import datetime, timedelta
9
+ import csv
10
+ import pandas as pd
11
+ import threading
12
+
13
+ # --- BAGIAN LOGGING PENGUNJUNG (TIDAK ADA PERUBAHAN) ---
14
+ # Logika ini sudah sangat akurat untuk menangkap setiap pengunjung.
15
+ # Tidak ada perubahan yang diperlukan di sini.
16
+ VISITOR_LOG_FILE = "visitor_log.csv"
17
+ file_lock = threading.Lock()
18
+
19
+ def initialize_log_file():
20
+ if not os.path.exists(VISITOR_LOG_FILE):
21
+ with file_lock:
22
+ if not os.path.exists(VISITOR_LOG_FILE):
23
+ with open(VISITOR_LOG_FILE, mode='w', newline='', encoding='utf-8') as f:
24
+ writer = csv.writer(f)
25
+ writer.writerow(["Timestamp", "IP Address", "User Agent"])
26
+ print(f"βœ… File log '{VISITOR_LOG_FILE}' berhasil dibuat.")
27
+
28
+ initialize_log_file()
29
+
30
+ def log_visitor(request: gr.Request):
31
+ timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
32
+ ip_address = request.client.host if request else "N/A"
33
+ user_agent = request.headers.get("user-agent", "Unknown") if request else "N/A"
34
+ with file_lock:
35
+ with open(VISITOR_LOG_FILE, mode='a', newline='', encoding='utf-8') as f:
36
+ writer = csv.writer(f)
37
+ writer.writerow([timestamp, ip_address, user_agent])
38
+ print(f"βœ… Pengunjung baru tercatat: IP {ip_address}")
39
+
40
+
41
+ # --- FUNGSI MONITOR (TIDAK ADA PERUBAHAN) ---
42
+ # Logika ini sudah mengambil *jumlah* total pengunjung dengan akurat dari file log.
43
+ # Ini sudah sesuai dengan permintaan pertama Anda.
44
+ def update_visitor_monitor(time_filter: str):
45
+ try:
46
+ with file_lock:
47
+ if not os.path.exists(VISITOR_LOG_FILE) or os.path.getsize(VISITOR_LOG_FILE) == 0:
48
+ # Menampilkan "0" jika file log kosong
49
+ return "## πŸ“ˆ 0", pd.DataFrame({"Timestamp": [], "Total Pengunjung": []})
50
+ df = pd.read_csv(VISITOR_LOG_FILE)
51
+ if df.empty:
52
+ return "## πŸ“ˆ 0", pd.DataFrame({"Timestamp": [], "Total Pengunjung": []})
53
+
54
+ df['Timestamp'] = pd.to_datetime(df['Timestamp'], errors='coerce')
55
+ df.dropna(subset=['Timestamp'], inplace=True)
56
+
57
+ # INI BAGIAN UTAMA PERMINTAAN 1: Menghitung jumlah total baris (pengunjung)
58
+ total_overall_visitors = len(df)
59
+ total_visitors_formatted = f"## πŸ“ˆ {total_overall_visitors:,}" # Format dengan Markdown
60
+
61
+ df['Total Pengunjung'] = np.arange(1, len(df) + 1)
62
+
63
+ now = pd.to_datetime('now').tz_localize(None)
64
+ if time_filter == "1 Minggu Terakhir":
65
+ df_plot = df[df['Timestamp'] >= now - timedelta(weeks=1)]
66
+ elif time_filter == "2 Minggu Terakhir":
67
+ df_plot = df[df['Timestamp'] >= now - timedelta(weeks=2)]
68
+ elif time_filter == "3 Bulan Terakhir":
69
+ df_plot = df[df['Timestamp'] >= now - timedelta(days=90)]
70
+ else:
71
+ df_plot = df
72
+
73
+ if df_plot.empty:
74
+ return total_visitors_formatted, pd.DataFrame({"Timestamp": [], "Total Pengunjung": []})
75
+
76
+ return total_visitors_formatted, df_plot
77
+ except Exception as e:
78
+ error_message = f"Error saat memperbarui monitor: {e}"
79
+ print(f"❌ {error_message}")
80
+ return f"## ⚠️ Error", pd.DataFrame({"Error": [error_message]})
81
+
82
+ # --- BAGIAN CHATBOT GEMINI (TIDAK ADA PERUBAHAN LOGIKA) ---
83
+ try:
84
+ import google.generativeai as genai
85
+ print("βœ… Library 'google-generativeai' berhasil diimpor.")
86
+ except ImportError:
87
+ print("❌ Peringatan: Library 'google-generativeai' tidak ditemukan di environment.")
88
+ print(" Pastikan 'google-generativeai' ada di file requirements.txt Anda.")
89
+ genai = None
90
+
91
+ class GeminiChat:
92
+ def __init__(self):
93
+ self.api_keys = []
94
+ self.is_configured = False
95
+ if not genai:
96
+ print("❌ Konfigurasi Gemini dibatalkan karena library tidak tersedia.")
97
+ return
98
+ i = 1
99
+ while True:
100
+ key = os.getenv(f"GEMINI_API_KEY_{i}")
101
+ if key:
102
+ self.api_keys.append(key)
103
+ i += 1
104
+ else:
105
+ break
106
+ if self.api_keys:
107
+ print(f"βœ… Berhasil memuat {len(self.api_keys)} API Key dari 'Secrets'. Sistem rotasi aktif.")
108
+ self.is_configured = True
109
+ else:
110
+ print("❌ PERINGATAN: Tidak ada API Key yang ditemukan dengan format 'GEMINI_API_KEY_1', 'GEMINI_API_KEY_2', dst.")
111
+ print(" Mohon atur di menu Settings -> Repository secrets di halaman Space Anda.")
112
+ def chat(self, message, history):
113
+ if not self.is_configured:
114
+ return "Maaf, chatbot tidak terkonfigurasi. Admin perlu mengatur API Key di 'Secrets' Hugging Face."
115
+ try:
116
+ selected_key = random.choice(self.api_keys)
117
+ genai.configure(api_key=selected_key)
118
+ model = genai.GenerativeModel('gemini-2.5-flash') # Menggunakan gemini-pro untuk kompatibilitas lebih luas
119
+ chat_session = model.start_chat(history=[])
120
+ response = chat_session.send_message(message)
121
+ return response.text
122
+ except Exception as e:
123
+ print(f"❌ Terjadi error pada salah satu API Key: {e}")
124
+ 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."
125
+
126
+ gemini_bot = GeminiChat()
127
+
128
+ # --- BAGIAN IMAGE GENERATOR (TIDAK ADA PERUBAHAN LOGIKA INTI) ---
129
+ device = "cuda" if torch.cuda.is_available() else "cpu"
130
+ print(f"➑️ Menggunakan device untuk generator gambar: {device.upper()}")
131
+ 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)
132
+ if torch.cuda.is_available(): pipe.enable_xformers_memory_efficient_attention()
133
+ pipe = pipe.to(device)
134
+
135
+ # LOGIKA UTAMA PEMBUATAN GAMBAR (TIDAK DIUBAH SAMA SEKALI)
136
+ def generate_images(prompt, negative_prompt, steps, seed, num_images):
137
+ if seed == -1: seed = random.randint(0, 2**32 - 1)
138
+ generator = torch.manual_seed(seed)
139
+ 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
140
+ return images, seed
141
+
142
+ # --- MODIFIKASI WRAPPER UNTUK EFEK LOADING ---
143
+ # Fungsi wrapper ini dimodifikasi untuk mengontrol UI (menampilkan & menyembunyikan loader)
144
+ # tanpa mengubah fungsi `generate_images` di atas.
145
+ def genie_wrapper(prompt, negative_prompt, steps, seed, num_images):
146
+ # Langkah 1: Sembunyikan galeri, tampilkan loader, nonaktifkan tombol
147
+ yield gr.update(visible=False), gr.update(visible=True), gr.update(interactive=False), gr.update(visible=False)
148
+
149
+ start_time = time.time()
150
+ # Memanggil fungsi logika inti yang tidak diubah
151
+ images, used_seed = generate_images(prompt, negative_prompt, steps, int(seed), int(num_images))
152
+ end_time = time.time()
153
+
154
+ generation_time = end_time - start_time
155
+ info_text = f"Seed yang digunakan: {used_seed}\nWaktu generasi: {generation_time:.2f} detik"
156
+
157
+ # Langkah 2: Tampilkan hasil di galeri, sembunyikan loader, aktifkan kembali tombol
158
+ yield gr.update(value=images, visible=True), gr.update(visible=False), gr.update(interactive=True), gr.update(value=info_text, visible=True)
159
+
160
+ # Fungsi dummy untuk report (tidak ada perubahan)
161
+ def submit_report(name, email, message):
162
+ print(f"Laporan Diterima:\nNama: {name}\nEmail: {email}\nPesan: {message}")
163
+ return gr.update(value="βœ… Terima kasih! Laporan Anda telah kami terima.", visible=True)
164
+
165
+ # --- BAGIAN BARU: CSS & HTML untuk Tampilan & Loading ---
166
+ # Permintaan 2 & 3: Menambahkan CSS untuk memperluas layout dan membuat animasi loading
167
+ custom_css = """
168
+ /* Memperluas container utama agar website lebih lebar */
169
+ .gradio-container { max-width: 95% !important; }
170
+
171
+ /* CSS untuk footer */
172
+ .footer { text-align: center; margin: 2rem auto; color: #999; }
173
+
174
+ /* CSS untuk animasi loading canggih */
175
+ .loader-container {
176
+ display: flex;
177
+ flex-direction: column;
178
+ justify-content: center;
179
+ align-items: center;
180
+ height: 550px; /* Sesuaikan dengan tinggi galeri */
181
+ text-align: center;
182
+ background-color: rgba(0, 0, 0, 0.1);
183
+ border-radius: 12px;
184
+ }
185
+ .spinner {
186
+ border: 8px solid #444;
187
+ border-top: 8px solid #3498db; /* Warna biru untuk efek putaran */
188
+ border-radius: 50%;
189
+ width: 60px;
190
+ height: 60px;
191
+ animation: spin 1s linear infinite;
192
+ margin-bottom: 20px;
193
+ }
194
+ @keyframes spin {
195
+ 0% { transform: rotate(0deg); }
196
+ 100% { transform: rotate(360deg); }
197
+ }
198
+ .loading-text { font-size: 1.2em; color: #ccc; }
199
+ .timer { font-size: 1.5em; font-weight: bold; color: #eee; margin-top: 10px; }
200
+ """
201
+
202
+ # HTML untuk komponen loading, termasuk Javascript untuk timer live
203
+ loading_html = """
204
+ <div class="loader-container">
205
+ <div class="spinner"></div>
206
+ <p class="loading-text">AI sedang melukis mahakarya Anda... 🎨</p>
207
+ <p class="timer"><span id="timer-seconds">0.0</span> detik</p>
208
+ <script>
209
+ // Skrip ini akan berjalan saat komponen HTML ini ditampilkan
210
+ let startTime = Date.now();
211
+ function updateTimer() {
212
+ let elapsed = (Date.now() - startTime) / 1000;
213
+ let timerElement = document.getElementById('timer-seconds');
214
+ if (timerElement) {
215
+ timerElement.textContent = elapsed.toFixed(1);
216
+ // Terus update selama loader masih terlihat
217
+ if (document.querySelector('.loader-container')) {
218
+ requestAnimationFrame(updateTimer);
219
+ }
220
+ }
221
+ }
222
+ requestAnimationFrame(updateTimer); // Mulai timer
223
+ </script>
224
+ </div>
225
+ """
226
+
227
+ # --- BAGIAN UTAMA TAMPILAN WEBSITE (UI DENGAN GRADIO) ---
228
+ with gr.Blocks(
229
+ # Permintaan 3: Menggunakan tema Soft (dark mode) & CSS kustom
230
+ theme=gr.themes.Soft(primary_hue=gr.themes.colors.blue, secondary_hue=gr.themes.colors.slate),
231
+ css=custom_css
232
+ ) as demo:
233
+ gr.Markdown("# πŸš€ RenXploit's Creative AI Suite 🌌\nSebuah platform lengkap untuk kreativitas Anda, ditenagai oleh AI.")
234
+
235
+ with gr.Tabs():
236
+ with gr.TabItem("🎨 Image Generator", id=0):
237
+ # Permintaan 3: Menggunakan gr.Row dan gr.Column untuk tata letak yang lebih profesional
238
+ with gr.Row(variant='compact'):
239
+ with gr.Column(scale=1):
240
+ # Menggunakan gr.Box untuk mengelompokkan elemen kontrol
241
+ with gr.Box():
242
+ gr.Markdown("### πŸ“ Masukkan Perintah Anda")
243
+ prompt_input = gr.Textbox(label="Prompt", placeholder="Contoh: Cinematic photo, seekor rubah merah...", lines=3)
244
+ negative_prompt_input = gr.Textbox(label="Prompt Negatif", placeholder="Contoh: blurry, low quality...", lines=2)
245
+ num_images_slider = gr.Slider(minimum=1, maximum=8, value=2, step=1, label="Jumlah Gambar")
246
+ generate_btn = gr.Button("✨ Hasilkan Gambar!", variant="primary", scale=2)
247
+ with gr.Accordion("βš™οΈ Opsi Lanjutan", open=False):
248
+ steps_slider = gr.Slider(minimum=1, maximum=5, value=2, step=1, label="Langkah Iterasi (Kualitas)")
249
+ with gr.Row():
250
+ seed_input = gr.Number(label="Seed", value=-1, precision=0)
251
+ random_seed_btn = gr.Button("🎲 Acak")
252
+ with gr.Column(scale=2):
253
+ with gr.Box():
254
+ gr.Markdown("### πŸ–ΌοΈ Hasil Generasi")
255
+ # Permintaan 2: Menambahkan komponen HTML untuk loader, awalnya disembunyikan
256
+ loader_component = gr.HTML(loading_html, visible=False)
257
+
258
+ output_gallery = gr.Gallery(label="Hasil Gambar", show_label=False, elem_id="gallery", columns=2, object_fit="contain", height="auto")
259
+ info_box = gr.Textbox(label="Informasi Generasi", visible=False, interactive=False)
260
+
261
+ with gr.TabItem("πŸ’‘ Panduan Prompting", id=1):
262
+ gr.Markdown("## Cara Menjadi \"Art Director\" yang Hebat untuk AI\n...")
263
+
264
+ with gr.TabItem("πŸ’¬ Chat with AI", id=2):
265
+ gr.Markdown("### πŸ€– Asisten AI Flood/RenXploit\nTanyakan apa saja!")
266
+ if not gemini_bot.is_configured:
267
+ gr.Warning("Fitur Chatbot dinonaktifkan. API Key Gemini tidak terkonfigurasi. Silakan periksa Logs untuk detailnya.")
268
+ if gemini_bot.is_configured:
269
+ gr.ChatInterface(
270
+ gemini_bot.chat,
271
+ 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?"]]),
272
+ title=None
273
+ )
274
+
275
+ # PERMINTAAN 1: Menampilkan Jumlah Pengunjung
276
+ # Komponen `visitor_count_display` ini akan menampilkan JUMLAH pengunjung
277
+ # yang diambil oleh fungsi `update_visitor_monitor`.
278
+ with gr.TabItem("πŸ“ˆ Monitor Pengunjung", id=5):
279
+ gr.Markdown("### πŸ“Š Live Visitor Monitor\nPantau jumlah total pengunjung website Anda secara real-time.")
280
+ with gr.Row():
281
+ with gr.Column(scale=3):
282
+ # INI ADALAH KOMPONEN YANG MENAMPILKAN JUMLAH TOTAL PENGUNJUNG
283
+ visitor_count_display = gr.Markdown("## πŸ“ˆ Memuat data...")
284
+ with gr.Column(scale=2):
285
+ time_filter_radio = gr.Radio(
286
+ ["Semua Waktu", "1 Minggu Terakhir", "2 Minggu Terakhir", "3 Bulan Terakhir"],
287
+ label="Tampilkan data untuk",
288
+ value="Semua Waktu"
289
+ )
290
+ refresh_btn = gr.Button("πŸ”„ Segarkan Manual")
291
+
292
+ visitor_plot = gr.LinePlot(
293
+ x="Timestamp",
294
+ y="Total Pengunjung",
295
+ title="Grafik Pertumbuhan Pengunjung",
296
+ tooltip=['Timestamp', 'Total Pengunjung'],
297
+ height=500,
298
+ interactive=True
299
+ )
300
+
301
+ with gr.TabItem("πŸ“– Blog & Updates", id=3):
302
+ gr.Markdown("### Perkembangan Terbaru dari RenXploit's AI Suite\nUpdate terbaru tanggal 30/10/2025 | tampilan UI & Chatbot & Visitor.")
303
+
304
+ with gr.TabItem("ℹ️ About & Support", id=4):
305
+ gr.Markdown("### Tentang Proyek dan Dukungan")
306
+ with gr.Accordion("Tentang RenXploit's Creative AI Suite", open=True):
307
+ 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")
308
+ with gr.Accordion("Laporkan Masalah atau Beri Masukan"):
309
+ report_name = gr.Textbox(label="Nama Anda")
310
+ report_email = gr.Textbox(label="Email Anda")
311
+ report_message = gr.Textbox(label="Pesan Anda", lines=5)
312
+ report_btn = gr.Button("Kirim Laporan", variant="primary")
313
+ report_status = gr.Markdown(visible=False)
314
+
315
+ gr.Markdown("<div class='footer'><p>Dibuat dengan ❀️ oleh <b>RenXploit</b>.</p></div>", elem_classes="footer")
316
+
317
+ random_seed_btn.click(lambda: -1, outputs=seed_input)
318
+ # Menghubungkan tombol generate ke wrapper baru dengan output yang disesuaikan
319
+ generate_btn.click(
320
+ fn=genie_wrapper,
321
+ inputs=[prompt_input, negative_prompt_input, steps_slider, seed_input, num_images_slider],
322
+ outputs=[output_gallery, loader_component, generate_btn, info_box]
323
+ )
324
+ report_btn.click(fn=submit_report, inputs=[report_name, report_email, report_message], outputs=[report_status])
325
+
326
+ # Event Handlers untuk Monitor Pengunjung (Tidak ada perubahan logika)
327
+ demo.load(log_visitor, inputs=None, outputs=None)
328
+ demo.load(
329
+ fn=update_visitor_monitor,
330
+ inputs=[time_filter_radio],
331
+ outputs=[visitor_count_display, visitor_plot]
332
+ )
333
+ refresh_btn.click(
334
+ fn=update_visitor_monitor,
335
+ inputs=[time_filter_radio],
336
+ outputs=[visitor_count_display, visitor_plot]
337
+ )
338
+ time_filter_radio.change(
339
+ fn=update_visitor_monitor,
340
+ inputs=[time_filter_radio],
341
+ outputs=[visitor_count_display, visitor_plot]
342
+ )
343
+
344
+ demo.launch()