agkan 2.15.0 → 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ja.md +60 -236
- package/README.md +46 -239
- package/dist/board/boardRenderer.d.ts.map +1 -1
- package/dist/board/boardRenderer.js +27 -2
- package/dist/board/boardRenderer.js.map +1 -1
- package/dist/board/boardRoutes.d.ts +2 -0
- package/dist/board/boardRoutes.d.ts.map +1 -1
- package/dist/board/boardRoutes.js +122 -10
- package/dist/board/boardRoutes.js.map +1 -1
- package/dist/board/boardScript.d.ts +2 -0
- package/dist/board/boardScript.d.ts.map +1 -0
- package/dist/board/boardScript.js +1202 -0
- package/dist/board/boardScript.js.map +1 -0
- package/dist/board/boardStyles.d.ts +1 -1
- package/dist/board/boardStyles.d.ts.map +1 -1
- package/dist/board/boardStyles.js +49 -3
- package/dist/board/boardStyles.js.map +1 -1
- package/dist/board/client/board.js +639 -17
- package/dist/board/server.d.ts +2 -1
- package/dist/board/server.d.ts.map +1 -1
- package/dist/board/server.js +4 -2
- package/dist/board/server.js.map +1 -1
- package/dist/cli/commands/tag/add.d.ts.map +1 -1
- package/dist/cli/commands/tag/add.js +10 -11
- package/dist/cli/commands/tag/add.js.map +1 -1
- package/dist/cli/commands/tag/attach.d.ts.map +1 -1
- package/dist/cli/commands/tag/attach.js +10 -11
- package/dist/cli/commands/tag/attach.js.map +1 -1
- package/dist/cli/commands/tag/rename.d.ts.map +1 -1
- package/dist/cli/commands/tag/rename.js +10 -11
- package/dist/cli/commands/tag/rename.js.map +1 -1
- package/dist/cli/commands/task/update-parent.d.ts.map +1 -1
- package/dist/cli/commands/task/update-parent.js +10 -11
- package/dist/cli/commands/task/update-parent.js.map +1 -1
- package/dist/cli/index.js +4 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/utils/board-daemon.d.ts.map +1 -1
- package/dist/cli/utils/board-daemon.js +10 -4
- package/dist/cli/utils/board-daemon.js.map +1 -1
- package/dist/db/adapters/sqlite-storage-backend.d.ts +2 -1
- package/dist/db/adapters/sqlite-storage-backend.d.ts.map +1 -1
- package/dist/db/adapters/sqlite-storage-backend.js +53 -2
- package/dist/db/adapters/sqlite-storage-backend.js.map +1 -1
- package/dist/db/connection.d.ts +5 -0
- package/dist/db/connection.d.ts.map +1 -1
- package/dist/db/connection.js +9 -0
- package/dist/db/connection.js.map +1 -1
- package/dist/db/types/repository.d.ts +34 -0
- package/dist/db/types/repository.d.ts.map +1 -1
- package/dist/errors.d.ts +27 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +51 -0
- package/dist/errors.js.map +1 -0
- package/dist/models/Task.d.ts +2 -0
- package/dist/models/Task.d.ts.map +1 -1
- package/dist/services/ClaudeProcessService.d.ts.map +1 -1
- package/dist/services/ClaudeProcessService.js +2 -1
- package/dist/services/ClaudeProcessService.js.map +1 -1
- package/dist/services/CommentService.d.ts.map +1 -1
- package/dist/services/CommentService.js +3 -2
- package/dist/services/CommentService.js.map +1 -1
- package/dist/services/ExportImportService.d.ts.map +1 -1
- package/dist/services/ExportImportService.js +17 -15
- package/dist/services/ExportImportService.js.map +1 -1
- package/dist/services/ProcessService.d.ts +54 -0
- package/dist/services/ProcessService.d.ts.map +1 -0
- package/dist/services/ProcessService.js +147 -0
- package/dist/services/ProcessService.js.map +1 -0
- package/dist/services/TagService.d.ts.map +1 -1
- package/dist/services/TagService.js +7 -6
- package/dist/services/TagService.js.map +1 -1
- package/dist/services/TaskBlockService.d.ts.map +1 -1
- package/dist/services/TaskBlockService.js +5 -4
- package/dist/services/TaskBlockService.js.map +1 -1
- package/dist/services/TaskService.d.ts +2 -0
- package/dist/services/TaskService.d.ts.map +1 -1
- package/dist/services/TaskService.js +28 -10
- package/dist/services/TaskService.js.map +1 -1
- package/dist/services/TaskTagService.d.ts.map +1 -1
- package/dist/services/TaskTagService.js +4 -3
- package/dist/services/TaskTagService.js.map +1 -1
- package/dist/services/TmuxService.d.ts +2 -0
- package/dist/services/TmuxService.d.ts.map +1 -0
- package/dist/services/TmuxService.js +7 -0
- package/dist/services/TmuxService.js.map +1 -0
- package/dist/services/index.d.ts +2 -0
- package/dist/services/index.d.ts.map +1 -1
- package/dist/services/index.js +3 -1
- package/dist/services/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/models/Attachment.d.ts +0 -25
- package/dist/models/Attachment.d.ts.map +0 -1
- package/dist/models/Attachment.js +0 -7
- package/dist/models/Attachment.js.map +0 -1
- package/dist/services/AttachmentService.d.ts +0 -62
- package/dist/services/AttachmentService.d.ts.map +0 -1
- package/dist/services/AttachmentService.js +0 -95
- package/dist/services/AttachmentService.js.map +0 -1
|
@@ -30,6 +30,258 @@
|
|
|
30
30
|
setTimeout(() => toast.classList.remove("show"), 3e3);
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
// src/board/client/claudeButton.ts
|
|
34
|
+
var _claudeModalCallback = null;
|
|
35
|
+
var _runningTaskIds = /* @__PURE__ */ new Set();
|
|
36
|
+
var _planningTaskIds = /* @__PURE__ */ new Set();
|
|
37
|
+
var _inFlightTaskIds = /* @__PURE__ */ new Set();
|
|
38
|
+
function registerClaudeModalCallback(callback) {
|
|
39
|
+
_claudeModalCallback = callback;
|
|
40
|
+
}
|
|
41
|
+
function getRunningTaskIds() {
|
|
42
|
+
return _runningTaskIds;
|
|
43
|
+
}
|
|
44
|
+
function updateButtonStates(runningTaskIds, planningTaskIds = /* @__PURE__ */ new Set()) {
|
|
45
|
+
_runningTaskIds = runningTaskIds;
|
|
46
|
+
const onlyPlanningRunning = runningTaskIds.size > 0 && [...runningTaskIds].every((id) => planningTaskIds.has(id));
|
|
47
|
+
const anyRunning = runningTaskIds.size > 0 && !onlyPlanningRunning;
|
|
48
|
+
const indicator = document.getElementById("header-running-indicator");
|
|
49
|
+
if (indicator) {
|
|
50
|
+
indicator.style.display = runningTaskIds.size > 0 ? "" : "none";
|
|
51
|
+
}
|
|
52
|
+
document.querySelectorAll(".claude-run-split").forEach((split) => {
|
|
53
|
+
const taskId = Number(split.dataset.taskId);
|
|
54
|
+
if (runningTaskIds.has(taskId)) {
|
|
55
|
+
replaceWithDetailBtn(split, taskId);
|
|
56
|
+
} else {
|
|
57
|
+
split.querySelectorAll(".claude-run-btn, .claude-run-toggle, .claude-run-menu-item").forEach((btn) => {
|
|
58
|
+
btn.disabled = anyRunning;
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
document.querySelectorAll(".claude-plan-btn").forEach((btn) => {
|
|
63
|
+
const taskId = Number(btn.dataset.taskId);
|
|
64
|
+
if (runningTaskIds.has(taskId)) {
|
|
65
|
+
replaceWithDetailBtn(btn, taskId);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
document.querySelectorAll(".claude-detail-btn").forEach((btn) => {
|
|
69
|
+
const taskId = Number(btn.dataset.taskId);
|
|
70
|
+
if (!runningTaskIds.has(taskId)) {
|
|
71
|
+
const card = btn.closest(".card");
|
|
72
|
+
const status = card?.dataset.status;
|
|
73
|
+
replaceWithRunOrPlanBtn(btn, taskId, status);
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
function replaceWithDetailBtn(btn, taskId) {
|
|
78
|
+
const detailBtn = document.createElement("button");
|
|
79
|
+
detailBtn.className = "claude-detail-btn";
|
|
80
|
+
detailBtn.dataset.taskId = String(taskId);
|
|
81
|
+
detailBtn.textContent = "\u25CF Details";
|
|
82
|
+
attachDetailBtnListener(detailBtn);
|
|
83
|
+
btn.replaceWith(detailBtn);
|
|
84
|
+
}
|
|
85
|
+
function replaceWithRunOrPlanBtn(btn, taskId, status) {
|
|
86
|
+
_planningTaskIds.delete(taskId);
|
|
87
|
+
if (["review", "done", "closed"].includes(status ?? "")) {
|
|
88
|
+
btn.remove();
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
if (["ready", "in_progress"].includes(status ?? "")) {
|
|
92
|
+
const split = createRunSplitElement(taskId);
|
|
93
|
+
btn.replaceWith(split);
|
|
94
|
+
} else {
|
|
95
|
+
const newBtn = document.createElement("button");
|
|
96
|
+
newBtn.className = "claude-plan-btn";
|
|
97
|
+
newBtn.dataset.taskId = String(taskId);
|
|
98
|
+
newBtn.innerHTML = "📋 Planning";
|
|
99
|
+
attachPlanBtnListener(newBtn);
|
|
100
|
+
btn.replaceWith(newBtn);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function createRunSplitElement(taskId) {
|
|
104
|
+
const split = document.createElement("div");
|
|
105
|
+
split.className = "claude-run-split";
|
|
106
|
+
split.dataset.taskId = String(taskId);
|
|
107
|
+
const mainBtn = document.createElement("button");
|
|
108
|
+
mainBtn.className = "claude-run-btn";
|
|
109
|
+
mainBtn.dataset.taskId = String(taskId);
|
|
110
|
+
mainBtn.innerHTML = "▶ Run";
|
|
111
|
+
const toggleBtn = document.createElement("button");
|
|
112
|
+
toggleBtn.className = "claude-run-toggle";
|
|
113
|
+
toggleBtn.dataset.taskId = String(taskId);
|
|
114
|
+
toggleBtn.title = "More options";
|
|
115
|
+
toggleBtn.innerHTML = "▼";
|
|
116
|
+
const menu = document.createElement("div");
|
|
117
|
+
menu.className = "claude-run-menu";
|
|
118
|
+
const directItem = document.createElement("button");
|
|
119
|
+
directItem.className = "claude-run-menu-item";
|
|
120
|
+
directItem.dataset.taskId = String(taskId);
|
|
121
|
+
directItem.dataset.command = "direct";
|
|
122
|
+
directItem.innerHTML = "▶ Run (current branch)";
|
|
123
|
+
const prItem = document.createElement("button");
|
|
124
|
+
prItem.className = "claude-run-menu-item";
|
|
125
|
+
prItem.dataset.taskId = String(taskId);
|
|
126
|
+
prItem.dataset.command = "pr";
|
|
127
|
+
prItem.innerHTML = "▶ Run (create PR)";
|
|
128
|
+
menu.appendChild(directItem);
|
|
129
|
+
menu.appendChild(prItem);
|
|
130
|
+
split.appendChild(mainBtn);
|
|
131
|
+
split.appendChild(toggleBtn);
|
|
132
|
+
split.appendChild(menu);
|
|
133
|
+
attachRunSplitListeners(split, mainBtn, toggleBtn, directItem, prItem, taskId);
|
|
134
|
+
return split;
|
|
135
|
+
}
|
|
136
|
+
function attachRunSplitListeners(split, mainBtn, toggleBtn, directItem, prItem, taskId) {
|
|
137
|
+
split.dataset.listenersAttached = "true";
|
|
138
|
+
mainBtn.addEventListener("click", async (e) => {
|
|
139
|
+
e.stopPropagation();
|
|
140
|
+
await triggerRunTask(taskId, split, {});
|
|
141
|
+
});
|
|
142
|
+
toggleBtn.addEventListener("click", (e) => {
|
|
143
|
+
e.stopPropagation();
|
|
144
|
+
split.classList.toggle("open");
|
|
145
|
+
});
|
|
146
|
+
directItem.addEventListener("click", async (e) => {
|
|
147
|
+
e.stopPropagation();
|
|
148
|
+
split.classList.remove("open");
|
|
149
|
+
await triggerRunTask(taskId, split, {});
|
|
150
|
+
});
|
|
151
|
+
prItem.addEventListener("click", async (e) => {
|
|
152
|
+
e.stopPropagation();
|
|
153
|
+
split.classList.remove("open");
|
|
154
|
+
await triggerRunTask(taskId, split, { command: "pr" });
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
function attachPlanBtnListener(btn) {
|
|
158
|
+
btn.dataset.listenersAttached = "true";
|
|
159
|
+
btn.addEventListener("click", async (e) => {
|
|
160
|
+
e.stopPropagation();
|
|
161
|
+
const taskId = Number(btn.dataset.taskId);
|
|
162
|
+
await triggerRunTask(taskId, btn, { command: "planning" });
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
function attachDetailBtnListener(btn) {
|
|
166
|
+
btn.dataset.listenersAttached = "true";
|
|
167
|
+
btn.addEventListener("click", (e) => {
|
|
168
|
+
e.stopPropagation();
|
|
169
|
+
const taskId = Number(btn.dataset.taskId);
|
|
170
|
+
if (_claudeModalCallback) {
|
|
171
|
+
_claudeModalCallback(taskId);
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
async function triggerRunTask(taskId, btn, body) {
|
|
176
|
+
if (_inFlightTaskIds.has(taskId)) return;
|
|
177
|
+
_inFlightTaskIds.add(taskId);
|
|
178
|
+
btn.disabled = true;
|
|
179
|
+
btn.querySelectorAll("button").forEach((b) => b.disabled = true);
|
|
180
|
+
try {
|
|
181
|
+
const res = await fetch(`/api/claude/tasks/${taskId}/run`, {
|
|
182
|
+
method: "POST",
|
|
183
|
+
headers: { "Content-Type": "application/json" },
|
|
184
|
+
body: JSON.stringify(body)
|
|
185
|
+
});
|
|
186
|
+
if (res.ok) {
|
|
187
|
+
_runningTaskIds = new Set(_runningTaskIds).add(taskId);
|
|
188
|
+
if (body.command === "planning") {
|
|
189
|
+
_planningTaskIds = new Set(_planningTaskIds).add(taskId);
|
|
190
|
+
}
|
|
191
|
+
replaceWithDetailBtn(btn, taskId);
|
|
192
|
+
} else {
|
|
193
|
+
btn.disabled = false;
|
|
194
|
+
btn.querySelectorAll("button").forEach((b) => b.disabled = false);
|
|
195
|
+
let errorDetail = `HTTP ${res.status}`;
|
|
196
|
+
try {
|
|
197
|
+
const data = await res.json();
|
|
198
|
+
if (data.error) errorDetail += `: ${data.error}`;
|
|
199
|
+
} catch {
|
|
200
|
+
}
|
|
201
|
+
console.error(`[claude] Failed to start task ${taskId}: ${errorDetail}`);
|
|
202
|
+
if (res.status === 409) {
|
|
203
|
+
alert(`\u3053\u306E\u30BF\u30B9\u30AF\u306F\u3059\u3067\u306B\u5B9F\u884C\u4E2D\u3067\u3059 (task ${taskId})`);
|
|
204
|
+
} else {
|
|
205
|
+
alert(`Claude\u8D77\u52D5\u30A8\u30E9\u30FC (task ${taskId}): ${errorDetail}`);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
} catch (err) {
|
|
209
|
+
btn.disabled = false;
|
|
210
|
+
btn.querySelectorAll("button").forEach((b) => b.disabled = false);
|
|
211
|
+
console.error(`[claude] Network error starting task ${taskId}:`, err);
|
|
212
|
+
} finally {
|
|
213
|
+
_inFlightTaskIds.delete(taskId);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
async function pollRunningTasks() {
|
|
217
|
+
try {
|
|
218
|
+
const res = await fetch("/api/running-tasks");
|
|
219
|
+
if (!res.ok) return;
|
|
220
|
+
const data = await res.json();
|
|
221
|
+
const allIds = new Set(data.tasks.map((t) => t.taskId));
|
|
222
|
+
const planningIds = new Set(data.tasks.filter((t) => t.command === "planning").map((t) => t.taskId));
|
|
223
|
+
updateButtonStates(allIds, planningIds);
|
|
224
|
+
} catch {
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
function updateCardButton(card, newStatus) {
|
|
228
|
+
const taskId = Number(card.dataset.id);
|
|
229
|
+
if (_runningTaskIds.has(taskId)) return;
|
|
230
|
+
const existingEl = card.querySelector(".claude-run-split, .claude-plan-btn, .claude-detail-btn");
|
|
231
|
+
if (!existingEl) return;
|
|
232
|
+
if (["review", "done", "closed"].includes(newStatus)) {
|
|
233
|
+
existingEl.remove();
|
|
234
|
+
} else if (["ready", "in_progress"].includes(newStatus)) {
|
|
235
|
+
if (!existingEl.classList.contains("claude-run-split")) {
|
|
236
|
+
const split = createRunSplitElement(taskId);
|
|
237
|
+
existingEl.replaceWith(split);
|
|
238
|
+
}
|
|
239
|
+
} else {
|
|
240
|
+
if (!existingEl.classList.contains("claude-plan-btn")) {
|
|
241
|
+
const newBtn = document.createElement("button");
|
|
242
|
+
newBtn.className = "claude-plan-btn";
|
|
243
|
+
newBtn.dataset.taskId = String(taskId);
|
|
244
|
+
newBtn.innerHTML = "📋 Planning";
|
|
245
|
+
attachPlanBtnListener(newBtn);
|
|
246
|
+
existingEl.replaceWith(newBtn);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
function attachClaudeButtonListeners(body) {
|
|
251
|
+
body.querySelectorAll(".claude-run-split").forEach((split) => {
|
|
252
|
+
if (split.dataset.listenersAttached) return;
|
|
253
|
+
const taskId = Number(split.dataset.taskId);
|
|
254
|
+
const mainBtn = split.querySelector(".claude-run-btn");
|
|
255
|
+
const toggleBtn = split.querySelector(".claude-run-toggle");
|
|
256
|
+
const directItem = split.querySelector('[data-command="direct"]');
|
|
257
|
+
const prItem = split.querySelector('[data-command="pr"]');
|
|
258
|
+
if (mainBtn && toggleBtn && directItem && prItem) {
|
|
259
|
+
attachRunSplitListeners(split, mainBtn, toggleBtn, directItem, prItem, taskId);
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
body.querySelectorAll(".claude-plan-btn").forEach((btn) => {
|
|
263
|
+
if (btn.dataset.listenersAttached) return;
|
|
264
|
+
attachPlanBtnListener(btn);
|
|
265
|
+
});
|
|
266
|
+
body.querySelectorAll(".claude-detail-btn").forEach((btn) => {
|
|
267
|
+
if (btn.dataset.listenersAttached) return;
|
|
268
|
+
attachDetailBtnListener(btn);
|
|
269
|
+
});
|
|
270
|
+
updateButtonStates(_runningTaskIds, _planningTaskIds);
|
|
271
|
+
}
|
|
272
|
+
function initClaudeButton() {
|
|
273
|
+
document.querySelectorAll(".column-body").forEach((body) => {
|
|
274
|
+
attachClaudeButtonListeners(body);
|
|
275
|
+
});
|
|
276
|
+
document.addEventListener("click", () => {
|
|
277
|
+
document.querySelectorAll(".claude-run-split.open").forEach((el) => {
|
|
278
|
+
el.classList.remove("open");
|
|
279
|
+
});
|
|
280
|
+
});
|
|
281
|
+
setInterval(pollRunningTasks, 2500);
|
|
282
|
+
pollRunningTasks();
|
|
283
|
+
}
|
|
284
|
+
|
|
33
285
|
// src/board/client/dragDrop.ts
|
|
34
286
|
var _redrawDependencies = null;
|
|
35
287
|
function registerDependencyRedrawCallback(callback) {
|
|
@@ -37,6 +289,7 @@
|
|
|
37
289
|
}
|
|
38
290
|
var draggedCard = null;
|
|
39
291
|
var sourceBody = null;
|
|
292
|
+
var isPendingStatusUpdate = false;
|
|
40
293
|
var _dragMouseX = 0;
|
|
41
294
|
var _dragMouseY = 0;
|
|
42
295
|
var _dragOffsetX = 0;
|
|
@@ -70,6 +323,8 @@
|
|
|
70
323
|
draggedCard.dataset.status = newStatus;
|
|
71
324
|
updateCount(oldStatus);
|
|
72
325
|
updateCount(newStatus);
|
|
326
|
+
updateCardButton(draggedCard, newStatus);
|
|
327
|
+
isPendingStatusUpdate = true;
|
|
73
328
|
try {
|
|
74
329
|
const res = await fetch("/api/tasks/" + taskId, {
|
|
75
330
|
method: "PATCH",
|
|
@@ -88,6 +343,8 @@
|
|
|
88
343
|
updateCount(newStatus);
|
|
89
344
|
}
|
|
90
345
|
showToast();
|
|
346
|
+
} finally {
|
|
347
|
+
isPendingStatusUpdate = false;
|
|
91
348
|
}
|
|
92
349
|
}
|
|
93
350
|
var _documentDragOverListener = null;
|
|
@@ -423,6 +680,7 @@
|
|
|
423
680
|
var _renderDetailPanel = null;
|
|
424
681
|
var _showUpdateWarning = null;
|
|
425
682
|
var _getDetailTaskId2 = null;
|
|
683
|
+
var _getDetailActiveTab = null;
|
|
426
684
|
var _setActiveCard = null;
|
|
427
685
|
var _redrawDependencies2 = null;
|
|
428
686
|
function registerDetailPanelCallbacks(callbacks) {
|
|
@@ -430,6 +688,7 @@
|
|
|
430
688
|
_renderDetailPanel = callbacks.renderDetailPanel;
|
|
431
689
|
_showUpdateWarning = callbacks.showUpdateWarning;
|
|
432
690
|
_getDetailTaskId2 = callbacks.getDetailTaskId;
|
|
691
|
+
_getDetailActiveTab = callbacks.getDetailActiveTab;
|
|
433
692
|
_setActiveCard = callbacks.setActiveCard;
|
|
434
693
|
}
|
|
435
694
|
function registerDependencyRedrawCallback2(callback) {
|
|
@@ -497,6 +756,8 @@
|
|
|
497
756
|
}
|
|
498
757
|
attachCardListeners(body);
|
|
499
758
|
attachAutoScrollToBody(body);
|
|
759
|
+
attachClaudeButtonListeners(body);
|
|
760
|
+
updateButtonStates(getRunningTaskIds());
|
|
500
761
|
}
|
|
501
762
|
function isEditingDetailPanel() {
|
|
502
763
|
const editableFields = ["detail-edit-title", "detail-edit-body", "detail-edit-status", "detail-edit-priority"];
|
|
@@ -507,6 +768,9 @@
|
|
|
507
768
|
if (_showUpdateWarning) _showUpdateWarning();
|
|
508
769
|
return;
|
|
509
770
|
}
|
|
771
|
+
if (_getDetailActiveTab && _getDetailActiveTab() === "run-logs") {
|
|
772
|
+
return;
|
|
773
|
+
}
|
|
510
774
|
try {
|
|
511
775
|
const taskRes = await fetch("/api/tasks/" + detailTaskId2);
|
|
512
776
|
if (taskRes.ok) {
|
|
@@ -538,7 +802,7 @@
|
|
|
538
802
|
}
|
|
539
803
|
}
|
|
540
804
|
async function pollBoardUpdates() {
|
|
541
|
-
if (draggedCard !== null) return;
|
|
805
|
+
if (draggedCard !== null || isPendingStatusUpdate) return;
|
|
542
806
|
try {
|
|
543
807
|
const res = await fetch("/api/board/updated-at");
|
|
544
808
|
if (!res.ok) return;
|
|
@@ -911,8 +1175,8 @@
|
|
|
911
1175
|
});
|
|
912
1176
|
if (!res.ok) throw new Error("Server error");
|
|
913
1177
|
}
|
|
914
|
-
async function fetchTaskDetail(taskId) {
|
|
915
|
-
const res = await fetch("/api/tasks/" + taskId);
|
|
1178
|
+
async function fetchTaskDetail(taskId, signal) {
|
|
1179
|
+
const res = await fetch("/api/tasks/" + taskId, signal ? { signal } : void 0);
|
|
916
1180
|
if (!res.ok) throw new Error("Server error");
|
|
917
1181
|
return res.json();
|
|
918
1182
|
}
|
|
@@ -958,6 +1222,12 @@
|
|
|
958
1222
|
}).catch(function() {
|
|
959
1223
|
});
|
|
960
1224
|
}
|
|
1225
|
+
async function fetchRunLogs(taskId) {
|
|
1226
|
+
const res = await fetch("/api/claude/tasks/" + taskId + "/run-logs");
|
|
1227
|
+
if (!res.ok) throw new Error("Server error");
|
|
1228
|
+
const data = await res.json();
|
|
1229
|
+
return data.logs || [];
|
|
1230
|
+
}
|
|
961
1231
|
|
|
962
1232
|
// src/board/client/detailPanelHtml.ts
|
|
963
1233
|
function renderCommentItemHtml(comment, taskId) {
|
|
@@ -1084,8 +1354,36 @@
|
|
|
1084
1354
|
html += renderEditableTextFields(task);
|
|
1085
1355
|
return html;
|
|
1086
1356
|
}
|
|
1357
|
+
function renderRunLogsHtml(logs) {
|
|
1358
|
+
if (logs.length === 0) {
|
|
1359
|
+
return '<div class="run-log-empty">No run logs yet.</div>';
|
|
1360
|
+
}
|
|
1361
|
+
let html = '<div class="run-log-list">';
|
|
1362
|
+
logs.forEach((log, index) => {
|
|
1363
|
+
const date = log.started_at ? log.started_at.replace("T", " ").replace(/\.\d+Z$/, "") : "";
|
|
1364
|
+
const exitOk = log.exit_code === 0;
|
|
1365
|
+
const exitLabel = log.exit_code !== null ? "exit: " + String(log.exit_code) : "running";
|
|
1366
|
+
const exitClass = exitOk ? "success" : "failure";
|
|
1367
|
+
const isFirst = index === 0;
|
|
1368
|
+
html += '<div class="run-log-item' + (isFirst ? " open" : "") + '" data-log-id="' + log.id + '"><div class="run-log-header" data-action="toggle-run-log"><span class="run-log-toggle">▶</span><span class="run-log-date">' + escapeHtmlClient(date) + '</span><span class="run-log-exit ' + exitClass + '">' + escapeHtmlClient(exitLabel) + '</span></div><div class="run-log-body">';
|
|
1369
|
+
log.events.forEach((evt) => {
|
|
1370
|
+
if (evt.kind === "text" && evt.text) {
|
|
1371
|
+
html += escapeHtmlClient(evt.text);
|
|
1372
|
+
} else if (evt.kind === "tool_use" && evt.name) {
|
|
1373
|
+
const mainArg = evt.input && typeof evt.input === "object" ? String(
|
|
1374
|
+
evt.input.path ?? evt.input.command ?? ""
|
|
1375
|
+
) : "";
|
|
1376
|
+
const display = mainArg ? "\u{1F527} " + evt.name + ": " + mainArg : "\u{1F527} " + evt.name;
|
|
1377
|
+
html += '<span class="run-log-tool-use">' + escapeHtmlClient(display) + "\n</span>";
|
|
1378
|
+
}
|
|
1379
|
+
});
|
|
1380
|
+
html += "</div></div>";
|
|
1381
|
+
});
|
|
1382
|
+
html += "</div>";
|
|
1383
|
+
return html;
|
|
1384
|
+
}
|
|
1087
1385
|
function buildDetailPanelHtml() {
|
|
1088
|
-
return '<div class="detail-panel" id="detail-panel"><div class="detail-panel-resize-handle" id="detail-panel-resize-handle"></div><div class="detail-panel-header"><h2 id="detail-panel-title">Task Detail</h2><button class="detail-panel-copy-id" id="detail-panel-copy-id" title="Copy task ID">⎘</button><button class="detail-panel-close" id="detail-panel-close" title="Close">×</button></div><div class="detail-tabs" id="detail-tabs"><button class="detail-tab active" data-tab="details">Details</button><button class="detail-tab" data-tab="comments" id="detail-tab-comments">Comments</button></div><div class="detail-panel-body" id="detail-panel-body"><div class="detail-tab-content active" id="detail-tab-content-details"></div><div class="detail-tab-content" id="detail-tab-content-comments"></div></div><div class="detail-panel-footer" id="detail-panel-footer"><button id="detail-save-btn">Save</button></div></div>';
|
|
1386
|
+
return '<div class="detail-panel" id="detail-panel"><div class="detail-panel-resize-handle" id="detail-panel-resize-handle"></div><div class="detail-panel-header"><h2 id="detail-panel-title">Task Detail</h2><button class="detail-panel-copy-id" id="detail-panel-copy-id" title="Copy task ID">⎘</button><button class="detail-panel-close" id="detail-panel-close" title="Close">×</button></div><div class="detail-tabs" id="detail-tabs"><button class="detail-tab active" data-tab="details">Details</button><button class="detail-tab" data-tab="comments" id="detail-tab-comments">Comments</button><button class="detail-tab" data-tab="run-logs" id="detail-tab-run-logs">Run Logs</button></div><div class="detail-panel-body" id="detail-panel-body"><div class="detail-tab-content active" id="detail-tab-content-details"></div><div class="detail-tab-content" id="detail-tab-content-comments"></div><div class="detail-tab-content" id="detail-tab-content-run-logs"></div></div><div class="detail-panel-footer" id="detail-panel-footer"><button id="detail-save-btn">Save</button></div></div>';
|
|
1089
1387
|
}
|
|
1090
1388
|
function autoResizeTextarea(el) {
|
|
1091
1389
|
const scrollContainer = el.closest(".detail-tab-content");
|
|
@@ -1098,9 +1396,22 @@
|
|
|
1098
1396
|
// src/board/client/detailPanel.ts
|
|
1099
1397
|
var detailTaskId = null;
|
|
1100
1398
|
var lastTab = "details";
|
|
1399
|
+
var runLogPollingInterval = null;
|
|
1400
|
+
var currentFetchController = null;
|
|
1401
|
+
var runLogLoadSeq = 0;
|
|
1402
|
+
var runLogsLoadedTaskId = null;
|
|
1403
|
+
function stopRunLogPolling() {
|
|
1404
|
+
if (runLogPollingInterval !== null) {
|
|
1405
|
+
clearInterval(runLogPollingInterval);
|
|
1406
|
+
runLogPollingInterval = null;
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1101
1409
|
function getDetailTaskId() {
|
|
1102
1410
|
return detailTaskId;
|
|
1103
1411
|
}
|
|
1412
|
+
function getDetailActiveTab() {
|
|
1413
|
+
return lastTab;
|
|
1414
|
+
}
|
|
1104
1415
|
function setActiveCard(taskId) {
|
|
1105
1416
|
document.querySelectorAll(".card.active").forEach((card) => {
|
|
1106
1417
|
card.classList.remove("active");
|
|
@@ -1111,6 +1422,13 @@
|
|
|
1111
1422
|
}
|
|
1112
1423
|
}
|
|
1113
1424
|
function closeDetailPanel() {
|
|
1425
|
+
stopRunLogPolling();
|
|
1426
|
+
runLogsLoadedTaskId = null;
|
|
1427
|
+
const runLogsPane = document.getElementById("detail-tab-content-run-logs");
|
|
1428
|
+
if (runLogsPane) {
|
|
1429
|
+
delete runLogsPane.dataset.runLogsTaskId;
|
|
1430
|
+
delete runLogsPane.dataset.runLogsSignature;
|
|
1431
|
+
}
|
|
1114
1432
|
const detailPanel = document.getElementById("detail-panel");
|
|
1115
1433
|
detailPanel.classList.remove("open");
|
|
1116
1434
|
detailPanel.style.width = "";
|
|
@@ -1118,26 +1436,41 @@
|
|
|
1118
1436
|
detailTaskId = null;
|
|
1119
1437
|
}
|
|
1120
1438
|
function switchTab(tabName) {
|
|
1439
|
+
const activeTabBtn = document.querySelector(".detail-tab.active");
|
|
1440
|
+
const currentTab = activeTabBtn?.dataset.tab ?? null;
|
|
1441
|
+
const isSameTab = currentTab === tabName;
|
|
1442
|
+
if (tabName !== "run-logs") {
|
|
1443
|
+
stopRunLogPolling();
|
|
1444
|
+
}
|
|
1121
1445
|
lastTab = tabName;
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1446
|
+
if (!isSameTab) {
|
|
1447
|
+
document.querySelectorAll(".detail-tab").forEach((btn) => {
|
|
1448
|
+
btn.classList.toggle("active", btn.dataset.tab === tabName);
|
|
1449
|
+
});
|
|
1450
|
+
document.querySelectorAll(".detail-tab-content").forEach((el) => {
|
|
1451
|
+
el.classList.toggle("active", el.id === "detail-tab-content-" + tabName);
|
|
1452
|
+
});
|
|
1453
|
+
}
|
|
1128
1454
|
const footer = document.getElementById("detail-panel-footer");
|
|
1129
1455
|
if (footer) footer.style.display = tabName === "details" ? "" : "none";
|
|
1456
|
+
if (tabName === "run-logs" && detailTaskId !== null && (!isSameTab || runLogsLoadedTaskId !== detailTaskId)) {
|
|
1457
|
+
loadRunLogs(detailTaskId).catch((err) => {
|
|
1458
|
+
console.error("[agkan] switchTab loadRunLogs failed", err);
|
|
1459
|
+
});
|
|
1460
|
+
}
|
|
1130
1461
|
}
|
|
1131
1462
|
async function loadComments(taskId) {
|
|
1132
|
-
const tabBtn = document.getElementById("detail-tab-comments");
|
|
1133
1463
|
const pane = document.getElementById("detail-tab-content-comments");
|
|
1134
1464
|
if (!pane) return;
|
|
1135
1465
|
try {
|
|
1136
1466
|
const comments = await fetchComments(taskId);
|
|
1467
|
+
if (detailTaskId !== taskId) return;
|
|
1468
|
+
const tabBtn = document.getElementById("detail-tab-comments");
|
|
1137
1469
|
if (tabBtn) tabBtn.textContent = "Comments (" + comments.length + ")";
|
|
1138
1470
|
renderComments(taskId, comments);
|
|
1139
1471
|
} catch (err) {
|
|
1140
1472
|
console.error("[agkan] loadComments failed for task", taskId, err);
|
|
1473
|
+
if (detailTaskId !== taskId) return;
|
|
1141
1474
|
if (pane) pane.innerHTML = '<div style="padding:20px;font-size:12px;color:#94a3b8;">Failed to load comments</div>';
|
|
1142
1475
|
}
|
|
1143
1476
|
}
|
|
@@ -1262,6 +1595,99 @@
|
|
|
1262
1595
|
const taskId = target.dataset.taskId ? Number(target.dataset.taskId) : NaN;
|
|
1263
1596
|
dispatchCommentAction(action, commentId, taskId);
|
|
1264
1597
|
}
|
|
1598
|
+
function buildRunLogsSignature(logs) {
|
|
1599
|
+
return JSON.stringify(logs);
|
|
1600
|
+
}
|
|
1601
|
+
function renderRunLogsInPane(pane, logs) {
|
|
1602
|
+
const nextSignature = buildRunLogsSignature(logs);
|
|
1603
|
+
if (pane.dataset.runLogsSignature === nextSignature) return;
|
|
1604
|
+
pane.dataset.runLogsSignature = nextSignature;
|
|
1605
|
+
const bodyScrollState = /* @__PURE__ */ new Map();
|
|
1606
|
+
const openLogIds = /* @__PURE__ */ new Set();
|
|
1607
|
+
const hadPreviousItems = pane.querySelector(".run-log-item") !== null;
|
|
1608
|
+
const paneScrollTop = pane.scrollTop;
|
|
1609
|
+
const paneIsNearBottom = pane.scrollHeight - pane.scrollTop - pane.clientHeight <= 50;
|
|
1610
|
+
pane.querySelectorAll(".run-log-item.open").forEach((item) => {
|
|
1611
|
+
const logId = Number(item.dataset.logId);
|
|
1612
|
+
if (!logId) return;
|
|
1613
|
+
openLogIds.add(logId);
|
|
1614
|
+
const body = item.querySelector(".run-log-body");
|
|
1615
|
+
if (body) {
|
|
1616
|
+
const isNearBottom = body.scrollHeight - body.scrollTop - body.clientHeight <= 50;
|
|
1617
|
+
bodyScrollState.set(logId, { scrollTop: body.scrollTop, isNearBottom });
|
|
1618
|
+
}
|
|
1619
|
+
});
|
|
1620
|
+
pane.innerHTML = renderRunLogsHtml(logs);
|
|
1621
|
+
requestAnimationFrame(() => {
|
|
1622
|
+
if (hadPreviousItems) {
|
|
1623
|
+
pane.scrollTop = paneIsNearBottom ? pane.scrollHeight : paneScrollTop;
|
|
1624
|
+
}
|
|
1625
|
+
pane.querySelectorAll(".run-log-item").forEach((item) => {
|
|
1626
|
+
const logId = Number(item.dataset.logId);
|
|
1627
|
+
if (!logId) return;
|
|
1628
|
+
const body = item.querySelector(".run-log-body");
|
|
1629
|
+
if (!body) return;
|
|
1630
|
+
if (hadPreviousItems) {
|
|
1631
|
+
if (openLogIds.has(logId)) {
|
|
1632
|
+
item.classList.add("open");
|
|
1633
|
+
const state = bodyScrollState.get(logId);
|
|
1634
|
+
if (state) {
|
|
1635
|
+
body.scrollTop = state.isNearBottom ? body.scrollHeight : state.scrollTop;
|
|
1636
|
+
} else {
|
|
1637
|
+
body.scrollTop = body.scrollHeight;
|
|
1638
|
+
}
|
|
1639
|
+
} else {
|
|
1640
|
+
item.classList.remove("open");
|
|
1641
|
+
}
|
|
1642
|
+
} else {
|
|
1643
|
+
if (item.classList.contains("open")) {
|
|
1644
|
+
body.scrollTop = body.scrollHeight;
|
|
1645
|
+
}
|
|
1646
|
+
}
|
|
1647
|
+
});
|
|
1648
|
+
});
|
|
1649
|
+
}
|
|
1650
|
+
async function loadRunLogs(taskId) {
|
|
1651
|
+
stopRunLogPolling();
|
|
1652
|
+
const seq = ++runLogLoadSeq;
|
|
1653
|
+
const pane = document.getElementById("detail-tab-content-run-logs");
|
|
1654
|
+
if (!pane) return;
|
|
1655
|
+
runLogsLoadedTaskId = taskId;
|
|
1656
|
+
if (pane.dataset.runLogsTaskId !== String(taskId)) {
|
|
1657
|
+
pane.dataset.runLogsTaskId = String(taskId);
|
|
1658
|
+
delete pane.dataset.runLogsSignature;
|
|
1659
|
+
}
|
|
1660
|
+
pane.removeEventListener("click", handleRunLogToggle);
|
|
1661
|
+
pane.addEventListener("click", handleRunLogToggle);
|
|
1662
|
+
try {
|
|
1663
|
+
const logs = await fetchRunLogs(taskId);
|
|
1664
|
+
if (seq !== runLogLoadSeq) return;
|
|
1665
|
+
renderRunLogsInPane(pane, logs);
|
|
1666
|
+
runLogPollingInterval = setInterval(() => {
|
|
1667
|
+
if (detailTaskId !== taskId) {
|
|
1668
|
+
stopRunLogPolling();
|
|
1669
|
+
return;
|
|
1670
|
+
}
|
|
1671
|
+
fetchRunLogs(taskId).then((updated) => {
|
|
1672
|
+
if (seq !== runLogLoadSeq) return;
|
|
1673
|
+
const p = document.getElementById("detail-tab-content-run-logs");
|
|
1674
|
+
if (p) renderRunLogsInPane(p, updated);
|
|
1675
|
+
}).catch(() => stopRunLogPolling());
|
|
1676
|
+
}, 2e3);
|
|
1677
|
+
} catch (err) {
|
|
1678
|
+
console.error("[agkan] loadRunLogs failed for task", taskId, err);
|
|
1679
|
+
if (seq !== runLogLoadSeq) return;
|
|
1680
|
+
runLogsLoadedTaskId = null;
|
|
1681
|
+
delete pane.dataset.runLogsSignature;
|
|
1682
|
+
pane.innerHTML = '<div style="padding:20px;font-size:12px;color:#94a3b8;">Failed to load run logs</div>';
|
|
1683
|
+
}
|
|
1684
|
+
}
|
|
1685
|
+
function handleRunLogToggle(e) {
|
|
1686
|
+
const target = e.target.closest('[data-action="toggle-run-log"]');
|
|
1687
|
+
if (!target) return;
|
|
1688
|
+
const item = target.closest(".run-log-item");
|
|
1689
|
+
if (item) item.classList.toggle("open");
|
|
1690
|
+
}
|
|
1265
1691
|
function renderDetailPanel(data) {
|
|
1266
1692
|
document.getElementById("detail-panel-update-warning")?.remove();
|
|
1267
1693
|
const detailPanelTitle = document.getElementById("detail-panel-title");
|
|
@@ -1288,9 +1714,36 @@
|
|
|
1288
1714
|
}
|
|
1289
1715
|
const textarea = document.getElementById("detail-edit-body");
|
|
1290
1716
|
if (textarea) {
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1717
|
+
const detailPanel = document.getElementById("detail-panel");
|
|
1718
|
+
if (detailPanel && !detailPanel.classList.contains("open")) {
|
|
1719
|
+
const prefersReducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
1720
|
+
if (prefersReducedMotion) {
|
|
1721
|
+
autoResizeTextarea(textarea);
|
|
1722
|
+
} else {
|
|
1723
|
+
let done = false;
|
|
1724
|
+
const finish = () => {
|
|
1725
|
+
if (done) return;
|
|
1726
|
+
done = true;
|
|
1727
|
+
detailPanel.removeEventListener("transitionend", onTransitionEnd);
|
|
1728
|
+
requestAnimationFrame(() => {
|
|
1729
|
+
requestAnimationFrame(() => {
|
|
1730
|
+
autoResizeTextarea(textarea);
|
|
1731
|
+
});
|
|
1732
|
+
});
|
|
1733
|
+
};
|
|
1734
|
+
const onTransitionEnd = (e) => {
|
|
1735
|
+
if (e.propertyName === "width") finish();
|
|
1736
|
+
};
|
|
1737
|
+
detailPanel.addEventListener("transitionend", onTransitionEnd);
|
|
1738
|
+
setTimeout(finish, 260);
|
|
1739
|
+
}
|
|
1740
|
+
} else {
|
|
1741
|
+
requestAnimationFrame(() => {
|
|
1742
|
+
requestAnimationFrame(() => {
|
|
1743
|
+
autoResizeTextarea(textarea);
|
|
1744
|
+
});
|
|
1745
|
+
});
|
|
1746
|
+
}
|
|
1294
1747
|
textarea.addEventListener("input", () => {
|
|
1295
1748
|
autoResizeTextarea(textarea);
|
|
1296
1749
|
});
|
|
@@ -1305,16 +1758,23 @@
|
|
|
1305
1758
|
async function openTaskDetail(taskId) {
|
|
1306
1759
|
const detailPanel = document.getElementById("detail-panel");
|
|
1307
1760
|
const PANEL_DEFAULT_WIDTH2 = 400;
|
|
1761
|
+
setActiveCard(Number(taskId));
|
|
1762
|
+
if (currentFetchController) {
|
|
1763
|
+
currentFetchController.abort();
|
|
1764
|
+
}
|
|
1765
|
+
currentFetchController = new AbortController();
|
|
1766
|
+
const { signal } = currentFetchController;
|
|
1308
1767
|
try {
|
|
1309
|
-
const data = await fetchTaskDetail(taskId);
|
|
1768
|
+
const data = await fetchTaskDetail(taskId, signal);
|
|
1769
|
+
currentFetchController = null;
|
|
1310
1770
|
renderDetailPanel(data);
|
|
1311
|
-
setActiveCard(Number(taskId));
|
|
1312
1771
|
if (!detailPanel.classList.contains("open")) {
|
|
1313
1772
|
const preferredWidth = detailPanel.dataset.preferredWidth || String(PANEL_DEFAULT_WIDTH2);
|
|
1314
1773
|
detailPanel.style.width = preferredWidth + "px";
|
|
1315
1774
|
detailPanel.classList.add("open");
|
|
1316
1775
|
}
|
|
1317
1776
|
} catch (err) {
|
|
1777
|
+
if (err instanceof DOMException && err.name === "AbortError") return;
|
|
1318
1778
|
console.error("[agkan] openTaskDetail failed for task", taskId, err);
|
|
1319
1779
|
showToast("Failed to load task details");
|
|
1320
1780
|
}
|
|
@@ -1339,7 +1799,7 @@
|
|
|
1339
1799
|
const reloadBtn = document.createElement("button");
|
|
1340
1800
|
reloadBtn.title = "Reload latest data";
|
|
1341
1801
|
reloadBtn.textContent = "\u21BA";
|
|
1342
|
-
reloadBtn.style.cssText = "background: none; border: none; cursor: pointer; font-size: 1.
|
|
1802
|
+
reloadBtn.style.cssText = "background: none; border: none; cursor: pointer; font-size: 1.5em; color: red; padding: 0 4px; line-height: 1; flex-shrink: 0;";
|
|
1343
1803
|
reloadBtn.addEventListener("click", async () => {
|
|
1344
1804
|
try {
|
|
1345
1805
|
if (detailTaskId !== null) {
|
|
@@ -1454,6 +1914,7 @@
|
|
|
1454
1914
|
renderDetailPanel,
|
|
1455
1915
|
showUpdateWarning,
|
|
1456
1916
|
getDetailTaskId,
|
|
1917
|
+
getDetailActiveTab,
|
|
1457
1918
|
setActiveCard
|
|
1458
1919
|
});
|
|
1459
1920
|
registerGetDetailTaskId(getDetailTaskId);
|
|
@@ -2069,6 +2530,161 @@
|
|
|
2069
2530
|
});
|
|
2070
2531
|
}
|
|
2071
2532
|
|
|
2533
|
+
// src/board/client/claudeStreamModal.ts
|
|
2534
|
+
var _currentEventSource = null;
|
|
2535
|
+
var _currentTaskId = null;
|
|
2536
|
+
var _claudeButtonUpdateCallback = null;
|
|
2537
|
+
function registerClaudeButtonUpdateCallback(cb) {
|
|
2538
|
+
_claudeButtonUpdateCallback = cb;
|
|
2539
|
+
}
|
|
2540
|
+
function stripAnsi(text) {
|
|
2541
|
+
return text.replace(/\x1b\[[0-9;]*m/g, "");
|
|
2542
|
+
}
|
|
2543
|
+
function getModalElements() {
|
|
2544
|
+
return {
|
|
2545
|
+
overlay: document.getElementById("claude-stream-modal"),
|
|
2546
|
+
title: document.getElementById("claude-stream-modal-title"),
|
|
2547
|
+
log: document.getElementById("claude-stream-log"),
|
|
2548
|
+
status: document.getElementById("claude-stream-status"),
|
|
2549
|
+
stopBtn: document.getElementById("claude-stream-stop-btn"),
|
|
2550
|
+
closeBtn: document.getElementById("claude-stream-close-btn"),
|
|
2551
|
+
modalClose: document.getElementById("claude-stream-modal-close")
|
|
2552
|
+
};
|
|
2553
|
+
}
|
|
2554
|
+
function isAutoScrollEnabled(log) {
|
|
2555
|
+
const threshold = 50;
|
|
2556
|
+
return log.scrollHeight - log.scrollTop - log.clientHeight <= threshold;
|
|
2557
|
+
}
|
|
2558
|
+
function appendToLog(log, text, className) {
|
|
2559
|
+
const shouldScroll = isAutoScrollEnabled(log);
|
|
2560
|
+
const line = document.createElement("div");
|
|
2561
|
+
line.textContent = text;
|
|
2562
|
+
if (className) {
|
|
2563
|
+
line.className = className;
|
|
2564
|
+
}
|
|
2565
|
+
log.appendChild(line);
|
|
2566
|
+
if (shouldScroll) {
|
|
2567
|
+
log.scrollTop = log.scrollHeight;
|
|
2568
|
+
}
|
|
2569
|
+
}
|
|
2570
|
+
function closeEventSource() {
|
|
2571
|
+
if (_currentEventSource) {
|
|
2572
|
+
_currentEventSource.close();
|
|
2573
|
+
_currentEventSource = null;
|
|
2574
|
+
}
|
|
2575
|
+
}
|
|
2576
|
+
function closeClaudeStreamModal() {
|
|
2577
|
+
closeEventSource();
|
|
2578
|
+
const { overlay } = getModalElements();
|
|
2579
|
+
overlay?.classList.remove("show");
|
|
2580
|
+
}
|
|
2581
|
+
function openClaudeStreamModal(taskId) {
|
|
2582
|
+
closeEventSource();
|
|
2583
|
+
_currentTaskId = taskId;
|
|
2584
|
+
const { overlay, title, log, status, stopBtn } = getModalElements();
|
|
2585
|
+
if (!overlay || !title || !log || !status || !stopBtn) return;
|
|
2586
|
+
title.textContent = `Claude Output #${taskId}`;
|
|
2587
|
+
log.innerHTML = "";
|
|
2588
|
+
status.textContent = "Connecting...";
|
|
2589
|
+
stopBtn.disabled = false;
|
|
2590
|
+
stopBtn.textContent = "Stop";
|
|
2591
|
+
overlay.classList.add("show");
|
|
2592
|
+
const es = new EventSource(`/api/claude/tasks/${taskId}/stream`);
|
|
2593
|
+
_currentEventSource = es;
|
|
2594
|
+
es.onopen = () => {
|
|
2595
|
+
if (_currentEventSource === es) {
|
|
2596
|
+
status.textContent = "Running";
|
|
2597
|
+
}
|
|
2598
|
+
};
|
|
2599
|
+
es.addEventListener("text", (event) => {
|
|
2600
|
+
const msgEvent = event;
|
|
2601
|
+
try {
|
|
2602
|
+
const data = JSON.parse(msgEvent.data);
|
|
2603
|
+
appendToLog(log, stripAnsi(data.text));
|
|
2604
|
+
} catch {
|
|
2605
|
+
}
|
|
2606
|
+
});
|
|
2607
|
+
es.addEventListener("tool_use", (event) => {
|
|
2608
|
+
const msgEvent = event;
|
|
2609
|
+
try {
|
|
2610
|
+
const data = JSON.parse(msgEvent.data);
|
|
2611
|
+
const mainArg = data.input?.path ?? data.input?.command ?? "";
|
|
2612
|
+
const displayText = mainArg ? `\u{1F527} ${data.name}: ${mainArg}` : `\u{1F527} ${data.name}`;
|
|
2613
|
+
appendToLog(log, displayText, "claude-stream-tool-use");
|
|
2614
|
+
} catch {
|
|
2615
|
+
}
|
|
2616
|
+
});
|
|
2617
|
+
es.addEventListener("end", (event) => {
|
|
2618
|
+
const msgEvent = event;
|
|
2619
|
+
try {
|
|
2620
|
+
const data = JSON.parse(msgEvent.data);
|
|
2621
|
+
status.textContent = `Done (exit ${data.exitCode})`;
|
|
2622
|
+
} catch {
|
|
2623
|
+
status.textContent = "Done";
|
|
2624
|
+
}
|
|
2625
|
+
closeEventSource();
|
|
2626
|
+
stopBtn.disabled = true;
|
|
2627
|
+
});
|
|
2628
|
+
es.addEventListener("error", (event) => {
|
|
2629
|
+
const msgEvent = event;
|
|
2630
|
+
try {
|
|
2631
|
+
const data = JSON.parse(msgEvent.data);
|
|
2632
|
+
status.textContent = `Error: ${data.message}`;
|
|
2633
|
+
} catch {
|
|
2634
|
+
status.textContent = "Error";
|
|
2635
|
+
}
|
|
2636
|
+
closeEventSource();
|
|
2637
|
+
stopBtn.disabled = true;
|
|
2638
|
+
});
|
|
2639
|
+
es.onerror = () => {
|
|
2640
|
+
if (_currentEventSource === es) {
|
|
2641
|
+
closeEventSource();
|
|
2642
|
+
status.textContent = "Disconnected";
|
|
2643
|
+
stopBtn.disabled = true;
|
|
2644
|
+
}
|
|
2645
|
+
};
|
|
2646
|
+
}
|
|
2647
|
+
async function handleStop() {
|
|
2648
|
+
if (_currentTaskId === null) return;
|
|
2649
|
+
const { stopBtn, status } = getModalElements();
|
|
2650
|
+
if (!stopBtn || !status) return;
|
|
2651
|
+
const taskId = _currentTaskId;
|
|
2652
|
+
closeEventSource();
|
|
2653
|
+
stopBtn.disabled = true;
|
|
2654
|
+
stopBtn.textContent = "Stopping...";
|
|
2655
|
+
try {
|
|
2656
|
+
await fetch(`/api/claude/tasks/${taskId}/run`, { method: "DELETE" });
|
|
2657
|
+
} catch {
|
|
2658
|
+
}
|
|
2659
|
+
stopBtn.textContent = "Stopped";
|
|
2660
|
+
status.textContent = "Stopped";
|
|
2661
|
+
if (_claudeButtonUpdateCallback) {
|
|
2662
|
+
_claudeButtonUpdateCallback();
|
|
2663
|
+
}
|
|
2664
|
+
}
|
|
2665
|
+
function initClaudeStreamModal() {
|
|
2666
|
+
const { overlay, stopBtn, closeBtn, modalClose } = getModalElements();
|
|
2667
|
+
modalClose?.addEventListener("click", () => {
|
|
2668
|
+
closeClaudeStreamModal();
|
|
2669
|
+
});
|
|
2670
|
+
closeBtn?.addEventListener("click", () => {
|
|
2671
|
+
closeClaudeStreamModal();
|
|
2672
|
+
});
|
|
2673
|
+
stopBtn?.addEventListener("click", () => {
|
|
2674
|
+
void handleStop();
|
|
2675
|
+
});
|
|
2676
|
+
overlay?.addEventListener("click", (e) => {
|
|
2677
|
+
if (e.target === overlay) {
|
|
2678
|
+
closeClaudeStreamModal();
|
|
2679
|
+
}
|
|
2680
|
+
});
|
|
2681
|
+
document.addEventListener("keydown", (e) => {
|
|
2682
|
+
if (e.key === "Escape" && overlay?.classList.contains("show")) {
|
|
2683
|
+
closeClaudeStreamModal();
|
|
2684
|
+
}
|
|
2685
|
+
});
|
|
2686
|
+
}
|
|
2687
|
+
|
|
2072
2688
|
// src/board/client/main.ts
|
|
2073
2689
|
initDragDrop();
|
|
2074
2690
|
initAutoScroll();
|
|
@@ -2079,4 +2695,10 @@
|
|
|
2079
2695
|
initFilters();
|
|
2080
2696
|
initBurgerMenu();
|
|
2081
2697
|
initDependencyVisualization();
|
|
2698
|
+
initClaudeButton();
|
|
2699
|
+
initClaudeStreamModal();
|
|
2700
|
+
registerClaudeModalCallback(openClaudeStreamModal);
|
|
2701
|
+
registerClaudeButtonUpdateCallback(() => {
|
|
2702
|
+
updateButtonStates(/* @__PURE__ */ new Set());
|
|
2703
|
+
});
|
|
2082
2704
|
})();
|