Render next run time in user timezone in config, chat UIs

- Pass timezone string from ipapi to khoj via clients
  - Pass this data from web, desktop and obsidian clients to server
- Use user tz to render next run time of scheduled task in user tz
This commit is contained in:
Debanjum Singh Solanky 2024-04-27 00:56:49 +05:30
parent 6736551ba3
commit c17dbbeb92
5 changed files with 33 additions and 11 deletions

View file

@ -80,6 +80,7 @@ dependencies = [
"psutil >= 5.8.0",
"huggingface-hub >= 0.22.2",
"apscheduler ~= 3.10.0",
"pytz ~= 2024.1",
]
dynamic = ["version"]

View file

@ -40,6 +40,7 @@
let region = null;
let city = null;
let countryName = null;
let timezone = null;
fetch("https://ipapi.co/json")
.then(response => response.json())
@ -47,6 +48,7 @@
region = data.region;
city = data.city;
countryName = data.country_name;
timezone = data.timezone;
})
.catch(err => {
console.log(err);
@ -463,7 +465,7 @@
}
// Generate backend API URL to execute query
let chatApi = `${hostURL}/api/chat?q=${encodeURIComponent(query)}&n=${resultsCount}&client=web&stream=true&conversation_id=${conversationID}&region=${region}&city=${city}&country=${countryName}`;
let chatApi = `${hostURL}/api/chat?q=${encodeURIComponent(query)}&n=${resultsCount}&client=web&stream=true&conversation_id=${conversationID}&region=${region}&city=${city}&country=${countryName}&timezone=${timezone}`;
let newResponseEl = document.createElement("div");
newResponseEl.classList.add("chat-message", "khoj");

View file

@ -15,6 +15,7 @@ export class KhojChatModal extends Modal {
region: string;
city: string;
countryName: string;
timezone: string;
constructor(app: App, setting: KhojSetting) {
super(app);
@ -30,6 +31,7 @@ export class KhojChatModal extends Modal {
this.region = data.region;
this.city = data.city;
this.countryName = data.country_name;
this.timezone = data.timezone;
})
.catch(err => {
console.log(err);
@ -393,7 +395,7 @@ export class KhojChatModal extends Modal {
// Get chat response from Khoj backend
let encodedQuery = encodeURIComponent(query);
let chatUrl = `${this.setting.khojUrl}/api/chat?q=${encodedQuery}&n=${this.setting.resultsCount}&client=obsidian&stream=true&region=${this.region}&city=${this.city}&country=${this.countryName}`;
let chatUrl = `${this.setting.khojUrl}/api/chat?q=${encodedQuery}&n=${this.setting.resultsCount}&client=obsidian&stream=true&region=${this.region}&city=${this.city}&country=${this.countryName}&timezone=${this.timezone}`;
let responseElement = this.createKhojResponseDiv();
// Temporary status message to indicate that Khoj is thinking

View file

@ -58,6 +58,7 @@ To get started, just start typing below. You can also type / to see a list of co
let region = null;
let city = null;
let countryName = null;
let timezone = null;
let waitingForLocation = true;
let websocketState = {
@ -74,13 +75,14 @@ To get started, just start typing below. You can also type / to see a list of co
region = data.region;
city = data.city;
countryName = data.country_name;
timezone = data.timezone;
})
.catch(err => {
console.log(err);
return;
})
.finally(() => {
console.debug("Region:", region, "City:", city, "Country:", countryName);
console.debug("Region:", region, "City:", city, "Country:", countryName, "Timezone:", timezone);
waitingForLocation = false;
setupWebSocket();
});
@ -511,7 +513,7 @@ To get started, just start typing below. You can also type / to see a list of co
chatInput.classList.remove("option-enabled");
// Generate backend API URL to execute query
let url = `/api/chat?q=${encodeURIComponent(query)}&n=${resultsCount}&client=web&stream=true&conversation_id=${conversationID}&region=${region}&city=${city}&country=${countryName}`;
let url = `/api/chat?q=${encodeURIComponent(query)}&n=${resultsCount}&client=web&stream=true&conversation_id=${conversationID}&region=${region}&city=${city}&country=${countryName}&timezone=${timezone}`;
// Call specified Khoj API
let response = await fetch(url);
@ -906,7 +908,7 @@ To get started, just start typing below. You can also type / to see a list of co
if (chatBody.dataset.conversationId) {
webSocketUrl += `?conversation_id=${chatBody.dataset.conversationId}`;
webSocketUrl += (!!region && !!city && !!countryName) ? `&region=${region}&city=${city}&country=${countryName}` : '';
webSocketUrl += (!!region && !!city && !!countryName) && !!timezone ? `&region=${region}&city=${city}&country=${countryName}&timezone=${timezone}` : '';
websocket = new WebSocket(webSocketUrl);
websocket.onmessage = function(event) {

View file

@ -8,6 +8,7 @@ from datetime import datetime
from typing import Dict, Optional
from urllib.parse import unquote
import pytz
from apscheduler.triggers.cron import CronTrigger
from asgiref.sync import sync_to_async
from fastapi import APIRouter, Depends, HTTPException, Request, WebSocket
@ -273,6 +274,7 @@ async def websocket_endpoint(
city: Optional[str] = None,
region: Optional[str] = None,
country: Optional[str] = None,
timezone: Optional[str] = None,
):
connection_alive = True
@ -426,13 +428,19 @@ async def websocket_endpoint(
f"Unable to schedule reminder. Ensure the reminder doesn't already exist."
)
continue
# Display next run time in user timezone instead of UTC
user_timezone = pytz.timezone(timezone)
next_run_time_utc = job.next_run_time.replace(tzinfo=pytz.utc)
next_run_time_user_tz = next_run_time_utc.astimezone(user_timezone)
next_run_time = next_run_time_user_tz.strftime("%Y-%m-%d %H:%M %Z (%z)")
# Remove /task prefix from inferred_query
unprefixed_inferred_query = re.sub(r"^\/task\s*", "", inferred_query)
next_run_time = job.next_run_time.strftime("%Y-%m-%d %H:%M:%S")
# Create the scheduled task response
llm_response = f"""
### 🕒 Scheduled Task
- Query: **"{unprefixed_inferred_query}"**
- Schedule: `{crontime}`
- Next Run At: **{next_run_time}** UTC.
- Schedule: `{crontime}` UTC (+0000)
- Next Run At: **{next_run_time}**.
""".strip()
await sync_to_async(save_to_conversation_log)(
@ -608,6 +616,7 @@ async def chat(
city: Optional[str] = None,
region: Optional[str] = None,
country: Optional[str] = None,
timezone: Optional[str] = None,
rate_limiter_per_minute=Depends(
ApiUserRateLimiter(requests=5, subscribed_requests=60, window=60, slug="chat_minute")
),
@ -691,13 +700,19 @@ async def chat(
status_code=500,
)
# Display next run time in user timezone instead of UTC
user_timezone = pytz.timezone(timezone)
next_run_time_utc = job.next_run_time.replace(tzinfo=pytz.utc)
next_run_time_user_tz = next_run_time_utc.astimezone(user_timezone)
next_run_time = next_run_time_user_tz.strftime("%Y-%m-%d %H:%M %Z (%z)")
# Remove /task prefix from inferred_query
unprefixed_inferred_query = re.sub(r"^\/task\s*", "", inferred_query)
next_run_time = job.next_run_time.strftime("%Y-%m-%d %H:%M:%S")
# Create the scheduled task response
llm_response = f"""
### 🕒 Scheduled Task
- Query: **"{unprefixed_inferred_query}"**
- Schedule: `{crontime}`
- Next Run At: **{next_run_time}** UTC.'
- Schedule: `{crontime}` UTC (+0000)
- Next Run At: **{next_run_time}**.'
""".strip()
await sync_to_async(save_to_conversation_log)(