Start a workflow from the switcher
This commit is contained in:
+85
-13
@@ -1,4 +1,4 @@
|
||||
import { post } from "./api.js";
|
||||
import { get, post, type Project, type WorkflowSummary } from "./api.js";
|
||||
import { errorText, h, replace, showAlert } from "./dom.js";
|
||||
import { focusLead } from "./panes.js";
|
||||
import { activeTasks, findTask, focusedProject, focusProject, focusTab, focusTask, lastVisits, onEach, state, taskState } from "./state.js";
|
||||
@@ -49,6 +49,7 @@ const shortcuts: [string, [string, string][]][] = [
|
||||
["↑ ↓", "Move the highlight"],
|
||||
["Enter", "Pick the highlighted row"],
|
||||
["name: title", "Create the task in the project whose name starts with name"],
|
||||
["Esc", "Back from the workflow list to the task list"],
|
||||
],
|
||||
],
|
||||
[
|
||||
@@ -89,12 +90,14 @@ export function openSwitcher(): void {
|
||||
return;
|
||||
}
|
||||
|
||||
const input = h("input", { placeholder: "Go to a task, or type a title to create one…", autocomplete: "off" });
|
||||
const placeholder = "Go to a task, or type a title to create one…";
|
||||
const input = h("input", { placeholder, autocomplete: "off" });
|
||||
const list = h("ul", { class: "switcher-list" });
|
||||
const backdrop = h("div", { class: "switcher-backdrop" });
|
||||
const box = h("div", { class: "switcher" }, input, list);
|
||||
let selected = 0;
|
||||
let rows: { el: HTMLElement; pick: () => void }[] = [];
|
||||
let picking: { project: Project; title: string; typed: string; workflows: WorkflowSummary[] | undefined } | null = null;
|
||||
|
||||
const offs = onEach(["projects", "tasks", "sessions", "inbox"], () => render());
|
||||
const onEscape = (e: KeyboardEvent) => {
|
||||
@@ -103,6 +106,14 @@ export function openSwitcher(): void {
|
||||
}
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
if (picking) {
|
||||
input.value = picking.typed;
|
||||
input.placeholder = placeholder;
|
||||
picking = null;
|
||||
selected = 0;
|
||||
render();
|
||||
return;
|
||||
}
|
||||
close();
|
||||
};
|
||||
addEventListener("keydown", onEscape, { capture: true });
|
||||
@@ -118,17 +129,79 @@ export function openSwitcher(): void {
|
||||
focusTask(taskId, state.inbox.some((item) => item.task_id === taskId) ? "inbox" : "transcript");
|
||||
};
|
||||
|
||||
const create = async (projectId: string, title: string) => {
|
||||
const create = async (projectId: string, title: string, workflow?: string) => {
|
||||
close();
|
||||
try {
|
||||
const task = await post("/api/projects/{projectId}/tasks", { path: { projectId }, body: { title } });
|
||||
const task = await post("/api/projects/{projectId}/tasks", { path: { projectId }, body: { title, workflow } });
|
||||
go(task.id);
|
||||
} catch (err) {
|
||||
void showAlert(errorText(err));
|
||||
}
|
||||
};
|
||||
|
||||
const pickWorkflow = async (project: Project, title: string) => {
|
||||
const p = { project, title, typed: input.value, workflows: undefined as WorkflowSummary[] | undefined };
|
||||
picking = p;
|
||||
input.value = "";
|
||||
input.placeholder = `Pick a workflow to start in ${project.name}: ${title}`;
|
||||
selected = 0;
|
||||
render();
|
||||
input.focus();
|
||||
try {
|
||||
p.workflows = await get("/api/workflows");
|
||||
} catch (err) {
|
||||
close();
|
||||
void showAlert(errorText(err));
|
||||
return;
|
||||
}
|
||||
if (picking === p) {
|
||||
render();
|
||||
}
|
||||
};
|
||||
|
||||
const renderWorkflows = (p: NonNullable<typeof picking>) => {
|
||||
const q = input.value.trim().toLowerCase();
|
||||
rows = (p.workflows ?? [])
|
||||
.filter((w) => `${w.name} ${w.description}`.toLowerCase().includes(q))
|
||||
.map((w) => {
|
||||
const el = h(
|
||||
"li",
|
||||
{ class: "new-task" },
|
||||
h(
|
||||
"span",
|
||||
{ class: "task-title" },
|
||||
"Start ",
|
||||
h("span", { class: "workflow-name" }, w.name),
|
||||
" in ",
|
||||
h("span", { class: "project-name" }, p.project.name),
|
||||
": ",
|
||||
p.title,
|
||||
w.description && h("span", { class: "workflow-description" }, w.description),
|
||||
),
|
||||
h("span", { class: "task-state" }, "workflow"),
|
||||
);
|
||||
return { el, pick: () => void create(p.project.id, p.title, w.name) };
|
||||
});
|
||||
if (p.workflows === undefined) {
|
||||
return "Loading workflows…";
|
||||
}
|
||||
return p.workflows.length === 0 ? "No saved workflows." : "No workflow matches.";
|
||||
};
|
||||
|
||||
const render = () => {
|
||||
const empty = picking ? renderWorkflows(picking) : renderTasks();
|
||||
selected = Math.min(selected, Math.max(rows.length - 1, 0));
|
||||
rows.forEach((row, i) => {
|
||||
row.el.classList.toggle("selected", i === selected);
|
||||
row.el.onmousedown = (e) => {
|
||||
e.preventDefault();
|
||||
row.pick();
|
||||
};
|
||||
});
|
||||
replace(list, ...rows.map((r) => r.el), rows.length === 0 && h("li", { class: "empty-row" }, empty));
|
||||
};
|
||||
|
||||
const renderTasks = () => {
|
||||
const typed = input.value.trim();
|
||||
const prefix = /^([^:\s]+):\s*(.*)$/.exec(typed);
|
||||
const named = prefix ? state.projects.filter((p) => p.name.toLowerCase().startsWith(prefix[1]!.toLowerCase())) : [];
|
||||
@@ -181,16 +254,15 @@ export function openSwitcher(): void {
|
||||
h("span", { class: "task-state", title: "Model for the first message" }, models[0]),
|
||||
);
|
||||
rows.push({ el, pick: () => void create(project.id, title) });
|
||||
const wf = h(
|
||||
"li",
|
||||
{ class: "new-task" },
|
||||
h("span", { class: "task-title" }, "Start workflow… in ", h("span", { class: "project-name" }, project.name), ": ", title),
|
||||
h("span", { class: "task-state" }, "workflow"),
|
||||
);
|
||||
rows.push({ el: wf, pick: () => void pickWorkflow(project, title) });
|
||||
}
|
||||
selected = Math.min(selected, Math.max(rows.length - 1, 0));
|
||||
rows.forEach((row, i) => {
|
||||
row.el.classList.toggle("selected", i === selected);
|
||||
row.el.onmousedown = (e) => {
|
||||
e.preventDefault();
|
||||
row.pick();
|
||||
};
|
||||
});
|
||||
replace(list, ...rows.map((r) => r.el), rows.length === 0 && h("li", { class: "empty-row" }, "No active tasks. Type a title to create one."));
|
||||
return "No active tasks. Type a title to create one.";
|
||||
};
|
||||
|
||||
input.oninput = () => {
|
||||
|
||||
Reference in New Issue
Block a user