ework-web 0.10.75 → 0.10.77
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/package.json +1 -1
- package/src/index.ts +12 -0
- package/src/render/layout.ts +15 -13
- package/src/static/issue-actions.js +46 -0
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -397,6 +397,7 @@ const REPO_LABEL_ADD_RE = /^\/([^/]+)\/([^/]+)\/settings\/labels\/add$/;
|
|
|
397
397
|
const REPO_LABEL_ACTION_RE = /^\/([^/]+)\/([^/]+)\/settings\/labels\/(\d+)\/(update|archive|unarchive|delete)$/;
|
|
398
398
|
const REPO_ISSUE_HALT_RE = /^\/([^/]+)\/([^/]+)\/issues\/(\d+)\/(halt|resume|dispatch-off|dispatch-on)$/;
|
|
399
399
|
const REPO_ISSUE_MODEL_RE = /^\/([^/]+)\/([^/]+)\/issues\/(\d+)\/model$/;
|
|
400
|
+
const REPO_ISSUE_STATUS_RE = /^\/([^/]+)\/([^/]+)\/issues\/(\d+)\/ai-status$/;
|
|
400
401
|
const API_ISSUE_LABELS_RE = /^\/api\/([^/]+)\/([^/]+)\/issues\/(\d+)\/labels$/;
|
|
401
402
|
const WH_ACTION_RE = /^\/__wh\/(\d+)\/(delete|toggle|test)$/;
|
|
402
403
|
const SESSIONS_RE = /^\/sessions$/;
|
|
@@ -1696,6 +1697,17 @@ async function handle(req: Request, url: URL, ip: string, ctx: { authed: boolean
|
|
|
1696
1697
|
}
|
|
1697
1698
|
}
|
|
1698
1699
|
|
|
1700
|
+
const aiStatusMatch = url.pathname.match(REPO_ISSUE_STATUS_RE);
|
|
1701
|
+
if (aiStatusMatch && req.method === "GET") {
|
|
1702
|
+
const [, owner, repo, numStr] = aiStatusMatch;
|
|
1703
|
+
if (!(owner && repo && numStr)) return json({ error: "bad path" }, 400);
|
|
1704
|
+
const project = await getProject(owner, repo);
|
|
1705
|
+
if (!project) return json({ error: "project not found" }, 404);
|
|
1706
|
+
const issue = await getIssueWithMeta(project.id, Number(numStr));
|
|
1707
|
+
if (!issue) return json({ error: "issue not found" }, 404);
|
|
1708
|
+
return json({ aiStatus: issue.ai_status ?? "", commentCount: issue.comment_count ?? 0 });
|
|
1709
|
+
}
|
|
1710
|
+
|
|
1699
1711
|
const issueLabelsApi = url.pathname.match(API_ISSUE_LABELS_RE);
|
|
1700
1712
|
if (issueLabelsApi) {
|
|
1701
1713
|
const [, owner, repo, numStr] = issueLabelsApi;
|
package/src/render/layout.ts
CHANGED
|
@@ -149,6 +149,7 @@ header.topbar .num{opacity:.7}
|
|
|
149
149
|
.ai-dispatch-off{background:#6f7781;color:#fff}
|
|
150
150
|
.ai-completed{background:#1a7f37;color:#fff}
|
|
151
151
|
.ai-failed{background:#cf222e;color:#fff}
|
|
152
|
+
.ai-idle{background:#eaeef2;color:#57606a}
|
|
152
153
|
@keyframes ai-pulse{0%,100%{opacity:1}50%{opacity:.6}}
|
|
153
154
|
.action-btn{font-size:12px;padding:.15rem .6rem;border-radius:6px;border:1px solid var(--border);background:var(--bg-elev);color:#cf222e;cursor:pointer;font-weight:600}
|
|
154
155
|
.action-btn:hover{background:#cf222e;color:#fff}
|
|
@@ -178,31 +179,32 @@ export function renderLayout(props: LayoutProps, inner: string, initialItems: st
|
|
|
178
179
|
: "";
|
|
179
180
|
const aiBadgeHtml = (() => {
|
|
180
181
|
const s = props.aiStatus ?? "";
|
|
181
|
-
// halted/dispatch_off are represented by their action buttons — skip badge
|
|
182
|
-
if (!s || s === "halted" || s === "dispatch_off") return "";
|
|
183
182
|
const map: Record<string, { cls: string; label: string }> = {
|
|
184
|
-
processing: { cls: "ai-processing", label: "
|
|
185
|
-
completed: { cls: "ai-completed", label: "
|
|
186
|
-
failed: { cls: "ai-failed", label: "
|
|
183
|
+
processing: { cls: "ai-processing", label: "🔄 AI 处理中" },
|
|
184
|
+
completed: { cls: "ai-completed", label: "✅ AI 已完成" },
|
|
185
|
+
failed: { cls: "ai-failed", label: "⚠️ AI 失败" },
|
|
186
|
+
halted: { cls: "ai-halted", label: "⏹ 已停止" },
|
|
187
|
+
dispatch_off: { cls: "ai-dispatch-off", label: "🔕 不接单中" },
|
|
188
|
+
idle: { cls: "ai-idle", label: "💤 空闲" },
|
|
187
189
|
...props.extraStatusBadges,
|
|
188
190
|
};
|
|
189
|
-
const m = map[s];
|
|
190
|
-
|
|
191
|
-
return `<span class="ai-badge ${m.cls}">${m.label}</span>`;
|
|
191
|
+
const m = map[s] ?? { cls: "ai-idle", label: "💤 空闲" };
|
|
192
|
+
return `<span class="ai-badge ${m.cls}" id="aiStatusBadge" data-status="${escapeAttr(s)}" data-comment-count="${props.totalComments}">${m.label}</span>`;
|
|
192
193
|
})();
|
|
193
|
-
const
|
|
194
|
-
const showActions = props.writesEnabled !== false && !isTerminal;
|
|
194
|
+
const showActions = props.writesEnabled !== false;
|
|
195
195
|
const haltBtnHtml = showActions
|
|
196
196
|
? (props.aiStatus === "halted" || props.aiStatus === "failed")
|
|
197
197
|
? `<button type="button" class="action-btn resume-btn" data-action-href="${escapeAttr(repoIssuesHref)}/${props.issueNumber}/resume" data-action-confirm="确认恢复 AI 处理?" title="恢复 AI 处理">▶ 恢复</button>`
|
|
198
|
-
:
|
|
198
|
+
: props.aiStatus === "processing"
|
|
199
|
+
? `<button type="button" class="action-btn" id="aiStopBtn" data-action-href="${escapeAttr(repoIssuesHref)}/${props.issueNumber}/halt" data-action-confirm="确认停止 AI 处理?" title="停止正在运行的 AI">⏹ 停止运行</button>`
|
|
200
|
+
: ""
|
|
199
201
|
: "";
|
|
200
202
|
const dispatchBtnHtml = showActions
|
|
201
203
|
? props.projectDispatchOff
|
|
202
204
|
? `<span class="action-btn dispatch-btn" style="opacity:.5;cursor:not-allowed" title="项目已关闭自动接单">🔕 项目不接单</span>`
|
|
203
205
|
: props.aiStatus === "dispatch_off"
|
|
204
|
-
? `<button type="button" class="action-btn dispatch-btn" data-action-href="${escapeAttr(repoIssuesHref)}/${props.issueNumber}/dispatch-on" title="允许自动接单">🔔
|
|
205
|
-
: `<button type="button" class="action-btn dispatch-btn" data-action-href="${escapeAttr(repoIssuesHref)}/${props.issueNumber}/dispatch-off" data-action-confirm="设为不自动接单?" title="
|
|
206
|
+
? `<button type="button" class="action-btn dispatch-btn" data-action-href="${escapeAttr(repoIssuesHref)}/${props.issueNumber}/dispatch-on" title="允许自动接单">🔔 恢复接单</button>`
|
|
207
|
+
: `<button type="button" class="action-btn dispatch-btn" data-action-href="${escapeAttr(repoIssuesHref)}/${props.issueNumber}/dispatch-off" data-action-confirm="设为不自动接单?" title="关闭此 issue 的自动接单">🔕 暂停接单</button>`
|
|
206
208
|
: "";
|
|
207
209
|
const modelSelectHtml = props.modelSelect && props.modelSelect.options.length > 0 && showActions
|
|
208
210
|
? `<span class="model-select-wrap"><select class="model-select" id="issueModelSelect" title="此 issue 的模型(覆盖项目/全局默认)">
|
|
@@ -6,6 +6,52 @@
|
|
|
6
6
|
// data-action-confirm="确认停止?" — if set, confirm() first
|
|
7
7
|
// data-action-reload="true" — default true, reload on success
|
|
8
8
|
(function () {
|
|
9
|
+
var BADGES = {
|
|
10
|
+
"processing": ["ai-processing", "🔄 AI 处理中"],
|
|
11
|
+
"completed": ["ai-completed", "✅ AI 已完成"],
|
|
12
|
+
"failed": ["ai-failed", "⚠️ AI 失败"],
|
|
13
|
+
"halted": ["ai-halted", "⏹ 已停止"],
|
|
14
|
+
"dispatch_off": ["ai-dispatch-off", "🔕 不接单中"]
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
function issuePath() {
|
|
18
|
+
var m = location.pathname.match(/^(\/[^/]+\/[^/]+\/issues\/\d+)/);
|
|
19
|
+
return m ? m[1] : null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function refreshBadge() {
|
|
23
|
+
var p = issuePath();
|
|
24
|
+
var badge = document.getElementById("aiStatusBadge");
|
|
25
|
+
if (!p || !badge) return;
|
|
26
|
+
fetch(p + "/ai-status", { headers: { Accept: "application/json" } })
|
|
27
|
+
.then(function (r) { return r.ok ? r.json() : null; })
|
|
28
|
+
.then(function (d) {
|
|
29
|
+
if (!d) return;
|
|
30
|
+
var b = BADGES[d.aiStatus] || ["ai-idle", "💤 空闲"];
|
|
31
|
+
badge.textContent = b[1];
|
|
32
|
+
badge.className = "ai-badge " + b[0];
|
|
33
|
+
var stopBtn = document.getElementById("aiStopBtn");
|
|
34
|
+
if (stopBtn) stopBtn.style.display = d.aiStatus === "processing" ? "" : "none";
|
|
35
|
+
var initial = Number(badge.getAttribute("data-comment-count") || 0);
|
|
36
|
+
if (d.commentCount > initial) {
|
|
37
|
+
var bar = document.getElementById("newReplyBar");
|
|
38
|
+
if (!bar) {
|
|
39
|
+
bar = document.createElement("div");
|
|
40
|
+
bar.id = "newReplyBar";
|
|
41
|
+
bar.style.cssText = "margin:.6rem 0;padding:.5rem .8rem;border-radius:8px;background:#ddf4ff;border:1px solid #54aeff;color:#0969da;cursor:pointer";
|
|
42
|
+
bar.textContent = "💬 有新回复 · 点击刷新查看";
|
|
43
|
+
bar.onclick = function () { location.reload(); };
|
|
44
|
+
badge.parentNode.insertBefore(bar, badge.nextSibling);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
})
|
|
48
|
+
.catch(function () {});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (document.getElementById("aiStatusBadge")) {
|
|
52
|
+
setInterval(refreshBadge, 8000);
|
|
53
|
+
}
|
|
54
|
+
|
|
9
55
|
document.addEventListener("click", function (e) {
|
|
10
56
|
var btn = e.target.closest("[data-action-href]");
|
|
11
57
|
if (!btn) return;
|