VietCat commited on
Commit
14277a4
·
1 Parent(s): 9c21ccb

quick fix timestamp

Browse files
Files changed (1) hide show
  1. app/sheets.py +134 -86
app/sheets.py CHANGED
@@ -16,20 +16,22 @@ from .constants import SHEET_RANGE
16
 
17
  SCOPES = ['https://www.googleapis.com/auth/spreadsheets']
18
 
19
- def generate_conversation_id(user_id: str, page_id: str, timestamp: Any) -> str:
20
  """Tạo ID hội thoại duy nhất."""
21
  hash_input = f"{user_id}:{page_id}:{timestamp}"
22
  return hashlib.sha256(hash_input.encode()).hexdigest()[:32]
23
 
24
- def _flatten_and_unique_timestamps(items: List[Any]) -> List[Any]:
 
25
  """
26
- Hàm tiện ích để làm phẳng danh sách timestamp loại bỏ các giá trị trùng lặp.
27
- Xử được cả list lồng nhau.
28
  """
29
- flat_list = []
30
  if not isinstance(items, list):
 
31
  return [items]
32
 
 
33
  for item in items:
34
  if isinstance(item, list):
35
  flat_list.extend(_flatten_and_unique_timestamps(item))
@@ -70,118 +72,164 @@ class SheetsClient:
70
 
71
  @timing_decorator_sync
72
  def get_conversation_history(self, user_id: str, page_id: str) -> List[Dict[str, Any]]:
73
- """Lấy lịch sử hội thoại từ sheet, xử lý timestamp một cách an toàn."""
74
  try:
75
  if not self.service:
76
  self.authenticate()
77
-
78
  result = self.service.spreadsheets().values().get(
79
  spreadsheetId=self.sheet_id,
80
- range=SHEET_RANGE
81
  ).execute()
82
  values = result.get('values', [])
83
- if not values:
 
84
  return []
85
-
86
  header = values[0]
87
- history = []
88
- for row in values[1:]: # Bỏ qua dòng header
89
- # Đảm bảo row có đủ cột
90
  row_data = dict(zip(header, row + [""] * (len(header) - len(row))))
91
 
92
  if row_data.get('recipient_id') == user_id and row_data.get('page_id') == page_id:
93
- # Cải tiến: Xử lý an toàn việc đọc timestamp
94
  try:
95
- timestamps_raw = json.loads(row_data.get('timestamp', '[]'))
96
- timestamps = _flatten_and_unique_timestamps(timestamps_raw)
97
  except (json.JSONDecodeError, TypeError):
98
  timestamps = []
99
-
100
  row_data['timestamp'] = timestamps
101
  row_data['originalattachments'] = json.loads(row_data.get('originalattachments', '[]'))
102
  row_data['isdone'] = str(row_data.get('isdone', 'false')).lower() == 'true'
103
  history.append(row_data)
104
  return history
105
  except Exception as e:
106
- logger.error(f"Lỗi khi lấy lịch sử hội thoại: {e}")
107
  return []
108
 
109
  @timing_decorator_sync
110
- def log_conversation(self, **kwargs: Any) -> Optional[Dict[str, Any]]:
111
- """
112
- Ghi lại hoặc cập nhật một hội thoại.
113
- - Nếu có 'conversation_id', sẽ tìm và CẬP NHẬT dòng đó.
114
- - Nếu không, sẽ THÊM MỚI một dòng.
115
- """
 
 
 
 
 
 
 
 
 
 
 
 
116
  try:
117
  if not self.service:
118
  self.authenticate()
119
 
120
- # Lấy toàn bộ dữ liệu từ sheet một lần
121
- sheet = self.service.spreadsheets().values().get(spreadsheetId=self.sheet_id, range=SHEET_RANGE).execute()
122
- values = sheet.get('values', [])
 
 
123
  header = values[0] if values else []
124
- if not header:
125
- logger.error("Sheet rỗng hoặc không header.")
126
- return None
127
-
128
- conversation_id = kwargs.get('conversation_id')
129
- row_index_to_update = -1
130
-
131
- # Tìm dòng cần cập nhật nếu có conversation_id
132
- if conversation_id:
133
- for i, row in enumerate(values[1:], start=2): # start=2 vì sheet index bắt đầu từ 1 và bỏ qua header
134
- if row and row[0] == conversation_id:
135
- row_index_to_update = i
136
- break
137
-
138
- # Làm phẳng và đảm bảo timestamp là một list duy nhất
139
- timestamps_list = _flatten_and_unique_timestamps(kwargs.get('timestamp', []))
140
- kwargs['timestamp'] = timestamps_list # Cập nhật lại kwargs
141
-
142
- # Chuẩn bị dữ liệu cho dòng mới/cập nhật
143
- row_data = [str(kwargs.get(h, '')) for h in header]
144
- # Chuyển đổi các trường đặc biệt sang JSON string
145
- row_data[header.index('originalattachments')] = json.dumps(kwargs.get('originalattachments', []))
146
- row_data[header.index('timestamp')] = json.dumps(timestamps_list)
147
- row_data[header.index('isdone')] = str(kwargs.get('isdone', False)).lower()
148
-
149
- if row_index_to_update != -1:
150
- # --- CẬP NHẬT DÒNG HIỆN CÓ ---
151
- logger.info(f"Đang cập nhật conversation: {conversation_id}")
152
- range_to_update = f"A{row_index_to_update}"
153
- body = {'values': [row_data]}
154
- self.service.spreadsheets().values().update(
155
- spreadsheetId=self.sheet_id,
156
- range=range_to_update,
157
- valueInputOption='RAW',
158
- body=body
159
- ).execute()
160
  else:
161
- # --- THÊM DÒNG MỚI ---
162
- if not conversation_id:
163
- # Tạo ID mới nếu chưa có
164
- new_id = generate_conversation_id(
165
- kwargs.get('recipient_id', ''),
166
- kwargs.get('page_id', ''),
167
- timestamps_list[0] if timestamps_list else datetime.now().isoformat()
168
- )
169
- kwargs['conversation_id'] = new_id
170
- row_data[header.index('conversation_id')] = new_id
171
-
172
- logger.info(f"Đang thêm mới conversation: {kwargs['conversation_id']}")
173
- body = {'values': [row_data]}
174
- self.service.spreadsheets().values().append(
175
- spreadsheetId=self.sheet_id,
176
- range=SHEET_RANGE,
177
- valueInputOption='RAW',
178
- insertDataOption='INSERT_ROWS',
179
- body=body
180
- ).execute()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
181
 
182
- # Trả về dữ liệu đã được xử lý
183
- return kwargs
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
184
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
185
  except Exception as e:
186
- logger.error(f"Lỗi khi ghi/cập nhật conversation: {e}", exc_info=True)
187
  return None
 
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
  """Tạo ID hội thoại duy nhất."""
21
  hash_input = f"{user_id}:{page_id}:{timestamp}"
22
  return hashlib.sha256(hash_input.encode()).hexdigest()[:32]
23
 
24
+ # --- HÀM TIỆN ÍCH ĐƯỢC THÊM VÀO ĐỂ FIX LỖI ---
25
+ def _flatten_and_unique_timestamps(items: Any) -> List[Any]:
26
  """
27
+ Hàm tiện ích để làm phẳng danh sách timestamp (xử list lồng nhau)
28
+ loại bỏ các giá trị trùng lặp, giữ nguyên thứ tự.
29
  """
 
30
  if not isinstance(items, list):
31
+ # Nếu đầu vào không phải list, gói nó vào một list
32
  return [items]
33
 
34
+ flat_list = []
35
  for item in items:
36
  if isinstance(item, list):
37
  flat_list.extend(_flatten_and_unique_timestamps(item))
 
72
 
73
  @timing_decorator_sync
74
  def get_conversation_history(self, user_id: str, page_id: str) -> List[Dict[str, Any]]:
75
+ """Lấy lịch sử hội thoại từ sheet."""
76
  try:
77
  if not self.service:
78
  self.authenticate()
79
+ range_name = SHEET_RANGE
80
  result = self.service.spreadsheets().values().get(
81
  spreadsheetId=self.sheet_id,
82
+ range=range_name
83
  ).execute()
84
  values = result.get('values', [])
85
+ history = []
86
+ if not values or len(values) < 1:
87
  return []
88
+
89
  header = values[0]
90
+ for row in values[1:]:
 
 
91
  row_data = dict(zip(header, row + [""] * (len(header) - len(row))))
92
 
93
  if row_data.get('recipient_id') == user_id and row_data.get('page_id') == page_id:
 
94
  try:
95
+ # Dùng hàm tiện ích để đảm bảo timestamp đọc ra luôn phẳng
96
+ timestamps = _flatten_and_unique_timestamps(json.loads(row_data.get('timestamp', '[]')))
97
  except (json.JSONDecodeError, TypeError):
98
  timestamps = []
99
+
100
  row_data['timestamp'] = timestamps
101
  row_data['originalattachments'] = json.loads(row_data.get('originalattachments', '[]'))
102
  row_data['isdone'] = str(row_data.get('isdone', 'false')).lower() == 'true'
103
  history.append(row_data)
104
  return history
105
  except Exception as e:
106
+ logger.error(f"Error getting conversation history: {e}")
107
  return []
108
 
109
  @timing_decorator_sync
110
+ def log_conversation(
111
+ self,
112
+ conversation_id: str,
113
+ recipient_id: str,
114
+ page_id: str,
115
+ originaltext: str = "",
116
+ originalcommand: str = "",
117
+ originalcontent: str = "",
118
+ originalattachments: Optional[List[str]] = None,
119
+ originalvehicle: str = "",
120
+ originalaction: str = "",
121
+ originalpurpose: str = "",
122
+ originalquestion: str = "",
123
+ systemresponse: str = "",
124
+ timestamp: Optional[Any] = None, # Cho phép timestamp có thể là list
125
+ isdone: bool = False
126
+ ) -> Optional[Dict[str, Any]]:
127
+ """Ghi lại một hội thoại, giữ nguyên logic gốc của người dùng."""
128
  try:
129
  if not self.service:
130
  self.authenticate()
131
 
132
+ result = self.service.spreadsheets().values().get(
133
+ spreadsheetId=self.sheet_id,
134
+ range=SHEET_RANGE
135
+ ).execute()
136
+ values = result.get('values', [])
137
  header = values[0] if values else []
138
+
139
+ # --- SỬA LỖI LỒNG TIMESTAMP TẠI ĐÂY ---
140
+ # 1. Luôn đảm bảo `timestamps_list` là một danh sách phẳng, không lồng nhau.
141
+ timestamps_list = _flatten_and_unique_timestamps(timestamp or [])
142
+
143
+ # 2. Xác định timestamp cho sự kiện hiện tại (dùng để kiểm tra trùng lặp).
144
+ # Lấy timestamp mới nhất (cuối cùng trong list). Nếu list rỗng thì tạo mới.
145
+ if not timestamps_list:
146
+ current_ts = datetime.now().isoformat()
147
+ timestamps_list.append(current_ts)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
148
  else:
149
+ current_ts = timestamps_list[-1]
150
+ # --- KẾT THÚC SỬA LỖI ---
151
+
152
+ # Logic kiểm tra trùng lặp của bạn được giữ nguyên
153
+ if header:
154
+ for row in values[1:]:
155
+ row_data = dict(zip(header, row + [""] * (len(header) - len(row))))
156
+ try:
157
+ row_timestamps = json.loads(row_data.get('timestamp', '[]'))
158
+ if not isinstance(row_timestamps, list):
159
+ row_timestamps = [row_timestamps]
160
+ except Exception:
161
+ row_timestamps = []
162
+
163
+ if str(current_ts) in [str(ts) for ts in row_timestamps] and \
164
+ row_data.get('recipient_id') == str(recipient_id) and \
165
+ row_data.get('page_id') == str(page_id):
166
+ logger.info(f"Found duplicate conversation for user {recipient_id}, page {page_id}, timestamp {current_ts}")
167
+ # Trả về dữ liệu tìm thấy, logic không đổi
168
+ return {
169
+ 'conversation_id': row_data.get('conversation_id'),
170
+ 'originalcommand': row_data.get('originalcommand'),
171
+ 'originalcontent': row_data.get('originalcontent'),
172
+ 'originalattachments': json.loads(row_data.get('originalattachments', '[]')),
173
+ 'recipient_id': row_data.get('recipient_id'),
174
+ 'page_id': row_data.get('page_id'),
175
+ 'originaltext': row_data.get('originaltext'),
176
+ 'originalvehicle': row_data.get('originalvehicle'),
177
+ 'originalaction': row_data.get('originalaction'),
178
+ 'originalpurpose': row_data.get('originalpurpose'),
179
+ 'originalquestion': row_data.get('originalquestion'),
180
+ 'systemresponse': row_data.get('systemresponse'),
181
+ 'timestamp': row_timestamps,
182
+ 'isdone': str(row_data.get('isdone', 'false')).lower() == 'true'
183
+ }
184
+
185
+ # Logic tạo dòng mới và append vào sheet được giữ nguyên
186
+ if not conversation_id:
187
+ conversation_id = generate_conversation_id(recipient_id, page_id, current_ts)
188
 
189
+ new_row = [
190
+ conversation_id,
191
+ originalcommand,
192
+ originalcontent,
193
+ json.dumps(originalattachments or []),
194
+ recipient_id,
195
+ page_id,
196
+ originaltext,
197
+ originalvehicle,
198
+ originalaction,
199
+ originalpurpose,
200
+ originalquestion,
201
+ systemresponse,
202
+ json.dumps(timestamps_list), # Lưu danh sách đã được làm phẳng
203
+ str(isdone).lower()
204
+ ]
205
+
206
+ body = {'values': [new_row]}
207
+ self.service.spreadsheets().values().append(
208
+ spreadsheetId=self.sheet_id,
209
+ range=SHEET_RANGE,
210
+ valueInputOption='RAW',
211
+ body=body
212
+ ).execute()
213
 
214
+ logger.info(f"Thêm mới conversation: {conversation_id}")
215
+
216
+ # Trả về dữ liệu đã được ghi, logic không đổi
217
+ return {
218
+ 'conversation_id': conversation_id,
219
+ 'originalcommand': originalcommand,
220
+ 'originalcontent': originalcontent,
221
+ 'originalattachments': originalattachments or [],
222
+ 'recipient_id': recipient_id,
223
+ 'page_id': page_id,
224
+ 'originaltext': originaltext,
225
+ 'originalvehicle': originalvehicle,
226
+ 'originalaction': originalaction,
227
+ 'originalpurpose': originalpurpose,
228
+ 'originalquestion': originalquestion,
229
+ 'systemresponse': systemresponse,
230
+ 'timestamp': timestamps_list, # Trả về danh sách đã được làm phẳng
231
+ 'isdone': isdone
232
+ }
233
  except Exception as e:
234
+ logger.error(f"Error logging conversation: {e}", exc_info=True)
235
  return None