VietCat commited on
Commit
c84afa0
·
1 Parent(s): 1d43d4c

update message flow

Browse files
Files changed (2) hide show
  1. app/main.py +83 -28
  2. app/sheets.py +100 -31
app/main.py CHANGED
@@ -119,11 +119,6 @@ async def webhook(request: Request):
119
 
120
  @timing_decorator_async
121
  async def process_message(message_data: Dict[str, Any]):
122
- """
123
- Xử lý message từ người dùng Facebook, phân tích, truy vấn, gửi phản hồi và log lại.
124
- Input: message_data (dict) - thông tin message đã parse từ Facebook.
125
- Output: None (gửi message và log hội thoại).
126
- """
127
  sender_id = message_data["sender_id"]
128
  page_id = message_data["page_id"]
129
  message_text = message_data["text"]
@@ -150,51 +145,111 @@ async def process_message(message_data: Dict[str, Any]):
150
  logger.info(f"[DEBUG] history: {history}")
151
 
152
  response = ""
153
- if command == "xong":
154
- if not keywords:
155
- response = "Vui lòng cho biết loại phương tiện bạn cần tìm (xe máy, ô tô...)"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
156
  else:
157
- # Create embedding from message
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
158
  embedding = await embedding_client.create_embedding(message_text)
159
  logger.info(f"[DEBUG] embedding: {embedding[:5]} ... (total {len(embedding)})")
160
-
161
- # Search for similar documents (sync)
162
  matches = supabase_client.match_documents(embedding)
163
  logger.info(f"[DEBUG] matches: {matches}")
164
-
165
  if matches:
166
  response = format_search_results(matches)
167
  else:
168
  response = "Xin lỗi, tôi không tìm thấy thông tin phù hợp."
 
 
 
 
 
 
 
 
 
 
169
  else:
170
- response = "Vui lòng cung cấp thêm thông tin và gõ lệnh \\xong khi hoàn tất."
171
-
172
- # Send response
173
- await facebook_client.send_message(page_token, sender_id, response)
174
-
175
- # Log conversation (run in thread pool)
176
- await loop.run_in_executor(
177
- executor, lambda: sheets_client.log_conversation(sender_id, page_id, message_text, keywords, response)
178
- )
 
 
 
 
 
 
 
 
 
 
179
 
180
  def format_search_results(matches: List[Dict[str, Any]]) -> str:
181
- """
182
- Format kết quả truy vấn vector search thành chuỗi gửi về user.
183
- Input: matches (list[dict]) - danh sách kết quả từ Supabase.
184
- Output: Chuỗi kết quả đã format.
185
- """
186
  if not matches:
187
  return "Không tìm thấy kết quả phù hợp."
188
-
189
  result = "Đây là một số kết quả phù hợp:\n\n"
190
  for i, match in enumerate(matches, 1):
191
  result += f"{i}. {match['content']}\n"
192
  if match.get('metadata', {}).get('url'):
193
  result += f" Link: {match['metadata']['url']}\n"
194
  result += "\n"
195
-
196
  return result.strip()
197
 
 
 
 
 
 
 
 
 
198
  if __name__ == "__main__":
199
  import uvicorn
200
  logger.info("[STARTUP] Bắt đầu chạy uvicorn server...")
 
119
 
120
  @timing_decorator_async
121
  async def process_message(message_data: Dict[str, Any]):
 
 
 
 
 
122
  sender_id = message_data["sender_id"]
123
  page_id = message_data["page_id"]
124
  message_text = message_data["text"]
 
145
  logger.info(f"[DEBUG] history: {history}")
146
 
147
  response = ""
148
+ log_needed = True
149
+ log_kwargs = {
150
+ 'user_id': sender_id,
151
+ 'page_id': page_id,
152
+ 'message': message_text,
153
+ 'command': command,
154
+ 'content': remaining_text,
155
+ 'attachments': message_data.get('attachments', []),
156
+ 'vehicle': ','.join(keywords),
157
+ 'is_done': False
158
+ }
159
+
160
+ # 1. Nếu history rỗng (conversation mới)
161
+ if not history:
162
+ if not command:
163
+ if keywords:
164
+ # Có thông tin phương tiện
165
+ embedding = await embedding_client.create_embedding(message_text)
166
+ logger.info(f"[DEBUG] embedding: {embedding[:5]} ... (total {len(embedding)})")
167
+ matches = supabase_client.match_documents(embedding)
168
+ logger.info(f"[DEBUG] matches: {matches}")
169
+ if matches:
170
+ response = format_search_results(matches)
171
+ else:
172
+ response = "Xin lỗi, tôi không tìm thấy thông tin phù hợp."
173
+ log_kwargs['is_done'] = True
174
+ else:
175
+ # Không có thông tin phương tiện
176
+ response = "Vui lòng cho biết loại phương tiện bạn cần tìm (xe máy, ô tô...)"
177
+ log_kwargs['is_done'] = False
178
  else:
179
+ # command
180
+ response = "Vui lòng cung cấp thêm thông tin và gõ lệnh \\xong khi hoàn tất."
181
+ log_kwargs['is_done'] = False
182
+ await facebook_client.send_message(page_token, sender_id, response)
183
+ await loop.run_in_executor(executor, lambda: sheets_client.log_conversation(**log_kwargs))
184
+ return
185
+
186
+ # 2. Nếu history có conversation (conversation cũ)
187
+ last_conv = history[-1] if history else None
188
+ last_command = last_conv['originalcommand'] if last_conv else ''
189
+ last_isdone = last_conv['isdone'] if last_conv else False
190
+
191
+ if not last_command:
192
+ # Lịch sử không có command
193
+ if keywords:
194
+ # Có thông tin phương tiện
195
  embedding = await embedding_client.create_embedding(message_text)
196
  logger.info(f"[DEBUG] embedding: {embedding[:5]} ... (total {len(embedding)})")
 
 
197
  matches = supabase_client.match_documents(embedding)
198
  logger.info(f"[DEBUG] matches: {matches}")
 
199
  if matches:
200
  response = format_search_results(matches)
201
  else:
202
  response = "Xin lỗi, tôi không tìm thấy thông tin phù hợp."
203
+ log_kwargs['is_done'] = True
204
+ await facebook_client.send_message(page_token, sender_id, response)
205
+ await loop.run_in_executor(executor, lambda: sheets_client.log_conversation(**log_kwargs))
206
+ return
207
+ else:
208
+ # Không có thông tin phương tiện
209
+ response = "Vui lòng cho biết loại phương tiện bạn cần tìm (xe máy, ô tô...)"
210
+ log_needed = False
211
+ await facebook_client.send_message(page_token, sender_id, response)
212
+ return
213
  else:
214
+ # Lịch sử command
215
+ if command == "xong":
216
+ # Tạo bài viết mới trên page (placeholder)
217
+ # TODO: Thay thế hàm này bằng logic thực tế
218
+ post_url = await create_facebook_post(page_token, sender_id, history)
219
+ if post_url:
220
+ response = f"Bài viết đã được tạo thành công! Bạn có thể xem tại: {post_url}"
221
+ else:
222
+ response = "Đã xảy ra lỗi khi tạo bài viết. Vui lòng thử lại sau."
223
+ log_kwargs['is_done'] = True
224
+ await facebook_client.send_message(page_token, sender_id, response)
225
+ await loop.run_in_executor(executor, lambda: sheets_client.log_conversation(**log_kwargs))
226
+ return
227
+ else:
228
+ response = "Vui lòng cung cấp thêm thông tin và gõ lệnh \\xong khi hoàn tất."
229
+ log_kwargs['is_done'] = False
230
+ await facebook_client.send_message(page_token, sender_id, response)
231
+ await loop.run_in_executor(executor, lambda: sheets_client.log_conversation(**log_kwargs))
232
+ return
233
 
234
  def format_search_results(matches: List[Dict[str, Any]]) -> str:
 
 
 
 
 
235
  if not matches:
236
  return "Không tìm thấy kết quả phù hợp."
 
237
  result = "Đây là một số kết quả phù hợp:\n\n"
238
  for i, match in enumerate(matches, 1):
239
  result += f"{i}. {match['content']}\n"
240
  if match.get('metadata', {}).get('url'):
241
  result += f" Link: {match['metadata']['url']}\n"
242
  result += "\n"
 
243
  return result.strip()
244
 
245
+ async def create_facebook_post(page_token: str, sender_id: str, history: List[Dict[str, Any]]) -> str:
246
+ """
247
+ Placeholder: Tạo bài viết mới trên page Facebook. Trả về URL bài viết nếu thành công, None nếu thất bại.
248
+ """
249
+ # TODO: Thay thế bằng logic thực tế
250
+ logger.info(f"[MOCK] Creating Facebook post for sender_id={sender_id} with history={history}")
251
+ return "https://facebook.com/mock_post_url"
252
+
253
  if __name__ == "__main__":
254
  import uvicorn
255
  logger.info("[STARTUP] Bắt đầu chạy uvicorn server...")
app/sheets.py CHANGED
@@ -8,6 +8,7 @@ import pickle
8
  from datetime import datetime
9
  from loguru import logger
10
  import json
 
11
  from google.oauth2.service_account import Credentials
12
 
13
  from .utils import timing_decorator_sync
@@ -15,6 +16,13 @@ from .constants import SHEET_RANGE
15
 
16
  SCOPES = ['https://www.googleapis.com/auth/spreadsheets']
17
 
 
 
 
 
 
 
 
18
  class SheetsClient:
19
  def __init__(self, credentials_file: str, token_file: str, sheet_id: str):
20
  """
@@ -62,15 +70,16 @@ class SheetsClient:
62
  @timing_decorator_sync
63
  def get_conversation_history(self, user_id: str, page_id: str) -> List[Dict[str, Any]]:
64
  """
65
- Lấy lịch sử hội thoại của user từ Google Sheets.
66
  Input: user_id (str), page_id (str)
67
- Output: list[dict] các dòng hội thoại (nếu có).
68
  """
69
  try:
70
  if not self.service:
71
  self.authenticate()
72
  if not self.service:
73
  raise RuntimeError("Google Sheets service not initialized")
 
74
  range_name = SHEET_RANGE
75
  result = self.service.spreadsheets().values().get(
76
  spreadsheetId=self.sheet_id,
@@ -81,14 +90,17 @@ class SheetsClient:
81
  history = []
82
 
83
  for row in values:
84
- if len(row) >= 6 and row[0] == user_id and row[1] == page_id:
85
  history.append({
86
- 'user_id': row[0],
87
- 'page_id': row[1],
88
- 'timestamp': row[2],
89
- 'message': row[3],
90
- 'keywords': row[4],
91
- 'response': row[5]
 
 
 
92
  })
93
 
94
  return history
@@ -102,12 +114,15 @@ class SheetsClient:
102
  user_id: str,
103
  page_id: str,
104
  message: str,
105
- keywords: List[str],
106
- response: str
 
 
 
107
  ) -> bool:
108
  """
109
  Ghi log hội thoại vào Google Sheets.
110
- Input: user_id (str), page_id (str), message (str), keywords (list[str]), response (str)
111
  Output: bool (True nếu thành công, False nếu lỗi)
112
  """
113
  try:
@@ -115,27 +130,81 @@ class SheetsClient:
115
  self.authenticate()
116
  if not self.service:
117
  raise RuntimeError("Google Sheets service not initialized")
 
 
 
118
  timestamp = datetime.now().isoformat()
119
- values = [[
120
- user_id,
121
- page_id,
122
- timestamp,
123
- message,
124
- ','.join(keywords),
125
- response
126
- ]]
127
-
128
- body = {
129
- 'values': values
130
- }
131
 
132
- range_name = SHEET_RANGE
133
- self.service.spreadsheets().values().append(
134
- spreadsheetId=self.sheet_id,
135
- range=range_name,
136
- valueInputOption='RAW',
137
- body=body
138
- ).execute()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
139
 
140
  return True
141
  except Exception as e:
 
8
  from datetime import datetime
9
  from loguru import logger
10
  import json
11
+ import hashlib
12
  from google.oauth2.service_account import Credentials
13
 
14
  from .utils import timing_decorator_sync
 
16
 
17
  SCOPES = ['https://www.googleapis.com/auth/spreadsheets']
18
 
19
+ def generate_conversation_id(user_id: str, page_id: str, timestamp: str) -> str:
20
+ """
21
+ Tạo conversation_id duy nhất dựa trên user_id, page_id và timestamp.
22
+ """
23
+ hash_input = f"{user_id}:{page_id}:{timestamp}"
24
+ return hashlib.sha256(hash_input.encode()).hexdigest()[:32]
25
+
26
  class SheetsClient:
27
  def __init__(self, credentials_file: str, token_file: str, sheet_id: str):
28
  """
 
70
  @timing_decorator_sync
71
  def get_conversation_history(self, user_id: str, page_id: str) -> List[Dict[str, Any]]:
72
  """
73
+ Lấy lịch sử hội thoại chưa hoàn thành của user từ Google Sheets.
74
  Input: user_id (str), page_id (str)
75
+ Output: list[dict] các dòng hội thoại chưa hoàn thành.
76
  """
77
  try:
78
  if not self.service:
79
  self.authenticate()
80
  if not self.service:
81
  raise RuntimeError("Google Sheets service not initialized")
82
+
83
  range_name = SHEET_RANGE
84
  result = self.service.spreadsheets().values().get(
85
  spreadsheetId=self.sheet_id,
 
90
  history = []
91
 
92
  for row in values:
93
+ if len(row) >= 9 and row[4] == user_id and row[5] == page_id and row[8].lower() == 'false':
94
  history.append({
95
+ 'conversation_id': row[0],
96
+ 'originalcommand': row[1],
97
+ 'originalcontent': row[2],
98
+ 'originalattachments': json.loads(row[3]) if row[3] else [],
99
+ 'recipient_id': row[4],
100
+ 'page_id': row[5],
101
+ 'originaltext': row[6],
102
+ 'originalvehicle': row[7],
103
+ 'isdone': row[8].lower() == 'true'
104
  })
105
 
106
  return history
 
114
  user_id: str,
115
  page_id: str,
116
  message: str,
117
+ command: str = "",
118
+ content: str = "",
119
+ attachments: Optional[List[str]] = None,
120
+ vehicle: str = "",
121
+ is_done: bool = False
122
  ) -> bool:
123
  """
124
  Ghi log hội thoại vào Google Sheets.
125
+ Input: user_id, page_id, message, command, content, attachments, vehicle, is_done
126
  Output: bool (True nếu thành công, False nếu lỗi)
127
  """
128
  try:
 
130
  self.authenticate()
131
  if not self.service:
132
  raise RuntimeError("Google Sheets service not initialized")
133
+
134
+ # Get existing conversations
135
+ existing_conversations = self.get_conversation_history(user_id, page_id)
136
  timestamp = datetime.now().isoformat()
 
 
 
 
 
 
 
 
 
 
 
 
137
 
138
+ if not existing_conversations:
139
+ # Create new conversation
140
+ conversation_id = generate_conversation_id(user_id, page_id, timestamp)
141
+ values = [[
142
+ conversation_id,
143
+ command,
144
+ content,
145
+ json.dumps(attachments or []),
146
+ user_id,
147
+ page_id,
148
+ message,
149
+ vehicle,
150
+ str(is_done).lower()
151
+ ]]
152
+
153
+ body = {
154
+ 'values': values
155
+ }
156
+
157
+ range_name = SHEET_RANGE
158
+ self.service.spreadsheets().values().append(
159
+ spreadsheetId=self.sheet_id,
160
+ range=range_name,
161
+ valueInputOption='RAW',
162
+ body=body
163
+ ).execute()
164
+ else:
165
+ # Update existing conversation
166
+ conversation = existing_conversations[0]
167
+ conversation_id = conversation['conversation_id']
168
+
169
+ # Find the row with this conversation_id
170
+ result = self.service.spreadsheets().values().get(
171
+ spreadsheetId=self.sheet_id,
172
+ range=SHEET_RANGE
173
+ ).execute()
174
+
175
+ values = result.get('values', [])
176
+ row_index = None
177
+
178
+ for i, row in enumerate(values):
179
+ if row[0] == conversation_id:
180
+ row_index = i
181
+ break
182
+
183
+ if row_index is not None:
184
+ # Update the row
185
+ update_range = f"{SHEET_RANGE.split('!')[0]}!A{row_index + 1}"
186
+ update_values = [[
187
+ conversation_id,
188
+ command or conversation['originalcommand'],
189
+ content or conversation['originalcontent'],
190
+ json.dumps(attachments or json.loads(conversation['originalattachments'])),
191
+ user_id,
192
+ page_id,
193
+ message,
194
+ vehicle or conversation['originalvehicle'],
195
+ str(is_done).lower()
196
+ ]]
197
+
198
+ body = {
199
+ 'values': update_values
200
+ }
201
+
202
+ self.service.spreadsheets().values().update(
203
+ spreadsheetId=self.sheet_id,
204
+ range=update_range,
205
+ valueInputOption='RAW',
206
+ body=body
207
+ ).execute()
208
 
209
  return True
210
  except Exception as e: