VietCat commited on
Commit
5f02751
·
1 Parent(s): 968d612

update message flow

Browse files
Files changed (5) hide show
  1. app/constants.py +1 -1
  2. app/llm.py +11 -9
  3. app/main.py +0 -2
  4. app/message_processor.py +82 -66
  5. app/sheets.py +15 -8
app/constants.py CHANGED
@@ -191,4 +191,4 @@ FOUND_REGULATIONS_MESSAGES = [
191
  "Thông tin đang được mình kiểm tra lại lần cuối, mình sẽ cập nhật sớm nhất nhé."
192
  ]
193
 
194
- SHEET_RANGE = 'chat!A2:L'
 
191
  "Thông tin đang được mình kiểm tra lại lần cuối, mình sẽ cập nhật sớm nhất nhé."
192
  ]
193
 
194
+ SHEET_RANGE = 'chat!A2:M'
app/llm.py CHANGED
@@ -408,17 +408,19 @@ class LLMClient:
408
  """
409
 
410
  prompt = f"""
411
- Bạn một AI chuyên phân tích ngữ nghĩa câu hỏi về luật giao thông. Với mỗi câu đầu vào, hãy trích xuất các thông tin sau dưới dạng JSON:
412
-
413
  {{
414
- "muc_dich": "mục đích của câu hỏi, chỉ chọn một trong các giá trị: "hỏi về mức phạt", "hỏi về quy tắc giao thông", "hỏi về báo hiệu đường bộ", "hỏi về quy trình xử lý vi phạm giao thông", "thông tin cá nhân của AI", 'khác'",
415
- "phuong_tien": "loại phương tiện giao thông (xe máy, ô tô, xe tải, người đi bộ...)",
416
- "hanh_vi_vi_pham": "hành vi vi phạm pháp luật giao thông"
 
417
  }}
418
-
419
- Nếu một trường không trong câu hỏi, hãy để giá trị chuỗi rỗng "". Ví dụ: nếu câu không đề cập phương tiện, thì "phuong_tien": "".
420
-
421
- Lưu ý: Trả về đúng format JSON, và giá trị của "muc_dich" chỉ được chọn một trong các giá trị sau: "hỏi về mức phạt", "hỏi về quy tắc giao thông", "hỏi về báo hiệu đường bộ", "hỏi về quy trình xử lý vi phạm giao thông", "thông tin cá nhân của AI", "khác".
 
422
 
423
  Câu hỏi:
424
  \"{text}\"
 
408
  """
409
 
410
  prompt = f"""
411
+ Nhiệm vụ: Với mỗi câu hỏi, trích xuất thông tin ra JSON theo định dạng hướng dẫn sau. Chỉ trả về đối tượng JSON.
412
+ Định dạng:
413
  {{
414
+ "muc_dich": "...",
415
+ "phuong_tien": "...",
416
+ "hanh_vi": "...",
417
+ "cau_hoi": "..."
418
  }}
419
+ Hướng dẫn:
420
+ - "cau_hoi": Diễn đạt lại nội dung câu hỏi bằng thuật ngữ pháp chuẩn.
421
+ - "hanh_vi": Tóm tắt hành vi được mô tả trong câu hỏi
422
+ - "phuong_tien": Để chuỗi rỗng "" nếu không được đề cập.
423
+ - "muc_dich": Phải là một trong các giá trị sau: "hỏi về mức phạt", "hỏi về quy tắc giao thông", "hỏi về báo hiệu đường bộ", "hỏi về quy trình xử lý vi phạm giao thông", "thông tin cá nhân của AI", "khác".
424
 
425
  Câu hỏi:
426
  \"{text}\"
app/main.py CHANGED
@@ -59,8 +59,6 @@ supabase_client = SupabaseClient(settings.supabase_url, settings.supabase_key)
59
  logger.info("[STARTUP] Khởi tạo EmbeddingClient...")
60
  embedding_client = EmbeddingClient()
61
 
62
- # Keywords to look for in messages
63
- VEHICLE_KEYWORDS = ["xe máy", "ô tô", "xe đạp", "xe hơi"]
64
 
65
  # Khởi tạo LLM client (ví dụ dùng HFS, bạn có thể đổi provider tuỳ ý)
66
  # llm_client = create_llm_client(
 
59
  logger.info("[STARTUP] Khởi tạo EmbeddingClient...")
60
  embedding_client = EmbeddingClient()
61
 
 
 
62
 
63
  # Khởi tạo LLM client (ví dụ dùng HFS, bạn có thể đổi provider tuỳ ý)
64
  # llm_client = create_llm_client(
app/message_processor.py CHANGED
@@ -1,4 +1,4 @@
1
- from typing import Dict, Any, List
2
  import asyncio
3
  import traceback
4
  from loguru import logger
@@ -7,6 +7,7 @@ from .utils import get_random_message
7
  from .facebook import FacebookClient
8
  from app.config import get_settings
9
  import re
 
10
 
11
  class MessageProcessor:
12
  def __init__(self, channel, sender_id):
@@ -63,6 +64,7 @@ class MessageProcessor:
63
  'originalvehicle': '',
64
  'originalaction': '',
65
  'originalpurpose': '',
 
66
  'timestamp': [timestamp],
67
  'isdone': False
68
  }
@@ -94,6 +96,7 @@ class MessageProcessor:
94
  'originalvehicle': row.get('originalvehicle'),
95
  'originalaction': row.get('originalaction'),
96
  'originalpurpose': row.get('originalpurpose'),
 
97
  'timestamp': row_timestamps,
98
  'isdone': row.get('isdone')
99
  }
@@ -146,22 +149,25 @@ class MessageProcessor:
146
  llm_analysis = await self.channel.llm.analyze(message_text)
147
  logger.info(f"[LLM][RAW] Kết quả trả về từ analyze: {llm_analysis}")
148
  muc_dich = None
149
- hanh_vi_vi_pham = None
 
150
  if isinstance(llm_analysis, dict):
151
  keywords = [self.normalize_vehicle_keyword(llm_analysis.get('phuong_tien', ''))]
152
  muc_dich = llm_analysis.get('muc_dich')
153
- hanh_vi_vi_pham = llm_analysis.get('hanh_vi_vi_pham')
 
154
  elif isinstance(llm_analysis, list) and len(llm_analysis) > 0:
155
  keywords = [self.normalize_vehicle_keyword(llm_analysis[0].get('phuong_tien', ''))]
156
  muc_dich = llm_analysis[0].get('muc_dich')
157
- hanh_vi_vi_pham = llm_analysis[0].get('hanh_vi_vi_pham')
 
158
  else:
159
  keywords = extract_keywords(message_text, VEHICLE_KEYWORDS)
160
- hanh_vi_vi_pham = message_text
161
  for kw in keywords:
162
- hanh_vi_vi_pham = hanh_vi_vi_pham.replace(kw, "")
163
- hanh_vi_vi_pham = hanh_vi_vi_pham.strip()
164
- logger.info(f"[DEBUG] Phương tiện: {keywords} - Hành vi: {hanh_vi_vi_pham} - Mục đích: {muc_dich}")
165
  # await self.channel.facebook.send_message(message=f"... đang tìm kiếm quy định liên quan đến {hanh_vi_vi_pham} .....")
166
  # 4. Update lại conversation với thông tin đầy đủ
167
  update_kwargs = {
@@ -173,8 +179,9 @@ class MessageProcessor:
173
  'originalcontent': remaining_text,
174
  'originalattachments': attachments,
175
  'originalvehicle': ','.join(keywords),
176
- 'originalaction': hanh_vi_vi_pham,
177
  'originalpurpose': muc_dich,
 
178
  'timestamp': self.flatten_timestamp(conv['timestamp']),
179
  'isdone': False
180
  }
@@ -323,18 +330,20 @@ class MessageProcessor:
323
  if not top or (match.get('similarity', 0) > top.get('similarity', 0)):
324
  top = match
325
  full_result_text += f"\n{(match.get('structure') or '').strip()}:\n"
326
- tieude = (match.get('tieude') or '').strip()
327
- noidung = (match.get('noidung') or '').strip()
328
- hanhvi = (tieude + "\n" + noidung).strip().replace('\n', ' ')
329
- full_result_text += f"Thực hiện hành vi:\n{hanhvi}"
330
- canhantu = arr_to_str(match.get('canhantu'))
331
- canhanden = arr_to_str(match.get('canhanden'))
332
- if canhantu or canhanden:
333
- full_result_text += f"\nCá nhân sẽ bị phạt tiền từ {canhantu} VNĐ đến {canhanden} VNĐ"
334
- tochuctu = arr_to_str(match.get('tochuctu'))
335
- tochucden = arr_to_str(match.get('tochucden'))
336
- if tochuctu or tochucden:
337
- full_result_text += f"\nTổ chức sẽ bị phạt tiền từ {tochuctu} VNĐ đến {tochucden} VNĐ"
 
 
338
  hpbsnoidung = arr_to_str(match.get('hpbsnoidung'), sep="; ")
339
  if hpbsnoidung:
340
  full_result_text += f"\nNgoài việc bị phạt tiền, người vi phạm còn bị: {hpbsnoidung}"
@@ -345,18 +354,21 @@ class MessageProcessor:
345
  if impounding:
346
  full_result_text += f"\nTạm giữ phương tiên: 07 ngày"
347
  if top and (top.get('tieude') or top.get('noidung')):
348
- tieude = (top.get('tieude') or '').strip()
349
- noidung = (top.get('noidung') or '').strip()
350
- hanhvi = (tieude + "\n" + noidung).strip().replace('\n', ' ')
351
- top_result_text += f"Thực hiện hành vi:\n{hanhvi}"
352
- canhantu = arr_to_str(top.get('canhantu'))
353
- canhanden = arr_to_str(top.get('canhanden'))
354
- if canhantu or canhanden:
355
- top_result_text += f"\nCá nhân sẽ bị phạt tiền từ {canhantu} VNĐ đến {canhanden} VNĐ"
356
- tochuctu = arr_to_str(top.get('tochuctu'))
357
- tochucden = arr_to_str(top.get('tochucden'))
358
- if tochuctu or tochucden:
359
- top_result_text += f"\nTổ chức sẽ bị phạt tiền từ {tochuctu} VNĐ đến {tochucden} VNĐ"
 
 
 
360
  hpbsnoidung = arr_to_str(top.get('hpbsnoidung'), sep="; ")
361
  if hpbsnoidung:
362
  top_result_text += f"\nNgoài việc bị phạt tiền, người vi phạm còn bị: {hpbsnoidung}"
@@ -387,19 +399,21 @@ class MessageProcessor:
387
  logger.error(f"LLM không sẵn sàng: {e}\n{traceback.format_exc()}")
388
  fallback = "Tóm tắt các đoạn luật liên quan:\n\n"
389
  for i, match in enumerate(matches, 1):
390
- fallback += f"Đoạn {i}:\n"
391
- tieude = (match.get('tieude') or '').strip()
392
- noidung = (match.get('noidung') or '').strip()
393
- if tieude or noidung:
394
- fallback += f" - Hành vi: {(tieude + ' ' + noidung).strip()}\n"
395
- canhantu = arr_to_str(match.get('canhantu'))
396
- canhanden = arr_to_str(match.get('canhanden'))
397
- if canhantu or canhanden:
398
- fallback += f" - Cá nhân bị phạt tiền từ {canhantu} VNĐ đến {canhanden} VNĐ\n"
399
- tochuctu = arr_to_str(match.get('tochuctu'))
400
- tochucden = arr_to_str(match.get('tochucden'))
401
- if tochuctu or tochucden:
402
- fallback += f" - Tổ chức bị phạt tiền từ {tochuctu} VNĐ đến {tochucden} VNĐ\n"
 
 
403
  hpbsnoidung = arr_to_str(match.get('hpbsnoidung'), sep="; ")
404
  if hpbsnoidung:
405
  fallback += f" - Hình phạt bổ sung: {hpbsnoidung}\n"
@@ -419,29 +433,31 @@ class MessageProcessor:
419
  async def handle_muc_phat(self, conv, message_text, page_token, sender_id):
420
  vehicle = conv.get('originalvehicle', '')
421
  action = conv.get('originalaction', '')
 
422
  keywords = [kw.strip() for kw in vehicle.split(',') if kw.strip()]
423
- if keywords:
424
- if action:
425
- logger.info(f"[DEBUG] tạo embedding: {action}")
426
- embedding = await self.channel.embedder.create_embedding(action)
427
- logger.info(f"[DEBUG] embedding: {embedding[:5]} ... (total {len(embedding)})")
428
- matches = self.channel.supabase.match_documents(
429
- embedding,
430
- vehicle_keywords=keywords,
431
- user_question=action
432
- )
433
- logger.info(f"[DEBUG] matches: {matches}")
434
- if matches:
435
- response = await self.format_search_results(message_text, matches, page_token, sender_id)
436
- else:
437
- response = "Xin lỗi, tôi không tìm thấy thông tin phù hợp."
438
  else:
439
- logger.info(f"[DEBUG] Không hành vi vi phạm: {message_text}")
440
- response = "Xin lỗi, tôi không tìm thấy thông tin về hành vi vi phạm trong câu hỏi của bạn."
441
- conv['isdone'] = True
442
  else:
443
- response = "Vui lòng cho biết loại phương tiện bạn cần tìm (xe máy, ô tô...)"
444
- conv['isdone'] = False
 
 
 
 
445
  return response
446
 
447
  async def handle_quy_tac(self, conv, message_text):
 
1
+ from typing import Dict, Any, List, Optional
2
  import asyncio
3
  import traceback
4
  from loguru import logger
 
7
  from .facebook import FacebookClient
8
  from app.config import get_settings
9
  import re
10
+ import json
11
 
12
  class MessageProcessor:
13
  def __init__(self, channel, sender_id):
 
64
  'originalvehicle': '',
65
  'originalaction': '',
66
  'originalpurpose': '',
67
+ 'originalquestion': '',
68
  'timestamp': [timestamp],
69
  'isdone': False
70
  }
 
96
  'originalvehicle': row.get('originalvehicle'),
97
  'originalaction': row.get('originalaction'),
98
  'originalpurpose': row.get('originalpurpose'),
99
+ 'originalquestion': row.get('originalquestion'),
100
  'timestamp': row_timestamps,
101
  'isdone': row.get('isdone')
102
  }
 
149
  llm_analysis = await self.channel.llm.analyze(message_text)
150
  logger.info(f"[LLM][RAW] Kết quả trả về từ analyze: {llm_analysis}")
151
  muc_dich = None
152
+ hanh_vi = None
153
+ cau_hoi = None
154
  if isinstance(llm_analysis, dict):
155
  keywords = [self.normalize_vehicle_keyword(llm_analysis.get('phuong_tien', ''))]
156
  muc_dich = llm_analysis.get('muc_dich')
157
+ hanh_vi = llm_analysis.get('hanh_vi')
158
+ cau_hoi = llm_analysis.get('cau_hoi')
159
  elif isinstance(llm_analysis, list) and len(llm_analysis) > 0:
160
  keywords = [self.normalize_vehicle_keyword(llm_analysis[0].get('phuong_tien', ''))]
161
  muc_dich = llm_analysis[0].get('muc_dich')
162
+ hanh_vi = llm_analysis[0].get('hanh_vi')
163
+ cau_hoi = llm_analysis[0].get('cau_hoi')
164
  else:
165
  keywords = extract_keywords(message_text, VEHICLE_KEYWORDS)
166
+ cau_hoi = message_text
167
  for kw in keywords:
168
+ cau_hoi = cau_hoi.replace(kw, "")
169
+ cau_hoi = cau_hoi.strip()
170
+ logger.info(f"[DEBUG] Phương tiện: {keywords} - Hành vi: {hanh_vi} - Mục đích: {muc_dich} - Câu hỏi: {cau_hoi}")
171
  # await self.channel.facebook.send_message(message=f"... đang tìm kiếm quy định liên quan đến {hanh_vi_vi_pham} .....")
172
  # 4. Update lại conversation với thông tin đầy đủ
173
  update_kwargs = {
 
179
  'originalcontent': remaining_text,
180
  'originalattachments': attachments,
181
  'originalvehicle': ','.join(keywords),
182
+ 'originalaction': hanh_vi,
183
  'originalpurpose': muc_dich,
184
+ 'originalquestion': cau_hoi or "",
185
  'timestamp': self.flatten_timestamp(conv['timestamp']),
186
  'isdone': False
187
  }
 
330
  if not top or (match.get('similarity', 0) > top.get('similarity', 0)):
331
  top = match
332
  full_result_text += f"\n{(match.get('structure') or '').strip()}:\n"
333
+ # tieude = (match.get('tieude') or '').strip()
334
+ # noidung = (match.get('noidung') or '').strip()
335
+ # hanhvi = (tieude + "\n" + noidung).strip().replace('\n', ' ')
336
+ # full_result_text += f"Thực hiện hành vi:\n{hanhvi}"
337
+ # canhantu = arr_to_str(match.get('canhantu'))
338
+ # canhanden = arr_to_str(match.get('canhanden'))
339
+ # if canhantu or canhanden:
340
+ # full_result_text += f"\nCá nhân sẽ bị phạt tiền từ {canhantu} VNĐ đến {canhanden} VNĐ"
341
+ # tochuctu = arr_to_str(match.get('tochuctu'))
342
+ # tochucden = arr_to_str(match.get('tochucden'))
343
+ # if tochuctu or tochucden:
344
+ # full_result_text += f"\nTổ chức sẽ bị phạt tiền từ {tochuctu} VNĐ đến {tochucden} VNĐ"
345
+ fullContent = (match.get('fullContent') or '').strip()
346
+ full_result_text += f"{fullContent}"
347
  hpbsnoidung = arr_to_str(match.get('hpbsnoidung'), sep="; ")
348
  if hpbsnoidung:
349
  full_result_text += f"\nNgoài việc bị phạt tiền, người vi phạm còn bị: {hpbsnoidung}"
 
354
  if impounding:
355
  full_result_text += f"\nTạm giữ phương tiên: 07 ngày"
356
  if top and (top.get('tieude') or top.get('noidung')):
357
+ top_result_text += f"\n{(match.get('structure') or '').strip()}:\n"
358
+ # tieude = (top.get('tieude') or '').strip()
359
+ # noidung = (top.get('noidung') or '').strip()
360
+ # hanhvi = (tieude + "\n" + noidung).strip().replace('\n', ' ')
361
+ # top_result_text += f"Thực hiện hành vi:\n{hanhvi}"
362
+ # canhantu = arr_to_str(top.get('canhantu'))
363
+ # canhanden = arr_to_str(top.get('canhanden'))
364
+ # if canhantu or canhanden:
365
+ # top_result_text += f"\nCá nhân sẽ bị phạt tiền từ {canhantu} VNĐ đến {canhanden} VNĐ"
366
+ # tochuctu = arr_to_str(top.get('tochuctu'))
367
+ # tochucden = arr_to_str(top.get('tochucden'))
368
+ # if tochuctu or tochucden:
369
+ # top_result_text += f"\nTổ chức sẽ bị phạt tiền từ {tochuctu} VNĐ đến {tochucden} VNĐ"
370
+ fullContent = (match.get('fullContent') or '').strip()
371
+ top_result_text += f"{fullContent}"
372
  hpbsnoidung = arr_to_str(top.get('hpbsnoidung'), sep="; ")
373
  if hpbsnoidung:
374
  top_result_text += f"\nNgoài việc bị phạt tiền, người vi phạm còn bị: {hpbsnoidung}"
 
399
  logger.error(f"LLM không sẵn sàng: {e}\n{traceback.format_exc()}")
400
  fallback = "Tóm tắt các đoạn luật liên quan:\n\n"
401
  for i, match in enumerate(matches, 1):
402
+ fallback += f"\n{(match.get('structure') or '').strip()}:\n"
403
+ # tieude = (match.get('tieude') or '').strip()
404
+ # noidung = (match.get('noidung') or '').strip()
405
+ # if tieude or noidung:
406
+ # fallback += f" - Hành vi: {(tieude + ' ' + noidung).strip()}\n"
407
+ # canhantu = arr_to_str(match.get('canhantu'))
408
+ # canhanden = arr_to_str(match.get('canhanden'))
409
+ # if canhantu or canhanden:
410
+ # fallback += f" - Cá nhân bị phạt tiền từ {canhantu} VNĐ đến {canhanden} VNĐ\n"
411
+ # tochuctu = arr_to_str(match.get('tochuctu'))
412
+ # tochucden = arr_to_str(match.get('tochucden'))
413
+ # if tochuctu or tochucden:
414
+ # fallback += f" - Tổ chức bị phạt tiền từ {tochuctu} VNĐ đến {tochucden} VNĐ\n"
415
+ fullContent = (match.get('fullContent') or '').strip()
416
+ fallback += f"{fullContent}"
417
  hpbsnoidung = arr_to_str(match.get('hpbsnoidung'), sep="; ")
418
  if hpbsnoidung:
419
  fallback += f" - Hình phạt bổ sung: {hpbsnoidung}\n"
 
433
  async def handle_muc_phat(self, conv, message_text, page_token, sender_id):
434
  vehicle = conv.get('originalvehicle', '')
435
  action = conv.get('originalaction', '')
436
+ question = conv.get('originalquestion', '')
437
  keywords = [kw.strip() for kw in vehicle.split(',') if kw.strip()]
438
+ #remove the requirement of having to have vehicle
439
+ # if keywords:
440
+ if question:
441
+ logger.info(f"[DEBUG] tạo embedding: {question}")
442
+ embedding = await self.channel.embedder.create_embedding(question)
443
+ logger.info(f"[DEBUG] embedding: {embedding[:5]} ... (total {len(embedding)})")
444
+ matches = self.channel.supabase.match_documents(
445
+ embedding,
446
+ # vehicle_keywords=keywords,
447
+ user_question=question
448
+ )
449
+ logger.info(f"[DEBUG] matches: {matches}")
450
+ if matches:
451
+ response = await self.format_search_results(message_text, matches, page_token, sender_id)
 
452
  else:
453
+ response = "Xin lỗi, tôi không tìm thấy thông tin phù hợp."
 
 
454
  else:
455
+ logger.info(f"[DEBUG] Không hành vi vi phạm: {message_text}")
456
+ response = "Xin lỗi, tôi không tìm thấy thông tin về hành vi vi phạm trong câu hỏi của bạn."
457
+ conv['isdone'] = True
458
+ # else:
459
+ # response = "Vui lòng cho biết loại phương tiện bạn cần tìm (xe máy, ô tô...)"
460
+ # conv['isdone'] = False
461
  return response
462
 
463
  async def handle_quy_tac(self, conv, message_text):
app/sheets.py CHANGED
@@ -87,14 +87,14 @@ class SheetsClient:
87
  values = result.get('values', [])
88
  history = []
89
  for row in values:
90
- row = row + [""] * (12 - len(row))
91
  try:
92
- timestamps = json.loads(row[10]) if row[10] else []
93
  except Exception:
94
  timestamps = []
95
  if not isinstance(timestamps, list):
96
  timestamps = [timestamps]
97
- if row[4] == user_id and row[5] == page_id and row[11].lower() == 'false':
98
  history.append({
99
  'conversation_id': row[0],
100
  'originalcommand': row[1],
@@ -106,8 +106,9 @@ class SheetsClient:
106
  'originalvehicle': row[7],
107
  'originalaction': row[8],
108
  'originalpurpose': row[9],
 
109
  'timestamp': timestamps,
110
- 'isdone': row[11].lower() == 'true'
111
  })
112
  return history
113
  except Exception as e:
@@ -127,6 +128,7 @@ class SheetsClient:
127
  originalvehicle: str = "",
128
  originalaction: str = "",
129
  originalpurpose: str = "",
 
130
  timestamp: Any = None,
131
  isdone: bool = False
132
  ) -> Optional[Dict[str, Any]]:
@@ -179,6 +181,7 @@ class SheetsClient:
179
  'originalvehicle': row[7],
180
  'originalaction': row[8],
181
  'originalpurpose': row[9],
 
182
  'timestamp': row_timestamps,
183
  'isdone': row[11].lower() == 'true' if len(row) > 11 else False
184
  }
@@ -196,6 +199,7 @@ class SheetsClient:
196
  originalvehicle,
197
  originalaction,
198
  originalpurpose,
 
199
  json.dumps(timestamp),
200
  str(isdone).lower()
201
  ]
@@ -209,7 +213,7 @@ class SheetsClient:
209
  valueInputOption='RAW',
210
  body=body
211
  ).execute()
212
- logger.info(f"Thêm mới conversation: {conversation_id} | Giá trị: {dict(zip(['conversation_id','originalcommand','originalcontent','originalattachments','recipient_id','page_id','originaltext','originalvehicle','originalaction','originalpurpose','timestamp','isdone'], new_row))}")
213
 
214
  # Return the conversation data directly
215
  return {
@@ -223,6 +227,7 @@ class SheetsClient:
223
  'originalvehicle': originalvehicle,
224
  'originalaction': originalaction,
225
  'originalpurpose': originalpurpose,
 
226
  'timestamp': timestamp,
227
  'isdone': isdone
228
  }
@@ -264,8 +269,9 @@ class SheetsClient:
264
  originalvehicle if originalvehicle else current_row[7],
265
  originalaction if originalaction else current_row[8],
266
  originalpurpose if originalpurpose else current_row[9],
 
267
  json.dumps(current_timestamps),
268
- str(isdone).lower() if isdone is not None else current_row[11]
269
  ]
270
  update_range = f"{SHEET_RANGE.split('!')[0]}!A{sheet_row_number}"
271
  logger.info(f"[DEBUG] Gsheet update range {update_range}")
@@ -278,7 +284,7 @@ class SheetsClient:
278
  valueInputOption='RAW',
279
  body=body
280
  ).execute()
281
- changed_cols = ['conversation_id','originalcommand','originalcontent','originalattachments','recipient_id','page_id','originaltext','originalvehicle','originalaction','originalpurpose','timestamp','isdone']
282
  for idx, (old, new) in enumerate(zip(current_row, new_row)):
283
  if old != new:
284
  changed_cols.append(changed_cols[idx])
@@ -296,8 +302,9 @@ class SheetsClient:
296
  'originalvehicle': new_row[7],
297
  'originalaction': new_row[8],
298
  'originalpurpose': new_row[9],
 
299
  'timestamp': current_timestamps,
300
- 'isdone': new_row[11].lower() == 'true'
301
  }
302
  return None
303
  except Exception as e:
 
87
  values = result.get('values', [])
88
  history = []
89
  for row in values:
90
+ row = row + [""] * (13 - len(row))
91
  try:
92
+ timestamps = json.loads(row[11]) if row[11] else []
93
  except Exception:
94
  timestamps = []
95
  if not isinstance(timestamps, list):
96
  timestamps = [timestamps]
97
+ if row[4] == user_id and row[5] == page_id and row[12].lower() == 'false':
98
  history.append({
99
  'conversation_id': row[0],
100
  'originalcommand': row[1],
 
106
  'originalvehicle': row[7],
107
  'originalaction': row[8],
108
  'originalpurpose': row[9],
109
+ 'originalquestion': row[10],
110
  'timestamp': timestamps,
111
+ 'isdone': row[12].lower() == 'true'
112
  })
113
  return history
114
  except Exception as e:
 
128
  originalvehicle: str = "",
129
  originalaction: str = "",
130
  originalpurpose: str = "",
131
+ originalquestion: str = "", # <-- thêm dòng này
132
  timestamp: Any = None,
133
  isdone: bool = False
134
  ) -> Optional[Dict[str, Any]]:
 
181
  'originalvehicle': row[7],
182
  'originalaction': row[8],
183
  'originalpurpose': row[9],
184
+ 'originalquestion': row[10],
185
  'timestamp': row_timestamps,
186
  'isdone': row[11].lower() == 'true' if len(row) > 11 else False
187
  }
 
199
  originalvehicle,
200
  originalaction,
201
  originalpurpose,
202
+ originalquestion, # <-- thêm dòng này
203
  json.dumps(timestamp),
204
  str(isdone).lower()
205
  ]
 
213
  valueInputOption='RAW',
214
  body=body
215
  ).execute()
216
+ logger.info(f"Thêm mới conversation: {conversation_id} | Giá trị: {dict(zip(['conversation_id','originalcommand','originalcontent','originalattachments','recipient_id','page_id','originaltext','originalvehicle','originalaction','originalpurpose','originalquestion','timestamp','isdone'], new_row))}")
217
 
218
  # Return the conversation data directly
219
  return {
 
227
  'originalvehicle': originalvehicle,
228
  'originalaction': originalaction,
229
  'originalpurpose': originalpurpose,
230
+ 'originalquestion': originalquestion,
231
  'timestamp': timestamp,
232
  'isdone': isdone
233
  }
 
269
  originalvehicle if originalvehicle else current_row[7],
270
  originalaction if originalaction else current_row[8],
271
  originalpurpose if originalpurpose else current_row[9],
272
+ originalquestion if originalquestion else current_row[10], # <-- thêm dòng này
273
  json.dumps(current_timestamps),
274
+ str(isdone).lower() if isdone is not None else current_row[12]
275
  ]
276
  update_range = f"{SHEET_RANGE.split('!')[0]}!A{sheet_row_number}"
277
  logger.info(f"[DEBUG] Gsheet update range {update_range}")
 
284
  valueInputOption='RAW',
285
  body=body
286
  ).execute()
287
+ changed_cols = ['conversation_id','originalcommand','originalcontent','originalattachments','recipient_id','page_id','originaltext','originalvehicle','originalaction','originalpurpose','originalquestion','timestamp','isdone']
288
  for idx, (old, new) in enumerate(zip(current_row, new_row)):
289
  if old != new:
290
  changed_cols.append(changed_cols[idx])
 
302
  'originalvehicle': new_row[7],
303
  'originalaction': new_row[8],
304
  'originalpurpose': new_row[9],
305
+ 'originalquestion': new_row[10], # <-- thêm dòng này
306
  'timestamp': current_timestamps,
307
+ 'isdone': new_row[12].lower() == 'true'
308
  }
309
  return None
310
  except Exception as e: