diff --git a/src/khoj/interface/web/config_automation.html b/src/khoj/interface/web/config_automation.html index aa83bc7f..d8089053 100644 --- a/src/khoj/interface/web/config_automation.html +++ b/src/khoj/interface/web/config_automation.html @@ -2,16 +2,6 @@ {% block content %}
-
-
-

- Hold up! Our Automations are taking a well-deserved break for some maintenance magic. They'll be back in action soon, better than ever. Thanks for your patience! -

-
- Back to Chat -
-
-

Automate Automate (Preview) @@ -218,32 +208,6 @@ grid-gap: 8px; } - div#maintenance-notice { - display: flex; - justify-content: center; - align-items: center; - position: fixed; - top: 0; - right: 0; - bottom: 0; - left: 0; - z-index: 2; - background-image: linear-gradient(to bottom, rgba(250, 130, 130, 0.8), rgba(255, 165, 0, 0.8)); - } - - a.maintenance-action{ - color: white; - text-decoration: none; - } - - div.urgent-notice p, - div.urgent-notice a { - font-size: 24px; - font-weight: bold; - padding: 20px; - text-align: left; - } - div.subject-wrapper p { margin: 0; } @@ -273,7 +237,7 @@ } - + {% endblock %} diff --git a/src/khoj/main.py b/src/khoj/main.py index a8afc6d5..64cfc194 100644 --- a/src/khoj/main.py +++ b/src/khoj/main.py @@ -134,17 +134,17 @@ def run(should_start_server=True): poll_task_scheduler() # Setup Background Scheduler - # from django_apscheduler.jobstores import DjangoJobStore + from django_apscheduler.jobstores import DjangoJobStore - # state.scheduler = BackgroundScheduler( - # { - # "apscheduler.timezone": "UTC", - # "apscheduler.job_defaults.misfire_grace_time": "60", # Useful to run scheduled jobs even when worker delayed because it was busy or down - # "apscheduler.job_defaults.coalesce": "true", # Combine multiple jobs into one if they are scheduled at the same time - # } - # ) - # state.scheduler.add_jobstore(DjangoJobStore(), "default") - # state.scheduler.start() + state.scheduler = BackgroundScheduler( + { + "apscheduler.timezone": "UTC", + "apscheduler.job_defaults.misfire_grace_time": "60", # Useful to run scheduled jobs even when worker delayed because it was busy or down + "apscheduler.job_defaults.coalesce": "true", # Combine multiple jobs into one if they are scheduled at the same time + } + ) + state.scheduler.add_jobstore(DjangoJobStore(), "default") + state.scheduler.start() # Start Server configure_routes(app) diff --git a/src/khoj/routers/api.py b/src/khoj/routers/api.py index 9d4a8e6b..3b90dd80 100644 --- a/src/khoj/routers/api.py +++ b/src/khoj/routers/api.py @@ -6,6 +6,7 @@ import os import threading import time import uuid +from random import random from typing import Any, Callable, List, Optional, Union import cron_descriptor @@ -467,8 +468,15 @@ async def post_automation( crontime = " ".join(crontime.split(" ")[:5]) # Convert crontime to standard unix crontime crontime = crontime.replace("?", "*") - if crontime == "* * * * *": - return Response(content="Invalid crontime. Please create a more specific schedule.", status_code=400) + + # Disallow minute level automation recurrence + minute_value = crontime.split(" ")[0] + if not minute_value.isdigit(): + return Response( + content="Recurrence of every X minutes is unsupported. Please create a less frequent schedule.", + status_code=400, + ) + subject = await acreate_title_from_query(q) # Create new Conversation Session associated with this new task @@ -560,6 +568,14 @@ def edit_job( # Convert crontime to standard unix crontime crontime = crontime.replace("?", "*") + # Disallow minute level automation recurrence + minute_value = crontime.split(" ")[0] + if not minute_value.isdigit(): + return Response( + content="Recurrence of every X minutes is unsupported. Please create a less frequent schedule.", + status_code=400, + ) + # Construct updated automation metadata automation_metadata = json.loads(automation.name) automation_metadata["scheduling_request"] = q diff --git a/src/khoj/routers/helpers.py b/src/khoj/routers/helpers.py index ed526f5e..6581d253 100644 --- a/src/khoj/routers/helpers.py +++ b/src/khoj/routers/helpers.py @@ -4,10 +4,12 @@ import hashlib import io import json import logging +import math import re from concurrent.futures import ThreadPoolExecutor from datetime import datetime, timedelta, timezone from functools import partial +from random import random from typing import ( Annotated, Any, @@ -1014,8 +1016,6 @@ def scheduled_chat( async def create_automation(q: str, timezone: str, user: KhojUser, calling_url: URL, meta_log: dict = {}): crontime, query_to_run, subject = await schedule_query(q, meta_log) - if crontime == "* * * * *": - raise HTTPException(status_code=400, detail="Cannot run jobs constantly. Please provide a valid crontime.") job = await schedule_automation(query_to_run, subject, crontime, timezone, q, user, calling_url) return job, crontime, query_to_run, subject @@ -1029,9 +1029,13 @@ async def schedule_automation( user: KhojUser, calling_url: URL, ): + # Disable minute level automation recurrence + minute_value = crontime.split(" ")[0] + if not minute_value.isdigit(): + # Run automation at some random minute (to distribute request load) instead of running every X minutes + crontime = " ".join([str(math.floor(random() * 60))] + crontime.split(" ")[1:]) + user_timezone = pytz.timezone(timezone) - if crontime == "* * * * *": - raise HTTPException(status_code=400, detail="Cannot run jobs constantly. Please provide a valid crontime.") trigger = CronTrigger.from_crontab(crontime, user_timezone) trigger.jitter = 60 # Generate id and metadata used by task scheduler and process locks for the task runs