From 0244b9bcb04148f6112a55d75060a4bec1862a44 Mon Sep 17 00:00:00 2001
From: sanj <67624670+iodrift@users.noreply.github.com>
Date: Wed, 26 Jun 2024 18:40:25 -0700
Subject: [PATCH] Auto-update: Wed Jun 26 18:40:25 PDT 2024

---
 sijapi/routers/llm.py     | 26 +++++++++++++++
 sijapi/routers/note.py    | 69 ++++++++++++++++++++++++++++++++-------
 sijapi/routers/serve.py   |  6 ++--
 sijapi/routers/weather.py |  2 +-
 4 files changed, 88 insertions(+), 15 deletions(-)

diff --git a/sijapi/routers/llm.py b/sijapi/routers/llm.py
index 87c8417..3433140 100644
--- a/sijapi/routers/llm.py
+++ b/sijapi/routers/llm.py
@@ -94,6 +94,32 @@ async def query_ollama(usr: str, sys: str = LLM_SYS_MSG, model: str = DEFAULT_LL
     else:
         L.DEBUG("No choices found in response")
         return None
+    
+async def query_ollama_multishot(
+    message_list: List[str],
+    sys: str = LLM_SYS_MSG,
+    model: str = DEFAULT_LLM,
+    max_tokens: int = 200
+):
+    if len(message_list) % 2 == 0:
+        raise ValueError("message_list must contain an odd number of strings")
+
+    messages = [{"role": "system", "content": sys}]
+    
+    for i in range(0, len(message_list), 2):
+        messages.append({"role": "user", "content": message_list[i]})
+        if i + 1 < len(message_list):
+            messages.append({"role": "assistant", "content": message_list[i+1]})
+
+    LLM = Ollama()
+    response = await LLM.chat(model=model, messages=messages, options={"num_predict": max_tokens})
+    L.DEBUG(response)
+
+    if "message" in response and "content" in response["message"]:
+        return response["message"]["content"]
+    else:
+        L.DEBUG("No content found in response")
+        return None
 
 def is_vision_request(content):
     return False
diff --git a/sijapi/routers/note.py b/sijapi/routers/note.py
index 318faf9..2491cb0 100644
--- a/sijapi/routers/note.py
+++ b/sijapi/routers/note.py
@@ -647,8 +647,29 @@ async def banner_endpoint(dt: str, location: str = None, mood: str = None, other
     return jpg_path
 
 
+async def get_note(date_time: datetime):
+    date_time = await locate.localize_datetime(date_time);
+    absolute_path, local_path = assemble_journal_path(date_time, filename = "Notes", extension = ".md", no_timestamp = True)
+
+    if absolute_path.is_file():
+        with open(absolute_path, 'r', encoding='utf-8') as file:
+            content = file.read()
+        return content if content else None
+
+async def sentiment_analysis(date_time: datetime):
+    most_recent_note = await get_note(date_time)
+    most_recent_note = most_recent_note or await get_note(date_time - timedelta(days=1))
+    if most_recent_note:
+        sys_msg = "You are a sentiment analysis AI bot. Your task is to analyze text and give a one-word description of the mood it contains, such as 'optimistic', 'pensive', 'nostalgic', 'livid', et cetera."
+        prompt = f"Provide sentiment analysis of the following notes: {most_recent_note}"
+        multishot_prompt = ["Provide sentiment analysis of the following notes: I am sad today my girlfriend broke up with me", "lonely", "Provide sentiment analysis of the following notes: Work has been so busy lately it is like there are not enough hours in the day", "hectic", prompt]
+        analysis = await llm.query_ollama_multishot(multishot_prompt, sys_msg, max_tokens = 10)
+        return analysis
+    else:
+        return ""
+
 async def generate_banner(dt, location: Location = None, forecast: str = None, mood: str = None, other_context: str = None):
-    L.DEBUG(f"Location: {location}, forecast: {forecast}, mood: {mood}, other_context: {other_context}")
+    # L.DEBUG(f"Location: {location}, forecast: {forecast}, mood: {mood}, other_context: {other_context}")
     date_time = await locate.localize_datetime(dt)
     L.DEBUG(f"generate_banner called with date_time: {date_time}")
     destination_path, local_path = assemble_journal_path(date_time, filename="Banner", extension=".jpg", no_timestamp = True)
@@ -662,12 +683,12 @@ async def generate_banner(dt, location: Location = None, forecast: str = None, m
     display_name = "Location: "
     if location:
         lat, lon = location.latitude, location.longitude
-        override_location = locate.find_override_locations(lat, lon)
+        override_location = await locate.find_override_locations(lat, lon)
         display_name += f"{override_location}, " if override_location else ""
         if location.display_name:
             display_name += f"{location.display_name}"
 
-        elif:
+        else:
             display_name += f"{location.road}, " if location.road else ""
             display_name += f"the {location.neighbourhood} neighbourhood of " if location.neighbourhood else ""
             display_name += f"the {location.suburb} suburb of " if location.suburb else ""
@@ -676,18 +697,43 @@ async def generate_banner(dt, location: Location = None, forecast: str = None, m
             display_name += f"{location.state} " if location.state else ""
             display_name += f"{location.country} " if location.country else ""
 
+        if display_name == "Location: ":
+            geocoded_location = await locate.reverse_geocode(lat, lon)
+            if geocoded_location.display_name or geocoded_location.city or geocoded_location.country:
+                return await generate_banner(dt, geocoded_location, forecast, mood, other_context)
+            else:
+                L.WARN(f"Failed to get a useable location for purposes of generating a banner, but we'll generate one anyway.")
     
     if not forecast:
         forecast = "The weather forecast is: " + await update_dn_weather(date_time)
-    
+
+    sentiment = await sentiment_analysis(date_time)
+    mood = sentiment if not mood else mood
     mood = f"Mood: {mood}" if mood else ""
-    other_context = f"Additional information: {other_context}" if other_context else ""
-    
+
+    if mood and sentiment: mood = f"Mood: {mood}, {sentiment}"
+    elif mood and not sentiment: mood = f"Mood: {mood}"
+    elif sentiment and not mood: mood = f"Mood: {sentiment}"
+    else: mood = ""
+
+    events = await calendar.get_events(date_time, date_time)
+    formatted_events = []
+    for event in events:
+        event_str = event.get('name')
+        if event.get('location'):
+            event_str += f" at {event.get('location')}"
+        formatted_events.append(event_str)
+
+    additional_info = ', '.join(formatted_events) if formatted_events else ''
+
+    other_context = f"{other_context}, {additional_info}" if other_context else additional_info
+    other_context = f"Additional information: {other_context}" if other_context else "" 
+
     prompt = "Generate an aesthetically appealing banner image for a daily note that helps to visualize the following scene information: "
     prompt += "\n".join([display_name, forecast, mood, other_context])
     L.DEBUG(f"Prompt: {prompt}")
     # sd.workflow(prompt: str, scene: str = None, size: str = None, style: str = "photorealistic", earlyurl: bool = False, destination_path: str = None):
-    final_path = await sd.workflow(prompt, scene=OBSIDIAN_BANNER_SCENE, size="1080x512", style="romantic", earlyout="local", destination_path=destination_path)
+    final_path = await sd.workflow(prompt, scene=OBSIDIAN_BANNER_SCENE, size="1080x512", style="romantic", destination_path=destination_path)
     if not str(local_path) in str(final_path):
         L.INFO(f"Apparent mismatch between local path, {local_path}, and final_path, {final_path}")
 
@@ -705,7 +751,7 @@ async def note_weather_get(
 ):
 
     try:
-        date_time = datetime.now() if date == "0" else locate.localize_datetime(date)
+        date_time = datetime.now() if date == "0" else await locate.localize_datetime(date)
         L.DEBUG(f"date: {date} .. date_time: {date_time}")
         content = await update_dn_weather(date_time) #, lat, lon)
         return JSONResponse(content={"forecast": content}, status_code=200)
@@ -735,7 +781,7 @@ async def update_dn_weather(date_time: datetime):
         lat = place.latitude
         lon = place.longitude
 
-        city = locate.find_override_locations(lat, lon)
+        city = await locate.find_override_locations(lat, lon)
         if city:
             L.INFO(f"Using override location: {city}")
 
@@ -745,7 +791,7 @@ async def update_dn_weather(date_time: datetime):
                 L.INFO(f"City in data: {city}")
 
             else:
-                loc = locate.reverse_geocode(lat, lon)
+                loc = await locate.reverse_geocode(lat, lon)
                 L.DEBUG(f"loc: {loc}")
                 city = loc.name
                 city = city if city else loc.city
@@ -801,7 +847,8 @@ async def update_dn_weather(date_time: datetime):
                     detailed_forecast = (
                         f"---\n"
                         f"date: {date_str}\n"
-                        f"zip: {zip}\n"
+                        f"latitude: {lat}"
+                        f"longitude: {lon}"
                         f"tags:\n"
                         f" - weather\n"
                         f"updated: {now}\n"
diff --git a/sijapi/routers/serve.py b/sijapi/routers/serve.py
index b5240e7..5452c4a 100644
--- a/sijapi/routers/serve.py
+++ b/sijapi/routers/serve.py
@@ -33,7 +33,7 @@ from sijapi import (
     MAC_UN, MAC_PW, MAC_ID, TS_TAILNET, DATA_DIR, SD_IMAGE_DIR, PUBLIC_KEY, OBSIDIAN_VAULT_DIR
 )
 from sijapi.utilities import bool_convert, sanitize_filename, assemble_journal_path
-from sijapi.routers.locate import localize_datetime
+from sijapi.routers import note, locate
 
 
 serve = APIRouter(tags=["public"])
@@ -67,9 +67,9 @@ def is_valid_date(date_str: str) -> bool:
         return False
 
 @serve.get("/notes/{file_path:path}")
-async def get_file(file_path: str):
+async def get_file_endpoint(file_path: str):
     try:
-        date_time = await localize_datetime(file_path);
+        date_time = await locate.localize_datetime(file_path);
         absolute_path, local_path = assemble_journal_path(date_time, no_timestamp = True)
     except ValueError as e:
         L.DEBUG(f"Unable to parse {file_path} as a date, now trying to use it as a local path")
diff --git a/sijapi/routers/weather.py b/sijapi/routers/weather.py
index 7175684..6f2ab5d 100644
--- a/sijapi/routers/weather.py
+++ b/sijapi/routers/weather.py
@@ -99,7 +99,7 @@ async def store_weather_to_db(date_time: datetime, weather_data: dict):
             # Get location details from weather data if available
             longitude = weather_data.get('longitude')
             latitude = weather_data.get('latitude')
-            elevation = locate.get_elevation(latitude, longitude) # 152.4  # default until we add a geocoder that can look up actual elevation; weather_data.get('elevation')  # assuming 'elevation' key, replace if different
+            elevation = await locate.get_elevation(latitude, longitude) # 152.4  # default until we add a geocoder that can look up actual elevation; weather_data.get('elevation')  # assuming 'elevation' key, replace if different
             location_point = f"POINTZ({longitude} {latitude} {elevation})" if longitude and latitude and elevation else None
 
             # Correct for the datetime objects