Files

253 lines
20 KiB
JavaScript
Raw Permalink Normal View History

window.__ModuleLoader__.load({
id: "dsh-plugin-mcn-schedule",
factory: (require) => {
var module = { exports: {} };
var exports = module.exports;
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
let react = require("react");
let react_dom = require("react-dom");
let react_jsx_runtime = require("react/jsx-runtime");
//#region plugin css
const css = ".mcnSched_page{position:relative;background:var(--dsw-alias-bg-base);width:100%;min-width:0;height:100%;min-height:0;color:var(--dsw-alias-label-primary);font-family:var(--dsw-font-family);flex:1;grid-template-rows:minmax(0,1fr);display:grid;overflow:hidden}.mcnSched_body{display:flex;flex-direction:column;height:100%;min-height:0;padding:16px;box-sizing:border-box;gap:12px}.mcnSched_toolbar{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.mcnSched_title{font-size:16px;font-weight:600;margin:0}.mcnSched_spacer{flex:1}.mcnSched_input{border:1px solid var(--dsw-alias-border-l2,var(--dsw-alias-border));border-radius:8px;padding:6px 10px;font:var(--dsw-font-s-14);background:var(--dsw-alias-bg-layer-1,var(--dsw-alias-bg-elevated));color:var(--dsw-alias-label-primary);min-width:200px;box-sizing:border-box}.mcnSched_select{border:1px solid var(--dsw-alias-border-l2,var(--dsw-alias-border));border-radius:8px;padding:6px 8px;font:var(--dsw-font-s-14);background:var(--dsw-alias-bg-layer-1,var(--dsw-alias-bg-elevated));color:var(--dsw-alias-label-primary)}.mcnSched_textarea{border:1px solid var(--dsw-alias-border-l2,var(--dsw-alias-border));border-radius:8px;padding:6px 10px;font:var(--dsw-font-s-14);background:var(--dsw-alias-bg-layer-1,var(--dsw-alias-bg-elevated));color:var(--dsw-alias-label-primary);resize:vertical;box-sizing:border-box;width:100%;min-height:64px}.mcnSched_btn{border:1px solid var(--dsw-alias-border-l2,var(--dsw-alias-border));border-radius:8px;padding:6px 14px;font:var(--dsw-font-s-14);cursor:pointer;background:var(--dsw-alias-bg-layer-1,var(--dsw-alias-bg-elevated));color:var(--dsw-alias-label-primary);transition:background var(--ds-transition-duration-fast) var(--ds-ease-in-out);white-space:nowrap}.mcnSched_btn:hover{background:var(--dsw-alias-interactive-bg-hover)}.mcnSched_btnPrimary{background:var(--mcn-brand,#4176e6);color:#fff;border-color:transparent}.mcnSched_btnPrimary:hover{background:var(--mcn-brand-hover,#3359c9)}.mcnSched_btnSm{padding:2px 10px;font-size:12px;line-height:1.8}.mcnSched_tableWrap{flex:1;min-height:0;overflow:auto;border:1px solid var(--dsw-alias-border-l2,var(--dsw-alias-border));border-radius:8px}.mcnSched_table{width:100%;border-collapse:collapse;font:var(--dsw-font-s-14);min-width:860px}.mcnSched_table th{position:sticky;top:0;background:var(--dsw-alias-bg-layer-1,var(--dsw-alias-bg-elevated));text-align:left;padding:8px 10px;border-bottom:1px solid var(--dsw-alias-border-l2,var(--dsw-alias-border));font-weight:600;white-space:nowrap;user-select:none}.mcnSched_table td{padding:8px 10px;border-bottom:1px solid var(--dsw-alias-border-l2,var(--dsw-alias-border));vertical-align:middle}.mcnSched_table tr:hover{background:var(--dsw-alias-interactive-bg-hover)}.mcnSched_cellTrack{white-space:nowrap}.mcnSched_cellContent{color:var(--dsw-alias-label-secondary);max-width:280px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.mcnSched_badge{display:inline-block;padding:1px 8px;border-radius:10px;font-size:12px;line-height:18px;white-space:nowrap}.mcnSched_badgeOn{background:rgba(46,190,140,.18);color:var(--dsw-alias-label-primary)}.mcnSched_badgeOff{background:rgba(236,19,19,.14);color:var(--dsw-alias-label-primary)}.mcnSched_badgeAt{background:rgba(65,118,230,.15);color:var(--dsw-alias-label-primary)}.mcnSched_badgeEvery{background:rgba(255,122,69,.15);color:var(--dsw-alias-label-primary)}.mcnSched_badgeAgent{background:rgba(139,92,246,.16);color:var(--dsw-alias-label-primary)}.mcnSched_loading{display:flex;align-items:center;justify-content:center;height:100%;color:var(--dsw-alias-label-secondary);font:var(--dsw-font-s-14)}.mcnSched_empty{padding:40px 20px;text-align:center;color:var(--dsw-alias-label-secondary);font:var(--dsw-font-s-14);line-height:1.8}.mcnSched_mask{position:fixed;inset:0;background:rgba(0,0,0,.4);display:flex;align-items:flex-start;justify-content:center;padding-top:24vh;z-index:100}.mcnSched_modal{background:var(--dsw-alias-bg-layer-1,var(--dsw-alias-bg-elevated));border-radius:12px;padding:20px;min-width:380px;max-width:460px;box-sha
const key = "dsh-plugin-mcn-schedule/Schedule.module.css";
if (!document.querySelector("style[data-plugin-css=" + JSON.stringify(key) + "]")) {
const el = document.createElement("style");
el.dataset.plugin = "dsh-plugin-mcn-schedule";
el.dataset.pluginCss = key;
el.textContent = css;
document.head.appendChild(el);
}
const cssMap = {
"page": "mcnSched_page",
"body": "mcnSched_body",
"toolbar": "mcnSched_toolbar",
"title": "mcnSched_title",
"spacer": "mcnSched_spacer",
"input": "mcnSched_input",
"select": "mcnSched_select",
"textarea": "mcnSched_textarea",
"btn": "mcnSched_btn",
"btnPrimary": "mcnSched_btnPrimary",
"btnSm": "mcnSched_btnSm",
"tableWrap": "mcnSched_tableWrap",
"table": "mcnSched_table",
"cellTrack": "mcnSched_cellTrack",
"cellContent": "mcnSched_cellContent",
"badge": "mcnSched_badge",
"badgeOn": "mcnSched_badgeOn",
"badgeOff": "mcnSched_badgeOff",
"badgeAt": "mcnSched_badgeAt",
"badgeEvery": "mcnSched_badgeEvery",
"badgeAgent": "mcnSched_badgeAgent",
"loading": "mcnSched_loading",
"empty": "mcnSched_empty",
"mask": "mcnSched_mask",
"modal": "mcnSched_modal",
"modalTitle": "mcnSched_modalTitle",
"modalText": "mcnSched_modalText",
"modalBtns": "mcnSched_modalBtns",
"formGrid": "mcnSched_formGrid",
"formItem": "mcnSched_formItem",
"formItemWide": "mcnSched_formItemWide",
"toast": "mcnSched_toast"
};
//#endregion
//#region 图标
function IconClock({ size = 22 }) {
return (0, react_jsx_runtime.jsx)("svg", {
viewBox: "0 0 24 24",
width: size,
height: size,
fill: "none",
stroke: "currentColor",
strokeWidth: "2",
strokeLinecap: "round",
strokeLinejoin: "round",
"aria-hidden": "true",
style: { color: "var(--mcn-brand,#4176e6)" },
children: [(0, react_jsx_runtime.jsx)("circle", { cx: "12", cy: "12", r: "10" }), (0, react_jsx_runtime.jsx)("polyline", { points: "12 6 12 12 16 14" })]
});
}
//#endregion
//#region 计划任务页面
function SchedulePage() {
const [tasks, setTasks] = (0, react.useState)([]);
const [events, setEvents] = (0, react.useState)([]);
const [loading, setLoading] = (0, react.useState)(true);
const [modalOpen, setModalOpen] = (0, react.useState)(false);
const [editing, setEditing] = (0, react.useState)(null);
const [toast, setToast] = (0, react.useState)(null);
const lastSeenRef = (0, react.useRef)(Date.now());
const toastTimer = (0, react.useRef)(null);
const fmtTime = (ts) => {
if (ts == null) return "-";
const d = new Date(ts);
const p = (n) => String(n).padStart(2, "0");
return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}`;
};
const showToast = (msg) => {
setToast(msg);
if (toastTimer.current) clearTimeout(toastTimer.current);
toastTimer.current = setTimeout(() => setToast(null), 6000);
};
const load = () => {
fetch("/mcn/schedule/list")
.then((r) => r.json())
.then((d) => { if (d.ok) { setTasks(d.tasks || []); } })
.catch(() => {})
.finally(() => setLoading(false));
};
const pollEvents = () => {
const since = lastSeenRef.current;
fetch("/mcn/schedule/events?since=" + since)
.then((r) => r.json())
.then((d) => {
if (!d.ok || !d.events || d.events.length === 0) return;
const last = Math.max(...d.events.map((e) => new Date(e.at).getTime()));
lastSeenRef.current = Math.max(lastSeenRef.current, last);
setEvents((prev) => [...d.events, ...prev].slice(0, 20));
for (const ev of d.events) {
showToast(`⏰ 计划任务「${ev.name}」${ev.prompt ? ":" + ev.prompt : "已触发"}`);
}
load();
})
.catch(() => {});
};
(0, react.useEffect)(() => {
load();
const t1 = setInterval(pollEvents, 12000);
const t2 = setInterval(load, 30000);
return () => { clearInterval(t1); clearInterval(t2); if (toastTimer.current) clearTimeout(toastTimer.current); };
}, []);
const openCreate = () => { setEditing(null); setModalOpen(true); };
const openEdit = (t) => { setEditing(t); setModalOpen(true); };
const toggleEnabled = (t) => {
fetch("/mcn/schedule/update", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ id: t.id, enabled: !t.enabled }),
}).then((r) => r.json()).then((d) => { if (d.ok) { load(); showToast(!t.enabled ? "已启用" : "已停用"); } }).catch(() => {});
};
const fireNow = (t) => {
fetch("/mcn/schedule/fire-now", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ id: t.id }),
}).then((r) => r.json()).then((d) => { if (d.ok) { showToast("已触发测试"); setTimeout(load, 500); } }).catch(() => {});
};
const remove = (t) => {
if (!window.confirm(`确认删除任务「${t.name}」?`)) return;
fetch("/mcn/schedule/delete", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ id: t.id }),
}).then((r) => r.json()).then((d) => { if (d.ok) { load(); showToast("已删除"); } }).catch(() => {});
};
const submit = (form) => {
const url = editing ? "/mcn/schedule/update" : "/mcn/schedule/create";
const body = { name: form.name, prompt: form.prompt, enabled: form.enabled, type: form.type, action: form.action || "remind" };
if (form.type === "at") body.at = form.at;
else body.intervalMinutes = Number(form.intervalMinutes);
if (editing) body.id = editing.id;
fetch(url, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body),
}).then((r) => r.json()).then((d) => {
if (d.ok) { setModalOpen(false); load(); showToast(editing ? "已保存" : "已创建"); }
else { showToast(d.error || "操作失败"); }
}).catch(() => showToast("请求失败"));
};
return (0, react_jsx_runtime.jsxs)("div", {
className: cssMap.page,
children: [(0, react_jsx_runtime.jsxs)("div", {
className: cssMap.body,
children: [(0, react_jsx_runtime.jsxs)("div", {
className: cssMap.toolbar,
children: [(0, react_jsx_runtime.jsx)("span", { className: cssMap.title, children: "计划任务" }), (0, react_jsx_runtime.jsx)("span", { className: cssMap.spacer }), (0, react_jsx_runtime.jsx)("button", { type: "button", className: cssMap.btn + " " + cssMap.btnPrimary, onClick: openCreate, children: "+ 新建任务" })]
}), events.length > 0 && (0, react_jsx_runtime.jsx)("div", {
className: cssMap.cellTrack,
style: { font: "var(--dsw-font-s-13)", color: "var(--dsw-alias-label-secondary)" },
children: "最近触发:" + events.slice(0, 3).map((e) => `${e.name}(${fmtTime(new Date(e.at).getTime())})`).join("、")
}), loading ? (0, react_jsx_runtime.jsx)("div", { className: cssMap.loading, children: "加载中…" }) : tasks.length === 0 ? (0, react_jsx_runtime.jsx)("div", { className: cssMap.empty, children: "暂无计划任务,点击右上角「+ 新建任务」创建" }) : (0, react_jsx_runtime.jsx)("div", {
className: cssMap.tableWrap,
children: (0, react_jsx_runtime.jsx)("table", {
className: cssMap.table,
children: [(0, react_jsx_runtime.jsx)("thead", {
children: (0, react_jsx_runtime.jsx)("tr", {
children: ["任务名称", "类型", "状态", "下次执行", "上次执行", "提醒内容", "操作"].map((h) => (0, react_jsx_runtime.jsx)("th", { children: h }))
})
}), (0, react_jsx_runtime.jsx)("tbody", {
children: tasks.map((t) => (0, react_jsx_runtime.jsx)("tr", {
children: [(0, react_jsx_runtime.jsx)("td", { className: cssMap.cellTrack, children: t.name }), (0, react_jsx_runtime.jsx)("td", { children: [(0, react_jsx_runtime.jsx)("span", { className: cssMap.badge + " " + (t.type === "at" ? cssMap.badgeAt : cssMap.badgeEvery), children: t.type === "at" ? "一次性" : "循环" }), t.action === "agent" && (0, react_jsx_runtime.jsx)("span", { className: cssMap.badge + " " + cssMap.badgeAgent, children: "AI执行" })] }), (0, react_jsx_runtime.jsx)("td", { children: (0, react_jsx_runtime.jsx)("span", { className: cssMap.badge + " " + (t.enabled ? cssMap.badgeOn : cssMap.badgeOff), children: t.enabled ? "启用" : "停用" }) }), (0, react_jsx_runtime.jsx)("td", { className: cssMap.cellTrack, children: t.type === "at" ? fmtTime(t.nextRunAt) + (t.firedAt ? "(已触发)" : "") : fmtTime(t.nextRunAt) }), (0, react_jsx_runtime.jsx)("td", { className: cssMap.cellTrack, children: fmtTime(t.lastFiredAt) }), (0, react_jsx_runtime.jsx)("td", { className: cssMap.cellContent, children: t.prompt || "-" }), (0, react_jsx_runtime.jsx)("td", {
children: [(0, react_jsx_runtime.jsx)("button", { type: "button", className: cssMap.btn + " " + cssMap.btnSm, onClick: () => openEdit(t), children: "编辑" }), " ", (0, react_jsx_runtime.jsx)("button", { type: "button", className: cssMap.btn + " " + cssMap.btnSm, onClick: () => toggleEnabled(t), children: t.enabled ? "停用" : "启用" }), " ", (0, react_jsx_runtime.jsx)("button", { type: "button", className: cssMap.btn + " " + cssMap.btnSm, onClick: () => fireNow(t), children: "触发" }), " ", (0, react_jsx_runtime.jsx)("button", { type: "button", className: cssMap.btn + " " + cssMap.btnSm, onClick: () => remove(t), children: "删除" })]
})]
}, t.id))
})]
})
})]
}), modalOpen && (0, react_jsx_runtime.jsxs)("div", {
className: cssMap.mask,
onClick: () => setModalOpen(false),
children: [(0, react_jsx_runtime.jsxs)("div", {
className: cssMap.modal,
onClick: (e) => e.stopPropagation(),
children: [(0, react_jsx_runtime.jsx)("div", { className: cssMap.modalTitle, children: editing ? `编辑任务「${editing.name}」` : "新建计划任务" }), (0, react_jsx_runtime.jsx)(ScheduleForm, { initial: editing, onSubmit: submit, onCancel: () => setModalOpen(false) })]
})]
}), toast && (0, react_jsx_runtime.jsx)("div", { className: cssMap.toast, children: toast })]
});
}
function ScheduleForm({ initial, onSubmit, onCancel }) {
const [name, setName] = (0, react.useState)(initial?.name || "");
const [type, setType] = (0, react.useState)(initial?.type || "at");
const [at, setAt] = (0, react.useState)(initial?.at || "");
const [intervalMinutes, setIntervalMinutes] = (0, react.useState)(String(initial?.intervalMinutes || 10));
const [prompt, setPrompt] = (0, react.useState)(initial?.prompt || "");
const [action, setAction] = (0, react.useState)(initial?.action || "remind");
const [enabled, setEnabled] = (0, react.useState)(initial?.enabled !== false);
const [err, setErr] = (0, react.useState)("");
const submit = () => {
if (!name.trim()) { setErr("请填写任务名称"); return; }
if (type === "at" && !at) { setErr("请选择执行时间"); return; }
if (type === "every" && (!Number(intervalMinutes) || Number(intervalMinutes) < 1)) { setErr("周期间隔不能小于 1 分钟"); return; }
onSubmit({ name, type, at, intervalMinutes, prompt, action, enabled });
};
return (0, react_jsx_runtime.jsxs)("div", {
children: [(0, react_jsx_runtime.jsxs)("div", {
className: cssMap.formGrid,
children: [(0, react_jsx_runtime.jsx)("label", { className: cssMap.formItemWide, children: [(0, react_jsx_runtime.jsx)("span", { children: "任务名称" }), (0, react_jsx_runtime.jsx)("input", { className: cssMap.input, style: { width: "100%" }, value: name, onChange: (e) => setName(e.target.value), placeholder: "如:每日检查日榜" })] }), (0, react_jsx_runtime.jsx)("label", { className: cssMap.formItem, children: [(0, react_jsx_runtime.jsx)("span", { children: "任务类型" }), (0, react_jsx_runtime.jsx)("select", { className: cssMap.select, value: type, onChange: (e) => setType(e.target.value), children: [(0, react_jsx_runtime.jsx)("option", { value: "at", children: "一次性(指定时间)" }), (0, react_jsx_runtime.jsx)("option", { value: "every", children: "循环(固定间隔)" })] })] }), (0, react_jsx_runtime.jsx)("label", { className: cssMap.formItem, children: [(0, react_jsx_runtime.jsx)("span", { children: "动作" }), (0, react_jsx_runtime.jsx)("select", { className: cssMap.select, value: action, onChange: (e) => setAction(e.target.value), children: [(0, react_jsx_runtime.jsx)("option", { value: "remind", children: "仅页面提醒" }), (0, react_jsx_runtime.jsx)("option", { value: "agent", children: "触发 AI 执行" })] })] }), type === "every" ? (0, react_jsx_runtime.jsx)("label", { className: cssMap.formItem, children: [(0, react_jsx_runtime.jsx)("span", { children: "间隔(分钟)" }), (0, react_jsx_runtime.jsx)("input", { className: cssMap.input, type: "number", min: 1, value: intervalMinutes, onChange: (e) => setIntervalMinutes(e.target.value) })] }) : (0, react_jsx_runtime.jsx)("label", { className: cssMap.formItem, children: [(0, react_jsx_runtime.jsx)("span", { children: "执行时间" }), (0, react_jsx_runtime.jsx)("input", { className: cssMap.input, type: "datetime-local", value: at, onChange: (e) => setAt(e.target.value) })] }), (0, react_jsx_runtime.jsx)("label", { className: cssMap.formItemWide, children: [(0, react_jsx_runtime.jsx)("span", { children: action === "agent" ? "任务指令(AI 将执行的内容)" : "提醒内容(可选)" }), (0, react_jsx_runtime.jsx)("textarea", { className: cssMap.textarea, value: prompt, onChange: (e) => setPrompt(e.target.value), placeholder: action === "agent" ? "如:分析今日抖音日榜数据,生成简报保存到工作区" : "到点后提醒的内容,如:检查今日日榜数据" })] }), (0, react_jsx_runtime.jsx)("label", { className: cssMap.formItemWide, children: [(0, react_jsx_runtime.jsx)("span", { children: "状态" }), (0, react_jsx_runtime.jsx)("select", { className: cssMap.select, value: enabled ? "1" : "0", onChange: (e) => setEnabled(e.target.value === "1"), children: [(0, react_jsx_runtime.jsx)("option", { value: "1", children: "启用" }), (0, react_jsx_runtime.jsx)("option", { value: "0", children: "停用" })] })] }), err && (0, react_jsx_runtime.jsx)("div", { className: cssMap.formItemWide, style: { color: "#ff6b6b" }, children: err })]
}), (0, react_jsx_runtime.jsxs)("div", {
className: cssMap.modalBtns,
children: [(0, react_jsx_runtime.jsx)("button", { type: "button", className: cssMap.btn, onClick: onCancel, children: "取消" }), (0, react_jsx_runtime.jsx)("button", { type: "button", className: cssMap.btn + " " + cssMap.btnPrimary, onClick: submit, children: initial ? "保存" : "创建" })]
})]
});
}
//#endregion
//#region plugin entry
const inject = [];
function apply() {
window.__mcnEntries = window.__mcnEntries || [];
// 幂等:避免插件热重载重复注册
if (!window.__mcnEntries.some((e) => e.id === "mcn-schedule")) {
// unshift 置顶:入口放到 MCN 工作台上方
window.__mcnEntries.unshift({
id: "mcn-schedule",
icon: IconClock,
title: "计划任务",
desc: "定时提醒 / 周期任务",
component: SchedulePage,
});
}
}
//#endregion
exports.apply = apply;
exports.inject = inject;
return module.exports;
}
});