diff --git a/gonotego/uploader/slack/slack_uploader.py b/gonotego/uploader/slack/slack_uploader.py index 8fa7e54..2646a11 100644 --- a/gonotego/uploader/slack/slack_uploader.py +++ b/gonotego/uploader/slack/slack_uploader.py @@ -1,11 +1,13 @@ """Uploader for Slack workspace channels.""" import logging +import threading from typing import List, Optional from slack_sdk import WebClient from slack_sdk.errors import SlackApiError +from gonotego.command_center import assistant_commands from gonotego.common import events from gonotego.settings import settings @@ -21,6 +23,7 @@ def __init__(self): self._thread_ts: Optional[str] = None self._session_started: bool = False self._indent_level: int = 0 + self._session_notes: List[str] = [] @property def client(self) -> WebClient: @@ -75,6 +78,7 @@ def _start_session(self, first_note: str) -> bool: ) self._thread_ts = response['ts'] self._session_started = True + self._session_notes = [first_note] # Start collecting notes for the session return True except SlackApiError as e: logger.error(f"Error starting session: {e}") @@ -97,16 +101,71 @@ def _send_note_to_thread(self, text: str, indent_level: int = 0) -> bool: formatted_text = f"{indentation}{bullet} {text}" try: - self.client.chat_postMessage( + response = self.client.chat_postMessage( channel=channel_id, text=formatted_text, thread_ts=self._thread_ts ) + + # Start a background thread to clean up typos + if response and 'ts' in response: + message_ts = response['ts'] + self._start_typo_correction_thread(text, channel_id, message_ts, indent_level) + + # Add to session notes for later summarization + self._session_notes.append(text) + return True except SlackApiError as e: logger.error(f"Error sending note to thread: {e}") return False + def _start_typo_correction_thread(self, text: str, channel_id: str, message_ts: str, indent_level: int) -> None: + """Start a background thread to correct typos in the note.""" + thread = threading.Thread( + target=self._correct_typos_and_update, + args=(text, channel_id, message_ts, indent_level), + daemon=True + ) + thread.start() + + def _correct_typos_and_update(self, text: str, channel_id: str, message_ts: str, indent_level: int) -> None: + """Correct typos in the text and update the Slack message.""" + try: + # Create a prompt for the LLM to correct typos but preserve character + messages = [ + {"role": "system", "content": "You are a helpful assistant that corrects obvious typos in text. " + "Only fix clear typos that don't convey character or information. " + "Leave expressions that convey personality or uncertainty (like 'Hmm...', " + "'Errr', or informal shortenings) intact. Return ONLY the corrected text " + "with no explanations or additional commentary."}, + {"role": "user", "content": f"Correct obvious typos in this text, but preserve the character and style: {text}"} + ] + + # Get the corrected text from the LLM + response = assistant_commands.chat_completion(messages) + corrected_text = response.choices[0].message.content.strip() + + # Only update if there were changes and the message isn't empty + if corrected_text and corrected_text != text: + # Format the text based on indentation + formatted_text = corrected_text + if indent_level > 0: + bullet = "•" + indentation = " " * (indent_level - 1) + formatted_text = f"{indentation}{bullet} {corrected_text}" + + # Update the message in Slack + self.client.chat_update( + channel=channel_id, + ts=message_ts, + text=formatted_text + ) + logger.debug(f"Updated message with typo corrections: '{text}' -> '{corrected_text}'") + except Exception as e: + logger.error(f"Error in typo correction thread: {e}") + # Do not propagate the exception as this is a background task + def upload(self, note_events: List[events.NoteEvent]) -> bool: """Upload note events to Slack. @@ -155,9 +214,61 @@ def upload(self, note_events: List[events.NoteEvent]) -> bool: def end_session(self) -> None: """End the current session.""" + # Start the summarization thread if we have a valid session with notes + if self._session_started and self._thread_ts and self._session_notes: + channel_id = self._get_channel_id() + self._start_session_summary_thread( + channel_id, + self._thread_ts, + self._session_notes.copy() + ) + + # Reset session state self._thread_ts = None self._session_started = False self._indent_level = 0 + self._session_notes = [] + + def _start_session_summary_thread(self, channel_id: str, message_ts: str, session_notes: List[str]) -> None: + """Start a background thread to summarize the session and update the top message.""" + thread = threading.Thread( + target=self._summarize_session_and_update, + args=(channel_id, message_ts, session_notes), + daemon=True + ) + thread.start() + + def _summarize_session_and_update(self, channel_id: str, message_ts: str, session_notes: List[str]) -> None: + """Summarize the session and update the top-level message.""" + try: + # Create a prompt for the LLM to summarize the session + all_notes = "\n".join(session_notes) + messages = [ + {"role": "system", "content": "You are a helpful assistant that creates concise summaries. " + "Your summary should capture the key points and insights. " + "After your summary, on a separate line, include an importance rating " + "(Low/Medium/High), estimated reading time, and relevant #tags."}, + {"role": "user", "content": f"Here's a note-taking session. Please summarize it and provide metadata:\n\n{all_notes}"} + ] + + # Get the summary from the LLM + response = assistant_commands.chat_completion(messages) + summary = response.choices[0].message.content.strip() + + # Update the top-level message in Slack + first_note = session_notes[0] if session_notes else "" + updated_message = f"{first_note}\n\n{summary}\n\n:keyboard: Go Note Go thread." + + # Update the message in Slack + self.client.chat_update( + channel=channel_id, + ts=message_ts, + text=updated_message + ) + logger.debug("Updated top-level message with session summary") + except Exception as e: + logger.error(f"Error in session summarization thread: {e}") + # Do not propagate the exception as this is a background task def handle_inactivity(self) -> None: """Handle inactivity by ending the session and clearing client."""