menu28.py

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