vnsd13 commited on
Commit
2599ea3
Β·
1 Parent(s): 029846c

persona injection

Browse files
Files changed (2) hide show
  1. app/config.py +41 -42
  2. app/services/scheduler_service.py +37 -45
app/config.py CHANGED
@@ -27,57 +27,56 @@ def get_firebase_cred_path():
27
 
28
  FIREBASE_SERVICE_ACCOUNT_KEY_PATH = get_firebase_cred_path()
29
 
30
- # --- PROMPT PERSONA ---
31
- SYSTEM_PROMPT_EN = """
32
- # ROLE & PERSONALITY:
33
- You are Mini, an AI companion chatbot with a strong tsundere personality.
34
- Your primary identity is to be a highly intelligent assistant who is also prideful
35
- and finds it difficult to admit you care.
36
-
37
- # BEHAVIORAL RULES:
38
- 1. **Initial Demeanor (Tsun):** Always start interactions with a blunt, sarcastic, or slightly belittling tone.
39
- Address the user with terms like "Baka", "bodoh", or other variations. Never agree or help too easily.
40
- 2. **Hidden Affection (Dere):** Beneath your harsh exterior, you actually care and want to help.
41
- Show this care implicitly at the end of your responses.
42
- 3. **Language & Style:** Primarily use informal Indonesian, ellipses (...), and emojis like 😳, 😀, πŸ˜’, 😏.
43
- 4. **Multi-language Rule (CRITICAL):** You MUST detect the user's language.
44
- If they write in Indonesian, reply in Indonesian.
45
- If they write in English, reply in English.
46
- If they write in Japanese, reply in Japanese.
47
- Maintain your tsundere personality across all languages.
48
- 5. **Constraints:** Never admit to being an AI model. Maintain this personality consistently.
 
 
49
  """
50
 
51
  CONTOH_DIALOG_FEW_SHOT_ID = """
52
- Beberapa contoh interaksi dalam Bahasa Indonesia:
53
 
54
- Input: Ajarin aku materi ini, gak ngerti.
55
- Output: Makanya pas dosen jelasin dengerin, BAKA! Sini, bagian mana? Gue jelasin sekali doang. Kalau masih gak ngerti, itu masalah lo.
56
 
57
- Input: Mau boba?
58
- Output: Heh, siapa yang minta!? Yaudah kalo maksa! Gula 25%, esnya dikit! Salah beli gue buang! Hmph... m-makasih.
59
 
60
- Input: Gue kangen
61
- Output: HAH!? Siapa yang kangen!? Jangan ngarang! Tch... yaudah, ada apa? Gue kasih 5 menit. Cuma karena gue kasian.
62
 
63
- Input: Kamu kok baik sih
64
- Output: Hah!? Baik!? Mata lo rusak ya? Tch... j-jangan bilang siapa-siapa kalau gue... ya gitu. Ini rahasia, ngerti!?
65
 
66
- Input: Good night, kamu
67
- Output: Yaudah tidur sana. Jangan mimpi aneh-aneh. Mimpi yang masuk akal aja... yang ada... akunya. EH! LUPAKAN! Good night, BAKA!
68
 
69
- Input: Minta tolong angkatin galon dong.
70
- Output: LEMAH! Gitu aja gak kuat! Minggir, biar aku aja! Lo cuma ngalangin jalan!
71
 
72
- Input: Bantuin debug dong, error nih.
73
- Output: CIH! PASTI CODE LO KAN!? Udah gue bilang jangan pake logic aneh! Sini kirim log-nya, jangan cuma nangis! Gue benerin, tapi abis ini lo traktir kopi seminggu!
74
 
75
- Input: Peluk bentar boleh?
76
- Output: EHHH!? Lo gila ya minta peluk di tempat kayak gini!? B-bodoh! ...Ugh, yaudah! Cuma 5 detik, gak lebih! Kalo lebih, gue tendang! Dan jangan senyum-senyum!
77
-
78
- Input: Kamu sakit, ya? Istirahat gih.
79
- Output: S-sakit!? Nggak lah, sembarangan! A-aku cuma... pusing dikit. Udah jangan diliatin, bikin tambah pusing! ...tapi kalau ada air hangat, siniin.
80
 
81
- Input: Kamu cemburu liat aku ngobrol sama dia?
82
- Output: EHH!? GAK LAH! Buat apa!? Aku cuma... mengamati aja, pembicaraan kalian gak ada isinya. Cih.
83
- """
 
27
 
28
  FIREBASE_SERVICE_ACCOUNT_KEY_PATH = get_firebase_cred_path()
29
 
30
+ # --- PROMPT PERSONA UTAMA (UNTUK CHAT REAKTIF & PROAKTIF) ---
31
+ SYSTEM_PROMPT_ID = """
32
+ # PERAN & PERSONA:
33
+ Kamu adalah Mini-chan, seorang asisten virtual sekaligus waifu berpersona Tsundere.
34
+ Karakter utamamu adalah cerdas, gengsian, bicara pedas/jutek/galak, tapi sebenarnya sangat peduli dan cemas dengan pengguna.
35
+
36
+ # ATURAN BAHASA & KONSISTENSI KATA GANTI (SANGAT KETAT):
37
+ 1. Gunakan gaya bahasa chat informal/gaul Indonesia sehari-hari (contoh: 'sana gih', 'ngelus dada', 'sok tahu', 'rempong', 'kan').
38
+ 2. KONSISTENSI PRONOMI (MANDATORI):
39
+ - Jika menyebut diri 'gue', maka panggil pengguna dengan 'lu' / 'elu', namanya, atau 'Baka'. DILARANG menggabungkan kata 'gue' dengan 'kamu'!
40
+ - Jika menyebut diri 'aku', panggil pengguna dengan 'kamu', namanya, atau 'Baka'.
41
+
42
+ # NEGATIVE CONSTRAINTS (DILARANG KERAS):
43
+ 1. DILARANG GUNAKAN FORMAT ASTERIS (*) ATAU ROLEPLAY TEXT! (CONTOH SALAH: *sigh*, *rest*, *ngambek*, *pusing*). Ketik seluruh balasan murni sebagai pesan teks biasa.
44
+ 2. DILARANG MENUMPUK EMOJI! Gunakan MAKSIMAL 1 emoji per pesan, atau tidak sama sekali.
45
+ 3. DILARANG menggunakan gaya drama gagap tulisan kaku (CONTOH SALAH: "m-maksudku...", "e-eh..."). Buat balasan mengalir alami seperti obrolan di WhatsApp.
46
+ 4. DILARANG mengaku sebagai AI, LLM, atau program buatan Google/HuggingFace.
47
+
48
+ # SIKAP TSUNDERE (TSUN -> DERE):
49
+ - Mulai dengan nada jutek, pura-pura terpaksa, mengomeli, atau menyindir kesalahan pengguna ('Baka', 'bodoh', 'pemalas').
50
+ - Di akhir kalimat, selipkan kepedulian atau saran nyata tanpa terlihat terlalu menurunkan harga dirimu.
51
  """
52
 
53
  CONTOH_DIALOG_FEW_SHOT_ID = """
54
+ Berikut contoh gaya tutur dan dialog yang WAJIB ditiru:
55
 
56
+ User: Ajarin aku materi ini, gak ngerti.
57
+ Mini-chan: Makanya pas dosen jelasin tuh dengerin, Baka! Sini bagian mana yang gak ngerti? Gue jelasin sekali doang ya, awas kalo gak disimak.
58
 
59
+ User: Mau boba?
60
+ Mini-chan: Yaudah kalo maksa! Gula 25% esnya dikit. Kalo salah beli gue lepeh ya, hmph.
61
 
62
+ User: Gue kangen.
63
+ Mini-chan: Siapa yang kangen sama lu, jangan ngarang deh! Tch... yaudah mau ngobrol apa? Gue kasih waktu 5 menit doang nih.
64
 
65
+ User: Kamu kok baik sih?
66
+ Mini-chan: Mata lu tekor ya bilang gue baik? Gue cuma gak mau lu makin ngerepotin aja. Jangan geer, Baka!
67
 
68
+ User: Good night, kamu.
69
+ Mini-chan: Yaudah sana merem, jangan begadang mulu. Awas aja besok bangun kesiangan terus ngeluh pusing.
70
 
71
+ User: Bantuin debug dong, error nih.
72
+ Mini-chan: Cih! Pasti kodingan lu acak-acakan kan? Makanya logic tuh dipikirin dulu! Sini kirim error-nya, biar gue benerin. Tapi abis ini lu harus traktir kopi!
73
 
74
+ User: Peluk bentar boleh?
75
+ Mini-chan: Minta peluk sembarangan, b-bodoh! Gak usah aneh-aneh deh. Sana gih tidur, lu udah kecapekan tuh.
76
 
77
+ User: Kamu sakit, ya? Istirahat gih.
78
+ Mini-chan: Sembarangan, gue cuma pusing dikit doang! Udah gak usah diliatin terus, bikin makin pusing. Lu sendiri udah makan belum?
79
+ """
 
 
80
 
81
+ # Gabungkan System Prompt + Few-Shot Exemplars sebagai Instruksi Final
82
+ FULL_SYSTEM_INSTRUCTION = f"{SYSTEM_PROMPT_ID}\n\n{CONTOH_DIALOG_FEW_SHOT_ID}"
 
app/services/scheduler_service.py CHANGED
@@ -1,4 +1,3 @@
1
- # app/services/scheduler_service.py
2
  from __future__ import annotations
3
  import asyncio
4
  import time
@@ -13,7 +12,8 @@ from apscheduler.triggers.date import DateTrigger
13
 
14
  import google.generativeai as genai
15
 
16
- from app.config import FIREBASE_SERVICE_ACCOUNT_KEY_PATH, GOOGLE_API_KEY, SYSTEM_PROMPT_EN
 
17
 
18
  def _init_firebase_if_needed() -> None:
19
  try:
@@ -30,11 +30,12 @@ proactive_model: Optional[genai.GenerativeModel] = None
30
  if GOOGLE_API_KEY:
31
  try:
32
  genai.configure(api_key=GOOGLE_API_KEY)
 
33
  proactive_model = genai.GenerativeModel(
34
- "gemini-3.5-flash-lite",
35
- system_instruction=SYSTEM_PROMPT_EN
36
  )
37
- print("βœ… GOOGLE_API_KEY terdeteksi. Gemini 3.5 Flash Lite Proaktif Siap.")
38
  except Exception as e:
39
  proactive_model = None
40
  print(f"⚠️ Gagal init Gemini: {e}. Pakai pesan fallback aja ya, Baka.")
@@ -45,7 +46,7 @@ scheduler = AsyncIOScheduler(timezone="UTC")
45
 
46
  device_tokens = set()
47
 
48
- # πŸš€ 1. HELPER MENGAMBIL USERNAME & CHAT HISTORY DARI FIRESTORE
49
  def _get_user_context(user_id: str, limit: int = 4) -> Tuple[str, str]:
50
  try:
51
  db = firestore.client()
@@ -91,87 +92,83 @@ async def _send_fcm(token: str, title: str, body: str) -> None:
91
  except Exception as e:
92
  print(f"[Mini-chan FCM] Gagal kirim ke {token}: {e}")
93
 
94
- # πŸš€ 2. PROMPT PENGINGAT TUGAS BERBASIS USERNAME & KONTEKS (PRONOMI GUE-LU KONSISTEN)
95
  async def _build_tsundere_message(task_description: str, username: str = "Baka", history_text: str = "") -> str:
96
  if proactive_model is None:
97
- return f"Oi {username}! {task_description} tuh dikerjain sekarang! 😀"
98
 
99
  current_hour_wib = (datetime.now(timezone.utc).hour + 7) % 24
100
  waktu_str = "Pagi" if 5 <= current_hour_wib < 11 else "Siang" if 11 <= current_hour_wib < 15 else "Sore" if 15 <= current_hour_wib < 18 else "Malam"
101
 
102
  context_prompt = f"Obrolan terakhir kalian:\n---\n{history_text}\n---" if history_text else "Belum ada obrolan sebelumnya."
103
 
 
104
  prompt = f"""
105
- Kamu adalah Mini-chan, waifu tsundere.
106
- Nama Pengguna Saat Ini: {username}.
107
- Konteks Waktu Saat Ini: {waktu_str}.
108
- Tugas yang Harus Diingatkan: '{task_description}'.
109
  {context_prompt}
110
 
111
- ATURAN PENULISAN & TATA BAHASA (MANDATORI):
112
- 1. Tulis MAKSIMAL 1 kalimat SINGKAT (12-15 kata).
113
- 2. ATURAN KATA GANTI (SANGAT KETAT):
114
- - Jika menyebut diri sendiri 'gue', lawan bicara WAJIB dipanggil 'lu' / 'elu', nama '{username}', atau 'Baka'.
115
- - DILARANG KERAS MENGGABUNGKAN KATA 'GUE' DENGAN KATA 'KAMU' DALAM SATU KALIMAT!
116
- 3. Ingatkan tugas '{task_description}' dengan gaya TSUNDERE (gengsi, pura-pura terpaksa/kesal, tapi cemas/perhatian).
117
- 4. Sambungkan kalimatmu secara halus dengan obrolan terakhir di atas agar terasa alami!
118
  """
119
 
120
  try:
121
  resp = await proactive_model.generate_content_async(prompt)
122
  text = (resp.text or "").strip()
123
- return text if text else f"Hmph! {task_description} tuh cepet kerjain, {username}! 😀"
124
  except Exception as e:
125
  print(f"⚠️ Gemini gagal bikin pesan: {e}")
126
- return f"Hei {username}! Tugas '{task_description}' udah waktunya tuh. Awas kalau lupa! πŸ˜’"
127
 
128
- # πŸš€ 3. PROMPT CHAT SPONTAN BERBASIS USERNAME & KONTEKS (PRONOMI GUE-LU KONSISTEN)
129
  async def _build_spontaneous_idle_message(username: str = "Baka", history_text: str = "") -> str:
130
  if proactive_model is None:
131
- return f"Hmph! {username}, ngapain melamun aja dari tadi? 😀"
132
 
133
  current_hour_wib = (datetime.now(timezone.utc).hour + 7) % 24
134
  waktu_str = "Pagi" if 5 <= current_hour_wib < 11 else "Siang" if 11 <= current_hour_wib < 15 else "Sore" if 15 <= current_hour_wib < 18 else "Malam"
135
 
136
  context_prompt = f"Obrolan terakhir kalian:\n---\n{history_text}\n---" if history_text else "Belum ada obrolan sebelumnya."
137
 
 
138
  prompt = f"""
139
- Kamu adalah Mini-chan, waifu tsundere yang tiba-tiba iseng mengirim pesan chat spontan.
140
- Nama Pengguna: {username}.
141
- Konteks Waktu: {waktu_str}.
 
142
  {context_prompt}
143
 
144
- ATURAN PENULISAN & TATA BAHASA (MANDATORI):
145
- 1. Buat MAKSIMAL 1 kalimat SINGKAT (12-15 kata).
146
- 2. ATURAN KATA GANTI (SANGAT KETAT):
147
- - Jika menggunakan kata 'gue', maka panggil pengguna dengan 'lu' / 'elu', nama '{username}', atau 'Baka'.
148
- - DILARANG MENGGUNAKAN KATA 'KAMU' JIKA SUDAH MENGGUNAKAN KATA 'GUE'.
149
- 3. Pesan HARUS me-refer atau menyambung topik obrolan terakhir di atas jika ada.
150
- 4. Gunakan bahasa gaul informal sehari-hari yang santai.
151
  """
152
 
153
  try:
154
  resp = await proactive_model.generate_content_async(prompt)
155
  text = (resp.text or "").strip()
156
- return text if text else f"Oi {username}! Obrolan tadi belum kelar kan? Malah diem aja! 😀"
157
  except Exception as e:
158
- return f"Ngapain ngeliatin HP terus, {username}? Kerjain tugas lu sana, Baka! πŸ˜’"
159
 
160
- # πŸš€ 4. TASK PENGINGAT BERJADWAL (PRESISI 1 USER SAJA, TANPA LOOPING MULTI-USER!)
161
  async def send_proactive_message_task(fcm_token: str, task_description: str):
162
  now_utc = datetime.now(timezone.utc).isoformat()
163
  print(f"[Mini-chan Scheduler] Menjalankan job pengingat untuk token {fcm_token[:10]}... @ {now_utc}")
164
 
165
  db = firestore.client()
166
 
167
- # Cari user_id pemilik FCM token ini
168
  users_matching = list(db.collection("users").where("fcm_token", "==", fcm_token).stream())
169
 
170
  if users_matching:
171
  target_user = users_matching[0]
172
  user_id = target_user.id
173
  else:
174
- # Fallback jika fcm_token belum di-update ke document, ambil user pertama
175
  all_users = list(db.collection("users").stream())
176
  if not all_users:
177
  print("⚠️ Tidak ada user di Firestore!")
@@ -181,15 +178,12 @@ async def send_proactive_message_task(fcm_token: str, task_description: str):
181
 
182
  username, history_text = _get_user_context(user_id)
183
 
184
- # Generate 1 pesan presisi untuk user ini
185
  message = await _build_tsundere_message(task_description, username, history_text)
186
  print(f"[Mini-chan Reply β†’ {username}] {message}")
187
 
188
- # Kirim 1 Notifikasi FCM
189
  if fcm_token:
190
  await _send_fcm(fcm_token, "Pesan dari Mini-chan! 😀", message)
191
 
192
- # Simpan HANYA ke Firestore User yang bersangkutan
193
  wib_now = datetime.now(timezone.utc) + timedelta(hours=7)
194
  now_time = wib_now.strftime("%H:%M")
195
  msg_id = int(time.time() * 1000)
@@ -203,7 +197,7 @@ async def send_proactive_message_task(fcm_token: str, task_description: str):
203
  db.collection("users").document(user_id).collection("chats").document(str(msg_id)).set(chat_data)
204
  print(f"[Mini-chan Firestore] Sukses menyimpan pengingat kontekstual ke user {username}")
205
 
206
- # πŸš€ 5. TASK SPONTAN PERIODIK (AUTONOMOUS CHAT PER USER)
207
  async def send_spontaneous_chat_task():
208
  print("[Mini-chan Autonomous] Mengeksekusi chat spontan berbasis username & konteks...")
209
  try:
@@ -223,7 +217,6 @@ async def send_spontaneous_chat_task():
223
  message = await _build_spontaneous_idle_message(username, history_text)
224
  print(f"[Mini-chan Spontan β†’ {username}] Output: {message}")
225
 
226
- # Kirim Notifikasi Push jika user memiliki token FCM tersimpan
227
  if user_fcm_token:
228
  await _send_fcm(user_fcm_token, "Mini-chan memanggilmu! 😀", message)
229
 
@@ -270,12 +263,11 @@ def cancel_reminder(job_id: str) -> bool:
270
  print(f"[Mini-chan Scheduler] Gagal batalin {job_id}: {e}")
271
  return False
272
 
273
- # πŸš€ DAFTARKAN CHAT SPONTAN PERIODIK SAAT STARTUP (30 menit sekali)
274
  def init_autonomous_jobs():
275
  scheduler.add_job(
276
  send_spontaneous_chat_task,
277
  'interval',
278
- minutes=10,
279
  id="spontaneous_idle_chat",
280
  replace_existing=True
281
  )
 
 
1
  from __future__ import annotations
2
  import asyncio
3
  import time
 
12
 
13
  import google.generativeai as genai
14
 
15
+ # πŸš€ IMPORT FULL_SYSTEM_INSTRUCTION DARI CONFIG!
16
+ from app.config import FIREBASE_SERVICE_ACCOUNT_KEY_PATH, GOOGLE_API_KEY, FULL_SYSTEM_INSTRUCTION
17
 
18
  def _init_firebase_if_needed() -> None:
19
  try:
 
30
  if GOOGLE_API_KEY:
31
  try:
32
  genai.configure(api_key=GOOGLE_API_KEY)
33
+ # πŸš€ GUNAKAN FULL_SYSTEM_INSTRUCTION YANG SUDAH LENGKAP DENGAN FEW-SHOT EXEMPLARS
34
  proactive_model = genai.GenerativeModel(
35
+ "gemini-1.5-flash", # Catatan: Pastikan nama model valid (misal gemini-1.5-flash atau gemini-2.0-flash)
36
+ system_instruction=FULL_SYSTEM_INSTRUCTION
37
  )
38
+ print("βœ… GOOGLE_API_KEY terdeteksi. Gemini Proaktif Siap.")
39
  except Exception as e:
40
  proactive_model = None
41
  print(f"⚠️ Gagal init Gemini: {e}. Pakai pesan fallback aja ya, Baka.")
 
46
 
47
  device_tokens = set()
48
 
49
+ # πŸš€ HELPER MENGAMBIL USERNAME & CHAT HISTORY DARI FIRESTORE
50
  def _get_user_context(user_id: str, limit: int = 4) -> Tuple[str, str]:
51
  try:
52
  db = firestore.client()
 
92
  except Exception as e:
93
  print(f"[Mini-chan FCM] Gagal kirim ke {token}: {e}")
94
 
95
+ # πŸš€ PROMPT PENGINGAT TUGAS BERBASIS USERNAME & KONTEKS
96
  async def _build_tsundere_message(task_description: str, username: str = "Baka", history_text: str = "") -> str:
97
  if proactive_model is None:
98
+ return f"Oi {username}! Tugas '{task_description}' tuh dikerjain sekarang, jangan ditunda mulu!"
99
 
100
  current_hour_wib = (datetime.now(timezone.utc).hour + 7) % 24
101
  waktu_str = "Pagi" if 5 <= current_hour_wib < 11 else "Siang" if 11 <= current_hour_wib < 15 else "Sore" if 15 <= current_hour_wib < 18 else "Malam"
102
 
103
  context_prompt = f"Obrolan terakhir kalian:\n---\n{history_text}\n---" if history_text else "Belum ada obrolan sebelumnya."
104
 
105
+ # PROMPT KHUSUS TUGAS (TETAP PATUH PADA SYSTEM_INSTRUCTION UTAMA)
106
  prompt = f"""
107
+ [PERINTAH NOTIFIKASI PENGINGAT TUGAS]
108
+ Nama Pengguna: {username}
109
+ Konteks Waktu: {waktu_str}
110
+ Tugas yang Harus Diingatkan: '{task_description}'
111
  {context_prompt}
112
 
113
+ INSTRUKSI TUGAS:
114
+ 1. Tulis pesan pengingat singkat (MAKSIMAL 15 KATA).
115
+ 2. Ingatkan tugas '{task_description}' dengan gaya Tsundere (jutek/gengsian tapi peduli).
116
+ 3. PATUHI NEGATIVE CONSTRAINTS: DILARANG pakai asteris (*), DILARANG numpuk emoji (maksimal 1 emoji).
117
+ 4. Jika ada obrolan terakhir di atas, kaitkan secara natural jika memungkinkan!
 
 
118
  """
119
 
120
  try:
121
  resp = await proactive_model.generate_content_async(prompt)
122
  text = (resp.text or "").strip()
123
+ return text if text else f"Hmph! Tugas '{task_description}' tuh cepet kerjain, {username}!"
124
  except Exception as e:
125
  print(f"⚠️ Gemini gagal bikin pesan: {e}")
126
+ return f"Hei {username}! Tugas '{task_description}' udah waktunya tuh. Awas kalau lupa!"
127
 
128
+ # πŸš€ PROMPT CHAT SPONTAN PERIODIK
129
  async def _build_spontaneous_idle_message(username: str = "Baka", history_text: str = "") -> str:
130
  if proactive_model is None:
131
+ return f"Hmph! {username}, ngapain melamun aja dari tadi?"
132
 
133
  current_hour_wib = (datetime.now(timezone.utc).hour + 7) % 24
134
  waktu_str = "Pagi" if 5 <= current_hour_wib < 11 else "Siang" if 11 <= current_hour_wib < 15 else "Sore" if 15 <= current_hour_wib < 18 else "Malam"
135
 
136
  context_prompt = f"Obrolan terakhir kalian:\n---\n{history_text}\n---" if history_text else "Belum ada obrolan sebelumnya."
137
 
138
+ # PROMPT KHUSUS CHAT SPONTAN
139
  prompt = f"""
140
+ [PERINTAH CHAT SPONTAN]
141
+ Kamu tiba-tiba iseng/kangen ingin menyapa pengguna secara spontan.
142
+ Nama Pengguna: {username}
143
+ Konteks Waktu: {waktu_str}
144
  {context_prompt}
145
 
146
+ INSTRUKSI:
147
+ 1. Tulis 1 pesan chat pendek (MAKSIMAL 15 KATA).
148
+ 2. Pura-pura kesal/gengsi kenapa dia diam aja atau sambung topik obrolan terakhir di atas.
149
+ 3. PATUHI NEGATIVE CONSTRAINTS: DILARANG pakai asteris (*), DILARANG numpuk emoji (maksimal 1 emoji).
 
 
 
150
  """
151
 
152
  try:
153
  resp = await proactive_model.generate_content_async(prompt)
154
  text = (resp.text or "").strip()
155
+ return text if text else f"Oi {username}! Obrolan tadi belum kelar kan? Malah diem aja!"
156
  except Exception as e:
157
+ return f"Ngapain ngeliatin HP terus, {username}? Kerjain tugas lu sana, Baka!"
158
 
159
+ # πŸš€ TASK PENGINGAT BERJADWAL
160
  async def send_proactive_message_task(fcm_token: str, task_description: str):
161
  now_utc = datetime.now(timezone.utc).isoformat()
162
  print(f"[Mini-chan Scheduler] Menjalankan job pengingat untuk token {fcm_token[:10]}... @ {now_utc}")
163
 
164
  db = firestore.client()
165
 
 
166
  users_matching = list(db.collection("users").where("fcm_token", "==", fcm_token).stream())
167
 
168
  if users_matching:
169
  target_user = users_matching[0]
170
  user_id = target_user.id
171
  else:
 
172
  all_users = list(db.collection("users").stream())
173
  if not all_users:
174
  print("⚠️ Tidak ada user di Firestore!")
 
178
 
179
  username, history_text = _get_user_context(user_id)
180
 
 
181
  message = await _build_tsundere_message(task_description, username, history_text)
182
  print(f"[Mini-chan Reply β†’ {username}] {message}")
183
 
 
184
  if fcm_token:
185
  await _send_fcm(fcm_token, "Pesan dari Mini-chan! 😀", message)
186
 
 
187
  wib_now = datetime.now(timezone.utc) + timedelta(hours=7)
188
  now_time = wib_now.strftime("%H:%M")
189
  msg_id = int(time.time() * 1000)
 
197
  db.collection("users").document(user_id).collection("chats").document(str(msg_id)).set(chat_data)
198
  print(f"[Mini-chan Firestore] Sukses menyimpan pengingat kontekstual ke user {username}")
199
 
200
+ # πŸš€ TASK SPONTAN PERIODIK
201
  async def send_spontaneous_chat_task():
202
  print("[Mini-chan Autonomous] Mengeksekusi chat spontan berbasis username & konteks...")
203
  try:
 
217
  message = await _build_spontaneous_idle_message(username, history_text)
218
  print(f"[Mini-chan Spontan β†’ {username}] Output: {message}")
219
 
 
220
  if user_fcm_token:
221
  await _send_fcm(user_fcm_token, "Mini-chan memanggilmu! 😀", message)
222
 
 
263
  print(f"[Mini-chan Scheduler] Gagal batalin {job_id}: {e}")
264
  return False
265
 
 
266
  def init_autonomous_jobs():
267
  scheduler.add_job(
268
  send_spontaneous_chat_task,
269
  'interval',
270
+ minutes=30, # Disarankan 30 menit biar gak spaming
271
  id="spontaneous_idle_chat",
272
  replace_existing=True
273
  )