mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-05-29 19:35:20 +02:00
Closes the create loop in chat: the agent describes user intent → the drafter sub-LLM produces an AutomationCreate JSON → this card surfaces a structured preview → approve persists; reject cancels. Edits flow through chat refinement (re-call with a refined intent), not in-card, so the card stays simple and the multi-turn checkpointer carries the context. Tool UI (components/tool-ui/automation/): - create-automation.tsx — entry dispatcher + ApprovalCard chrome (pending/processing/complete/rejected via useHitlPhase) + SavedCard (links to the detail page) + InvalidCard (lists drafter validation issues) + ErrorCard (verbatim message). Rejection result is hidden because the approval card itself shows the rejected phase inline. - automation-draft-preview.tsx — structured preview body: name + description + goal, triggers (humanised cron + tz + static-input keys), plan steps (step_id → action), and a collapsible raw JSON for power users. Wiring: - components/tool-ui/index.ts — re-export. - features/chat-messages/timeline/tool-registry/registry.ts — register create_automation → CreateAutomationToolUI (dynamic import, same pattern as other connector tools). - contracts/enums/toolIcons.tsx — Workflow icon + "Create automation" display name so fallback chrome (and timeline headers) are honest. Shared util: - lib/automations/describe-cron.ts — lifted from the route slice's lib/ folder since both the dashboard slice and the new approval card now render schedule descriptions. Slice imports updated; the now- empty slice lib/ folder is gone. Backend prompt fragments: - main_agent/system_prompt/.../create_automation/description.md and the tool's docstring no longer promise in-card edits. They make the refinement path explicit: if the user wants changes after seeing the draft, they reply in chat and the agent calls the tool again with a refined intent. v1 deliberately excludes: - In-card edit form / right-side edit panel — defer until we see real demand. The chat refinement loop covers the common case. - approve_always / persistent allow rules — automations are a single artifact, not a repeated mutation, so the "trust this kind of call" affordance doesn't apply.
67 lines
2 KiB
TypeScript
67 lines
2 KiB
TypeScript
/**
|
||
* Minimal cron describer for the 5-field patterns the SurfSense drafter LLM
|
||
* actually produces (daily, weekdays, weekly, monthly, hourly). Falls back
|
||
* to the raw expression when unrecognized so the user still sees something
|
||
* honest instead of a guess.
|
||
*
|
||
* Lives under ``lib/automations/`` because both the dashboard slice and the
|
||
* chat ``create_automation`` approval card render schedule descriptions —
|
||
* keeping the helper outside either feature avoids a layering violation.
|
||
*/
|
||
|
||
const DAY_NAMES = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
||
|
||
export function describeCron(cron: string): string {
|
||
const parts = cron.trim().split(/\s+/);
|
||
if (parts.length !== 5) return cron;
|
||
|
||
const [minute, hour, dom, month, dow] = parts;
|
||
|
||
// Daily at H:MM ("0 9 * * *")
|
||
if (month === "*" && dom === "*" && dow === "*" && /^\d+$/.test(minute) && /^\d+$/.test(hour)) {
|
||
return `Daily at ${formatTime(hour, minute)}`;
|
||
}
|
||
|
||
// Weekdays at H:MM ("0 9 * * 1-5")
|
||
if (month === "*" && dom === "*" && dow === "1-5" && /^\d+$/.test(minute) && /^\d+$/.test(hour)) {
|
||
return `Mon–Fri at ${formatTime(hour, minute)}`;
|
||
}
|
||
|
||
// Specific weekday(s) ("0 9 * * 1" or "0 9 * * 1,3,5")
|
||
if (
|
||
month === "*" &&
|
||
dom === "*" &&
|
||
/^\d+$/.test(minute) &&
|
||
/^\d+$/.test(hour) &&
|
||
/^[\d,]+$/.test(dow)
|
||
) {
|
||
const days = dow
|
||
.split(",")
|
||
.map((d) => DAY_NAMES[Number(d) % 7])
|
||
.filter(Boolean)
|
||
.join(", ");
|
||
if (days) return `${days} at ${formatTime(hour, minute)}`;
|
||
}
|
||
|
||
// Monthly on day N ("0 9 1 * *")
|
||
if (
|
||
month === "*" &&
|
||
dow === "*" &&
|
||
/^\d+$/.test(dom) &&
|
||
/^\d+$/.test(hour) &&
|
||
/^\d+$/.test(minute)
|
||
) {
|
||
return `Day ${dom} of each month at ${formatTime(hour, minute)}`;
|
||
}
|
||
|
||
// Hourly ("0 * * * *")
|
||
if (month === "*" && dom === "*" && dow === "*" && hour === "*" && /^\d+$/.test(minute)) {
|
||
return minute === "0" ? "Every hour" : `Every hour at :${minute.padStart(2, "0")}`;
|
||
}
|
||
|
||
return cron;
|
||
}
|
||
|
||
function formatTime(hour: string, minute: string): string {
|
||
return `${hour.padStart(2, "0")}:${minute.padStart(2, "0")}`;
|
||
}
|