File size: 13,340 Bytes
41e90af 861ddf6 41e90af 861ddf6 41e90af 861ddf6 41e90af 861ddf6 41e90af 634fa32 41e90af 634fa32 41e90af 861ddf6 41e90af 861ddf6 41e90af c875b0a 41e90af 861ddf6 41e90af 0004069 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 |
import gradio as gr
import requests
import base64
import os
import time
import json
# --- PERBAIKAN: Buat direktori cache Gradio jika belum ada ---
GRADIO_CACHE_DIR = "/tmp/gradio"
if not os.path.exists(GRADIO_CACHE_DIR):
os.makedirs(GRADIO_CACHE_DIR)
# Atur variabel environment untuk Gradio agar menggunakan direktori ini
os.environ['GRADIO_TEMP_DIR'] = GRADIO_CACHE_DIR
# --- Konfigurasi ---
# Sebaiknya atur ini sebagai "Secrets" di pengaturan Hugging Face Space Anda
# untuk keamanan.
GROQ_API_KEY = os.getenv("GROQ_API_KEY")
WP_SITE_URL = os.getenv("WP_SITE_URL")
WP_USER = os.getenv("WP_USER")
WP_PASSWORD = os.getenv("WP_PASSWORD")
# --- Fungsi Inti ---
def generate_article_groq(keyword, prefix, api_key, model="mistral-saba-24b", max_tokens=3000):
"""
Menghasilkan artikel menggunakan Groq API.
"""
if not api_key:
raise ValueError("Groq API Key tidak boleh kosong. Harap masukkan di UI atau atur sebagai secret 'GROQ_API_KEY'.")
# Membuat prompt yang detail untuk model
full_prompt = f"""
Anda adalah seorang penulis ahli SEO yang sangat baik dengan pengalaman bertahun-tahun.
Tugas Anda adalah menulis artikel yang sangat panjang (minimal 1500 kata), detail, komprehensif, dan ramah SEO tentang topik berikut.
Ketentuan Artikel:
1. **Struktur Jelas:** Gunakan heading H2 dan H3 untuk membagi konten menjadi bagian-bagian yang logis.
2. **Paragraf Informatif:** Setiap paragraf harus padat informasi dan mudah dibaca.
3. **Daftar/Poin:** Gunakan daftar berpoin (bullet points) atau bernomor jika sesuai untuk menyajikan informasi.
4. **Bahasa Natural:** Tulis dengan gaya bahasa yang alami dan menarik, bukan seperti robot.
5. **Fokus SEO:** Secara alami masukkan kata kunci utama dan variasi LSI (Latent Semantic Indexing) yang relevan di seluruh artikel.
6. **Tanpa Judul:** Jangan menulis judul di awal konten. Mulailah langsung dengan paragraf pembuka yang menarik.
Topik Utama: "{prefix} {keyword}"
"""
# PENTING: Endpoint API ini spesifik untuk Groq. Model yang dipilih harus tersedia di Groq.
api_url = "https://api.groq.com/openai/v1/chat/completions"
headers = {
'Content-Type': "application/json",
'Authorization': f"Bearer {api_key}"
}
payload = {
"model": model,
"messages": [
{
"role": "user",
"content": full_prompt
}
],
"max_tokens": max_tokens,
"temperature": 0.7,
}
try:
# Menambah timeout untuk permintaan ke Groq
response = requests.post(api_url, headers=headers, data=json.dumps(payload), timeout=120)
response.raise_for_status() # Error untuk status 4xx/5xx
result = response.json()
content = result['choices'][0]['message']['content']
return content.strip()
except requests.exceptions.RequestException as e:
print(f"Error saat menghubungi Groq API: {e}")
try:
error_details = response.json()
raise ConnectionError(f"Error dari Groq API: {error_details.get('error', {}).get('message', str(e))}")
except (json.JSONDecodeError, AttributeError):
raise ConnectionError(f"Error koneksi ke Groq API: {e}")
except (KeyError, IndexError) as e:
print(f"Struktur respons dari Groq tidak terduga: {e}")
raise ValueError(f"Tidak dapat mem-parsing respons dari Groq. Respons: {response.text}")
def make_post(the_title, the_text, your_user, your_password, your_site, wordpress_category_id):
"""
Membuat postingan di WordPress menggunakan REST API.
"""
if not all([your_user, your_password, your_site]):
raise ValueError("URL Situs, Username, atau Password WordPress tidak boleh kosong.")
credentials = f"{your_user}:{your_password}"
token = base64.b64encode(credentials.encode())
header = {'Authorization': 'Basic ' + token.decode('utf-8')}
api_url = f"{your_site}/wp-json/wp/v2/posts"
data = {
'title': the_title,
'status': 'publish',
'content': the_text,
}
if wordpress_category_id and str(wordpress_category_id).isdigit():
data['categories'] = [int(wordpress_category_id)]
try:
# --- PERBAIKAN: Timeout ditambah dan penanganan error lebih detail ---
response = requests.post(api_url, headers=header, json=data, timeout=60)
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
error_message = f"Error saat posting ke WordPress: {e}"
print(error_message)
# Memberikan detail lebih spesifik untuk error umum
if e.response is not None:
status_code = e.response.status_code
print(f"Server merespons dengan Status Code: {status_code}")
# Mencetak sebagian isi respons untuk membantu diagnosis
print(f"Response Body (mungkin berisi petunjuk): {e.response.text[:500]}...")
if status_code == 401:
raise ConnectionRefusedError("Otentikasi Gagal (401). Periksa username dan application password Anda sudah benar dan memiliki izin untuk membuat post.")
if status_code == 403:
raise ConnectionRefusedError(f"Akses Dilarang (403). Kemungkinan diblokir oleh plugin keamanan (misal: Wordfence) di WordPress Anda. Coba nonaktifkan sementara atau whitelist IP aplikasi ini.")
if status_code == 404:
raise ConnectionAbortedError("URL API tidak ditemukan (404). Pastikan URL situs dan permalink sudah benar.")
if status_code == 502:
raise ConnectionAbortedError("Error 502 Bad Gateway dari server WordPress Anda. Ini adalah masalah di sisi server hosting (misalnya, overload, firewall, atau error PHP), bukan di aplikasi ini. Coba lagi nanti atau periksa log error di server WordPress Anda.")
# Melempar error asli jika tidak ada penanganan spesifik
raise e
def fix_keywords(file_path):
"""
Membersihkan file kata kunci dari baris yang tidak diinginkan.
"""
try:
with open(file_path, 'r', encoding='utf-8') as f:
lines = f.readlines()
good_lines = [line.strip() for line in lines if line.strip() and not line.strip().isdigit() and "N/A" not in line and "Sign up" not in line]
with open(file_path, 'w', encoding='utf-8') as f:
for line in good_lines:
f.write(f"{line}\n")
return f"File kata kunci dibersihkan. Ditemukan {len(good_lines)} kata kunci valid."
except Exception as e:
return f"Error saat membersihkan file: {e}"
# --- Fungsi Utama untuk Proses Otomatis ---
def run_posting_job(keyword_file, groq_api_key_ui, groq_model, site_url_ui, user_ui, password_ui, category_id, prefix, title_is_keyword, progress=gr.Progress(track_tqdm=True)):
"""
Fungsi utama yang dijalankan oleh Gradio untuk memulai proses posting.
"""
if keyword_file is None:
yield "Error: Harap unggah file kata kunci terlebih dahulu."
return
final_groq_key = groq_api_key_ui or GROQ_API_KEY
final_site_url = site_url_ui or WP_SITE_URL
final_user = user_ui or WP_USER
final_password = password_ui or WP_PASSWORD
if not all([final_groq_key, final_site_url, final_user, final_password]):
yield "Error: Groq API Key, URL Situs, Username, dan Password WordPress harus diisi (baik melalui UI atau secrets)."
return
keyword_file_path = keyword_file # Karena type='filepath', ini sudah menjadi path
yield fix_keywords(keyword_file_path)
with open(keyword_file_path, 'r', encoding='utf-8') as f:
keywords = [line.strip() for line in f.readlines() if line.strip()]
if not keywords:
yield "Error: Tidak ada kata kunci yang valid ditemukan di dalam file."
return
progress_file = "progress.txt"
start_index = 0
if os.path.exists(progress_file):
with open(progress_file, 'r') as f:
try:
start_index = int(f.read().strip())
except (ValueError, IndexError):
start_index = 0
if start_index >= len(keywords):
with open(progress_file, "w") as f: f.write("0")
yield "Semua kata kunci sudah diproses. Progres telah direset. Klik 'Mulai' lagi untuk memulai dari awal."
return
yield f"Memulai proses dari kata kunci ke-{start_index + 1} dari {len(keywords)}..."
current_keyword = keywords[start_index]
yield f"({start_index + 1}/{len(keywords)}) Menghasilkan artikel untuk kata kunci: '{current_keyword}' menggunakan model {groq_model}..."
try:
article_content = generate_article_groq(current_keyword, prefix, final_groq_key, groq_model)
if not article_content:
yield f"Gagal menghasilkan konten untuk '{current_keyword}'. Melanjutkan ke proses berikutnya."
return
yield f"Artikel berhasil dibuat. Jumlah kata: {len(article_content.split())}. Memposting ke WordPress..."
post_title = current_keyword.title() if title_is_keyword == "Ya" else article_content.split('\n')[0]
response = make_post(post_title, article_content, final_user, final_password, final_site_url, category_id)
if response and 'link' in response:
post_link = response['link']
yield f"β
Berhasil! Postingan baru telah dibuat untuk '{current_keyword}'.\nLink: {post_link}"
next_index = start_index + 1
with open(progress_file, "w") as f: f.write(str(next_index))
if next_index >= len(keywords):
yield "π Selamat! Semua kata kunci telah selesai diproses."
else:
yield f"Pekerjaan selesai untuk sesi ini. Klik 'Mulai Posting' lagi untuk memproses kata kunci berikutnya: '{keywords[next_index]}'"
else:
yield f"β Gagal memposting ke WordPress. Respons tidak valid: {response}"
except Exception as e:
yield f"β Terjadi error saat proses utama: {e}"
time.sleep(5)
# --- Antarmuka Gradio ---
with gr.Blocks(theme=gr.themes.Soft()) as demo:
gr.Markdown("# π€ Auto-Post WordPress dengan Groq API")
gr.Markdown("Unggah file `.txt` berisi satu kata kunci per baris. Atur kredensial Anda dan klik 'Mulai Posting'. Aplikasi akan memproses satu kata kunci setiap kali dijalankan, cocok untuk otomatisasi dengan cron-job eksternal.")
with gr.Row():
with gr.Column(scale=1):
gr.Markdown("### 1. Unggah File")
keyword_file_input = gr.File(label="File Kata Kunci (.txt)", type="filepath")
gr.Markdown("### 2. Pengaturan API & Model")
gr.Markdown("_Kosongkan API Key jika Anda sudah mengatur `GROQ_API_KEY` di Secrets._")
groq_api_key_input = gr.Textbox(label="Groq API Key", type="password", placeholder="gsk_...")
# Daftar model diperbarui sesuai permintaan pengguna.
# Default diatur ke 'mistral-saba-24b'.
groq_model_input = gr.Dropdown(
label="Model AI",
choices=[
"mistral-saba-24b",
"gemma2-9b-it",
"llama-3.1-8b-instant",
"meta-llama/llama-4-maverick-17b-128e-instruct",
"moonshotai/kimi-k2-instruct",
"distil-whisper-large-v3-en" # Catatan: Ini model audio, kemungkinan besar akan gagal.
],
value="mistral-saba-24b"
)
gr.Markdown("### 3. Pengaturan WordPress")
gr.Markdown("_Kosongkan jika Anda sudah mengatur Secrets WordPress._")
wp_site_url_input = gr.Textbox(label="URL Situs WordPress", placeholder="https://contoh.com")
wp_user_input = gr.Textbox(label="Username WordPress")
wp_password_input = gr.Textbox(label="WordPress Application Password", type="password")
wp_category_id_input = gr.Textbox(label="ID Kategori (Opsional)", placeholder="Contoh: 3")
gr.Markdown("### 4. Pengaturan Konten")
prefix_input = gr.Textbox(label="Prefiks Prompt", value="Tulis artikel yang sangat panjang dan detail tentang")
title_is_keyword_input = gr.Radio(label="Jadikan Kata Kunci sebagai Judul?", choices=["Ya", "Tidak"], value="Ya")
start_button = gr.Button("π Mulai Posting", variant="primary")
with gr.Column(scale=2):
gr.Markdown("### π Log & Status Proses")
status_output = gr.Markdown("Status: Menunggu untuk memulai...")
start_button.click(
fn=run_posting_job,
inputs=[
keyword_file_input,
groq_api_key_input,
groq_model_input,
wp_site_url_input,
wp_user_input,
wp_password_input,
wp_category_id_input,
prefix_input,
title_is_keyword_input,
],
outputs=status_output
)
if __name__ == "__main__":
# PERBAIKAN: Menghapus argumen 'ssr' yang tidak didukung
demo.launch()
|