feat: add natural language reminder with LLM parsing and confirmation flow
This commit is contained in:
+99
-3
@@ -1,14 +1,20 @@
|
||||
import html
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from telegram import Update
|
||||
from telegram.ext import ContextTypes
|
||||
from openai import AsyncOpenAI
|
||||
from config import DEEPSEEK_API_KEY, DEEPSEEK_BASE_URL, DEEPSEEK_MODEL, ADMIN_TELEGRAM_ID
|
||||
from config import DEEPSEEK_API_KEY, DEEPSEEK_BASE_URL, DEEPSEEK_MODEL, ADMIN_TELEGRAM_ID, BOT_TIMEZONE
|
||||
from zoneinfo import ZoneInfo
|
||||
import database as db
|
||||
import rag
|
||||
import reminder as reminder_module
|
||||
import document as doc_processor
|
||||
from guard import is_academic_topic, REJECT_MESSAGE
|
||||
|
||||
_pending_confirmations: dict = {}
|
||||
REMINDER_KEYWORDS = ["ingatkan", "pengingat", "remind", "jadwalkan", "tolong ingat"]
|
||||
|
||||
_client = AsyncOpenAI(api_key=DEEPSEEK_API_KEY, base_url=DEEPSEEK_BASE_URL)
|
||||
|
||||
ACADEMIC_SYSTEM_PROMPT = (
|
||||
@@ -219,16 +225,106 @@ async def upload_doc(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
)
|
||||
|
||||
|
||||
async def list_reminders(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
user_data = db.get_user(update.effective_user.id)
|
||||
if not user_data:
|
||||
await update.message.reply_text("⛔ Belum terdaftar.")
|
||||
return
|
||||
|
||||
reminders = db.get_user_reminders(user_data["id"])
|
||||
if not reminders:
|
||||
await update.message.reply_text("Tidak ada pengingat aktif.")
|
||||
return
|
||||
|
||||
tz = ZoneInfo(BOT_TIMEZONE)
|
||||
lines = ["⏰ <b>Pengingat Aktif:</b>\n"]
|
||||
for r in reminders:
|
||||
if r["is_recurring"]:
|
||||
schedule_str = f"Setiap hari jam {r['remind_at'][:5]} WIB"
|
||||
else:
|
||||
try:
|
||||
dt = datetime.fromisoformat(r["remind_at"]).replace(tzinfo=tz)
|
||||
schedule_str = dt.strftime("%d %b %Y %H:%M WIB")
|
||||
except Exception:
|
||||
schedule_str = r["remind_at"]
|
||||
lines.append(f"#{r['id']} — <i>{html.escape(r['message'])}</i>\n 📅 {schedule_str}")
|
||||
lines.append("\nGunakan /cancel_remind ID untuk membatalkan.")
|
||||
await update.message.reply_text("\n".join(lines), parse_mode="HTML")
|
||||
|
||||
|
||||
async def cancel_remind(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
user_data = db.get_user(update.effective_user.id)
|
||||
if not user_data:
|
||||
await update.message.reply_text("⛔ Belum terdaftar.")
|
||||
return
|
||||
if not context.args:
|
||||
await update.message.reply_text("Gunakan: /cancel_remind ID")
|
||||
return
|
||||
try:
|
||||
reminder_id = int(context.args[0])
|
||||
except ValueError:
|
||||
await update.message.reply_text("ID harus berupa angka.")
|
||||
return
|
||||
|
||||
success = db.cancel_reminder(reminder_id, user_data["id"])
|
||||
if not success:
|
||||
await update.message.reply_text("❌ Pengingat tidak ditemukan.")
|
||||
return
|
||||
|
||||
for job in context.application.job_queue.get_jobs_by_name(f"reminder_{reminder_id}"):
|
||||
job.schedule_removal()
|
||||
await update.message.reply_text("✅ Pengingat berhasil dibatalkan.")
|
||||
|
||||
|
||||
async def handle_message(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
user_data = db.get_user(update.effective_user.id)
|
||||
if not user_data:
|
||||
await update.message.reply_text("⛔ Belum terdaftar. Gunakan /start TOKEN untuk registrasi.")
|
||||
return
|
||||
|
||||
question = update.message.text.strip()
|
||||
if not question:
|
||||
text = update.message.text.strip()
|
||||
if not text:
|
||||
return
|
||||
|
||||
user_id = update.effective_user.id
|
||||
|
||||
# Cek pending konfirmasi reminder
|
||||
if user_id in _pending_confirmations:
|
||||
answer = text.lower()
|
||||
if answer in ("ya", "iya", "yes", "y"):
|
||||
pending = _pending_confirmations.pop(user_id)
|
||||
parsed = pending["parsed"]
|
||||
reminder_id = db.create_reminder(
|
||||
telegram_id=user_id,
|
||||
user_db_id=user_data["id"],
|
||||
message=parsed["message"],
|
||||
remind_at=parsed["remind_at"],
|
||||
is_recurring=parsed.get("is_recurring", False),
|
||||
recur_type=parsed.get("recur_type"),
|
||||
)
|
||||
reminder_module.schedule_new_reminder(
|
||||
context.application.job_queue, reminder_id, user_id, parsed["message"], parsed
|
||||
)
|
||||
await update.message.reply_text("✅ Pengingat berhasil disimpan! Gunakan /reminders untuk melihat daftar.")
|
||||
return
|
||||
elif answer in ("tidak", "batal", "cancel", "no", "n"):
|
||||
_pending_confirmations.pop(user_id)
|
||||
await update.message.reply_text("❌ Pengingat dibatalkan.")
|
||||
return
|
||||
else:
|
||||
_pending_confirmations.pop(user_id)
|
||||
|
||||
# Deteksi intent reminder dari plain text
|
||||
text_lower = text.lower()
|
||||
if any(kw in text_lower for kw in REMINDER_KEYWORDS):
|
||||
await update.message.chat.send_action("typing")
|
||||
parsed = await reminder_module.parse_reminder_intent(text, _client, DEEPSEEK_MODEL)
|
||||
if parsed:
|
||||
_pending_confirmations[user_id] = {"parsed": parsed}
|
||||
await update.message.reply_text(reminder_module.format_confirmation(parsed), parse_mode="HTML")
|
||||
return
|
||||
|
||||
question = text
|
||||
await update.message.chat.send_action("typing")
|
||||
|
||||
if not await is_academic_topic(question, _client, DEEPSEEK_MODEL):
|
||||
|
||||
Reference in New Issue
Block a user