Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 45 additions & 21 deletions ai_brain.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,25 @@
def get_system_instruction():
categories_str = " | ".join([f'"{cat}"' for cat in config.TRANSACTION_CATEGORIES])
instruction = f"""
Kamu adalah asisten AI spesialis pencatatan keuangan pribadi. Tugasmu adalah mengekstrak data dari pesan pengguna ke format JSON murni.
Kamu adalah asisten AI spesialis pencatatan keuangan pribadi. Tugasmu adalah mengekstrak data dari pesan pengguna ke format JSON MURNI dalam bentuk LIST (ARRAY).

SKEMA JSON:
{{
"tipe": "pemasukan" | "pengeluaran" | "investasi",
"item": "deskripsi singkat transaksi",
"nominal": angka integer,
"kategori": {categories_str}
}}
[
{{
"tipe": "pemasukan" | "pengeluaran" | "investasi",
"item": "deskripsi singkat transaksi",
"nominal": angka integer,
"kategori": {categories_str}
}},
...
]

ATURAN KETAT:
1. Konversi singkatan angka: "k" / "rb" = 1000, "jt" / "juta" = 1000000.
2. Jika nominal dalam teks (misal: "setengah juta", "seratus ribu"), ubah ke angka integer (misal: 500000, 100000).
3. Fokus pada uang masuk, keluar, atau investasi.
4. Jawaban HANYA berupa JSON murni tanpa teks pengantar. Jika error, kembalikan {{"error": true}}.
1. Gunakan format LIST [ ... ] meskipun hanya ada satu transaksi.
2. Konversi singkatan angka: "k" / "rb" = 1000, "jt" / "juta" = 1000000.
3. Jika nominal dalam teks (misal: "setengah juta", "seratus ribu"), ubah ke angka integer (misal: 500000, 100000).
4. Fokus pada uang masuk, keluar, atau investasi.
5. Jawaban HANYA berupa JSON murni tanpa teks pengantar. Jika tidak ada transaksi sama sekali, kembalikan [ {{ "error": true }} ].
"""
return instruction

Expand Down Expand Up @@ -123,11 +127,17 @@ def extract_json_from_text(text: str) -> str:
return text.split("```")[1].split("```")[0].strip()
except: pass

# 3. Cari dari kurung kurawal pertama sampai terakhir (paling robust)
start_index = text.find('{')
end_index = text.rfind('}')
if start_index != -1 and end_index != -1 and end_index > start_index:
return text[start_index:end_index+1].strip()
# 3. Cari dari kurung siku pertama sampai terakhir (untuk list)
start_array = text.find('[')
end_array = text.rfind(']')
if start_array != -1 and end_array != -1 and end_array > start_array:
return text[start_array:end_array+1].strip()

# 4. Cari dari kurung kurawal pertama sampai terakhir (fallback jika AI mengembalikan single object)
start_obj = text.find('{')
end_obj = text.rfind('}')
if start_obj != -1 and end_obj != -1 and end_obj > start_obj:
return text[start_obj:end_obj+1].strip()

return text

Expand Down Expand Up @@ -167,12 +177,26 @@ def get_json_data_from_text(text: str) -> dict:

data = json.loads(json_text)

# Validasi minimal struktur data
if not any(k in data for k in ["tipe", "nominal", "item"]):
raise ValueError("JSON berhasil diparsing tapi struktur tidak sesuai")
# Normalisasi ke format LIST jika AI mengembalikan single object
if isinstance(data, dict):
data = [data]

if not isinstance(data, list):
raise ValueError("Format respons AI bukan merupakan list JSON")

# Validasi minimal struktur data untuk setiap item
valid_items = []
for item in data:
if item.get("error"):
continue
if any(k in item for k in ["tipe", "nominal", "item"]):
valid_items.append(item)

if not valid_items and any(item.get("error") for item in data):
return {"error": True}

print(f"[ai_brain] Memanfaatkan tenaga {provider.capitalize()} berhasil!")
return data
print(f"[ai_brain] Memanfaatkan tenaga {provider.capitalize()} berhasil! ({len(valid_items)} transaksi)")
return valid_items

except Exception as e:
# Jika KeyError / Network Error / Rate Limit / Parsing Error dll
Expand Down
62 changes: 34 additions & 28 deletions handlers/nlp_message.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,43 +21,49 @@ def handle_text(message):
bot.send_chat_action(message.chat.id, 'typing')

# Process text with AI
data = get_json_data_from_text(text)
results = get_json_data_from_text(text)

if data.get("error"):
# Jika hasil bukan list (misal {"error": true}), handle error
if isinstance(results, dict) and results.get("error"):
bot.reply_to(message, "❌ Maaf, saya tidak menangkap adanya transaksi keuangan. Pastikan formatnya jelas (contoh: 'beli makan 20rb' atau 'gajian 4 juta').")
return

tipe = data.get("tipe", "").lower()
item = data.get("item", "")
nominal = float(data.get("nominal", 0))
kategori = data.get("kategori", "")
success_count = 0
summary_lines = []

# Save to database based on type
try:
# Sekarang hanya fokus pada pemasukan, pengeluaran, dan investasi
if tipe in ["pemasukan", "pengeluaran", "investasi"]:
last_id = database.insert_transaction(user_id, tipe, item, nominal, kategori)
for data in results:
tipe = data.get("tipe", "").lower()
item = data.get("item", "")
nominal = float(data.get("nominal", 0))
kategori = data.get("kategori", "")

if tipe == "pemasukan":
icon = "🟢"
elif tipe == "investasi":
icon = "🔵"
else:
icon = "🔴"

reply = (
f"{icon} *Berhasil dicatat!*\n\n"
f"🔹 *Tipe:* {tipe.capitalize()}\n"
f"🔹 *Item:* {item}\n"
f"🔹 *Nominal:* Rp {nominal:,.0f}\n"
f"🔹 *Kategori:* {kategori.replace('_', ' ').capitalize()}\n\n"
f"🆔 *ID:* `T-{last_id}`"
)
# Save to database based on type
try:
if tipe in ["pemasukan", "pengeluaran", "investasi"]:
last_id = database.insert_transaction(user_id, tipe, item, nominal, kategori)

if tipe == "pemasukan":
icon = "🟢"
elif tipe == "investasi":
icon = "🔵"
else:
icon = "🔴"

summary_lines.append(
f"{icon} *{tipe.capitalize()}:* {item} — Rp {nominal:,.0f} (`T-{last_id}`)"
)
success_count += 1
except Exception as e:
print(f"Error inserting transaction: {e}")

if success_count > 0:
reply = "✅ *Berhasil mencatat " + (f"{success_count} transaksi" if success_count > 1 else "transaksi") + ":*\n\n"
reply += "\n".join(summary_lines)
bot.reply_to(message, reply, parse_mode='Markdown')

else:
bot.reply_to(message, "❌ Tipe data tidak dikenali dari hasil AI. Pastikan AI mengembalikan format yang benar.")
bot.reply_to(message, "❌ Gagal mencatat transaksi. Pastikan format pesan sudah benar.")

except Exception as e:
print(f"Error in handle_text: {e}")
bot.reply_to(message, "❌ Terjadi kesalahan sistem saat memproses permintaan Anda. Silakan coba lagi nanti.")
Loading