mirror of
https://github.com/khoj-ai/khoj.git
synced 2024-11-27 17:35:07 +01:00
Rename scheduled task to automations across code and UX
- Fix query, subject parameters passed to email template - Show 12 hour scheduled time in automation created chat message
This commit is contained in:
parent
230d160602
commit
2f9241b5a3
9 changed files with 128 additions and 134 deletions
|
@ -1,7 +1,7 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>Khoj AI - Task</title>
|
<title>Khoj AI - Automation</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<body style="font-family: 'Verdana', sans-serif; font-weight: 400; font-style: normal; padding: 0; text-align: left; width: 600px; margin: 20px auto;">
|
<body style="font-family: 'Verdana', sans-serif; font-weight: 400; font-style: normal; padding: 0; text-align: left; width: 600px; margin: 20px auto;">
|
||||||
|
@ -13,7 +13,7 @@
|
||||||
<div>
|
<div>
|
||||||
<h1 style="color: #333; font-size: large; font-weight: bold; margin: 0; line-height: 1.5; background-color: #fee285; padding: 8px; box-shadow: 6px 6px rgba(0, 0, 0, 1.5);">Your Open, Personal AI</h1>
|
<h1 style="color: #333; font-size: large; font-weight: bold; margin: 0; line-height: 1.5; background-color: #fee285; padding: 8px; box-shadow: 6px 6px rgba(0, 0, 0, 1.5);">Your Open, Personal AI</h1>
|
||||||
<p style="color: #333; font-size: medium; margin-top: 20px; padding: 0; line-height: 1.5;">Hey {{name}}! </p>
|
<p style="color: #333; font-size: medium; margin-top: 20px; padding: 0; line-height: 1.5;">Hey {{name}}! </p>
|
||||||
<p style="color: #333; font-size: medium; margin-top: 20px; padding: 0; line-height: 1.5;">I've shared your scheduled task results below:</p>
|
<p style="color: #333; font-size: medium; margin-top: 20px; padding: 0; line-height: 1.5;">I've shared your automation results below:</p>
|
||||||
|
|
||||||
<div style="display: grid; grid-template-columns: 1fr 1fr; grid-gap: 12px; margin-top: 20px;">
|
<div style="display: grid; grid-template-columns: 1fr 1fr; grid-gap: 12px; margin-top: 20px;">
|
||||||
<div style="border: 1px solid black; border-radius: 8px; padding: 8px; box-shadow: 6px 6px rgba(0, 0, 0, 1.0); margin-top: 20px;">
|
<div style="border: 1px solid black; border-radius: 8px; padding: 8px; box-shadow: 6px 6px rgba(0, 0, 0, 1.0); margin-top: 20px;">
|
||||||
|
@ -23,8 +23,8 @@
|
||||||
<p style="color: #333; font-size: medium; margin-top: 20px; padding: 0; line-height: 1.5;">{{result}}</p>
|
<p style="color: #333; font-size: medium; margin-top: 20px; padding: 0; line-height: 1.5;">{{result}}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p style="color: #333; font-size: medium; margin-top: 20px; padding: 0; line-height: 1.5;">The scheduled query I ran on your behalf: {query}</p>
|
<p style="color: #333; font-size: medium; margin-top: 20px; padding: 0; line-height: 1.5;">The automation query I ran on your behalf: {{query}}</p>
|
||||||
<p style="color: #333; font-size: medium; margin-top: 20px; padding: 0; line-height: 1.5;">You can view, delete and manage your scheduled tasks via <a href="https://app.khoj.dev/configure#tasks">the settings page</a></p>
|
<p style="color: #333; font-size: medium; margin-top: 20px; padding: 0; line-height: 1.5;">You can view, delete your automations via <a href="https://app.khoj.dev/configure#tasks">the settings page</a></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p style="color: #333; font-size: large; margin-top: 20px; padding: 0; line-height: 1.5;">- Khoj</p>
|
<p style="color: #333; font-size: large; margin-top: 20px; padding: 0; line-height: 1.5;">- Khoj</p>
|
||||||
|
|
|
@ -272,17 +272,17 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div id="tasks" class="section">
|
<div id="automations" class="section">
|
||||||
<h2 class="section-title">Scheduled Tasks</h2>
|
<h2 class="section-title">Automations</h2>
|
||||||
<div id="scheduled-tasks" class="api-settings">
|
<div id="automations" class="api-settings">
|
||||||
<div class="card-title-row">
|
<div class="card-title-row">
|
||||||
<img class="card-icon" src="/static/assets/icons/key.svg" alt="Scheduled Tasks">
|
<img class="card-icon" src="/static/assets/icons/key.svg" alt="Automations">
|
||||||
<h3 class="card-title">Tasks</h3>
|
<h3 class="card-title">Automations</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-description-row">
|
<div class="card-description-row">
|
||||||
<p id="tasks-settings-card-description" class="card-description">Manage your scheduled tasks</p>
|
<p id="tasks-settings-card-description" class="card-description">Manage your automations</p>
|
||||||
</div>
|
</div>
|
||||||
<table id="scheduled-tasks-table">
|
<table id="automations-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="col">Name</th>
|
<th scope="col">Name</th>
|
||||||
|
@ -292,10 +292,10 @@
|
||||||
<th scope="col">Actions</th>
|
<th scope="col">Actions</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody id="scheduled-tasks-list"></tbody>
|
<tbody id="automations-list"></tbody>
|
||||||
</table>
|
</table>
|
||||||
<div class="card-action-row">
|
<div class="card-action-row">
|
||||||
<button class="card-button happy" id="create-scheduled-task" onclick="createScheduledTask()">
|
<button class="card-button happy" id="create-automation" onclick="createAutomation()">
|
||||||
Create Task
|
Create Task
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -661,46 +661,42 @@
|
||||||
// List user's API keys on page load
|
// List user's API keys on page load
|
||||||
listApiKeys();
|
listApiKeys();
|
||||||
|
|
||||||
function deleteTask(taskId) {
|
function deleteAutomation(automationId) {
|
||||||
const scheduledTaskList = document.getElementById("scheduled-tasks-list");
|
const AutomationList = document.getElementById("automations-list");
|
||||||
fetch(`/api/task?task_id=${taskId}`, {
|
fetch(`/api/automation?automation_id=${automationId}`, {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
})
|
})
|
||||||
.then(response => {
|
.then(response => {
|
||||||
if (response.status == 200) {
|
if (response.status == 200) {
|
||||||
const scheduledTaskItem = document.getElementById(`scheduled-task-item-${taskId}`);
|
const AutomationItem = document.getElementById(`automation-item-${automationId}`);
|
||||||
scheduledTaskList.removeChild(scheduledTaskItem);
|
AutomationList.removeChild(AutomationItem);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateTaskRow(taskObj) {
|
function generateAutomationRow(automationObj) {
|
||||||
let taskId = taskObj.id;
|
let automationId = automationObj.id;
|
||||||
let taskSchedulingRequest = taskObj.scheduling_request;
|
let automationNextRun = `Next run at ${automationObj.next}`;
|
||||||
let taskQuery = taskObj.query_to_run;
|
|
||||||
let taskSubject = taskObj.subject;
|
|
||||||
let taskNextRun = `Next run at ${taskObj.next}`;
|
|
||||||
let taskSchedule = taskObj.schedule;
|
|
||||||
return `
|
return `
|
||||||
<tr id="scheduled-task-item-${taskId}">
|
<tr id="automation-item-${automationId}">
|
||||||
<td><b>${taskSubject}</b></td>
|
<td><b>${automationObj.subject}</b></td>
|
||||||
<td><b>${taskSchedulingRequest}</b></td>
|
<td><b>${automationObj.scheduling_request}</b></td>
|
||||||
<td><b>${taskQuery}</b></td>
|
<td><b>${automationObj.query_to_run}</b></td>
|
||||||
<td id="scheduled-task-${taskId}" title="${taskNextRun}">${taskSchedule}</td>
|
<td id="automation-${automationId}" title="${automationNextRun}">${automationObj.schedule}</td>
|
||||||
<td>
|
<td>
|
||||||
<img onclick="deleteTask('${taskId}')" class="configured-icon api-key-action enabled" src="/static/assets/icons/trash-solid.svg" alt="Delete Task" title="Delete Task">
|
<img onclick="deleteAutomation('${automationId}')" class="configured-icon api-key-action enabled" src="/static/assets/icons/trash-solid.svg" alt="Delete Automation" title="Delete Automation">
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function listScheduledTasks() {
|
function listAutomations() {
|
||||||
const scheduledTasksList = document.getElementById("scheduled-tasks-list");
|
const AutomationsList = document.getElementById("automations-list");
|
||||||
fetch('/api/tasks')
|
fetch('/api/automations')
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(tasks => {
|
.then(automations => {
|
||||||
if (!tasks?.length > 0) return;
|
if (!automations?.length > 0) return;
|
||||||
scheduledTasksList.innerHTML = tasks.map(generateTaskRow).join("");
|
AutomationsList.innerHTML = automations.map(generateAutomationRow).join("");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -713,8 +709,8 @@
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// List user's scheduled tasks on page load
|
// List user's automations on page load
|
||||||
listScheduledTasks();
|
listAutomations();
|
||||||
|
|
||||||
function removeFile(path) {
|
function removeFile(path) {
|
||||||
fetch('/api/config/data/file?filename=' + path, {
|
fetch('/api/config/data/file?filename=' + path, {
|
||||||
|
|
|
@ -507,7 +507,7 @@ Khoj:
|
||||||
""".strip()
|
""".strip()
|
||||||
)
|
)
|
||||||
|
|
||||||
# Schedule task
|
# Automations
|
||||||
# --
|
# --
|
||||||
crontime_prompt = PromptTemplate.from_template(
|
crontime_prompt = PromptTemplate.from_template(
|
||||||
"""
|
"""
|
||||||
|
@ -525,7 +525,7 @@ AI: Here is one I found: "It's not denial. I'm just selective about the reality
|
||||||
User: Hahah, nice! Show a new one every morning.
|
User: Hahah, nice! Show a new one every morning.
|
||||||
Khoj: {{
|
Khoj: {{
|
||||||
"crontime": "0 9 * * *",
|
"crontime": "0 9 * * *",
|
||||||
"query": "/task Share a funny Calvin and Hobbes or Bill Watterson quote from my notes",
|
"query": "/automated_task Share a funny Calvin and Hobbes or Bill Watterson quote from my notes",
|
||||||
"subject": "Your Calvin and Hobbes Quote for the Day"
|
"subject": "Your Calvin and Hobbes Quote for the Day"
|
||||||
}}
|
}}
|
||||||
|
|
||||||
|
@ -534,7 +534,7 @@ Khoj: {{
|
||||||
User: Every monday evening at 6 share the top posts on hacker news from last week. Format it as a newsletter
|
User: Every monday evening at 6 share the top posts on hacker news from last week. Format it as a newsletter
|
||||||
Khoj: {{
|
Khoj: {{
|
||||||
"crontime": "0 18 * * 1",
|
"crontime": "0 18 * * 1",
|
||||||
"query": "/task Top posts last week on Hacker News",
|
"query": "/automated_task Top posts last week on Hacker News",
|
||||||
"subject": "Your Weekly Top Hacker News Posts Newsletter"
|
"subject": "Your Weekly Top Hacker News Posts Newsletter"
|
||||||
}}
|
}}
|
||||||
|
|
||||||
|
@ -545,7 +545,7 @@ AI: The latest released Khoj python package version is 1.5.0.
|
||||||
User: Notify me when version 2.0.0 is released
|
User: Notify me when version 2.0.0 is released
|
||||||
Khoj: {{
|
Khoj: {{
|
||||||
"crontime": "0 10 * * *",
|
"crontime": "0 10 * * *",
|
||||||
"query": "/task What is the latest released version of the Khoj python package?",
|
"query": "/automated_task What is the latest released version of the Khoj python package?",
|
||||||
"subject": "Khoj Python Package Version 2.0.0 Release"
|
"subject": "Khoj Python Package Version 2.0.0 Release"
|
||||||
}}
|
}}
|
||||||
|
|
||||||
|
@ -554,7 +554,7 @@ Khoj: {{
|
||||||
User: Tell me the latest local tech news on the first sunday of every month
|
User: Tell me the latest local tech news on the first sunday of every month
|
||||||
Khoj: {{
|
Khoj: {{
|
||||||
"crontime": "0 8 1-7 * 0",
|
"crontime": "0 8 1-7 * 0",
|
||||||
"query": "/task Find the latest local tech, AI and engineering news. Format it as a newsletter.",
|
"query": "/automated_task Find the latest local tech, AI and engineering news. Format it as a newsletter.",
|
||||||
"subject": "Your Monthly Dose of Local Tech News"
|
"subject": "Your Monthly Dose of Local Tech News"
|
||||||
}}
|
}}
|
||||||
|
|
||||||
|
@ -563,7 +563,7 @@ Khoj: {{
|
||||||
User: Inform me when the national election results are declared. Run task at 4pm every thursday.
|
User: Inform me when the national election results are declared. Run task at 4pm every thursday.
|
||||||
Khoj: {{
|
Khoj: {{
|
||||||
"crontime": "0 16 * * 4",
|
"crontime": "0 16 * * 4",
|
||||||
"query": "/task Check if the Indian national election results are officially declared",
|
"query": "/automated_task Check if the Indian national election results are officially declared",
|
||||||
"subject": "Indian National Election Results Declared"
|
"subject": "Indian National Election Results Declared"
|
||||||
}}
|
}}
|
||||||
|
|
||||||
|
|
|
@ -102,7 +102,7 @@ def save_to_conversation_log(
|
||||||
intent_type: str = "remember",
|
intent_type: str = "remember",
|
||||||
client_application: ClientApplication = None,
|
client_application: ClientApplication = None,
|
||||||
conversation_id: int = None,
|
conversation_id: int = None,
|
||||||
job_id: str = None,
|
automation_id: str = None,
|
||||||
):
|
):
|
||||||
user_message_time = user_message_time or datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
user_message_time = user_message_time or datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||||
updated_conversation = message_to_log(
|
updated_conversation = message_to_log(
|
||||||
|
@ -113,7 +113,7 @@ def save_to_conversation_log(
|
||||||
"context": compiled_references,
|
"context": compiled_references,
|
||||||
"intent": {"inferred-queries": inferred_queries, "type": intent_type},
|
"intent": {"inferred-queries": inferred_queries, "type": intent_type},
|
||||||
"onlineContext": online_results,
|
"onlineContext": online_results,
|
||||||
"jobId": job_id,
|
"automationId": automation_id,
|
||||||
},
|
},
|
||||||
conversation_log=meta_log.get("chat", []),
|
conversation_log=meta_log.get("chat", []),
|
||||||
)
|
)
|
||||||
|
|
|
@ -391,59 +391,59 @@ def user_info(request: Request) -> Response:
|
||||||
return Response(content=json.dumps(user_info), media_type="application/json", status_code=200)
|
return Response(content=json.dumps(user_info), media_type="application/json", status_code=200)
|
||||||
|
|
||||||
|
|
||||||
@api.get("/tasks", response_class=Response)
|
@api.get("/automations", response_class=Response)
|
||||||
@requires(["authenticated"])
|
@requires(["authenticated"])
|
||||||
def get_jobs(request: Request) -> Response:
|
def get_automations(request: Request) -> Response:
|
||||||
user: KhojUser = request.user.object
|
user: KhojUser = request.user.object
|
||||||
tasks: list[Job] = state.scheduler.get_jobs()
|
automations: list[Job] = state.scheduler.get_jobs()
|
||||||
|
|
||||||
# Collate all tasks assigned by user that are still active
|
# Collate all automations created by user that are still active
|
||||||
tasks_info = []
|
automations_info = []
|
||||||
for task in tasks:
|
for automation in automations:
|
||||||
if task.id.startswith(f"job_{user.uuid}_"):
|
if automation.id.startswith(f"automation_{user.uuid}_"):
|
||||||
task_metadata = json.loads(task.name)
|
automation_metadata = json.loads(automation.name)
|
||||||
schedule = (
|
crontime = automation_metadata["crontime"]
|
||||||
f'{cron_descriptor.get_description(task_metadata["crontime"])} {task.next_run_time.strftime("%Z")}'
|
timezone = automation.next_run_time.strftime("%Z")
|
||||||
)
|
schedule = f"{cron_descriptor.get_description(crontime)} {timezone}"
|
||||||
tasks_info.append(
|
automations_info.append(
|
||||||
{
|
{
|
||||||
"id": task.id,
|
"id": automation.id,
|
||||||
"subject": task_metadata["subject"],
|
"subject": automation_metadata["subject"],
|
||||||
"query_to_run": re.sub(r"^/task\s*", "", task_metadata["query_to_run"]),
|
"query_to_run": re.sub(r"^/automated_task\s*", "", automation_metadata["query_to_run"]),
|
||||||
"scheduling_request": task_metadata["scheduling_request"],
|
"scheduling_request": automation_metadata["scheduling_request"],
|
||||||
"schedule": schedule,
|
"schedule": schedule,
|
||||||
"next": task.next_run_time.strftime("%Y-%m-%d %I:%M %p %Z"),
|
"next": automation.next_run_time.strftime("%Y-%m-%d %I:%M %p %Z"),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
# Return tasks information as a JSON response
|
# Return tasks information as a JSON response
|
||||||
return Response(content=json.dumps(tasks_info), media_type="application/json", status_code=200)
|
return Response(content=json.dumps(automations_info), media_type="application/json", status_code=200)
|
||||||
|
|
||||||
|
|
||||||
@api.delete("/task", response_class=Response)
|
@api.delete("/automation", response_class=Response)
|
||||||
@requires(["authenticated"])
|
@requires(["authenticated"])
|
||||||
def delete_job(request: Request, task_id: str) -> Response:
|
def delete_automation(request: Request, automation_id: str) -> Response:
|
||||||
user: KhojUser = request.user.object
|
user: KhojUser = request.user.object
|
||||||
|
|
||||||
# Perform validation checks
|
# Perform validation checks
|
||||||
# Check if user is allowed to delete this task id
|
# Check if user is allowed to delete this automation id
|
||||||
if not task_id.startswith(f"job_{user.uuid}_"):
|
if not automation_id.startswith(f"automation_{user.uuid}_"):
|
||||||
return Response(content="Unauthorized job deletion request", status_code=403)
|
return Response(content="Unauthorized job deletion request", status_code=403)
|
||||||
# Check if task with this task id exist
|
# Check if automation with this id exist
|
||||||
task: Job = state.scheduler.get_job(job_id=task_id)
|
automation: Job = state.scheduler.get_job(job_id=automation_id)
|
||||||
if not task:
|
if not automation:
|
||||||
return Response(content="Invalid job", status_code=403)
|
return Response(content="Invalid job", status_code=403)
|
||||||
|
|
||||||
# Collate info about user task to be deleted
|
# Collate info about user task to be deleted
|
||||||
task_metadata = json.loads(task.name)
|
automation_metadata = json.loads(automation.name)
|
||||||
task_info = {
|
automation_info = {
|
||||||
"id": task.id,
|
"id": automation.id,
|
||||||
"name": task_metadata["inferred_query"],
|
"name": automation_metadata["query_to_run"],
|
||||||
"next": task.next_run_time.strftime("%Y-%m-%d %H:%MS"),
|
"next": automation.next_run_time.strftime("%Y-%m-%d %I:%M %p %Z"),
|
||||||
}
|
}
|
||||||
|
|
||||||
# Delete job
|
# Delete job
|
||||||
task.remove()
|
automation.remove()
|
||||||
|
|
||||||
# Return delete task information as a JSON response
|
# Return deleted automation information as a JSON response
|
||||||
return Response(content=json.dumps(task_info), media_type="application/json", status_code=200)
|
return Response(content=json.dumps(automation_info), media_type="application/json", status_code=200)
|
||||||
|
|
|
@ -37,7 +37,7 @@ from khoj.routers.helpers import (
|
||||||
agenerate_chat_response,
|
agenerate_chat_response,
|
||||||
aget_relevant_information_sources,
|
aget_relevant_information_sources,
|
||||||
aget_relevant_output_modes,
|
aget_relevant_output_modes,
|
||||||
create_scheduled_task,
|
create_automation,
|
||||||
get_conversation_command,
|
get_conversation_command,
|
||||||
is_ready_to_chat,
|
is_ready_to_chat,
|
||||||
text_to_image,
|
text_to_image,
|
||||||
|
@ -217,7 +217,8 @@ async def chat_options(
|
||||||
) -> Response:
|
) -> Response:
|
||||||
cmd_options = {}
|
cmd_options = {}
|
||||||
for cmd in ConversationCommand:
|
for cmd in ConversationCommand:
|
||||||
cmd_options[cmd.value] = command_descriptions[cmd]
|
if cmd in command_descriptions:
|
||||||
|
cmd_options[cmd.value] = command_descriptions[cmd]
|
||||||
|
|
||||||
update_telemetry_state(
|
update_telemetry_state(
|
||||||
request=request,
|
request=request,
|
||||||
|
@ -373,14 +374,14 @@ async def websocket_endpoint(
|
||||||
continue
|
continue
|
||||||
|
|
||||||
meta_log = conversation.conversation_log
|
meta_log = conversation.conversation_log
|
||||||
is_task = conversation_commands == [ConversationCommand.Task]
|
is_automated_task = conversation_commands == [ConversationCommand.AutomatedTask]
|
||||||
|
|
||||||
if conversation_commands == [ConversationCommand.Default] or is_task:
|
if conversation_commands == [ConversationCommand.Default] or is_automated_task:
|
||||||
conversation_commands = await aget_relevant_information_sources(q, meta_log)
|
conversation_commands = await aget_relevant_information_sources(q, meta_log)
|
||||||
conversation_commands_str = ", ".join([cmd.value for cmd in conversation_commands])
|
conversation_commands_str = ", ".join([cmd.value for cmd in conversation_commands])
|
||||||
await send_status_update(f"**🗃️ Chose Data Sources to Search:** {conversation_commands_str}")
|
await send_status_update(f"**🗃️ Chose Data Sources to Search:** {conversation_commands_str}")
|
||||||
|
|
||||||
mode = await aget_relevant_output_modes(q, meta_log, is_task)
|
mode = await aget_relevant_output_modes(q, meta_log, is_automated_task)
|
||||||
await send_status_update(f"**🧑🏾💻 Decided Response Mode:** {mode.value}")
|
await send_status_update(f"**🧑🏾💻 Decided Response Mode:** {mode.value}")
|
||||||
if mode not in conversation_commands:
|
if mode not in conversation_commands:
|
||||||
conversation_commands.append(mode)
|
conversation_commands.append(mode)
|
||||||
|
@ -389,29 +390,31 @@ async def websocket_endpoint(
|
||||||
await conversation_command_rate_limiter.update_and_check_if_valid(websocket, cmd)
|
await conversation_command_rate_limiter.update_and_check_if_valid(websocket, cmd)
|
||||||
q = q.replace(f"/{cmd.value}", "").strip()
|
q = q.replace(f"/{cmd.value}", "").strip()
|
||||||
|
|
||||||
if ConversationCommand.Reminder in conversation_commands:
|
if ConversationCommand.Automation in conversation_commands:
|
||||||
try:
|
try:
|
||||||
job, crontime, inferred_query, subject = await create_scheduled_task(
|
automation, crontime, query_to_run, subject = await create_automation(
|
||||||
q, location, timezone, user, websocket.url, meta_log
|
q, location, timezone, user, websocket.url, meta_log
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error scheduling task {q} for {user.email}: {e}")
|
logger.error(f"Error scheduling task {q} for {user.email}: {e}")
|
||||||
await send_complete_llm_response(f"Unable to schedule task. Ensure the task doesn't already exist.")
|
await send_complete_llm_response(
|
||||||
|
f"Unable to create automation. Ensure the automation doesn't already exist."
|
||||||
|
)
|
||||||
continue
|
continue
|
||||||
# Display next run time in user timezone instead of UTC
|
# Display next run time in user timezone instead of UTC
|
||||||
schedule = f'{cron_descriptor.get_description(crontime)} {job.next_run_time.strftime("%Z")}'
|
schedule = f'{cron_descriptor.get_description(crontime)} {automation.next_run_time.strftime("%Z")}'
|
||||||
next_run_time = job.next_run_time.strftime("%Y-%m-%d %H:%M %Z")
|
next_run_time = automation.next_run_time.strftime("%Y-%m-%d %I:%M %p %Z")
|
||||||
# Remove /task prefix from inferred_query
|
# Remove /automated_task prefix from inferred_query
|
||||||
unprefixed_inferred_query = re.sub(r"^\/task\s*", "", inferred_query)
|
unprefixed_query_to_run = re.sub(r"^\/automated_task\s*", "", query_to_run)
|
||||||
# Create the scheduled task response
|
# Create the automation response
|
||||||
llm_response = f"""
|
llm_response = f"""
|
||||||
### 🕒 Scheduled Task
|
### 🕒 Automation
|
||||||
- Subject: **{subject}**
|
- Subject: **{subject}**
|
||||||
- Query: "{unprefixed_inferred_query}"
|
- Query to Run: "{unprefixed_query_to_run}"
|
||||||
- Schedule: `{schedule}`
|
- Schedule: `{schedule}`
|
||||||
- Next Run At: {next_run_time}
|
- Next Run At: {next_run_time}
|
||||||
|
|
||||||
Manage your tasks [here](/config#tasks).
|
Manage your tasks [here](/config#automations).
|
||||||
""".strip()
|
""".strip()
|
||||||
|
|
||||||
await sync_to_async(save_to_conversation_log)(
|
await sync_to_async(save_to_conversation_log)(
|
||||||
|
@ -420,11 +423,11 @@ Manage your tasks [here](/config#tasks).
|
||||||
user,
|
user,
|
||||||
meta_log,
|
meta_log,
|
||||||
user_message_time,
|
user_message_time,
|
||||||
intent_type="reminder",
|
intent_type="automation",
|
||||||
client_application=websocket.user.client_app,
|
client_application=websocket.user.client_app,
|
||||||
conversation_id=conversation_id,
|
conversation_id=conversation_id,
|
||||||
inferred_queries=[inferred_query],
|
inferred_queries=[query_to_run],
|
||||||
job_id=job.id,
|
automation_id=automation.id,
|
||||||
)
|
)
|
||||||
common = CommonQueryParamsClass(
|
common = CommonQueryParamsClass(
|
||||||
client=websocket.user.client_app,
|
client=websocket.user.client_app,
|
||||||
|
@ -621,7 +624,7 @@ async def chat(
|
||||||
else:
|
else:
|
||||||
meta_log = conversation.conversation_log
|
meta_log = conversation.conversation_log
|
||||||
|
|
||||||
is_task = conversation_commands == [ConversationCommand.Task]
|
is_task = conversation_commands == [ConversationCommand.AutomatedTask]
|
||||||
|
|
||||||
if conversation_commands == [ConversationCommand.Default] or is_task:
|
if conversation_commands == [ConversationCommand.Default] or is_task:
|
||||||
conversation_commands = await aget_relevant_information_sources(q, meta_log)
|
conversation_commands = await aget_relevant_information_sources(q, meta_log)
|
||||||
|
@ -640,32 +643,32 @@ async def chat(
|
||||||
|
|
||||||
user_name = await aget_user_name(user)
|
user_name = await aget_user_name(user)
|
||||||
|
|
||||||
if ConversationCommand.Reminder in conversation_commands:
|
if ConversationCommand.Automation in conversation_commands:
|
||||||
try:
|
try:
|
||||||
job, crontime, inferred_query, subject = await create_scheduled_task(
|
automation, crontime, query_to_run, subject = await create_automation(
|
||||||
q, location, timezone, user, request.url, meta_log
|
q, location, timezone, user, request.url, meta_log
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error scheduling task {q} for {user.email}: {e}")
|
logger.error(f"Error creating automation {q} for {user.email}: {e}")
|
||||||
return Response(
|
return Response(
|
||||||
content=f"Unable to schedule task. Ensure the task doesn't already exist.",
|
content=f"Unable to create automation. Ensure the automation doesn't already exist.",
|
||||||
media_type="text/plain",
|
media_type="text/plain",
|
||||||
status_code=500,
|
status_code=500,
|
||||||
)
|
)
|
||||||
# Display next run time in user timezone instead of UTC
|
# Display next run time in user timezone instead of UTC
|
||||||
schedule = f'{cron_descriptor.get_description(crontime)} {job.next_run_time.strftime("%Z")}'
|
schedule = f'{cron_descriptor.get_description(crontime)} {automation.next_run_time.strftime("%Z")}'
|
||||||
next_run_time = job.next_run_time.strftime("%Y-%m-%d %H:%M %Z")
|
next_run_time = automation.next_run_time.strftime("%Y-%m-%d %I:%M %p %Z")
|
||||||
# Remove /task prefix from inferred_query
|
# Remove /automated_task prefix from inferred_query
|
||||||
unprefixed_inferred_query = re.sub(r"^\/task\s*", "", inferred_query)
|
unprefixed_query_to_run = re.sub(r"^\/automated_task\s*", "", query_to_run)
|
||||||
# Create the scheduled task response
|
# Create the Automation response
|
||||||
llm_response = f"""
|
llm_response = f"""
|
||||||
### 🕒 Scheduled Task
|
### 🕒 Automation
|
||||||
- Subject: **{subject}**
|
- Subject: **{subject}**
|
||||||
- Query: "{unprefixed_inferred_query}"
|
- Query to Run: "{unprefixed_query_to_run}"
|
||||||
- Schedule: `{schedule}`
|
- Schedule: `{schedule}`
|
||||||
- Next Run At: {next_run_time}
|
- Next Run At: {next_run_time}
|
||||||
|
|
||||||
Manage your tasks [here](/config#tasks).
|
Manage your automations [here](/config#automations).
|
||||||
""".strip()
|
""".strip()
|
||||||
|
|
||||||
await sync_to_async(save_to_conversation_log)(
|
await sync_to_async(save_to_conversation_log)(
|
||||||
|
@ -674,11 +677,11 @@ Manage your tasks [here](/config#tasks).
|
||||||
user,
|
user,
|
||||||
meta_log,
|
meta_log,
|
||||||
user_message_time,
|
user_message_time,
|
||||||
intent_type="reminder",
|
intent_type="automation",
|
||||||
client_application=request.user.client_app,
|
client_application=request.user.client_app,
|
||||||
conversation_id=conversation_id,
|
conversation_id=conversation_id,
|
||||||
inferred_queries=[inferred_query],
|
inferred_queries=[query_to_run],
|
||||||
job_id=job.id,
|
automation_id=automation.id,
|
||||||
)
|
)
|
||||||
|
|
||||||
if stream:
|
if stream:
|
||||||
|
|
|
@ -58,7 +58,7 @@ def send_task_email(name, email, query, result, subject):
|
||||||
template = env.get_template("task.html")
|
template = env.get_template("task.html")
|
||||||
|
|
||||||
html_result = markdown_it.MarkdownIt().render(result)
|
html_result = markdown_it.MarkdownIt().render(result)
|
||||||
html_content = template.render(name=name, query=query, result=html_result)
|
html_content = template.render(name=name, subject=subject, query=query, result=html_result)
|
||||||
|
|
||||||
r = resend.Emails.send(
|
r = resend.Emails.send(
|
||||||
{
|
{
|
||||||
|
|
|
@ -170,8 +170,8 @@ def get_conversation_command(query: str, any_references: bool = False) -> Conver
|
||||||
return ConversationCommand.Online
|
return ConversationCommand.Online
|
||||||
elif query.startswith("/image"):
|
elif query.startswith("/image"):
|
||||||
return ConversationCommand.Image
|
return ConversationCommand.Image
|
||||||
elif query.startswith("/task"):
|
elif query.startswith("/automated_task"):
|
||||||
return ConversationCommand.Task
|
return ConversationCommand.AutomatedTask
|
||||||
# If no relevant notes found for the given query
|
# If no relevant notes found for the given query
|
||||||
elif not any_references:
|
elif not any_references:
|
||||||
return ConversationCommand.General
|
return ConversationCommand.General
|
||||||
|
@ -239,7 +239,7 @@ async def aget_relevant_output_modes(query: str, conversation_history: dict, is_
|
||||||
|
|
||||||
for mode, description in mode_descriptions_for_llm.items():
|
for mode, description in mode_descriptions_for_llm.items():
|
||||||
# Do not allow tasks to schedule another task
|
# Do not allow tasks to schedule another task
|
||||||
if is_task and mode == ConversationCommand.Reminder:
|
if is_task and mode == ConversationCommand.Automation:
|
||||||
continue
|
continue
|
||||||
mode_options[mode.value] = description
|
mode_options[mode.value] = description
|
||||||
mode_options_str += f'- "{mode.value}": "{description}"\n'
|
mode_options_str += f'- "{mode.value}": "{description}"\n'
|
||||||
|
@ -857,18 +857,14 @@ def should_notify(original_query: str, executed_query: str, ai_response: str) ->
|
||||||
response=ai_response,
|
response=ai_response,
|
||||||
)
|
)
|
||||||
|
|
||||||
with timer("Chat actor: Decide to notify user of AI response", logger):
|
with timer("Chat actor: Decide to notify user of automation response", logger):
|
||||||
try:
|
try:
|
||||||
response = send_message_to_model_wrapper_sync(to_notify_or_not)
|
response = send_message_to_model_wrapper_sync(to_notify_or_not)
|
||||||
should_notify_result = "no" not in response.lower()
|
should_notify_result = "no" not in response.lower()
|
||||||
logger.info(
|
logger.info(f'Decided to {"not " if not should_notify_result else ""}notify user of automation response.')
|
||||||
f'Decided to {"not " if not should_notify_result else ""}notify user of scheduled task response.'
|
|
||||||
)
|
|
||||||
return should_notify_result
|
return should_notify_result
|
||||||
except:
|
except:
|
||||||
logger.warning(
|
logger.warning(f"Fallback to notify user of automation response as failed to infer should notify or not.")
|
||||||
f"Fallback to notify user of scheduled task response as failed to infer should notify or not."
|
|
||||||
)
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
@ -904,7 +900,7 @@ def scheduled_chat(query_to_run: str, scheduling_request: str, subject: str, use
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Extract the AI response from the chat API response
|
# Extract the AI response from the chat API response
|
||||||
cleaned_query = re.sub(r"^/task\s*", "", query_to_run).strip()
|
cleaned_query = re.sub(r"^/automated_task\s*", "", query_to_run).strip()
|
||||||
if raw_response.headers.get("Content-Type") == "application/json":
|
if raw_response.headers.get("Content-Type") == "application/json":
|
||||||
response_map = raw_response.json()
|
response_map = raw_response.json()
|
||||||
ai_response = response_map.get("response") or response_map.get("image")
|
ai_response = response_map.get("response") or response_map.get("image")
|
||||||
|
@ -919,7 +915,7 @@ def scheduled_chat(query_to_run: str, scheduling_request: str, subject: str, use
|
||||||
return raw_response
|
return raw_response
|
||||||
|
|
||||||
|
|
||||||
async def create_scheduled_task(
|
async def create_automation(
|
||||||
q: str, location: LocationData, timezone: str, user: KhojUser, calling_url: URL, meta_log: dict = {}
|
q: str, location: LocationData, timezone: str, user: KhojUser, calling_url: URL, meta_log: dict = {}
|
||||||
):
|
):
|
||||||
user_timezone = pytz.timezone(timezone)
|
user_timezone = pytz.timezone(timezone)
|
||||||
|
@ -930,7 +926,7 @@ async def create_scheduled_task(
|
||||||
{"query_to_run": query_to_run, "scheduling_request": q, "subject": subject, "crontime": crontime_string}
|
{"query_to_run": query_to_run, "scheduling_request": q, "subject": subject, "crontime": crontime_string}
|
||||||
)
|
)
|
||||||
query_id = hashlib.md5(f"{query_to_run}".encode("utf-8")).hexdigest()
|
query_id = hashlib.md5(f"{query_to_run}".encode("utf-8")).hexdigest()
|
||||||
job_id = f"job_{user.uuid}_{crontime_string}_{query_id}"
|
job_id = f"automation_{user.uuid}_{crontime_string}_{query_id}"
|
||||||
job = state.scheduler.add_job(
|
job = state.scheduler.add_job(
|
||||||
run_with_process_lock,
|
run_with_process_lock,
|
||||||
trigger=trigger,
|
trigger=trigger,
|
||||||
|
|
|
@ -304,8 +304,8 @@ class ConversationCommand(str, Enum):
|
||||||
Online = "online"
|
Online = "online"
|
||||||
Webpage = "webpage"
|
Webpage = "webpage"
|
||||||
Image = "image"
|
Image = "image"
|
||||||
Reminder = "reminder"
|
Automation = "automation"
|
||||||
Task = "task"
|
AutomatedTask = "automated_task"
|
||||||
|
|
||||||
|
|
||||||
command_descriptions = {
|
command_descriptions = {
|
||||||
|
@ -315,8 +315,7 @@ command_descriptions = {
|
||||||
ConversationCommand.Online: "Search for information on the internet.",
|
ConversationCommand.Online: "Search for information on the internet.",
|
||||||
ConversationCommand.Webpage: "Get information from webpage links provided by you.",
|
ConversationCommand.Webpage: "Get information from webpage links provided by you.",
|
||||||
ConversationCommand.Image: "Generate images by describing your imagination in words.",
|
ConversationCommand.Image: "Generate images by describing your imagination in words.",
|
||||||
ConversationCommand.Reminder: "Schedule your query to run at a specified time or interval.",
|
ConversationCommand.Automation: "Automatically run your query at a specified time or interval.",
|
||||||
ConversationCommand.Task: "Scheduled task running at previously specified schedule.",
|
|
||||||
ConversationCommand.Help: "Display a help message with all available commands and other metadata.",
|
ConversationCommand.Help: "Display a help message with all available commands and other metadata.",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -330,7 +329,7 @@ tool_descriptions_for_llm = {
|
||||||
|
|
||||||
mode_descriptions_for_llm = {
|
mode_descriptions_for_llm = {
|
||||||
ConversationCommand.Image: "Use this if the user is requesting an image or visual response to their query.",
|
ConversationCommand.Image: "Use this if the user is requesting an image or visual response to their query.",
|
||||||
ConversationCommand.Reminder: "Use this if the user is requesting a response at a scheduled date or time.",
|
ConversationCommand.Automation: "Use this if the user is requesting a response at a scheduled date or time.",
|
||||||
ConversationCommand.Default: "Use this if the other response modes don't seem to fit the query.",
|
ConversationCommand.Default: "Use this if the other response modes don't seem to fit the query.",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue