Inbox consideration
This commit is contained in:
Vendored
+10
@@ -1,6 +1,7 @@
|
||||
import { copyable, h } from "./dom.js";
|
||||
import { diagram } from "./mermaid.js";
|
||||
import { taskStyle } from "./taskcolor.js";
|
||||
import { todoLink } from "./todolink.js";
|
||||
const choiceMarker = /^(\([ *]\)|\[[ xX]\])\s+(\S.*?)\s*$/;
|
||||
const detailsOpen = /^\s*<details>\s*(?:<summary>(.*?)<\/summary>)?\s*$/i;
|
||||
const summaryLine = /^\s*<summary>(.*?)<\/summary>\s*$/i;
|
||||
@@ -275,6 +276,15 @@ function link(href, children) {
|
||||
if (task) {
|
||||
return h("a", { href, class: "task-link", style: taskStyle(task[1]) }, ...children);
|
||||
}
|
||||
const todo = /^#\/todo\/(\w+)\/(.+)$/.exec(href);
|
||||
if (todo) {
|
||||
try {
|
||||
return todoLink(todo[1], decodeURIComponent(todo[2]), children);
|
||||
}
|
||||
catch {
|
||||
return h("span", {}, ...children);
|
||||
}
|
||||
}
|
||||
if (href.startsWith("#/")) {
|
||||
return h("a", { href }, ...children);
|
||||
}
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+4
@@ -5,6 +5,7 @@ import { previewURL } from "./preview.js";
|
||||
import { findingCount, reviewCounts } from "./reviews.js";
|
||||
import { currentSession, findTask, focusTab, focusTask, onEach, pinSnapshot, setFocus, showReview, state } from "./state.js";
|
||||
import { taskStyle } from "./taskcolor.js";
|
||||
import { fillTodoLinks } from "./todolink.js";
|
||||
export const models = [
|
||||
"claude-opus-5-5",
|
||||
"claude-sonnet-5-5",
|
||||
@@ -302,6 +303,7 @@ export function mountChat(root, src, makeHead) {
|
||||
stream.timer = 0;
|
||||
stream.el.hidden = ctx.held !== undefined || quietReply.startsWith(stream.text.trim());
|
||||
replace(stream.el, markdown(stream.text, "text md", { taskId: ctx.taskId }));
|
||||
fillTodoLinks(stream.el, models[0]);
|
||||
pin();
|
||||
}, 100);
|
||||
};
|
||||
@@ -414,6 +416,7 @@ export function mountChat(root, src, makeHead) {
|
||||
renderComposer();
|
||||
}
|
||||
}
|
||||
fillTodoLinks(transcript, models[0]);
|
||||
pin();
|
||||
};
|
||||
const renderAll = () => {
|
||||
@@ -442,6 +445,7 @@ export function mountChat(root, src, makeHead) {
|
||||
}),
|
||||
onEach([src.transcriptTopic], (fresh) => appendEvents(fresh)),
|
||||
onEach([src.streamTopic], (ev) => appendDelta(ev)),
|
||||
onEach(["tasks", "sessions"], () => fillTodoLinks(transcript, models[0])),
|
||||
];
|
||||
replace(root, h("div", { class: "transcript-view" }, head, transcript, composer));
|
||||
renderAll();
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+53
@@ -0,0 +1,53 @@
|
||||
import { post } from "./api.js";
|
||||
import { errorText, h, replace, showAlert } from "./dom.js";
|
||||
import { currentSession, focusTask, state } from "./state.js";
|
||||
import { taskStyle } from "./taskcolor.js";
|
||||
const starting = new Set();
|
||||
export function todoLink(projectId, title, children) {
|
||||
return h("span", { class: "todo-link", data: { project: projectId, title } }, h("span", { class: "todo-title" }, ...children), " ", h("span", { class: "todo-action" }));
|
||||
}
|
||||
export function fillTodoLinks(root, model) {
|
||||
for (const el of root.querySelectorAll(".todo-link")) {
|
||||
fill(el, model);
|
||||
}
|
||||
}
|
||||
function fill(el, model) {
|
||||
const projectId = el.dataset["project"];
|
||||
const title = el.dataset["title"];
|
||||
const action = el.querySelector(".todo-action");
|
||||
const titleEl = el.querySelector(".todo-title");
|
||||
const task = (state.tasks.get(projectId) ?? []).find((t) => t.status === "active" && t.todo_item === title);
|
||||
if (task) {
|
||||
const sess = currentSession(task.id);
|
||||
el.setAttribute("style", taskStyle(task.id));
|
||||
titleEl.classList.add("task-name");
|
||||
replace(action, h("a", {
|
||||
class: "todo-task",
|
||||
href: `#/task/${task.id}`,
|
||||
onclick: (e) => {
|
||||
e.preventDefault();
|
||||
focusTask(task.id);
|
||||
},
|
||||
}, h("span", { class: `dot ${sess?.state ?? "none"}`, title: sess?.state ?? "no session" }), sess?.state ?? "no agent"));
|
||||
return;
|
||||
}
|
||||
el.removeAttribute("style");
|
||||
titleEl.classList.remove("task-name");
|
||||
const key = `${projectId}\n${title}`;
|
||||
const busy = starting.has(key);
|
||||
const button = h("button", { disabled: busy }, busy ? "Starting…" : "Start");
|
||||
button.onclick = async () => {
|
||||
starting.add(key);
|
||||
fill(el, model);
|
||||
try {
|
||||
await post("/api/projects/{projectId}/todo/tasks", { path: { projectId }, body: { title, model } });
|
||||
}
|
||||
catch (err) {
|
||||
void showAlert(errorText(err));
|
||||
}
|
||||
starting.delete(key);
|
||||
fill(el, model);
|
||||
};
|
||||
replace(action, button);
|
||||
}
|
||||
//# sourceMappingURL=todolink.js.map
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"todolink.js","sourceRoot":"","sources":["../src/todolink.ts"],"names":[],"mappings":"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"}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { copyable, h } from "./dom.js";
|
||||
import { diagram } from "./mermaid.js";
|
||||
import { taskStyle } from "./taskcolor.js";
|
||||
import { todoLink } from "./todolink.js";
|
||||
|
||||
type Block = HTMLElement;
|
||||
|
||||
@@ -315,6 +316,14 @@ function link(href: string, children: Node[]): Node {
|
||||
if (task) {
|
||||
return h("a", { href, class: "task-link", style: taskStyle(task[1]!) }, ...children);
|
||||
}
|
||||
const todo = /^#\/todo\/(\w+)\/(.+)$/.exec(href);
|
||||
if (todo) {
|
||||
try {
|
||||
return todoLink(todo[1]!, decodeURIComponent(todo[2]!), children);
|
||||
} catch {
|
||||
return h("span", {}, ...children);
|
||||
}
|
||||
}
|
||||
if (href.startsWith("#/")) {
|
||||
return h("a", { href }, ...children);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { previewURL } from "./preview.js";
|
||||
import { findingCount, reviewCounts } from "./reviews.js";
|
||||
import { currentSession, findTask, focusTab, focusTask, onEach, pinSnapshot, setFocus, showReview, state, type Topic } from "./state.js";
|
||||
import { taskStyle } from "./taskcolor.js";
|
||||
import { fillTodoLinks } from "./todolink.js";
|
||||
|
||||
export type Usage = { used: number; window: number; model: string };
|
||||
|
||||
@@ -380,6 +381,7 @@ export function mountChat(root: HTMLElement, src: ChatSource, makeHead: (rerende
|
||||
stream.timer = 0;
|
||||
stream.el.hidden = ctx.held !== undefined || quietReply.startsWith(stream.text.trim());
|
||||
replace(stream.el, markdown(stream.text, "text md", { taskId: ctx.taskId }));
|
||||
fillTodoLinks(stream.el, models[0]!);
|
||||
pin();
|
||||
}, 100);
|
||||
};
|
||||
@@ -493,6 +495,7 @@ export function mountChat(root: HTMLElement, src: ChatSource, makeHead: (rerende
|
||||
renderComposer();
|
||||
}
|
||||
}
|
||||
fillTodoLinks(transcript, models[0]!);
|
||||
pin();
|
||||
};
|
||||
|
||||
@@ -523,6 +526,7 @@ export function mountChat(root: HTMLElement, src: ChatSource, makeHead: (rerende
|
||||
}),
|
||||
onEach([src.transcriptTopic], (fresh) => appendEvents(fresh as ByreEvent[])),
|
||||
onEach([src.streamTopic], (ev) => appendDelta(ev as ByreEvent)),
|
||||
onEach(["tasks", "sessions"], () => fillTodoLinks(transcript, models[0]!)),
|
||||
];
|
||||
|
||||
replace(root, h("div", { class: "transcript-view" }, head, transcript, composer));
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
import { post } from "./api.js";
|
||||
import { errorText, h, replace, showAlert } from "./dom.js";
|
||||
import { currentSession, focusTask, state } from "./state.js";
|
||||
import { taskStyle } from "./taskcolor.js";
|
||||
|
||||
const starting = new Set<string>();
|
||||
|
||||
export function todoLink(projectId: string, title: string, children: Node[]): HTMLElement {
|
||||
return h("span", { class: "todo-link", data: { project: projectId, title } }, h("span", { class: "todo-title" }, ...children), " ", h("span", { class: "todo-action" }));
|
||||
}
|
||||
|
||||
export function fillTodoLinks(root: HTMLElement, model: string): void {
|
||||
for (const el of root.querySelectorAll<HTMLElement>(".todo-link")) {
|
||||
fill(el, model);
|
||||
}
|
||||
}
|
||||
|
||||
function fill(el: HTMLElement, model: string): void {
|
||||
const projectId = el.dataset["project"]!;
|
||||
const title = el.dataset["title"]!;
|
||||
const action = el.querySelector<HTMLElement>(".todo-action")!;
|
||||
const titleEl = el.querySelector<HTMLElement>(".todo-title")!;
|
||||
const task = (state.tasks.get(projectId) ?? []).find((t) => t.status === "active" && t.todo_item === title);
|
||||
|
||||
if (task) {
|
||||
const sess = currentSession(task.id);
|
||||
el.setAttribute("style", taskStyle(task.id));
|
||||
titleEl.classList.add("task-name");
|
||||
replace(
|
||||
action,
|
||||
h(
|
||||
"a",
|
||||
{
|
||||
class: "todo-task",
|
||||
href: `#/task/${task.id}`,
|
||||
onclick: (e: MouseEvent) => {
|
||||
e.preventDefault();
|
||||
focusTask(task.id);
|
||||
},
|
||||
},
|
||||
h("span", { class: `dot ${sess?.state ?? "none"}`, title: sess?.state ?? "no session" }),
|
||||
sess?.state ?? "no agent",
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
el.removeAttribute("style");
|
||||
titleEl.classList.remove("task-name");
|
||||
const key = `${projectId}\n${title}`;
|
||||
const busy = starting.has(key);
|
||||
const button = h("button", { disabled: busy }, busy ? "Starting…" : "Start");
|
||||
button.onclick = async () => {
|
||||
starting.add(key);
|
||||
fill(el, model);
|
||||
try {
|
||||
await post("/api/projects/{projectId}/todo/tasks", { path: { projectId }, body: { title, model } });
|
||||
} catch (err) {
|
||||
void showAlert(errorText(err));
|
||||
}
|
||||
starting.delete(key);
|
||||
fill(el, model);
|
||||
};
|
||||
replace(action, button);
|
||||
}
|
||||
@@ -461,6 +461,16 @@ a.task-link {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.todo-link .todo-task {
|
||||
display: inline-flex;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.todo-link button {
|
||||
padding: 1px 8px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.project h2 {
|
||||
font-size: 13px;
|
||||
margin: 16px 0 6px;
|
||||
|
||||
Reference in New Issue
Block a user