import os, datetime, threading, json, urllib.parse, re, webbrowser
import customtkinter as ctk
from tkinter import messagebox
import requests
try:
from bs4 import BeautifulSoup
from duckduckgo_search import DDGS
HAS_WEB_TOOLS = True
except ImportError:
HAS_WEB_TOOLS = False
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
# --- KONFIGURASI URL SERVER WINAI ---
CALENDAR_DATA_URL = "https://hisabmu.com/aifikih/tahun4.php"
GOOGLE_SESSION_COOKIES = {}
class Menu28App(ctk.CTk):
def __init__(self):
super().__init__()
self.title("WinAI Assistant (Modul 28)")
self.geometry("1100x700")
ctk.set_appearance_mode("Dark")
self.winai_api_keys = [
'AIzaSyAVIBjSEWKdyeAYnhYue6Zw6tSJKzQpfEw',
'AIzaSyB8jOg5MPMrbpIWTmRT51rydJYLMFhRaXs',
'AIzaSyD2UOrD7VaYU21FI6z8Ps1N4oEh86IrvJ4'
] # (Gunakan API Key utuh dari master code Anda)
self.winai_primary_model = 'gemini-2.5-flash'
self.winai_tracker_file = os.path.join(BASE_DIR, 'last_successful_index_winai.txt')
self.winai_db_items = []
self.winai_db_loaded = False
self.winai_memori = ""
self.winai_instruksi = "PERAN: Asisten Fikih Muhammadiyah. KHGT Berlaku 1 Muharam 1447 H."
self.winai_data_kal = ""
self.setup_ui()
threading.Thread(target=self.load_winai_databases, daemon=True).start()
def create_ymd_row(self, parent, dy, dm, dd):
grid = ctk.CTkFrame(parent, fg_color="transparent")
grid.pack(fill="x", padx=10, pady=(0, 10))
ctk.CTkLabel(grid, text="dd", font=("Segoe UI", 10), text_color="#9E9E9E").grid(row=0, column=0)
ctk.CTkLabel(grid, text="mm", font=("Segoe UI", 10), text_color="#9E9E9E").grid(row=0, column=1)
ctk.CTkLabel(grid, text="yyyy", font=("Segoe UI", 10), text_color="#9E9E9E").grid(row=0, column=2)
d = ctk.CTkEntry(grid, width=45, justify="center"); d.insert(0, dd); d.grid(row=1, column=0, padx=2)
m = ctk.CTkEntry(grid, width=45, justify="center"); m.insert(0, dm); m.grid(row=1, column=1, padx=2)
y = ctk.CTkEntry(grid, width=70, justify="center"); y.insert(0, dy); y.grid(row=1, column=2, padx=2)
return y, m, d
def setup_ui(self):
self.sidebar = ctk.CTkFrame(self, width=280)
self.sidebar.pack(side="left", fill="y", padx=10, pady=10)
ctk.CTkLabel(self.sidebar, text="WINAI ASSISTANT", font=("Segoe UI", 16, "bold"), text_color="#00E5FF").pack(pady=20)
now = datetime.datetime.now()
self.winai_entry_y, self.winai_entry_m, self.winai_entry_d = self.create_ymd_row(self.sidebar, str(now.year), f"{now.month:02d}", f"{now.day:02d}")
self.entry_city = ctk.CTkEntry(self.sidebar, placeholder_text="Kota (Misal: Semarang)"); self.entry_city.insert(0, "Semarang"); self.entry_city.pack(pady=5, fill="x", padx=10)
self.winai_input_entry = ctk.CTkEntry(self.sidebar, placeholder_text="Ketik pertanyaan...", font=("Segoe UI", 13), height=40)
self.winai_input_entry.pack(fill="x", padx=10, pady=20)
self.winai_input_entry.bind("<Return>", lambda e: self.proses_pertanyaan_winai())
self.btn_winai_kirim = ctk.CTkButton(self.sidebar, text="Kirim Pertanyaan", font=("Segoe UI", 12, "bold"), fg_color="#1565C0", command=self.proses_pertanyaan_winai)
self.btn_winai_kirim.pack(fill="x", padx=10, pady=5)
self.btn_winai_reset = ctk.CTkButton(self.sidebar, text="🔄 Reset Memori", fg_color="#dc3545", command=self.reset_memori_winai)
self.btn_winai_reset.pack(fill="x", padx=10, pady=10)
# MAIN OUTPUT
self.main_frame = ctk.CTkFrame(self, fg_color="#050510", corner_radius=10)
self.main_frame.pack(side="right", fill="both", expand=True, padx=10, pady=10)
header_winai = ctk.CTkFrame(self.main_frame, fg_color="#181818", corner_radius=8)
header_winai.pack(fill="x", padx=15, pady=10)
ctk.CTkLabel(header_winai, text="🤖 Ruang Interaksi AI", font=("Segoe UI", 16, "bold"), text_color="#00E5FF").pack(side="left", padx=15, pady=10)
ctk.CTkButton(header_winai, text="📋 Salin", fg_color="#28a745", width=80, command=self.salin_teks_winai).pack(side="right", padx=15, pady=10)
self.winai_output_box = ctk.CTkTextbox(self.main_frame, wrap="word", fg_color="#FFFFFF", text_color="#000000", font=("Segoe UI", 15))
self.winai_output_box.pack(fill="both", expand=True, padx=15, pady=(0, 15))
tb = self.winai_output_box._textbox
tb.tag_configure("info", foreground="#856404", background="#FFF3CD", font=("Segoe UI", 13, "italic"), justify="center")
tb.tag_configure("user_header", foreground="#0056b3", font=("Segoe UI", 15, "bold"), spacing1=15)
tb.tag_configure("user_teks", background="#E3F2FD", foreground="#000000", lmargin1=10, lmargin2=10)
tb.tag_configure("ai_header", foreground="#28A745", font=("Segoe UI", 16, "bold"), spacing1=15)
tb.tag_configure("normal", foreground="#000000")
tb.tag_configure("bold", font=("Segoe UI", 15, "bold"))
tb.tag_configure("kode", font=("Consolas", 13), background="#E8F5E9", lmargin1=15, lmargin2=15)
self.winai_output_box.insert("0.0", "Selamat datang di WinAI V7.2\nSistem AI siap digunakan.\n\n", "bold")
self.winai_output_box.configure(state="disabled")
def load_winai_databases(self):
try:
res_txt = requests.get("https://hisabmu.com/aifikih/tanyajawabagama.txt", timeout=10)
if res_txt.status_code == 200: self.winai_db_items.extend(res_txt.json())
self.winai_db_loaded = True
except: pass
def reset_memori_winai(self):
self.winai_memori = ""
self.winai_output_box.configure(state="normal")
self.winai_output_box.delete("1.0", "end")
self.winai_output_box.insert("0.0", "Memori direset.\n\n", "bold")
self.winai_output_box.configure(state="disabled")
def salin_teks_winai(self):
self.clipboard_clear()
self.clipboard_append(self.winai_output_box.get("1.0", "end-1c").strip())
messagebox.showinfo("Sukses", "Teks disalin ke Clipboard!")
def sisipkan_teks_winai(self, teks, pengirim="AI"):
self.winai_output_box.configure(state="normal")
if pengirim == "Anda":
self.winai_output_box.insert("end", "🗣️ Anda:\n", "user_header")
self.winai_output_box.insert("end", f" {teks} \n\n", "user_teks")
elif pengirim == "Sistem":
self.winai_output_box.insert("end", "⏳ " + teks + "\n\n", "info")
else:
self.winai_output_box.insert("end", "🤖 WinAI:\n", "ai_header")
lines = teks.split('\n')
in_code = False
for line in lines:
if line.startswith("```"):
in_code = not in_code; continue
if in_code: self.winai_output_box.insert("end", line + "\n", "kode")
elif line.startswith("**") and line.endswith("**"): self.winai_output_box.insert("end", line.replace('*','') + "\n", "bold")
elif line.startswith("#"): self.winai_output_box.insert("end", line.replace('#','').strip() + "\n", "bold")
else: self.winai_output_box.insert("end", line.replace('*','') + "\n", "normal")
self.winai_output_box.insert("end", "\n" + "="*60 + "\n\n", "normal")
self.winai_output_box.see("end")
self.winai_output_box.configure(state="disabled")
def proses_pertanyaan_winai(self):
pertanyaan = self.winai_input_entry.get().strip()
if not pertanyaan: return
self.winai_input_entry.delete(0, "end")
self.sisipkan_teks_winai(pertanyaan, pengirim="Anda")
self.btn_winai_kirim.configure(state="disabled")
threading.Thread(target=self.tanya_ai_thread, args=(pertanyaan,), daemon=True).start()
def tanya_ai_thread(self, teks_pertanyaan):
self.after(0, lambda: self.sisipkan_teks_winai("Mengakses Database & Internet...", pengirim="Sistem"))
gabungan_internet = ""
if HAS_WEB_TOOLS:
try:
with DDGS() as ddgs:
# Menggunakan generator ddgs.text secara benar
results = list(ddgs.text(teks_pertanyaan, max_results=2))
if results:
gabungan_internet = "[Data DDG]:\n" + "\n".join(f"- {r['body']}" for r in results) + "\n"
except Exception as e:
print(f"Web Search Error: {e}")
loc_city = self.entry_city.get()
prompt_pintar = f"Lokasi User: {loc_city}.\nTanggal: {self.winai_entry_d.get()}/{self.winai_entry_m.get()}/{self.winai_entry_y.get()}.\n\n"
if gabungan_internet: prompt_pintar += f"=== REFERENSI INTERNET ===\n{gabungan_internet}\n\n"
if self.winai_memori: prompt_pintar += f"=== RIWAYAT OBROLAN LALU ===\n{self.winai_memori}\n\n"
prompt_pintar += f"=== PERTANYAAN ===\n{teks_pertanyaan}\n"
payload = {
"contents": [{"role": "user", "parts": [{"text": prompt_pintar}]}],
"systemInstruction": {"parts": [{"text": self.winai_instruksi}]},
"generationConfig": {"temperature": 0.2}
}
# PERBAIKAN: URL harus string bersih tanpa format markdown []()
# PERBAIKAN: Menggunakan model gemini-1.5-flash (yang tersedia saat ini)
api_key = self.winai_api_keys[0]
model_name = "gemini-1.5-flash"
api_url = f"https://generativelanguage.googleapis.com/v1beta/models/{model_name}:generateContent?key={api_key}"
try:
res = requests.post(api_url, headers={'Content-Type': 'application/json'}, json=payload, timeout=20)
res_json = res.json()
if res.status_code == 200:
# Pastikan struktur JSON respons sesuai
if 'candidates' in res_json and res_json['candidates']:
jawaban = res_json['candidates'][0]['content']['parts'][0]['text']
self.winai_memori += f"User: {teks_pertanyaan}\nAI: {jawaban[:500]}\n\n"
else:
jawaban = "AI tidak memberikan respon (Empty Response)."
else:
jawaban = f"API Error {res.status_code}: {res_json.get('error', {}).get('message', 'Unknown Error')}"
except Exception as e:
jawaban = f"Koneksi Gagal: {str(e)}"
self.after(0, lambda: self.sisipkan_teks_winai(jawaban, pengirim="AI"))
self.after(0, lambda: self.btn_winai_kirim.configure(state="normal"))
if __name__ == "__main__":
app = Menu28App()
app.mainloop()