personal-chatbot / utils /chatLogger.py
mkschulz9's picture
bugfix: bugfixes for LLM streaming
674e8c0
import io
import json
from typing import List, Dict, Literal, Optional
from googleapiclient.discovery import build
from googleapiclient.http import MediaIoBaseUpload, MediaIoBaseDownload
from google.oauth2 import service_account
class ChatUploader:
def __init__(
self,
service_account_json: dict,
root_folder_id: str = "1KtfVgL1Rg1iX-ZMHH4Im__ss-pgbaDM9",
):
"""
Initializes a new chat uploader instance using a service account JSON dict.
By default writes into a fixed root folder.
"""
credentials = service_account.Credentials.from_service_account_info(
service_account_json,
scopes=["https://www.googleapis.com/auth/drive"],
)
# cache_discovery=False avoids deprecation noise
self.drive_service = build(
"drive", "v3", credentials=credentials, cache_discovery=False
)
self.root_folder_id = root_folder_id
def _get_or_create_browser_folder(self, browser_id: str) -> str:
"""
Ensure a per-browser folder 'browser_{browser_id}' exists; return its file ID.
"""
folder_name = f"browser_{browser_id}"
query = (
f"name = '{folder_name}' and '{self.root_folder_id}' in parents and "
"mimeType = 'application/vnd.google-apps.folder' and trashed = false"
)
results = self.drive_service.files().list(q=query, fields="files(id)").execute()
folders = results.get("files", [])
if folders:
return folders[0]["id"]
metadata = {
"name": folder_name,
"mimeType": "application/vnd.google-apps.folder",
"parents": [self.root_folder_id],
}
folder = self.drive_service.files().create(body=metadata, fields="id").execute()
return folder["id"]
def _find_file(self, name: str, parent_id: str) -> Optional[str]:
"""
Return file ID for a JSON file with given name in parent, else None.
"""
query = (
f"name = '{name}' and '{parent_id}' in parents and "
"mimeType = 'application/json' and trashed = false"
)
results = self.drive_service.files().list(q=query, fields="files(id)").execute()
files = results.get("files", [])
return files[0]["id"] if files else None
def upload_chat_history(
self,
chat_history: List[Dict[str, str]],
browser_id: str,
filename: str = "chat_log.json",
mode: Literal["overwrite", "append"] = "overwrite",
) -> None:
"""
Write the chat log inside the browser's folder.
- overwrite (default): REPLACE file contents with the provided chat_history
(this is what you want to keep Drive in sync with the UI)
- append: read existing JSON array and extend it with chat_history
chat_history is expected to be the *complete* transcript you want stored
(for overwrite), already normalized to [{role, content}, ...].
"""
folder_id = self._get_or_create_browser_folder(browser_id)
file_id = self._find_file(filename, folder_id)
payload: List[Dict[str, str]] = chat_history
if mode == "append" and file_id:
# Load existing file and extend
request = self.drive_service.files().get_media(fileId=file_id)
existing_stream = io.BytesIO()
downloader = MediaIoBaseDownload(existing_stream, request)
done = False
while not done:
_, done = downloader.next_chunk()
existing_stream.seek(0)
try:
existing_chat = json.loads(existing_stream.read())
if isinstance(existing_chat, list):
payload = existing_chat + chat_history
except json.JSONDecodeError:
# Fall back to current chat_history only
payload = chat_history
content = json.dumps(payload, ensure_ascii=False, indent=2).encode("utf-8")
media = MediaIoBaseUpload(io.BytesIO(content), mimetype="application/json")
if file_id:
# REPLACE contents
self.drive_service.files().update(
fileId=file_id, media_body=media
).execute()
else:
metadata = {
"name": filename,
"parents": [folder_id],
"mimeType": "application/json",
}
self.drive_service.files().create(body=metadata, media_body=media).execute()