(WIP) feat: Task Scheduler Management UI/UX and tools - Part 3

This commit is contained in:
Rafael Uzarowski 2025-04-30 20:59:10 +02:00
parent 282cbf90dc
commit eb275d9c9c
No known key found for this signature in database
GPG key ID: DEDFAEC7F474C685
37 changed files with 3417 additions and 673 deletions

View file

@ -1,16 +1,28 @@
from python.helpers.api import ApiHandler, Input, Output, Request, Response
from agent import AgentContext
from python.helpers import persist_chat
from python.helpers.task_scheduler import TaskScheduler
class RemoveChat(ApiHandler):
async def process(self, input: Input, request: Request) -> Output:
ctxid = input.get("context", "")
# context instance - get or create
context = AgentContext.get(ctxid)
if context:
# stop processing any tasks
context.reset()
AgentContext.remove(ctxid)
persist_chat.remove_chat(ctxid)
scheduler = TaskScheduler.get()
await scheduler.reload()
tasks = scheduler.get_tasks_by_context_id(ctxid)
for task in tasks:
await scheduler.remove_task_by_uuid(task.uuid)
return {
"message": "Context removed.",
}

View file

@ -1,5 +1,5 @@
import time
from datetime import datetime
from python.helpers.api import ApiHandler
from flask import Request, Response
@ -7,6 +7,8 @@ from agent import AgentContext
from python.helpers import persist_chat
from python.helpers.task_scheduler import TaskScheduler
from python.helpers.localization import Localization
from python.helpers.dotenv import get_dotenv_value
class Poll(ApiHandler):
@ -15,6 +17,10 @@ class Poll(ApiHandler):
ctxid = input.get("context", None)
from_no = input.get("log_from", 0)
# Get timezone from input (default to dotenv default or UTC if not provided)
timezone = input.get("timezone", get_dotenv_value("DEFAULT_USER_TIMEZONE", "UTC"))
Localization.get().set_timezone(timezone)
# context instance - get or create
context = self.get_context(ctxid)
@ -28,7 +34,7 @@ class Poll(ApiHandler):
# Always reload the scheduler on each poll to ensure we have the latest task state
await scheduler.reload()
# loop AgentContext._contexts and number unnamed chats
# loop AgentContext._contexts and divide into contexts and tasks
ctxs = []
tasks = []
@ -42,21 +48,15 @@ class Poll(ApiHandler):
continue
# Create the base context data that will be returned
context_data = {
"id": ctx.id,
"name": ctx.name,
"created_at": ctx.created_at,
"no": ctx.no,
"log_guid": ctx.log.guid,
"log_version": len(ctx.log.updates),
"log_length": len(ctx.log.logs),
"paused": ctx.paused,
}
context_data = ctx.serialize()
# Determine if this is a task by checking if a task with this UUID exists
is_task = scheduler.get_task_by_uuid(ctx.id) is not None
context_task = scheduler.get_task_by_uuid(ctx.id)
# Determine if this is a task-dedicated context by checking if a task with this UUID exists
is_task_context = (
context_task is not None and context_task.context_id == ctx.id
)
if not is_task:
if not is_task_context:
ctxs.append(context_data)
else:
# If this is a task, get task details from the scheduler
@ -65,6 +65,7 @@ class Poll(ApiHandler):
# Add task details to context_data with the same field names
# as used in scheduler endpoints to maintain UI compatibility
context_data.update({
"task_name": task_details.get("name"), # name is for context, task_name for the task name
"uuid": task_details.get("uuid"),
"state": task_details.get("state"),
"type": task_details.get("type"),
@ -72,12 +73,15 @@ class Poll(ApiHandler):
"prompt": task_details.get("prompt"),
"last_run": task_details.get("last_run"),
"last_result": task_details.get("last_result"),
"attachments": task_details.get("attachments", [])
"attachments": task_details.get("attachments", []),
"context_id": task_details.get("context_id"),
})
# Add type-specific fields
if task_details.get("type") == "scheduled":
context_data["schedule"] = task_details.get("schedule")
elif task_details.get("type") == "planned":
context_data["plan"] = task_details.get("plan")
else:
context_data["token"] = task_details.get("token")

View file

@ -1,5 +0,0 @@
## JSON Body
# api_key
# task_id
# params
# callback

View file

@ -1,8 +1,11 @@
from python.helpers.api import ApiHandler, Input, Output, Request
from python.helpers.task_scheduler import (
TaskScheduler, ScheduledTask, AdHocTask, TaskSchedule,
serialize_task, parse_task_schedule
TaskScheduler, ScheduledTask, AdHocTask, PlannedTask, TaskSchedule,
serialize_task, parse_task_schedule, parse_task_plan, TaskType
)
from python.helpers.localization import Localization
from python.helpers.print_style import PrintStyle
import random
class SchedulerTaskCreate(ApiHandler):
@ -10,6 +13,12 @@ class SchedulerTaskCreate(ApiHandler):
"""
Create a new task in the scheduler
"""
printer = PrintStyle(italic=True, font_color="blue", padding=False)
# Get timezone from input (do not set if not provided, we then rely on poll() to set it)
if timezone := input.get("timezone", None):
Localization.get().set_timezone(timezone)
scheduler = TaskScheduler.get()
await scheduler.reload()
@ -18,11 +27,22 @@ class SchedulerTaskCreate(ApiHandler):
system_prompt = input.get("system_prompt")
prompt = input.get("prompt")
attachments = input.get("attachments", [])
context_id = input.get("context_id", None)
# Check if schedule is provided (for ScheduledTask)
schedule = input.get("schedule", {})
token: str = input.get("token", "")
# Debug log the token value
printer.print(f"Token received from frontend: '{token}' (type: {type(token)}, length: {len(token) if token else 0})")
# Generate a random token if empty or not provided
if not token:
token = str(random.randint(1000000000000000000, 9999999999999999999))
printer.print(f"Generated new token: '{token}'")
plan = input.get("plan", {})
# Validate required fields
if not name or not system_prompt or not prompt:
return {"error": "Missing required fields: name, system_prompt, prompt"}
@ -55,24 +75,61 @@ class SchedulerTaskCreate(ApiHandler):
system_prompt=system_prompt,
prompt=prompt,
schedule=task_schedule,
attachments=attachments
attachments=attachments,
context_id=context_id,
timezone=timezone
)
elif plan:
# Create a planned task
try:
# Use our standardized parsing function
task_plan = parse_task_plan(plan)
except ValueError as e:
return {"error": str(e)}
task = PlannedTask.create(
name=name,
system_prompt=system_prompt,
prompt=prompt,
plan=task_plan,
attachments=attachments,
context_id=context_id
)
else:
# Create an ad-hoc task
printer.print(f"Creating AdHocTask with token: '{token}'")
task = AdHocTask.create(
name=name,
system_prompt=system_prompt,
prompt=prompt,
token=token,
attachments=attachments
attachments=attachments,
context_id=context_id
)
# Verify token after creation
if isinstance(task, AdHocTask):
printer.print(f"AdHocTask created with token: '{task.token}'")
# Add the task to the scheduler
await scheduler.add_task(task)
# Verify the task was added correctly - retrieve by UUID to check persistence
saved_task = scheduler.get_task_by_uuid(task.uuid)
if saved_task:
if saved_task.type == TaskType.AD_HOC and isinstance(saved_task, AdHocTask):
printer.print(f"Task verified after save, token: '{saved_task.token}'")
else:
printer.print("Task verified after save, not an adhoc task")
else:
printer.print("WARNING: Task not found after save!")
# Return the created task using our standardized serialization function
task_dict = serialize_task(task)
# Debug log the serialized task
if task_dict and task_dict.get('type') == 'adhoc':
printer.print(f"Serialized adhoc task, token in response: '{task_dict.get('token')}'")
return {
"task": task_dict
}

View file

@ -1,5 +1,8 @@
from python.helpers.api import ApiHandler, Input, Output, Request
from python.helpers.task_scheduler import TaskScheduler
from python.helpers.task_scheduler import TaskScheduler, TaskState
from python.helpers.localization import Localization
from agent import AgentContext
from python.helpers import persist_chat
class SchedulerTaskDelete(ApiHandler):
@ -7,6 +10,10 @@ class SchedulerTaskDelete(ApiHandler):
"""
Delete a task from the scheduler by ID
"""
# Get timezone from input (do not set if not provided, we then rely on poll() to set it)
if timezone := input.get("timezone", None):
Localization.get().set_timezone(timezone)
scheduler = TaskScheduler.get()
await scheduler.reload()
@ -21,6 +28,24 @@ class SchedulerTaskDelete(ApiHandler):
if not task:
return {"error": f"Task with ID {task_id} not found"}
context = None
if task.context_id:
context = self.get_context(task.context_id)
# If the task is running, update its state to IDLE first
if task.state == TaskState.RUNNING:
if context:
context.reset()
# Update the state to IDLE so any ongoing processes know to terminate
await scheduler.update_task(task_id, state=TaskState.IDLE)
# Force a save to ensure the state change is persisted
await scheduler.save()
# This is a dedicated context for the task, so we remove it
if context and context.id == task.uuid:
AgentContext.remove(context.id)
persist_chat.remove_chat(context.id)
# Remove the task
await scheduler.remove_task_by_uuid(task_id)

View file

@ -1,6 +1,7 @@
from python.helpers.api import ApiHandler, Input, Output, Request
from python.helpers.task_scheduler import TaskScheduler, TaskState
from python.helpers.print_style import PrintStyle
from python.helpers.localization import Localization
class SchedulerTaskRun(ApiHandler):
@ -11,6 +12,9 @@ class SchedulerTaskRun(ApiHandler):
"""
Manually run a task from the scheduler by ID
"""
# Get timezone from input (do not set if not provided, we then rely on poll() to set it)
if timezone := input.get("timezone", None):
Localization.get().set_timezone(timezone)
# Get task ID from input
task_id: str = input.get("task_id", "")

View file

@ -1,8 +1,9 @@
from python.helpers.api import ApiHandler, Input, Output, Request
from python.helpers.task_scheduler import (
TaskScheduler, ScheduledTask, AdHocTask, TaskState,
serialize_task, parse_task_schedule
TaskScheduler, ScheduledTask, AdHocTask, PlannedTask, TaskState,
serialize_task, parse_task_schedule, parse_task_plan
)
from python.helpers.localization import Localization
class SchedulerTaskUpdate(ApiHandler):
@ -10,6 +11,10 @@ class SchedulerTaskUpdate(ApiHandler):
"""
Update an existing task in the scheduler
"""
# Get timezone from input (do not set if not provided, we then rely on poll() to set it)
if timezone := input.get("timezone", None):
Localization.get().set_timezone(timezone)
scheduler = TaskScheduler.get()
await scheduler.reload()
@ -47,11 +52,28 @@ class SchedulerTaskUpdate(ApiHandler):
if isinstance(task, ScheduledTask) and "schedule" in input:
schedule_data = input.get("schedule", {})
try:
update_params["schedule"] = parse_task_schedule(schedule_data)
# Parse the schedule with timezone handling
task_schedule = parse_task_schedule(schedule_data)
# Set the timezone from the request if not already in schedule_data
if not schedule_data.get('timezone', None) and timezone:
task_schedule.timezone = timezone
update_params["schedule"] = task_schedule
except ValueError as e:
return {"error": f"Invalid schedule format: {str(e)}"}
elif isinstance(task, AdHocTask) and "token" in input:
update_params["token"] = input.get("token", "")
token_value = input.get("token", "")
if token_value: # Only update if non-empty
update_params["token"] = token_value
elif isinstance(task, PlannedTask) and "plan" in input:
plan_data = input.get("plan", {})
try:
# Parse the plan data
task_plan = parse_task_plan(plan_data)
update_params["plan"] = task_plan
except ValueError as e:
return {"error": f"Invalid plan format: {str(e)}"}
# Use atomic update method to apply changes
updated_task = await scheduler.update_task(task_id, **update_params)

View file

@ -2,6 +2,7 @@ from python.helpers.api import ApiHandler, Input, Output, Request
from python.helpers.task_scheduler import TaskScheduler
import traceback
from python.helpers.print_style import PrintStyle
from python.helpers.localization import Localization
class SchedulerTasksList(ApiHandler):
@ -10,6 +11,10 @@ class SchedulerTasksList(ApiHandler):
List all tasks in the scheduler with their types
"""
try:
# Get timezone from input (do not set if not provided, we then rely on poll() to set it)
if timezone := input.get("timezone", None):
Localization.get().set_timezone(timezone)
# Get task scheduler
scheduler = TaskScheduler.get()
await scheduler.reload()

View file

@ -3,14 +3,19 @@ from datetime import datetime
from python.helpers.api import ApiHandler, Input, Output, Request
from python.helpers.print_style import PrintStyle
from python.helpers.task_scheduler import TaskScheduler
from python.helpers.localization import Localization
class SchedulerTick(ApiHandler):
@classmethod
def requires_loopback(cls):
def requires_loopback(cls) -> bool:
return True
async def process(self, input: Input, request: Request) -> Output:
# Get timezone from input (do not set if not provided, we then rely on poll() to set it)
if timezone := input.get("timezone", None):
Localization.get().set_timezone(timezone)
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
printer = PrintStyle(font_color="green", padding=False)
printer.print(f"Scheduler tick - API: {timestamp}")