File size: 15,061 Bytes
dbb6988 fdbc36f c84afa0 fdbc36f dbb6988 3c6d6b3 dbb6988 c84afa0 dbb6988 3c6d6b3 dbb6988 fdbc36f dbb6988 fdbc36f dbb6988 fdbc36f dbb6988 fdbc36f dbb6988 3c6d6b3 dbb6988 c84afa0 dbb6988 c84afa0 dbb6988 5f02751 fa9fe3e 5f02751 fa9fe3e 5f02751 dbb6988 c84afa0 19142de 5f02751 746514e 5f02751 dbb6988 3c6d6b3 dbb6988 109082c 135c478 dbb6988 a4c95d4 135c478 5f02751 fa9fe3e 7fffc3e 441d431 dbb6988 135c478 dbb6988 c84afa0 b5f9eee 87b2780 eaee65f fa9fe3e 109082c b5f9eee fa9fe3e 135c478 b5f9eee 135c478 b5f9eee 135c478 b5f9eee 5f02751 746514e b5f9eee 135c478 b5f9eee c84afa0 135c478 c84afa0 a4c95d4 135c478 5f02751 d317f57 7fffc3e b5f9eee c84afa0 b5f9eee c84afa0 5f02751 6535336 441d431 135c478 441d431 a4c95d4 135c478 5f02751 d317f57 7fffc3e 441d431 c84afa0 d317f57 c84afa0 511991a c84afa0 53b3e5a c84afa0 511991a 53b3e5a a98e93d 109082c fa9fe3e 109082c c84afa0 135c478 109082c a4c95d4 135c478 5f02751 746514e 5f02751 109082c 511991a 53b3e5a c84afa0 109082c c84afa0 5f02751 6535336 a4c95d4 6535336 441d431 5f02751 fa9fe3e 5f02751 441d431 dbb6988 441d431 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 |
from typing import Any, Dict, List, Optional
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
from googleapiclient.discovery import build
import os
import pickle
from datetime import datetime
from loguru import logger
import json
import hashlib
from google.oauth2.service_account import Credentials
from .utils import timing_decorator_sync
from .constants import SHEET_RANGE
SCOPES = ['https://www.googleapis.com/auth/spreadsheets']
def generate_conversation_id(user_id: str, page_id: str, timestamp: str) -> str:
"""
Tạo conversation_id duy nhất dựa trên user_id, page_id và timestamp.
"""
hash_input = f"{user_id}:{page_id}:{timestamp}"
return hashlib.sha256(hash_input.encode()).hexdigest()[:32]
class SheetsClient:
def __init__(self, credentials_file: str, token_file: str, sheet_id: str):
"""
Khởi tạo SheetsClient với thông tin xác thực và sheet_id.
Input: credentials_file (str), token_file (str), sheet_id (str)
Output: SheetsClient instance.
"""
self.credentials_file = credentials_file
self.token_file = token_file
self.sheet_id = sheet_id
self.creds = None
self.service = None
@timing_decorator_sync
def authenticate(self) -> None:
"""
Xác thực với Google Sheets API, tạo self.service.
Đọc credentials từ biến môi trường GOOGLE_SHEETS_CREDENTIALS_JSON nếu có, nếu không thì dùng file.
Input: None
Output: None (raise exception nếu lỗi)
"""
credentials_json = os.getenv("GOOGLE_SHEETS_CREDENTIALS_JSON")
if credentials_json:
info = json.loads(credentials_json)
creds = Credentials.from_service_account_info(info, scopes=SCOPES)
self.creds = creds
else:
if os.path.exists(self.token_file):
with open(self.token_file, 'rb') as token:
self.creds = pickle.load(token)
if not self.creds or not self.creds.valid:
if self.creds and self.creds.expired:
self.creds.refresh(Request())
else:
flow = InstalledAppFlow.from_client_secrets_file(
self.credentials_file, SCOPES)
self.creds = flow.run_local_server(port=0)
with open(self.token_file, 'wb') as token:
pickle.dump(self.creds, token)
self.service = build('sheets', 'v4', credentials=self.creds)
@timing_decorator_sync
def get_conversation_history(self, user_id: str, page_id: str) -> List[Dict[str, Any]]:
"""
Lấy lịch sử hội thoại chưa hoàn thành của user từ Google Sheets.
Input: user_id (str), page_id (str)
Output: list[dict] các dòng hội thoại chưa hoàn thành.
"""
try:
if not self.service:
self.authenticate()
if not self.service:
raise RuntimeError("Google Sheets service not initialized")
range_name = SHEET_RANGE
result = self.service.spreadsheets().values().get(
spreadsheetId=self.sheet_id,
range=range_name
).execute()
values = result.get('values', [])
history = []
for row in values:
row = row + [""] * (13 - len(row))
try:
timestamps = json.loads(row[11]) if row[11] else []
except Exception:
timestamps = []
if not isinstance(timestamps, list):
timestamps = [timestamps]
if row[4] == user_id and row[5] == page_id and row[12].lower() == 'false':
history.append({
'conversation_id': row[0],
'originalcommand': row[1],
'originalcontent': row[2],
'originalattachments': json.loads(row[3]) if row[3] else [],
'recipient_id': row[4],
'page_id': row[5],
'originaltext': row[6],
'originalvehicle': row[7],
'originalaction': row[8],
'originalpurpose': row[9],
'originalquestion': row[10],
'timestamp': timestamps,
'isdone': row[12].lower() == 'true'
})
return history
except Exception as e:
logger.error(f"Error getting conversation history: {e}")
return []
@timing_decorator_sync
def log_conversation(
self,
conversation_id: str,
recipient_id: str,
page_id: str,
originaltext: str = "",
originalcommand: str = "",
originalcontent: str = "",
originalattachments: Optional[List[str]] = None,
originalvehicle: str = "",
originalaction: str = "",
originalpurpose: str = "",
originalquestion: str = "", # <-- thêm dòng này
timestamp: Any = None,
isdone: bool = False
) -> Optional[Dict[str, Any]]:
"""
Ghi log hội thoại vào Google Sheets.
Dùng các trường original* cho các cột tương ứng trong sheet và các logic liên quan.
"""
try:
if not self.service:
self.authenticate()
if not self.service:
raise RuntimeError("Google Sheets service not initialized")
# Get existing data to check for duplicates
result = self.service.spreadsheets().values().get(
spreadsheetId=self.sheet_id,
range=SHEET_RANGE
).execute()
values = result.get('values', [])
# logger.info(f"[DEBUG] Gsheet values {values}")
ts = datetime.now().isoformat()
# Đảm bảo timestamp luôn là list
if timestamp is None:
timestamp = []
elif not isinstance(timestamp, list):
timestamp = [timestamp]
if not conversation_id:
# Check for duplicates before creating new conversation
for row in values:
if len(row) >= 11:
try:
row_timestamps = json.loads(row[10]) if row[10] else []
except Exception:
row_timestamps = []
if not isinstance(row_timestamps, list):
row_timestamps = [row_timestamps]
row_recipient_id = row[4]
row_page_id = row[5]
if (str(timestamp) in [str(ts) for ts in row_timestamps] and str(row_recipient_id) == str(recipient_id) and str(row_page_id) == str(page_id)):
# Found duplicate, return existing conversation
logger.info(f"Found duplicate conversation for user {recipient_id}, page {page_id}, timestamp {timestamp}")
return {
'conversation_id': row[0],
'originalcommand': row[1],
'originalcontent': row[2],
'originalattachments': json.loads(row[3]) if row[3] else [],
'recipient_id': row[4],
'page_id': row[5],
'originaltext': row[6],
'originalvehicle': row[7],
'originalaction': row[8],
'originalpurpose': row[9],
'originalquestion': row[10],
'timestamp': row_timestamps,
'isdone': row[11].lower() == 'true' if len(row) > 11 else False
}
# No duplicate found, create new conversation
conversation_id = generate_conversation_id(recipient_id, page_id, ts)
new_row = [
conversation_id,
originalcommand,
originalcontent,
json.dumps(originalattachments or []),
recipient_id,
page_id,
originaltext,
originalvehicle,
originalaction,
originalpurpose,
originalquestion, # <-- thêm dòng này
json.dumps(timestamp),
str(isdone).lower()
]
body = {
'values': [new_row]
}
range_name = SHEET_RANGE
self.service.spreadsheets().values().append(
spreadsheetId=self.sheet_id,
range=range_name,
valueInputOption='RAW',
body=body
).execute()
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))}")
# Return the conversation data directly
return {
'conversation_id': conversation_id,
'originalcommand': originalcommand,
'originalcontent': originalcontent,
'originalattachments': originalattachments or [],
'recipient_id': recipient_id,
'page_id': page_id,
'originaltext': originaltext,
'originalvehicle': originalvehicle,
'originalaction': originalaction,
'originalpurpose': originalpurpose,
'originalquestion': originalquestion,
'timestamp': timestamp,
'isdone': isdone
}
else:
# Update existing conversation
if not values:
logger.error("No data in sheet, cannot update conversation.")
return None
row_index = None
for i, row in enumerate(values):
if row[0] == conversation_id:
row_index = i
break
logger.info(f"[DEBUG] Gsheet row index {row_index}")
if row_index is not None:
sheet_row_number = row_index + 2 # +2 vì values[0] là dòng 2 trên sheet
current_row = values[row_index]
logger.info(f"[DEBUG] Gsheet current row {current_row}")
while len(current_row) < 13:
current_row.append("")
try:
current_timestamps = json.loads(current_row[10]) if current_row[10] else []
except Exception:
current_timestamps = []
if not isinstance(current_timestamps, list):
current_timestamps = [current_timestamps]
# Chỉ append nếu chưa có
for ts in timestamp:
if ts not in current_timestamps:
current_timestamps.append(ts)
new_row = [
conversation_id,
originalcommand if originalcommand else current_row[1],
originalcontent if originalcontent else current_row[2],
json.dumps(originalattachments) if originalattachments is not None else current_row[3],
recipient_id if recipient_id else current_row[4],
page_id if page_id else current_row[5],
originaltext if originaltext else current_row[6],
originalvehicle if originalvehicle else current_row[7],
originalaction if originalaction else current_row[8],
originalpurpose if originalpurpose else current_row[9],
originalquestion if originalquestion else current_row[10], # <-- thêm dòng này
json.dumps(current_timestamps),
str(isdone).lower() if isdone is not None else current_row[12]
]
update_range = f"{SHEET_RANGE.split('!')[0]}!A{sheet_row_number}"
logger.info(f"[DEBUG] Gsheet update range {update_range}")
body = {
'values': [new_row]
}
self.service.spreadsheets().values().update(
spreadsheetId=self.sheet_id,
range=update_range,
valueInputOption='RAW',
body=body
).execute()
changed_cols = ['conversation_id','originalcommand','originalcontent','originalattachments','recipient_id','page_id','originaltext','originalvehicle','originalaction','originalpurpose','originalquestion','timestamp','isdone']
for idx, (old, new) in enumerate(zip(current_row, new_row)):
if old != new:
changed_cols.append(changed_cols[idx])
logger.info(f"Cập nhật conversation: {conversation_id} tại dòng {sheet_row_number} | Cột cập nhật: {changed_cols} | Giá trị mới: {dict(zip(changed_cols, new_row))}")
# Return the updated conversation data
return {
'conversation_id': conversation_id,
'originalcommand': new_row[1],
'originalcontent': new_row[2],
'originalattachments': json.loads(new_row[3]) if new_row[3] else [],
'recipient_id': new_row[4],
'page_id': new_row[5],
'originaltext': new_row[6],
'originalvehicle': new_row[7],
'originalaction': new_row[8],
'originalpurpose': new_row[9],
'originalquestion': new_row[10], # <-- thêm dòng này
'timestamp': current_timestamps,
'isdone': new_row[12].lower() == 'true'
}
return None
except Exception as e:
logger.error(f"Error logging conversation: {e}")
return None |